Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GUI] Better event filtering system #801

Merged
merged 25 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions docs/gui.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
.. _gui:

GUI system
==========

Taichi has a built-in GUI system to help users display graphic results easier.


Create a window
---------------


.. function:: ti.GUI(title, res, bgcolor = 0x000000)

:parameter title: (string) the window title
:parameter res: (scalar or tuple) resolution / size of the window
:parameter bgcolor: (optional, RGB hex) background color of the window
:return: (GUI) an object represents the window

Create a window.
If ``res`` is scalar, then width will be equal to height.

This creates a window whose width is 1024, height is 768:

::

gui = ti.GUI('Window Title', (1024, 768))


.. function:: gui.show(filename = None)

:parameter gui: (GUI) the window object
:parameter filename: (optional, string) see notes below

Show the window on the screen.

.. note::
If `filename` is specified, screenshot will be saved to the file specified by the name. For example, this screenshots each frame of the window, and save it in ``.png``'s:

::

for frame in range(10000):
render(img)
gui.set_image(img)
gui.show(f'{frame:06d}.png')


Paint a window
--------------


.. function:: gui.set_image(img)

:parameter gui: (GUI) the window object
:parameter img: (np.array or Tensor) tensor containing the image, see notes below

Set a image to display on the window.

The pixel, ``i`` from bottom to up, ``j`` from left to right, is set to the value of ``img[i, j]``.


If the window size is ``(x, y)``, then the ``img`` must be one of:

* ``ti.var(shape=(x, y))``, a grey-scale image

* ``ti.var(shape=(x, y, 3))``, where `3` is for `(r, g, b)` channels

* ``ti.Vector(3, shape=(x, y))`` (see :ref:`vector`)

* ``np.ndarray(shape=(x, y))``

* ``np.ndarray(shape=(x, y, 3))``


The data type of ``img`` must be one of:

* float32, clamped into [0, 1]

* float64, clamped into [0, 1]

* uint8, range [0, 255]

* uint16, range [0, 65535]

* uint32, range [0, UINT_MAX]


.. function:: gui.circle(pos, color = 0xFFFFFF, radius = 1)

:parameter gui: (GUI) the window object
:parameter pos: (tuple of 2) the position of circle
:parameter color: (optional, RGB hex) color to fill the circle
:parameter radius: (optional, scalar) the radius of circle

Draw a solid circle.


.. function:: gui.circles(pos, color = 0xFFFFFF, radius = 1)

:parameter gui: (GUI) the window object
:parameter pos: (np.array) the position of circles
:parameter color: (optional, RGB hex or np.array of uint32) color(s) to fill circles
:parameter radius: (optional, scalar) the radius of circle

Draw solid circles.

.. note::

If ``color`` is a numpy array, circle at ``pos[i]`` will be colored with ``color[i]``, therefore it must have the same size with ``pos``.


.. function:: gui.line(begin, end, color = 0xFFFFFF, radius = 1)

:parameter gui: (GUI) the window object
:parameter begin: (tuple of 2) the first end point position of line
:parameter end: (tuple of 2) the second end point position of line
:parameter color: (optional, RGB hex) the color of line
:parameter radius: (optional, scalar) the width of line

Draw a line.


.. function:: gui.triangle(a, b, c, color = 0xFFFFFF)

:parameter gui: (GUI) the window object
:parameter a: (tuple of 2) the first end point position of triangle
:parameter b: (tuple of 2) the second end point position of triangle
:parameter c: (tuple of 2) the third end point position of triangle
:parameter color: (optional, RGB hex) the color to fill the triangle

Draw a solid triangle.


.. function:: gui.rect(topleft, bottomright, radius = 1, color = 0xFFFFFF)

:parameter gui: (GUI) the window object
:parameter topleft: (tuple of 2) the top-left point position of rectangle
:parameter bottomright: (tuple of 2) the bottom-right point position of rectangle
:parameter color: (optional, RGB hex) the color of stroke line
:parameter radius: (optional, scalar) the width of stroke line

Draw a hollow rectangle.


Event processing
----------------

Every event have a key and type.
*Event key* is the key that you pressed on keyboard or mouse, can be one of:

::

ti.GUI.ESCAPE
ti.GUI.SHIFT
ti.GUI.LEFT
'a'
'b'
...
ti.GUI.LMB
ti.GUI.RMB

*Event type* is the type of event, for now, there are just three type of event:

::

ti.GUI.RELEASE # key up
ti.GUI.PRESS # key down
ti.GUI.MOTION # mouse moved


A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.g.:

.. code-block::

# if ESC pressed or released:
gui.get_event(ti.GUI.ESCAPE)

# if any key is pressed:
gui.get_event(ti.GUI.PRESS)

# if ESC pressed or SPACE released:
gui.get_event((ti.GUI.PRESS, ti.GUI.ESCAPE), (ti.GUI.RELEASE, ti.GUI.SPACE))


.. function:: gui.get_event(a, ...)

