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
61
64 observer<label> on_label = tr("on");
65
68 observer<label> off_label = tr("off");
69
72 observer<label> other_label = tr("other");
73
76 observer<hi::alignment> alignment = alignment::top_left();
77
89 {
90 hi_assert_not_null(this->delegate);
91 this->set_attributes<0>(hi_forward(attributes)...);
92
93 _on_label_widget = std::make_unique<label_widget<prefix / "on">>(this, on_label, alignment);
94 _off_label_widget = std::make_unique<label_widget<prefix / "off">>(this, off_label, alignment);
95 _other_label_widget = std::make_unique<label_widget<prefix / "other">>(this, other_label, alignment);
96
97 _grid.add_cell(0, 0, cell_type::button);
98 _grid.add_cell(1, 0, cell_type::label);
99
100 _delegate_cbt = this->delegate->subscribe([&] {
101 ++global_counter<"toggle_widget:delegate:redraw">;
102 hi_assert_not_null(this->delegate);
103 state = this->delegate->state(this);
104
105 _on_label_widget->mode = *state == widget_state::on ? widget_mode::display : widget_mode::invisible;
106 _off_label_widget->mode = *state == widget_state::off ? widget_mode::display : widget_mode::invisible;
107 _other_label_widget->mode = *state == widget_state::other ? widget_mode::display : widget_mode::invisible;
108
109 process_event({gui_event_type::window_redraw});
110 });
111 this->delegate->init(*this);
112 (*_delegate_cbt)();
113 }
114
125 template<different_from<std::shared_ptr<delegate_type>> Value, toggle_widget_attribute... Attributes>
126 toggle_widget(widget *parent, Value&& value, Attributes&&...attributes) noexcept
127 requires requires { make_default_toggle_delegate(hi_forward(value)); }
129 {
130 }
131
143 template<
144 different_from<std::shared_ptr<delegate_type>> Value,
145 forward_of<observer<observer_decay_t<Value>>> OnValue,
146 toggle_widget_attribute... Attributes>
147 toggle_widget(widget *parent, Value&& value, OnValue&& on_value, Attributes&&...attributes) noexcept
148 requires requires { make_default_toggle_delegate(hi_forward(value), hi_forward(on_value)); }
150 {
151 }
152
165 template<
166 different_from<std::shared_ptr<delegate_type>> Value,
167 forward_of<observer<observer_decay_t<Value>>> OnValue,
168 forward_of<observer<observer_decay_t<Value>>> OffValue,
169 toggle_widget_attribute... Attributes>
170 toggle_widget(widget *parent, Value&& value, OnValue&& on_value, OffValue&& off_value, Attributes&&...attributes) noexcept
171 requires requires { make_default_toggle_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)); }
172 :
174 parent,
175 make_default_toggle_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)),
176 hi_forward(attributes)...)
177 {
178 }
179
181 [[nodiscard]] box_constraints update_constraints() noexcept override
182 {
183 for (auto& cell : _grid) {
184 if (cell.value == cell_type::button) {
185 cell.set_constraints(box_constraints{
186 theme<prefix>.size(this),
187 theme<prefix>.size(this),
188 theme<prefix>.size(this),
189 *alignment,
190 theme<prefix>.margin(this),
191 -vector2::infinity()});
192
193 } else if (cell.value == cell_type::label) {
194 cell.set_constraints(
195 max(_on_label_widget->update_constraints(),
196 _off_label_widget->update_constraints(),
197 _other_label_widget->update_constraints()));
198
199 } else {
201 }
202 }
203
204 return _grid.constraints(os_settings::left_to_right());
205 }
206
207 void set_layout(widget_layout const& context) noexcept override
208 {
209 if (compare_store(layout, context)) {
210 _grid.set_layout(context.shape, theme<prefix>.cap_height(this));
211 }
212
213 for (hilet& cell : _grid) {
214 if (cell.value == cell_type::button) {
215 _button_rectangle = align(cell.shape.rectangle, theme<prefix>.size(this), *alignment);
216
217 // The distance between bottom of the 'pip' and bottom of the 'button'
218 // is equal to the distance between the left of the 'pip' and
219 // the left of the 'button'.
220 _pip_edge_distance = (_button_rectangle.height() - theme<prefix / "pip">.height(this)) / 2;
221 _pip_move_range = _button_rectangle.width() - _pip_edge_distance * 2 - theme<prefix / "pip">.width(this);
222
223 } else if (cell.value == cell_type::label) {
224 _on_label_widget->set_layout(context.transform(cell.shape, 0.0f));
225 _off_label_widget->set_layout(context.transform(cell.shape, 0.0f));
226 _other_label_widget->set_layout(context.transform(cell.shape, 0.0f));
227
228 } else {
230 }
231 }
232 }
233
234 void draw(widget_draw_context& context) noexcept override
235 {
236 if (*mode > widget_mode::invisible and overlaps(context, layout)) {
237 for (hilet& cell : _grid) {
238 if (cell.value == cell_type::button) {
239 draw_button(context, _button_rectangle);
240 draw_pip(context, _button_rectangle);
241
242 } else if (cell.value == cell_type::label) {
243 _on_label_widget->draw(context);
244 _off_label_widget->draw(context);
245 _other_label_widget->draw(context);
246
247 } else {
249 }
250 }
251 }
252 }
253
254 [[nodiscard]] generator<widget const&> children(bool include_invisible) const noexcept override
255 {
256 co_yield *_on_label_widget;
257 co_yield *_off_label_widget;
258 co_yield *_other_label_widget;
259 }
260
261 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept final
262 {
263 hi_axiom(loop::main().on_thread());
264
265 if (*mode >= widget_mode::partial and layout.contains(position)) {
266 return {id, layout.elevation, hitbox_type::button};
267 } else {
268 return {};
269 }
270 }
271
272 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
273 {
274 hi_axiom(loop::main().on_thread());
275 return *mode >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal);
276 }
277
278 void activate() noexcept
279 {
281 delegate->activate(*this);
282 _state_changed();
283 }
284
285 bool handle_event(gui_event const& event) noexcept override
286 {
287 hi_axiom(loop::main().on_thread());
288
289 switch (event.type()) {
290 case gui_event_type::gui_activate:
291 if (*mode >= widget_mode::partial) {
292 activate();
293 return true;
294 }
295 break;
296
297 case gui_event_type::mouse_down:
298 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
299 clicked = true;
301 return true;
302 }
303 break;
304
305 case gui_event_type::mouse_up:
306 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
307 clicked = false;
308
309 if (layout.rectangle().contains(event.mouse().position)) {
310 handle_event(gui_event_type::gui_activate);
311 }
313 return true;
314 }
315 break;
316
317 default:;
318 }
319
320 return super::handle_event(event);
321 }
323private:
324 enum class cell_type { button, label };
325
326 static constexpr std::chrono::nanoseconds _animation_duration = std::chrono::milliseconds(150);
327
328 grid_layout<cell_type> _grid;
329
330 std::unique_ptr<label_widget<prefix / "on">> _on_label_widget;
331 std::unique_ptr<label_widget<prefix / "off">> _off_label_widget;
332 std::unique_ptr<label_widget<prefix / "other">> _other_label_widget;
333
334 notifier<>::callback_token _delegate_cbt;
335
336 aarectangle _button_rectangle;
337
338 animator<float> _animated_value = _animation_duration;
339 float _pip_move_range;
340 float _pip_edge_distance;
341
342 void draw_button(widget_draw_context& context, aarectangle shape) noexcept
343 {
344 context.draw_box(
345 layout,
346 shape,
347 theme<prefix>.background_color(this),
348 theme<prefix>.border_color(this),
349 theme<prefix>.border_width(this),
351 theme<prefix>.border_radius(this));
352 }
353
354 void draw_pip(widget_draw_context& context, aarectangle shape) noexcept
355 {
356 _animated_value.update(state != widget_state::off ? 1.0f : 0.0f, context.display_time_point);
357 if (_animated_value.is_animating()) {
359 }
360
361 hilet move_offset = [&] {
362 if (os_settings::left_to_right()) {
363 return _pip_move_range * _animated_value.current_value();
364 } else {
365 return _pip_move_range * (1.0f - _animated_value.current_value());
366 }
367 }();
368
369 hilet pip_translate = translate3{shape.x() + _pip_edge_distance + move_offset, shape.y() + _pip_edge_distance, 0.1f};
370 hilet pip_rectangle = pip_translate *aarectangle{theme<prefix / "pip">.size(this)};
371
372 context.draw_box(
373 layout,
374 pip_rectangle,
375 theme<prefix / "pip">.background_color(this),
376 theme<prefix / "pip">.border_color(this),
377 theme<prefix / "pip">.border_width(this),
379 theme<prefix / "pip">.border_radius(this));
380 }
381
382 template<size_t LabelCount>
383 void set_attributes() noexcept
384 {
385 }
386
387 template<size_t LabelCount>
388 void set_attributes(toggle_widget_attribute auto&& first, toggle_widget_attribute auto&&...rest) noexcept
389 {
390 if constexpr (forward_of<decltype(first), observer<hi::label>>) {
391 if constexpr (LabelCount == 0) {
392 on_label = first;
393 off_label = first;
394 other_label = hi_forward(first);
395 } else if constexpr (LabelCount == 1) {
396 other_label.reset();
397 off_label.reset();
398 off_label = hi_forward(first);
399 } else if constexpr (LabelCount == 2) {
400 other_label = hi_forward(first);
401 } else {
403 }
404 set_attributes<LabelCount + 1>(hi_forward(rest)...);
405
406 } else if constexpr (forward_of<decltype(first), observer<hi::alignment>>) {
407 alignment = hi_forward(first);
408 set_attributes<LabelCount>(hi_forward(rest)...);
409
410 } else {
412 }
413 }
414};
415}} // 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
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:545
constexpr extent< value_type, 2 > size() const noexcept
Get size of the rectangle.
Definition axis_aligned_rectangle.hpp:183
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:227
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:236
widget * parent
Pointer to the parent widget.
Definition widget.hpp:40
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:49
2D constraints.
Definition box_constraints.hpp:22
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:72
observer< label > on_label
The label to show when the button is in the 'on' state.
Definition toggle_widget.hpp:64
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:147
observer< hi::alignment > alignment
The alignment of the button and on/off/other label.
Definition toggle_widget.hpp:76
toggle_widget(widget *parent, Value &&value, Attributes &&...attributes) noexcept
Construct a toggle widget with a default button delegate.
Definition toggle_widget.hpp:126
std::shared_ptr< delegate_type > delegate
The delegate that controls the button widget.
Definition toggle_widget.hpp:60
toggle_widget(widget *parent, std::shared_ptr< delegate_type > delegate, toggle_widget_attribute auto &&...attributes) noexcept
Construct a toggle widget.
Definition toggle_widget.hpp:87
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:170
observer< label > off_label
The label to show when the button is in the 'off' state.
Definition toggle_widget.hpp:68
Definition label_widget.hpp:26
Definition toggle_widget.hpp:17
T align(T... args)
T max(T... args)
T move(T... args)