Skip to content

Commit

Permalink
[GUI] Add ti.GUI.EXIT event to close a window (#1152)
Browse files Browse the repository at this point in the history
* [GUI] Add ti.GUI.EXIT event to close a window

* force quit by RuntimeError

* gui.running

* mpm88 mpm128 really 88 128

* better message

* treat win32

* update doc for gui.running setter

* [skip ci] enforce code format

* [skip travis] Update taichi/gui/win32.cpp

* [skip ci] try to DestroyWindow

* [skip ci] RAII wmDeleteMessage

* [skip ci] how about this?

* [skip ci] better doc

* with ti.GUI() as gui

* [skip ci] enforce code format

* gui->send_window_close_message

* [skip ci] enforce code format

* [skip ci] Update docs/gui.rst

Co-authored-by: xumingkuan <xumingkuan0721@126.com>

Co-authored-by: Taichi Gardener <taichigardener@gmail.com>
Co-authored-by: xumingkuan <xumingkuan0721@126.com>
  • Loading branch information
3 people authored Jun 7, 2020
1 parent f21aba3 commit 4390448
Show file tree
Hide file tree
Showing 18 changed files with 123 additions and 32 deletions.
39 changes: 37 additions & 2 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,37 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
gui.get_event((ti.GUI.PRESS, ti.GUI.ESCAPE), (ti.GUI.RELEASE, ti.GUI.SPACE))
.. attribute:: gui.running

:parameter gui: (GUI)
:return: (bool) ``True`` if ``ti.GUI.EXIT`` event occurred, vice versa

``ti.GUI.EXIT`` occurs when you click on the close (X) button of a window.
So ``gui.running`` will obtain ``False`` when the GUI is being closed.

For example, loop until the close button is clicked:

::

while gui.running:
render()
gui.set_image(pixels)
gui.show()


You can also close the window by manually setting ``gui.running`` to ``False``:

::

while gui.running:
if gui.get_event(ti.GUI.ESCAPE):
gui.running = False

render()
gui.set_image(pixels)
gui.show()


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

:parameter gui: (GUI)
Expand All @@ -213,8 +244,8 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.

::

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


For example, loop until ESC is pressed:
Expand All @@ -226,6 +257,7 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
gui.set_image(img)
gui.show()


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

:parameter gui: (GUI)
Expand All @@ -244,6 +276,7 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
elif e.type in ['a', ti.GUI.LEFT]:
...


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

:parameter gui: (GUI)
Expand All @@ -264,6 +297,7 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
elif gui.is_pressed('d', ti.GUI.RIGHT):
print('Go right!')


.. function:: gui.get_cursor_pos()

:parameter gui: (GUI)
Expand All @@ -276,6 +310,7 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
mouse_x, mouse_y = gui.get_cursor_pos()



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

Expand Down
2 changes: 1 addition & 1 deletion examples/euler.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ def paint():
set_bc()

n = 0
while (1):
while gui.running:
calc_dt()
copy_to_old()
for rk_step in range(2):
Expand Down
4 changes: 2 additions & 2 deletions examples/game_of_life.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ def render():
print('Press the spacebar to run.')

render()
while True:
while gui.running:
while gui.get_event(ti.GUI.PRESS):
if gui.event.key == ti.GUI.SPACE:
run()
render()
elif gui.event.key == ti.GUI.ESCAPE:
exit()
gui.running = False
gui.set_image(img.to_numpy().astype(np.uint8))
gui.show()
4 changes: 2 additions & 2 deletions examples/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

gui = ti.GUI("Keyboard", res=(400, 400))

while True:
while gui.running:
while gui.get_event(ti.GUI.PRESS):
if gui.event.key == ti.GUI.ESCAPE:
exit()
gui.running = False
elif gui.event.key == ti.GUI.RMB:
x, y = gui.event.pos

Expand Down
4 changes: 3 additions & 1 deletion examples/mpm128.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import taichi as ti
import numpy as np

ti.init(arch=ti.gpu) # Try to run on GPU

quality = 1 # Use a larger value for higher-res simulations
n_particles, n_grid = 9000 * quality ** 2, 128 * quality
dx, inv_dx = 1 / n_grid, float(n_grid)
Expand Down Expand Up @@ -105,7 +107,7 @@ def reset():
for frame in range(20000):
while gui.get_event(ti.GUI.PRESS):
if gui.event.key == 'r': reset()
elif gui.event.key == ti.GUI.ESCAPE: exit(0)
elif gui.event.key in [ti.GUI.ESCAPE, ti.GUI.EXIT]: exit(0)
if gui.event is not None: gravity[None] = [0, 0] # if had any event
if gui.is_pressed(ti.GUI.LEFT, 'a'): gravity[None][0] = -1
if gui.is_pressed(ti.GUI.RIGHT, 'd'): gravity[None][0] = 1
Expand Down
9 changes: 2 additions & 7 deletions examples/mpm88.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import taichi as ti
import random

ti.init(arch=ti.gpu)

dim = 2
Expand Down Expand Up @@ -73,21 +72,17 @@ def substep():
J[p] *= 1 + dt * new_C.trace()
C[p] = new_C


gui = ti.GUI("MPM88", (512, 512))

for i in range(n_particles):
x[i] = [random.random() * 0.4 + 0.2, random.random() * 0.4 + 0.2]
v[i] = [0, -1]
J[i] = 1

gui = ti.GUI("MPM88", (512, 512))
for frame in range(20000):
for s in range(50):
grid_v.fill([0, 0])
grid_m.fill(0)
substep()

gui.clear(0x112F41)
pos = x.to_numpy()
gui.circles(pos, radius=1.5, color=0x068587)
gui.circles(x.to_numpy(), radius=1.5, color=0x068587)
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)
while not gui.get_event(ti.GUI.ESCAPE):
while not gui.get_event(ti.GUI.ESCAPE, ti.GUI.EXIT):
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.get_event(ti.GUI.ESCAPE):
while not gui.get_event(ti.GUI.ESCAPE, ti.GUI.EXIT):
_pos = pos.to_numpy()
gui.circles(_pos, radius=1, color=0x66ccff)
gui.show()
Expand Down
2 changes: 1 addition & 1 deletion examples/pbf2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def main():
print(f'boundary={boundary} grid={grid_size} cell_size={cell_size}')
gui = ti.GUI('PBF2D', screen_res)
print_counter = 0
while True:
while gui.running:
move_board()
run_pbf()
print_counter += 1
Expand Down
2 changes: 1 addition & 1 deletion examples/quadtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def vec2_npf32(m):


