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] Support slider, label and button as widgets #1490

Merged
merged 4 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
60 changes: 59 additions & 1 deletion docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ Paint on a window
Draw a line of text on screen.


.. _gui_event:

Event processing
----------------

Expand Down Expand Up @@ -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.

Expand Down
28 changes: 28 additions & 0 deletions examples/gui_widgets.py
Original file line number Diff line number Diff line change
@@ -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()
32 changes: 32 additions & 0 deletions python/taichi/misc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions taichi/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ class GUI : public GUIBase {
Vector2i cursor_pos;
bool button_status[3];
int widget_height;
std::vector<std::unique_ptr<float>> widget_values;

void set_mouse_pos(int x, int y) {
cursor_pos = Vector2i(x, y);
Expand Down
32 changes: 32 additions & 0 deletions taichi/python/export_visual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why a label must have a floating value? And there's no way to change its text?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run my example and use key 'a' and 'd'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying the printed text as a whole cannot be changed. Instead, it seems that only the floating value part could change? But now I looked at Label, I guess this is a restriction from the GUI system itself..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think people could use gui.text for more flexibility, given that the label is provided by the legacy C++ part, let's try improve it iapr, instead of itpr.

[](GUI *gui, std::string text, float init_value) {
auto val = std::make_unique<float>(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)
Expand Down