HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
checkbox_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 "checkbox_delegate.hpp"
12#include "../log.hpp"
13
14namespace hi { inline namespace v1 {
15
16template<typename Context>
18
45template<fixed_string Name = "">
46class checkbox_widget final : public widget {
47public:
48 using super = widget;
49 using delegate_type = checkbox_delegate;
50 constexpr static auto prefix = Name / "checkbox";
51
55
58 observer<label> on_label = tr("on");
59
62 observer<label> off_label = tr("off");
63
66 observer<label> other_label = tr("other");
67
70 observer<hi::alignment> alignment = alignment::top_left();
71
84 checkbox_widget_attribute auto&&...attributes) noexcept :
86 {
87 this->set_attributes<0>(hi_forward(attributes)...);
88
89 _on_label_widget = std::make_unique<label_widget<prefix / "on">>(this, on_label, alignment);
90 _off_label_widget = std::make_unique<label_widget<prefix / "off">>(this, off_label, alignment);
91 _other_label_widget = std::make_unique<label_widget<prefix / "other">>(this, other_label, alignment);
92
93 _delegate_cbt = this->delegate->subscribe([&] {
94 ++global_counter<"checkbox_widget:delegate:redraw">;
95 hi_assert_not_null(this->delegate);
96 state = this->delegate->state(this);
97 process_event({gui_event_type::window_redraw});
98 });
99 this->delegate->init(*this);
100 }
101
113 widget *parent,
114 different_from<std::shared_ptr<delegate_type>> auto&& value,
115 checkbox_widget_attribute auto&&...attributes) noexcept
116 requires requires { make_default_checkbox_delegate(hi_forward(value)); }
118 {
119 }
120
132 template<
133 different_from<std::shared_ptr<delegate_type>> Value,
134 forward_of<observer<observer_decay_t<Value>>> OnValue,
135 checkbox_widget_attribute... Attributes>
136 checkbox_widget(widget *parent, Value&& value, OnValue&& on_value, Attributes&&...attributes) noexcept
137 requires requires { make_default_checkbox_delegate(hi_forward(value), hi_forward(on_value)); }
138 :
140 parent,
142 hi_forward(attributes)...)
143 {
144 }
145
158 template<
159 different_from<std::shared_ptr<delegate_type>> Value,
160 forward_of<observer<observer_decay_t<Value>>> OnValue,
161 forward_of<observer<observer_decay_t<Value>>> OffValue,
162 checkbox_widget_attribute... Attributes>
163 checkbox_widget(widget *parent, Value&& value, OnValue&& on_value, OffValue&& off_value, Attributes&&...attributes) noexcept
164 requires requires { make_default_checkbox_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)); }
165 :
167 parent,
168 make_default_checkbox_delegate(hi_forward(value), hi_forward(on_value), hi_forward(off_value)),
169 hi_forward(attributes)...)
170 {
171 }
172
174 [[nodiscard]] box_constraints update_constraints() noexcept override
175 {
176 _on_label_constraints = _on_label_widget->update_constraints();
177 _off_label_constraints = _off_label_widget->update_constraints();
178 _other_label_constraints = _other_label_widget->update_constraints();
179 _label_constraints = max(_on_label_constraints, _off_label_constraints, _other_label_constraints);
180
181 _button_size = theme<prefix>.size(this);
182 hilet extra_size = extent2i{theme<prefix>.spacing_horizontal(this) + _button_size.width(), 0};
183
184 auto constraints = max(_label_constraints + extra_size, _button_size);
185 constraints.margins = theme<prefix>.margin(this);
186 constraints.alignment = *this->alignment;
187 return constraints;
188 }
189
190 void set_layout(widget_layout const& context) noexcept override
191 {
192 if (compare_store(this->layout, context)) {
193 auto alignment_ = os_settings::left_to_right() ? *this->alignment : mirror(*this->alignment);
194
195 if (alignment_ == horizontal_alignment::left or alignment_ == horizontal_alignment::right) {
196 _button_rectangle = align(context.rectangle(), _button_size, alignment_);
197 } else {
199 }
200
201 hilet inner_margin = theme<prefix>.spacing_horizontal(this);
202 hilet baseline_offset = theme<prefix>.cap_height(this);
203
204 hilet label_width = context.width() - (_button_rectangle.width() + inner_margin);
205 if (alignment_ == horizontal_alignment::left) {
206 hilet label_left = _button_rectangle.right() + inner_margin;
207 hilet label_rectangle = aarectanglei{label_left, 0, label_width, context.height()};
208 this->_on_label_shape = this->_off_label_shape = this->_other_label_shape =
209 box_shape(_label_constraints, label_rectangle, baseline_offset);
210
211 } else if (alignment_ == horizontal_alignment::right) {
212 hilet label_rectangle = aarectanglei{0, 0, label_width, context.height()};
213 this->_on_label_shape = this->_off_label_shape = this->_other_label_shape =
214 box_shape(_label_constraints, label_rectangle, baseline_offset);
215 } else {
217 }
218
219 _check_glyph = find_glyph(elusive_icon::Ok);
220 hilet check_glyph_bb =
221 narrow_cast<aarectanglei>(_check_glyph.get_bounding_rectangle() * theme<prefix>.line_height(this));
222 _check_glyph_rectangle = align(_button_rectangle, check_glyph_bb, alignment::middle_center());
223
224 _minus_glyph = find_glyph(elusive_icon::Minus);
225 hilet minus_glyph_bb =
226 narrow_cast<aarectanglei>(_minus_glyph.get_bounding_rectangle() * theme<prefix>.line_height(this));
227 _minus_glyph_rectangle = align(_button_rectangle, minus_glyph_bb, alignment::middle_center());
228 }
229
230 _on_label_widget->mode = *state == widget_state::on ? widget_mode::display : widget_mode::invisible;
231 _off_label_widget->mode = *state == widget_state::off ? widget_mode::display : widget_mode::invisible;
232 _other_label_widget->mode = *state == widget_state::other ? widget_mode::display : widget_mode::invisible;
233
234 _on_label_widget->set_layout(context.transform(_on_label_shape));
235 _off_label_widget->set_layout(context.transform(_off_label_shape));
236 _other_label_widget->set_layout(context.transform(_other_label_shape));
237 }
238
239 void draw(widget_draw_context& context) noexcept override
240 {
241 if (*this->mode > widget_mode::invisible and overlaps(context, this->layout)) {
242 draw_check_box(context);
243 draw_check_mark(context);
244 }
245
246 _on_label_widget->draw(context);
247 _off_label_widget->draw(context);
248 _other_label_widget->draw(context);
249 }
250
251 [[nodiscard]] generator<widget const&> children(bool include_invisible) const noexcept override
252 {
253 co_yield *_on_label_widget;
254 co_yield *_off_label_widget;
255 co_yield *_other_label_widget;
256 }
257
258 [[nodiscard]] hitbox hitbox_test(point2i position) const noexcept final
259 {
260 hi_axiom(loop::main().on_thread());
261
262 if (*mode >= widget_mode::partial and layout.contains(position)) {
263 return {id, layout.elevation, hitbox_type::button};
264 } else {
265 return {};
266 }
267 }
268
269 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
270 {
271 hi_axiom(loop::main().on_thread());
272 return *mode >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal);
273 }
274
275 void activate() noexcept
276 {
278 delegate->activate(*this);
279 this->_state_changed();
280 }
281
282 bool handle_event(gui_event const& event) noexcept override
283 {
284 hi_axiom(loop::main().on_thread());
285
286 switch (event.type()) {
287 case gui_event_type::gui_activate:
288 if (*mode >= widget_mode::partial) {
289 activate();
290 return true;
291 }
292 break;
293
294 case gui_event_type::mouse_down:
295 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
296 clicked = true;
298 return true;
299 }
300 break;
301
302 case gui_event_type::mouse_up:
303 if (*mode >= widget_mode::partial and event.mouse().cause.left_button) {
304 clicked = false;
305
306 if (layout.rectangle().contains(event.mouse().position)) {
307 handle_event(gui_event_type::gui_activate);
308 }
310 return true;
311 }
312 break;
313
314 default:;
315 }
316
317 return super::handle_event(event);
318 }
320private:
321 std::unique_ptr<label_widget<join_path(prefix, "on")>> _on_label_widget;
322 box_constraints _on_label_constraints;
323 box_shape _on_label_shape;
324
325 std::unique_ptr<label_widget<join_path(prefix, "off")>> _off_label_widget;
326 box_constraints _off_label_constraints;
327 box_shape _off_label_shape;
328
329 std::unique_ptr<label_widget<join_path(prefix, "other")>> _other_label_widget;
330 box_constraints _other_label_constraints;
331 box_shape _other_label_shape;
332
333 notifier<>::callback_token _delegate_cbt;
334
335 box_constraints _label_constraints;
336
337 extent2i _button_size;
338 aarectanglei _button_rectangle;
339 font_book::font_glyph_type _check_glyph;
340 aarectanglei _check_glyph_rectangle;
341 font_book::font_glyph_type _minus_glyph;
342 aarectanglei _minus_glyph_rectangle;
343
344 void draw_check_box(widget_draw_context& context) noexcept
345 {
346 context.draw_box(
347 this->layout,
348 _button_rectangle,
349 theme<prefix>.background_color(this),
350 theme<prefix>.border_color(this),
351 theme<prefix>.border_width(this),
353 }
354
355 void draw_check_mark(widget_draw_context& context) noexcept
356 {
357 if (this->state == widget_state::on) {
358 // Checkmark
359 context.draw_glyph(
360 this->layout,
361 translate_z(0.1f) * narrow_cast<aarectangle>(_check_glyph_rectangle),
362 *_check_glyph.font,
363 _check_glyph.glyph,
364 theme<prefix>.fill_color(this));
365
366 } else if (this->state == widget_state::off) {
367 ;
368
369 } else {
370 // Tri-state
371 context.draw_glyph(
372 this->layout,
373 translate_z(0.1f) * narrow_cast<aarectangle>(_minus_glyph_rectangle),
374 *_minus_glyph.font,
375 _minus_glyph.glyph,
376 theme<prefix>.fill_color(this));
377 }
378 }
379
380 template<size_t LabelCount>
381 void set_attributes() noexcept
382 {
383 }
384
385 template<size_t LabelCount>
386 void set_attributes(checkbox_widget_attribute auto&& first, checkbox_widget_attribute auto&&...rest) noexcept
387 {
388 if constexpr (forward_of<decltype(first), observer<hi::label>>) {
389 if constexpr (LabelCount == 0) {
390 on_label = first;
391 off_label = first;
392 other_label = hi_forward(first);
393 } else if constexpr (LabelCount == 1) {
394 other_label.reset();
395 off_label.reset();
396 off_label = hi_forward(first);
397 } else if constexpr (LabelCount == 2) {
398 other_label = hi_forward(first);
399 } else {
401 }
402 set_attributes<LabelCount + 1>(hi_forward(rest)...);
403
404 } else if constexpr (forward_of<decltype(first), observer<hi::alignment>>) {
405 alignment = hi_forward(first);
406 set_attributes<LabelCount>(hi_forward(rest)...);
407
408 } else {
410 }
411 }
412};
413
414}} // namespace hi::v1
Defines checkbox_delegate and some default checkbox_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_not_implemented(...)
This part of the code has not been implemented yet.
Definition assert.hpp:335
#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
geo::extent< int, 2 > extent2i
A 2D extent.
Definition extent.hpp:512
@ window_redraw
Request that part of the window gets redrawn on the next frame.
std::shared_ptr< checkbox_delegate > make_default_checkbox_delegate(auto &&value, auto &&...args) noexcept
Make a shared pointer to a toggle-button delegate.
Definition checkbox_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
auto find_glyph(font const &font, grapheme grapheme) noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:223
constexpr horizontal_alignment mirror(horizontal_alignment const &rhs) noexcept
Mirror the horizontal alignment.
Definition alignment.hpp:192
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.
@ 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
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
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
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
A toggle delegate controls the state of a toggle widget.
Definition checkbox_delegate.hpp:18
A GUI widget that permits the user to make a binary choice.
Definition checkbox_widget.hpp:46
observer< label > on_label
The label to show when the button is in the 'on' state.
Definition checkbox_widget.hpp:58
observer< label > other_label
The label to show when the button is in the 'other' state.
Definition checkbox_widget.hpp:66
observer< hi::alignment > alignment
The alignment of the button and on/off/other label.
Definition checkbox_widget.hpp:70
checkbox_widget(widget *parent, Value &&value, OnValue &&on_value, OffValue &&off_value, Attributes &&...attributes) noexcept
Construct a checkbox widget with a default button delegate.
Definition checkbox_widget.hpp:163
checkbox_widget(widget *parent, different_from< std::shared_ptr< delegate_type > > auto &&value, checkbox_widget_attribute auto &&...attributes) noexcept
Construct a checkbox widget with a default button delegate.
Definition checkbox_widget.hpp:112
std::shared_ptr< delegate_type > delegate
The delegate that controls the button widget.
Definition checkbox_widget.hpp:54
checkbox_widget(widget *parent, std::shared_ptr< delegate_type > delegate, checkbox_widget_attribute auto &&...attributes) noexcept
Construct a checkbox widget.
Definition checkbox_widget.hpp:81
checkbox_widget(widget *parent, Value &&value, OnValue &&on_value, Attributes &&...attributes) noexcept
Construct a checkbox widget with a default button delegate.
Definition checkbox_widget.hpp:136
observer< label > off_label
The label to show when the button is in the 'off' state.
Definition checkbox_widget.hpp:62
The GUI widget displays and lays out text together with an icon.
Definition label_widget.hpp:42
Definition checkbox_widget.hpp:17
Definition label_widget.hpp:26
T align(T... args)
T move(T... args)