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 8 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
18 changes: 18 additions & 0 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,24 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e.
mouse_x, mouse_y = gui.get_cursor_pos()


.. attribute:: gui.running

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

For example:

::

while gui.running:
if gui.get_event(ti.GUI.ESCAPE):
gui.running = False
archibate marked this conversation as resolved.
Show resolved Hide resolved

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


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
20 changes: 19 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 Down Expand Up @@ -173,7 +174,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 @@ -218,10 +220,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
2 changes: 1 addition & 1 deletion python/taichi/misc/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ def imread(filename, channels=0):
def imshow(img, winname='Taichi'):
img = cook_image(img)
gui = ti.GUI(winname, res=img.shape[:2])
while not gui.get_event(ti.GUI.ESCAPE):
while not gui.get_event(ti.GUI.ESCAPE, ti.GUI.EXIT):
gui.set_image(img)
gui.show()
14 changes: 14 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;
void *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 @@ -777,6 +779,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: 5 additions & 2 deletions taichi/gui/win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@ LRESULT CALLBACK WindowProc(HWND hwnd,
gui->cursor_pos});
break;
case WM_CLOSE:
exit(0);
break;
gui->key_events.push_back(GUI::KeyEvent{GUI::KeyEvent::Type::press,
"WMClose", gui->cursor_pos});
// 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
// Not to let DefWindowProc destroy the window yet:
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Expand Down
15 changes: 15 additions & 0 deletions taichi/gui/x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#if defined(TI_GUI_X11)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstdlib>

// Undo terrible unprefixed macros in X.h
#ifdef None
Expand Down Expand Up @@ -73,6 +74,14 @@ void GUI::process_event() {
switch (ev.type) {
case Expose:
break;
case ClientMessage:
// https://stackoverflow.com/questions/10792361/how-do-i-gracefully-exit-an-x11-event-loop
if (ev.xclient.data.l[0] == *(Atom *)wmDeleteMessage) {
key_events.push_back(
archibate marked this conversation as resolved.
Show resolved Hide resolved
KeyEvent{KeyEvent::Type::press, "WMClose", cursor_pos});
should_close++;
}
break;
case MotionNotify:
set_mouse_pos(ev.xbutton.x, height - ev.xbutton.y - 1);
mouse_event(MouseEvent{MouseEvent::Type::move, cursor_pos});
Expand Down Expand Up @@ -114,6 +123,10 @@ void GUI::create_window() {
ButtonPressMask | ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPress | ButtonReleaseMask | EnterWindowMask |
LeaveWindowMask | PointerMotionMask);
wmDeleteMessage = std::malloc(sizeof(Atom));
archibate marked this conversation as resolved.
Show resolved Hide resolved
*(Atom *)wmDeleteMessage =
XInternAtom((Display *)display, "WM_DELETE_WINDOW", False);
XSetWMProtocols((Display *)display, window, (Atom *)wmDeleteMessage, 1);
XMapWindow((Display *)display, window);
img = new CXImage((Display *)display, (Visual *)visual, width, height);
}
Expand All @@ -129,6 +142,8 @@ void GUI::set_title(std::string title) {
}

GUI::~GUI() {
XCloseDisplay((Display *)display);
std::free(wmDeleteMessage);
delete img;
}

Expand Down
1 change: 1 addition & 0 deletions taichi/python/export_visual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ void export_visual(py::module &m) {
using Circle = Canvas::Circle;
py::class_<GUI>(m, "GUI")
.def(py::init<std::string, Vector2i>())
.def_readwrite("should_close", &GUI::should_close)
.def("get_canvas", &GUI::get_canvas, py::return_value_policy::reference)
.def("set_img",
[&](GUI *gui, std::size_t ptr) {
Expand Down