gui = ti.GUI('Quadtree', (RES, RES))
while not gui.get_event(ti.GUI.PRESS):
while not gui.get_event(ti.GUI.ESCAPE, ti.GUI.EXIT):
Broot.deactivate_all()
pos = gui.get_cursor_pos()
action(vec2_npf32(pos))
Expand Down
2 changes: 1 addition & 1 deletion examples/taichi_logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ def paint():
paint()

gui = ti.GUI('Logo', (512, 512))
while True:
while gui.get_event(ti.GUI.ESCAPE, ti.GUI.EXIT):
gui.set_image(x.to_numpy())
gui.show()
2 changes: 1 addition & 1 deletion examples/waterwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def paint():
gui = ti.GUI("Water Wave", shape)
for frame in range(100000):
for e in gui.get_events(ti.GUI.PRESS):
if e.key == ti.GUI.ESCAPE:
if e.key in [ti.GUI.ESCAPE, ti.GUI.EXIT]:
exit()
elif e.key == 'r':
reset()
Expand Down
26 changes: 25 additions & 1 deletion python/taichi/misc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Event:
LMB = 'LMB'
MMB = 'MMB'
RMB = 'RMB'
EXIT = 'WMClose'
RELEASE = False
PRESS = True

Expand All @@ -44,6 +45,12 @@ def __init__(self, name, res=512, background_color=0x0):
self.core.set_profiler(
ti.core.get_current_program().get_profiler())

def __enter__(self):
return self

def __exit__(self, type, val, tb):
self.core = None # dereference to call GUI::~GUI()

def clear(self, color=None):
if color is None:
color = self.background_color
Expand Down Expand Up @@ -209,7 +216,8 @@ def get_event(self, *filter):
for e in self.get_events(*filter):
self.event = e
return True
return False
else:
return False

