From a93efc54421eb02125db0c48dfab80b0b7741323 Mon Sep 17 00:00:00 2001 From: archibate <1931127624@qq.com> Date: Tue, 14 Jul 2020 00:27:35 +0800 Subject: [PATCH 1/4] [GUI] Support slider, label and button as widgets --- examples/gui_widgets.py | 28 ++++++++++++++++++++++++++++ python/taichi/misc/gui.py | 33 +++++++++++++++++++++++++++++++++ taichi/gui/gui.h | 1 + taichi/python/export_visual.cpp | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 examples/gui_widgets.py diff --git a/examples/gui_widgets.py b/examples/gui_widgets.py new file mode 100644 index 0000000000000..f9cdf5603d3b5 --- /dev/null +++ b/examples/gui_widgets.py @@ -0,0 +1,28 @@ +import taichi as ti + +gui = ti.GUI('GUI widgets') + +radius = gui.slider('Radius', 1, 50, step=1) +xcoor = gui.label('X-coordinate') +okay = gui.button('OK') + +xcoor.value = 0.5 +radius.value = 10 + +while gui.running: + for e in gui.get_events(gui.PRESS): + if e.key == gui.ESCAPE: + gui.running = False + elif e.key == 'a': + xcoor.value -= 0.05 + elif e.key == 'd': + xcoor.value += 0.05 + elif e.key == 's': + radius.value -= 1 + elif e.key == 'w': + radius.value += 1 + elif e.key == okay: + print('OK clicked') + + gui.circle((xcoor.value, 0.5), radius=radius.value) + gui.show() diff --git a/python/taichi/misc/gui.py b/python/taichi/misc/gui.py index 3f9eb43841344..41260d4562f81 100644 --- a/python/taichi/misc/gui.py +++ b/python/taichi/misc/gui.py @@ -55,6 +55,37 @@ def __enter__(self): def __exit__(self, type, val, tb): self.core = None # dereference to call GUI::~GUI() + ## Widget system + + class WidgetValue: + def __init__(self, gui, wid): + self.gui = gui + self.wid = wid + + @property + def value(self): + return self.gui.core.get_widget_value(self.wid) + + @value.setter + def value(self, value): + self.gui.core.set_widget_value(self.wid, value) + + + def slider(self, text, minimum, maximum, step=1): + wid = self.core.make_slider(text, minimum, minimum, maximum, step) + return GUI.WidgetValue(self, wid) + + def label(self, text): + wid = self.core.make_label(text, 0) + return GUI.WidgetValue(self, wid) + + def button(self, text, event_name=None): + event_name = event_name or f'WidgetButton_{text}' + self.core.make_button(text, event_name) + return event_name + + ## Drawing system + def clear(self, color=None): if color is None: color = self.background_color @@ -204,6 +235,8 @@ def show(self, file=None): self.clear() self.frame += 1 + ## Event system + class EventFilter: def __init__(self, *filter): self.filter = set() diff --git a/taichi/gui/gui.h b/taichi/gui/gui.h index 37be87a396ab6..abafbe0db455b 100644 --- a/taichi/gui/gui.h +++ b/taichi/gui/gui.h @@ -488,6 +488,7 @@ class GUI : public GUIBase { Vector2i cursor_pos; bool button_status[3]; int widget_height; + std::vector> widget_values; void set_mouse_pos(int x, int y) { cursor_pos = Vector2i(x, y); diff --git a/taichi/python/export_visual.cpp b/taichi/python/export_visual.cpp index 3c7c122bb5ea8..3eaf118626259 100644 --- a/taichi/python/export_visual.cpp +++ b/taichi/python/export_visual.cpp @@ -41,6 +41,38 @@ void export_visual(py::module &m) { img.get_data_size()); }) .def("screenshot", &GUI::screenshot) + .def("set_widget_value", + [](GUI *gui, int wid, float value) { + *gui->widget_values.at(wid) = value; + }) + .def("get_widget_value", + [](GUI *gui, int wid) -> float { + return *gui->widget_values.at(wid); + }) + .def("make_slider", + [](GUI *gui, std::string text, float init_value, + float minimum, float maximum, float step) { + auto val = std::make_unique(init_value); + auto val_ptr = val.get(); + gui->widget_values.push_back(std::move(val)); + gui->slider(text, *val_ptr, minimum, maximum, step); + return gui->widget_values.size() - 1; + }) + .def("make_label", + [](GUI *gui, std::string text, float init_value) { + auto val = std::make_unique(init_value); + auto val_ptr = val.get(); + gui->widget_values.push_back(std::move(val)); + gui->label(text, *val_ptr); + return gui->widget_values.size() - 1; + }) + .def("make_button", + [](GUI *gui, std::string text, std::string event_name) { + gui->button(text, [=]() { + gui->key_events.push_back( + GUI::KeyEvent{GUI::KeyEvent::Type::press, event_name, gui->cursor_pos}); + }); + }) .def("canvas_untransform", &GUI::canvas_untransform) .def("has_key_event", &GUI::has_key_event) .def("wait_key_event", &GUI::wait_key_event) From 3e86f8075eb9e68650f4925e40dc037dc9305728 Mon Sep 17 00:00:00 2001 From: Taichi Gardener Date: Mon, 13 Jul 2020 12:29:41 -0400 Subject: [PATCH 2/4] [skip ci] enforce code format --- python/taichi/misc/gui.py | 1 - taichi/python/export_visual.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/taichi/misc/gui.py b/python/taichi/misc/gui.py index 41260d4562f81..89c2e322ab103 100644 --- a/python/taichi/misc/gui.py +++ b/python/taichi/misc/gui.py @@ -70,7 +70,6 @@ def value(self): def value(self, value): self.gui.core.set_widget_value(self.wid, value) - def slider(self, text, minimum, maximum, step=1): wid = self.core.make_slider(text, minimum, minimum, maximum, step) return GUI.WidgetValue(self, wid) diff --git a/taichi/python/export_visual.cpp b/taichi/python/export_visual.cpp index 3eaf118626259..a98c8beb4c305 100644 --- a/taichi/python/export_visual.cpp +++ b/taichi/python/export_visual.cpp @@ -50,8 +50,8 @@ void export_visual(py::module &m) { return *gui->widget_values.at(wid); }) .def("make_slider", - [](GUI *gui, std::string text, float init_value, - float minimum, float maximum, float step) { + [](GUI *gui, std::string text, float init_value, float minimum, + float maximum, float step) { auto val = std::make_unique(init_value); auto val_ptr = val.get(); gui->widget_values.push_back(std::move(val)); @@ -69,8 +69,8 @@ void export_visual(py::module &m) { .def("make_button", [](GUI *gui, std::string text, std::string event_name) { gui->button(text, [=]() { - gui->key_events.push_back( - GUI::KeyEvent{GUI::KeyEvent::Type::press, event_name, gui->cursor_pos}); + gui->key_events.push_back(GUI::KeyEvent{ + GUI::KeyEvent::Type::press, event_name, gui->cursor_pos}); }); }) .def("canvas_untransform", &GUI::canvas_untransform) From b489d1b851250e67f43a879d5c2a2c80be6f2226 Mon Sep 17 00:00:00 2001 From: archibate <1931127624@qq.com> Date: Tue, 14 Jul 2020 00:53:34 +0800 Subject: [PATCH 3/4] [skip ci] update doc --- docs/gui.rst | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/gui.rst b/docs/gui.rst index bdc536464be87..7eda641ba111b 100644 --- a/docs/gui.rst +++ b/docs/gui.rst @@ -161,6 +161,8 @@ Paint on a window Draw a line of text on screen. +.. _gui_event: + Event processing ---------------- @@ -315,12 +317,64 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e. mouse_x, mouse_y = gui.get_cursor_pos() +GUI Widgets +----------- + +Sometimes it's more intuitive to use widgets like slider, button to control program variables +instead of chaotic keyboard bindings. Taichi GUI provides a set of widgets that hopefully +could make variable control more intuitive: + + +.. function:: gui.slider(text, minimum, maximum, step=1) + + :parameter text: (str) the text to be displayed above this slider. + :parameter minumum: (float) the minimum value of the slider value. + :parameter maxumum: (float) the maximum value of the slider value. + :parameter step: (float) the step between two separate value. + + :return: (WidgetValue) a value getter / setter, see :class:`WidgetValue`. + + The widget will be display as: ``{text}: {value:.3f}``, followed with a slider. + + +.. function:: gui.label(text) + + :parameter text: (str) the text to be displayed in the label. + + :return: (WidgetValue) a value getter / setter, see :class:`WidgetValue`. + + The widget will be display as: ``{text}: {value:.3f}``. + + +.. function:: gui.button(text) + + :parameter text: (str) the text to be displayed in the button. + + :return: (EventKey) the event key for this button, see :ref:`gui_event`. + + +.. class:: WidgetValue + + A getter / setter for widget values. + + .. attribute:: value + + Get / set the current value in the widget where we're returned from. + + For example:: + + radius = gui.slider('Radius', 1, 50) + + while gui.running: + print('The radius now is', radius.value) + gui.show() + Image I/O --------- .. function:: gui.get_image() - :return a ``np.ndarray`` which is the current image shown on the GUI. + :return: a ``np.ndarray`` which is the current image shown on the GUI. Get the RGBA shown image from the current GUI system which has four channels. From 69a4e97988a3b8005f2ad3144a24894a9f05404c Mon Sep 17 00:00:00 2001 From: archibate <1931127624@qq.com> Date: Tue, 14 Jul 2020 00:55:54 +0800 Subject: [PATCH 4/4] [skip ci] doc optional --- docs/gui.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/gui.rst b/docs/gui.rst index 7eda641ba111b..394b68613c358 100644 --- a/docs/gui.rst +++ b/docs/gui.rst @@ -330,7 +330,7 @@ could make variable control more intuitive: :parameter text: (str) the text to be displayed above this slider. :parameter minumum: (float) the minimum value of the slider value. :parameter maxumum: (float) the maximum value of the slider value. - :parameter step: (float) the step between two separate value. + :parameter step: (optional, float) the step between two separate value. :return: (WidgetValue) a value getter / setter, see :class:`WidgetValue`. @@ -346,9 +346,10 @@ could make variable control more intuitive: The widget will be display as: ``{text}: {value:.3f}``. -.. function:: gui.button(text) +.. function:: gui.button(text, event_name=None) :parameter text: (str) the text to be displayed in the button. + :parameter event_name: (optional, str) customize the event name. :return: (EventKey) the event key for this button, see :ref:`gui_event`. @@ -367,6 +368,9 @@ could make variable control more intuitive: while gui.running: print('The radius now is', radius.value) + ... + radius.value += 0.01 + ... gui.show() Image I/O