diff --git a/docs/gui.rst b/docs/gui.rst index bdc536464be87..394b68613c358 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,68 @@ 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: (optional, 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, 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`. + + +.. 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) + ... + radius.value += 0.01 + ... + 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. 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..89c2e322ab103 100644 --- a/python/taichi/misc/gui.py +++ b/python/taichi/misc/gui.py @@ -55,6 +55,36 @@ 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 +234,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..a98c8beb4c305 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)