def get_events(self, *filter):
filter = filter and GUI.EventFilter(*filter) or None
Expand Down Expand Up @@ -254,10 +262,26 @@ def get_cursor_pos(self):
return pos[0], pos[1]

def has_key_pressed(self):
import warnings
warnings.warn(
'gui.has_key_pressed() is deprecated, use gui.get_event() instead.',
DeprecationWarning,
stacklevel=3)
if self.has_key_event():
self.get_key_event() # pop to update self.key_pressed
return len(self.key_pressed) != 0

@property
def running(self):
return not self.core.should_close

@running.setter
def running(self, value):
if value:
self.core.should_close = 0
elif not self.core.should_close:
self.core.should_close = 1


def rgb_to_hex(c):
to255 = lambda x: min(255, max(0, int(x * 255)))
Expand Down
14 changes: 9 additions & 5 deletions python/taichi/misc/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ def imshow(img, window_name='Taichi'):
img = img.to_numpy()
assert len(img.shape) in [2,
3], "Image must be either RGB/RGBA or greyscale"
gui = ti.GUI(window_name, res=img.shape[:2])
img = gui.cook_image(img)
while not gui.get_event(ti.GUI.ESCAPE):
gui.set_image(img)
gui.show()

with ti.GUI(window_name, res=img.shape[:2]) as gui:
img = gui.cook_image(img)
while gui.running:
if gui.get_event(ti.GUI.ESCAPE):
gui.running = False

gui.set_image(img)
gui.show()
20 changes: 20 additions & 0 deletions taichi/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ class GUIBaseX11 {
void *visual;
unsigned long window;
CXImage *img;
std::vector<char> wmDeleteMessage;
};

using GUIBase = GUIBaseX11;
Expand Down Expand Up @@ -470,6 +471,7 @@ class GUI : public GUIBase {
std::unique_ptr<Canvas> canvas;
float64 last_frame_time;
bool key_pressed;
int should_close{0};
std::vector<std::string> log_entries;
Vector2i cursor_pos;
bool button_status[3];
Expand Down Expand Up @@ -705,6 +707,12 @@ class GUI : public GUIBase {

void process_event();

void send_window_close_message() {
key_events.push_back(
GUI::KeyEvent{GUI::KeyEvent::Type::press, "WMClose", cursor_pos});
should_close++;
}

void mouse_event(MouseEvent e) {
if (e.type == MouseEvent::Type::press) {
button_status[0] = true;
Expand Down Expand Up @@ -777,6 +785,18 @@ class GUI : public GUIBase {
}
last_frame_time = taichi::Time::get_time();
redraw();
// Some old examples / users don't even provide a `break` statement for us
// to terminate loop. So we have to terminate the program with RuntimeError
// if ti.GUI.EXIT event is not processed. Pretty like SIGTERM, you can hook
// it, but you have to terminate after your handler is done.
if (should_close) {
if (++should_close > 5) {
// if the event is not processed in 5 frames, raise RuntimeError
throw std::string(
"Window close button clicked, exiting... (use `while gui.running` "
"to exit gracefully)");
}
}
process_event();
while (last_frame_interval.size() > 30) {
last_frame_interval.erase(last_frame_interval.begin());
Expand Down
7 changes: 2 additions & 5 deletions taichi/gui/win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ LRESULT CALLBACK WindowProc(HWND hwnd,
using namespace taichi;
int x, y;
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
exit(0);
return 0;
case WM_LBUTTONDOWN:
gui->mouse_event(
GUI::MouseEvent{GUI::MouseEvent::Type::press, gui->cursor_pos});
Expand Down Expand Up @@ -122,7 +118,8 @@ LRESULT CALLBACK WindowProc(HWND hwnd,
gui->cursor_pos});
break;
case WM_CLOSE:
exit(0);
// https://stackoverflow.com/questions/3155782/what-is-the-difference-between-wm-quit-wm-close-and-wm-destroy-in-a-windows-pr
gui->send_window_close_message();
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
Expand Down
Loading

0 comments on commit 4390448

Please sign in to comment.