HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
toggle_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
9#pragma once
10
11#include "../GUI/module.hpp"
12#include "toggle_delegate.hpp"
13
14namespace hi { inline namespace v1 {
15
16template<typename Context>
18
51template<fixed_string Name = "">
52class toggle_widget final : public widget {
53public:
54 using super = widget;
55 using delegate_type = toggle_delegate;
56 constexpr static auto prefix = Name / "toggle";
57
58 enum class cell_type { toggle, label };
59
63
66 observer<label> on_label = tr("on");
67
70 observer<label> off_label = tr("off");
71
74 observer<label> other_label = tr("other");
75
78 observer<hi::alignment> alignment = alignment::top_left();
79
91 {
92 hi_assert_not_null(this->delegate);
93 this->set_attributes<0>(hi_forward(attributes)...);
94
95 _on_label_widget = std::make_unique<label_widget<prefix / "on">>(this, on_label, alignment);
96 _off_label_widget = std::make_unique<label_widget<prefix / "off">>(this, off_label, alignment);
97 _other_label_widget = std::make_unique<label_widget<prefix / "other">>(this, other_label, alignment);
98
99 _grid.add_cell(0, 0, cell_type::toggle);
100 _grid.add_cell(1, 0, cell_type::label);
101
102 _delegate_cbt = this->delegate->subscribe([&] {
103 ++global_counter<"toggle_widget:delegate:redraw">;
104 hi_assert_not_null(this->delegate);
105 state = this->delegate->state(this);
106
107 _on_label_widget->mode = *state == widget_state::on ? widget_mode::display : widget_mode::invisible;
108 _off_label_widget->mode = *state == widget_state::off ? widget_mode::display : widget_mode::invisible;
109 _other_label_widget->mode = *state == widget_state::other ? widget_mode::display : widget_mode::invisible;
110
111 process_event({gui_event_type::window_redraw});
112 });
113 this->delegate->init(*this);
114 }
115
126 template<different_from<std::shared_ptr<delegate_type>> Value, toggle_widget_attribute... Attributes>
127 toggle_widget(widget *parent, Value&& value, Attributes&&...attributes) noexcept
128 requires requires { make_default_toggle_delegate(hi_forward(value)); }
130 {
131 }
132
144 template<
145 different_from<std::shared_ptr<delegate_type>> Value,
146 forward_of<observer<observer_decay_t<Value>>> OnValue,
147 toggle_widget_attribute... Attributes>
148 toggle_widget(widget *parent, Value&& value, OnValue&& on_value, Attributes&&...attributes) noexcept
149 requires requires { make_default_toggle_delegate(hi_forward(value), hi_forward(on_value)); }
151 {
152 }
153
166 template<
167 different_from<std::shared_ptr<delegate_type>> Value,
168 forward_of<observer<observer_decay_t<Value>>> OnValue,
169 forward_of<observer<observer_decay_t<Value>>> OffValue,
170 toggle_widget_attribute... Attributes>
171 toggle_widget(widget *parent, Value&& value, OnValue&& on_value, OffValue&& off_value, Attributes&&...attributes) noexcept
172 requires requires { make_default_toggle_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)); }
173 :
175 parent,
176 make_default_toggle_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)),
177 hi_forward(attributes)...)
178 {
179 }
180
182 [[nodiscard]] box_constraints update_constraints() noexcept override
183 {
184 for (auto& cell : _grid) {
185 if (cell.value == cell_type::toggle) {
186 cell.set_constraints(
188 theme<prefix>.size(this),
189 theme<prefix>.size(this),
190 theme<prefix>.size(this),
191 *alignment,
192 theme<prefix>.margin(this)});
193
194 } else if (cell.value == cell_type::label) {
195 cell.set_constraints(
196 max(_on_label_widget->update_constraints(),
197 _off_label_widget->update_constraints(),
198 _other_label_widget->update_constraints()));
199
200 } else {
202 }
203 }
204
205 return _grid.constraints(os_settings::left_to_right());
206 }
207
208 void set_layout(widget_layout const& context) noexcept override
209 {
210 if (compare_store(this->layout, context)) {
211 _grid.set_layout(context.shape, theme<prefix>.cap_height(this));
212 }
213
214 for (hilet& cell : _grid) {
215 if (cell.value == cell_type::toggle) {
216 // The distance between bottom of the 'pip' and bottom of the 'toggle'
217 // is equal to the distance between the left of the 'pip' and
218 // the left of the 'toggle'.
219 _pip_edge_distance = (cell.shape.height() - theme<prefix / "pip">.height(this)) / 2;
220 _pip_move_range = cell.shape.width() - _pip_edge_distance * 2 - theme<prefix / "pip">.width(this);
221
222 } else if (cell.value == cell_type::label) {
223 _on_label_widget->set_layout(context.transform(cell.shape, 0.0f));
224 _off_label_widget->set_layout(context.transform(cell.shape, 0.0f));
225 _other_label_widget->set_layout(context.transform(cell.shape, 0.0f));
226
227 } else {
229 }
230 }
231 }
232
233 void draw(widget_draw_context& context) noexcept override
234 {
235 if (*this->mode > widget_mode::invisible and overlaps(context, this->layout)) {
236 for (hilet& cell : _grid) {
237 if (cell.value == cell_type::toggle) {
238 draw_toggle_button(context, cell.shape);
239 draw_toggle_pip(context, cell.shape);
240
241 } else if (cell.value == cell_type::label) {
242 _on_label_widget->draw(context);
243 _off_label_widget->draw(context);
244 _other_label_widget->draw(context);
245
246 } else {
248 }
249 }
250 }
251 }
252
253 [[nodiscard]] generator<widget const&> children(bool include_invisible) const noexcept override
254 {
255 co_yield *_on_label_widget;
256 co_yield *_off_label_widget;
257 co_yield *_other_label_widget;
258 }
259
260 [[nodiscard]] hitbox hitbox_test(point2i position) const noexcept final
261 {
262 hi_axiom(loop::main().on_thread());
263
264 if (*mode >= widget_mode::partial and layout.contains(position)) {
265 return {id, layout.elevation, hitbox_type::button};
266 } else {
267 return {};
268 }
269 }
270
271 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
272 {
273 hi_axiom(loop::main().on_thread());
274 return *mode >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal);
275 }
276
277 void activate() noexcept
278 {
280 delegate->activate(*this);
281 this->_state_changed();
282 }
283
284 bool handle_event(gui_event const& event) noexcept override
285 {
286 hi_axiom(loop::main().on_thread());
287
288 switch (event.type()) {
289 case gui_event_type::gui_activate:
290 if (*mode >= widget_mode::partial) {
291 activate();
292 return true;
293 }
294 break;
295
296 case gui_event_type::mouse_down:
297 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
298 clicked = true;
300 return true;
301 }
302 break;
303
304 case gui_event_type::mouse_up:
305 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
306 clicked = false;
307
308 if (layout.rectangle().contains(event.mouse().position)) {
309 handle_event(gui_event_type::gui_activate);
310 }
312 return true;
313 }
314 break;
315
316 default:;
317 }
318
319 return super::handle_event(event);
320 }
322private:
323 static constexpr std::chrono::nanoseconds _animation_duration = std::chrono::milliseconds(150);
324
325 grid_layout<cell_type> _grid;
326
327 std::unique_ptr<label_widget<join_path(prefix, "on")>> _on_label_widget;
328 std::unique_ptr<label_widget<join_path(prefix, "off")>> _off_label_widget;
329 std::unique_ptr<label_widget<join_path(prefix, "other")>> _other_label_widget;
330
331 notifier<>::callback_token _delegate_cbt;
332
333 animator<float> _animated_value = _animation_duration;
334 int _pip_move_range;
335 int _pip_edge_distance;
336
337 void draw_toggle_button(widget_draw_context& context, box_shape const& shape) noexcept
338 {
339 context.draw_box(
340 this->layout,
341 shape.rectangle,
342 theme<prefix>.background_color(this),
343 theme<prefix>.border_color(this),
344 theme<prefix>.border_width(this),
346 theme<prefix>.border_radius(this));
347 }
348
349 void draw_toggle_pip(widget_draw_context& context, box_shape const& shape) noexcept
350 {
351 _animated_value.update(this->state != widget_state::off ? 1.0f : 0.0f, context.display_time_point);
352 if (_animated_value.is_animating()) {
353 this->request_redraw();
354 }
355
356 hilet move_offset = [&] {
357 if (os_settings::left_to_right()) {
358 return _pip_move_range * _animated_value.current_value();
359 } else {
360 return _pip_move_range * (1.0f - _animated_value.current_value());
361 }
362 }();
363
364 hilet pip_rectangle = aarectanglei{point2i{_pip_edge_distance, _pip_edge_distance}, theme<prefix / "pip">.size(this)};
365 hilet pip_rectangle_ =
366 translate3{move_offset + shape.x(), 0.0f + shape.y(), 0.1f} * narrow_cast<aarectangle>(pip_rectangle);
367
368 context.draw_box(
369 this->layout,
370 pip_rectangle,
371 theme<prefix / "pip">.background_color(this),
372 theme<prefix / "pip">.border_color(this),
373 theme<prefix / "pip">.border_width(this),
375 theme<prefix / "pip">.border_radius(this));
376 }
377
378 template<size_t LabelCount>
379 void set_attributes() noexcept
380 {
381 }
382
383 template<size_t LabelCount>
384 void set_attributes(toggle_widget_attribute auto&& first, toggle_widget_attribute auto&&...rest) noexcept
385 {
386 if constexpr (forward_of<decltype(first), observer<hi::label>>) {
387 if constexpr (LabelCount == 0) {
388 on_label = first;
389 off_label = first;
390 other_label = hi_forward(first);
391 } else if constexpr (LabelCount == 1) {
392 other_label.reset();
393 off_label.reset();
394 off_label = hi_forward(first);
395 } else if constexpr (LabelCount == 2) {
396 other_label = hi_forward(first);
397 } else {
399 }
400 set_attributes<LabelCount + 1>(hi_forward(rest)...);
401
402 } else if constexpr (forward_of<decltype(first), observer<hi::alignment>>) {
403 alignment = hi_forward(first);
404 set_attributes<LabelCount>(hi_forward(rest)...);
405
406 } else {
408 }
409 }
410};
411
412}} // namespace hi::v1
Defines toggle_delegate and some default toggle_delegate delegates.
#define hi_static_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:323
#define hi_no_default(...)
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:279
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hi_assert_not_null(x,...)
Assert if an expression is not nullptr.
Definition assert.hpp:238
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
@ window_redraw
Request that part of the window gets redrawn on the next frame.
std::shared_ptr< toggle_delegate > make_default_toggle_delegate(auto &&value, auto &&...args) noexcept
Make a shared pointer to a toggle-button delegate.
Definition toggle_delegate.hpp:144
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
@ display
The widget is in display-only mode.
DOXYGEN BUG.
Definition algorithm.hpp:13
constexpr auto join_path(fixed_string< L > const &lhs, fixed_string< R > const &rhs) noexcept
lhs / rhs
Definition fixed_string.hpp:273
geometry/margins.hpp
Definition cache.hpp:11
@ inside
The border is drawn inside the edge of a quad.
@ outside
The border is drawn outside the edge of a quad.
@ off
The widget in the off-state.
@ other
The widget is in the other-state.
@ on
The widget is in the on-state.
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition utility.hpp:212
auto theme
A tagged global variable to a theme model for a widget's component.
Definition theme_model.hpp:578
constexpr bool contains(point< value_type, 2 > const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition axis_aligned_rectangle.hpp:265
Definition widget.hpp:26
widget_id id
The numeric identifier of a widget.
Definition widget.hpp:35
virtual void request_redraw() const noexcept
Request the widget to be redrawn on the next frame.
Definition widget.hpp:265
observer< bool > clicked
The widget is being clicked by the mouse.
Definition widget.hpp:57
observer< widget_state > state
The state of the widget.
Definition widget.hpp:65
virtual bool handle_event(gui_event const &event) noexcept
Handle command.
Definition widget.hpp:274
widget * parent
Pointer to the parent widget.
Definition widget.hpp:40
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:49
constexpr bool contains(point3i mouse_position) const noexcept
Check if the mouse position is inside the widget.
Definition widget_layout.hpp:126
float elevation
The elevation of the widget above the window.
Definition widget_layout.hpp:72
2D constraints.
Definition box_constraints.hpp:22
constexpr reference add_cell(size_t first_column, size_t first_row, size_t last_column, size_t last_row, Value &&value, bool beyond_maximum=false) noexcept
Check if the cell on the grid is already in use.
Definition grid_layout.hpp:1056
constexpr void set_layout(box_shape const &shape, int baseline_adjustment) noexcept
Layout the cells based on the width and height.
Definition grid_layout.hpp:1132
The GUI widget displays and lays out text together with an icon.
Definition label_widget.hpp:42
A toggle delegate controls the state of a toggle widget.
Definition toggle_delegate.hpp:18
A GUI widget that permits the user to make a binary choice.
Definition toggle_widget.hpp:52
observer< label > other_label
The label to show when the button is in the 'other' state.
Definition toggle_widget.hpp:74
observer< label > on_label
The label to show when the button is in the 'on' state.
Definition toggle_widget.hpp:66
toggle_widget(widget *parent, Value &&value, OnValue &&on_value, Attributes &&...attributes) noexcept
Construct a toggle widget with a default button delegate.
Definition toggle_widget.hpp:148
observer< hi::alignment > alignment
The alignment of the button and on/off/other label.
Definition toggle_widget.hpp:78
toggle_widget(widget *parent, Value &&value, Attributes &&...attributes) noexcept
Construct a toggle widget with a default button delegate.
Definition toggle_widget.hpp:127
std::shared_ptr< delegate_type > delegate
The delegate that controls the button widget.
Definition toggle_widget.hpp:62
toggle_widget(widget *parent, std::shared_ptr< delegate_type > delegate, toggle_widget_attribute auto &&...attributes) noexcept
Construct a toggle widget.
Definition toggle_widget.hpp:89
toggle_widget(widget *parent, Value &&value, OnValue &&on_value, OffValue &&off_value, Attributes &&...attributes) noexcept
Construct a toggle widget with a default button delegate.
Definition toggle_widget.hpp:171
observer< label > off_label
The label to show when the button is in the 'off' state.
Definition toggle_widget.hpp:70
Definition label_widget.hpp:26
Definition toggle_widget.hpp:17
T max(T... args)
T move(T... args)