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] Add ti.GUI.EXIT event to close a window #1152

Merged
merged 20 commits into from
Jun 7, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
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 button / X button of a widnow.
archibate marked this conversation as resolved.
Show resolved Hide resolved
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(
archibate marked this conversation as resolved.
Show resolved Hide resolved
"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
archibate marked this conversation as resolved.
Show resolved Hide resolved
gui->send_window_close_message();
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
Expand Down
Loading