:parameter gui: (GUI)
:parameter a: (optional, EventFilter) filter out matched events
:return: (bool) ``False`` if there is no pending event, vise versa

Try to pop a event from the queue, and store it in ``gui.event``.

For example:

::

while gui.get_event():
print('Event key', gui.event.key)


For example, loop until ESC is pressed:

::

gui = ti.GUI('Title', (640, 480))
while not gui.get_event(ti.GUI.ESCAPE):
gui.set_image(img)
gui.show()

.. function:: gui.get_events(a, ...)

:parameter gui: (GUI)
:parameter a: (optional, EventFilter) filter out matched events
:return: (generator) a python generator, see below

Basically the same as ``gui.get_event``, except for this one returns a generator of events instead of storing into ``gui.event``:

::

for e in gui.get_events():
if e.key == ti.GUI.ESCAPE:
exit()
elif e.type == ti.GUI.SPACE:
do_something()
elif e.type in ['a', ti.GUI.LEFT]:
...

.. function:: gui.is_pressed(key, ...)

:parameter gui: (GUI)
:parameter key: (EventKey) keys you want to detect
:return: (bool) ``True`` if one of the keys pressed, vice versa

.. warning::

Must be used together with ``gui.get_event``, or it won't be updated!
For example:

::

while True:
gui.get_event() # must be called before is_pressed
if gui.is_pressed('a', ti.GUI.LEFT):
print('Go left!')
elif gui.is_pressed('d', ti.GUI.RIGHT):
print('Go right!')

.. function:: gui.get_cursor_pos()

:parameter gui: (GUI)
:return: (tuple of 2) current cursor position within the window

For example:

::

mouse_x, mouse_y = gui.get_cursor_pos()


Image I/O
---------

.. code-block:: python

img = ti.imread('hello.png')
ti.imshow(img, 'Window Title')
ti.imwrite(img, 'hello2.png')

TODO: complete here
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The Taichi Programming Language
:maxdepth: 3

utilities
gui
global_settings
performance
acknowledgments
Expand Down
25 changes: 0 additions & 25 deletions docs/utilities.rst
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
Utilities
==================================

TODO: update

GUI system
----------

.. code-block:: python

gui = ti.GUI('Title', (640, 480))
while not gui.is_pressed(ti.GUI.ESCAPE):
gui.set_image(img)
gui.show()


Also checkout ``examples/keyboard.py`` for more advanced event processing.


Image I/O
---------

.. code-block:: python

img = ti.imread('hello.png')
ti.imshow(img, 'Window Title')
ti.imwrite(img, 'hello2.png')


Logging
-------
Expand Down
2 changes: 2 additions & 0 deletions docs/vector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ A vector in Taichi can have two forms:
- as a temporary local variable. An ``n`` component vector consists of ``n`` scalar values.
- as an element of a global tensor. In this case, the tensor is an N-dimensional array of ``n`` component vectors

See :ref:`tensor_matrix` for more details.

Declaration
-----------

Expand Down
14 changes: 5 additions & 9 deletions examples/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
gui = ti.GUI("Keyboard", res=(400, 400))

while True:
while gui.has_key_event():
e = gui.get_key_event()
if e.type == ti.GUI.RELEASE:
continue
if e.key == ti.GUI.ESCAPE:
while gui.get_event(ti.GUI.PRESS):
if gui.event.key == ti.GUI.ESCAPE:
exit()
elif e.key == ti.GUI.RMB:
x, y = e.pos[0], e.pos[1]
elif gui.event.key == ti.GUI.RMB:
x, y = gui.event.pos

if gui.is_pressed(ti.GUI.LEFT, 'a'):
x -= delta
Expand All @@ -24,8 +21,7 @@
if gui.is_pressed(ti.GUI.DOWN, 's'):
y -= delta
if gui.is_pressed(ti.GUI.LMB):
pos = gui.get_cursor_pos()
x, y = pos[0], pos[1]
x, y = gui.get_cursor_pos()

gui.circle((x, y), 0xffffff, 8)
gui.show()
2 changes: 1 addition & 1 deletion examples/mpm99.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def initialize():
Jp[i] = 1
initialize()
gui = ti.GUI("Taichi MLS-MPM-99", res=512, background_color=0x112F41)
for frame in range(20000):
while not gui.get_event(ti.GUI.ESCAPE):
for s in range(int(2e-3 // dt)):
substep()
colors = np.array([0x068587, 0xED553B, 0xEEEEF0], dtype=np.uint32)
Expand Down
2 changes: 1 addition & 1 deletion examples/nbody_oscillator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def advance(dt: ti.f32):
gui = ti.GUI("n-body", res=(400, 400))

initialize()
while not gui.has_key_event() or gui.get_key_event().key == ti.GUI.MOTION:
while not gui.get_event(ti.GUI.ESCAPE):
_pos = pos.to_numpy()
gui.circles(_pos, radius=1, color=0x66ccff)
gui.show()
Expand Down
Loading