HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
radio_menu_button_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
12
13namespace hi { inline namespace v1 {
14
30template<fixed_string Name = "">
31class radio_menu_button_widget final : public widget {
32public:
33 using super = widget;
34 using delegate_type = typename radio_button_delegate;
35 constexpr static auto prefix = Name / "radio-menu";
36
49 button_widget_attribute auto&&...attributes) noexcept :
50 super(parent, std::move(delegate))
51 {
52 this->alignment = alignment::middle_flush();
53 this->set_attributes<0>(hi_forward(attributes)...);
54 }
55
68 template<
69 different_from<std::shared_ptr<delegate_type>> Value,
70 forward_of<observer<observer_decay_t<Value>>> OnValue,
71 button_widget_attribute... Attributes>
72 radio_menu_button_widget(widget *parent, Value&& value, OnValue&& on_value, Attributes&&...attributes) noexcept
73 requires requires { make_default_radio_button_delegate(hi_forward(value), hi_forward(on_value)); }
74 :
76 parent,
78 hi_forward(attributes)...)
79 {
80 }
81
83 [[nodiscard]] box_constraints update_constraints() noexcept override
84 {
85 _label_constraints = _label_widget.update_constraints();
86 _mark_label_constraints = _mark_label_widget.update_constraints();
87 _shortcut_label_constraints = _shortcut_label_widget.update_constraints();
88
89 auto constraints = max(_label_constraints, _mark_label_constraints, _shortcut_label_constraints);
90 inplace_max(constraints.margins, theme<prefix>.margin(this));
91 _padding = constraints.margins;
92
93 // clang-format off
94 hilet extra_width =
95 constraints.margins.left() +
96 theme<prefix / "mark">.width(this) +
97 _label_constraints.margins.left() +
98 // The label is here.
99 _label_constraints.margins.right() +
100 theme<prefix / "short-cut">.width(this) +
101 constraints.margins.right();
102 // clang-format on
103
104 // Internalize the margins inside the widget, as menu items are flush
105 // with each other and their container.
106 constraints.minimum.width() = _label_constraints.minimum.width() + extra_width;
107 constraints.preferred.width() = _label_constraints.preferred.width() + extra_width;
108 constraints.maximum.width() = _label_constraints.maximum.width() + extra_width;
109 constraints.minimum.height() += constraints.margins.top() + constraints.margins.bottom();
110 constraints.preferred.height() += constraints.margins.top() + constraints.margins.bottom();
111 constraints.maximum.height() += constraints.margins.top() + constraints.margins.bottom();
112 constraints.margins = {};
113 return constraints;
114 }
115
116 void set_layout(widget_layout const& context) noexcept override
117 {
118 if (compare_store(this->layout, context)) {
119 hilet outline = context.rectangle() - _padding;
120
121 if (os_settings::left_to_right()) {
122 hilet mark_outline_size = extent2{theme<prefix / "mark">.width(), outline.height()};
123 hilet shortcut_outline_size = extent2{theme<prefix / "short-cut">.width(), outline.height()};
124
125 hilet mark_shape = aarectangle{get<0>(outline), get<0>(outline) + mark_outline_size};
126 hilet shortcut_shape = aarectangle{get<3>(outline) - shortcut_outline_size, get<3>(outline)};
127 hilet label_shape = aarectangle{
128 point2{get<1>(mark_outline).x() + _label_constraint.margin.left(), get<1>(mark_outline).y()},
129 point2{get<2>(shortcut_outline).x() - _label_constraint.margin.right(), get<2>(shortcut_outline.y())}};
130
131 _mark_widget->set_layout(context.transform(mark_shape, 0.1f));
132 _shortcut_widget->set_layout(context.transform(shortcut_shape, 0.1f));
133 _label_widget->set_layout(context.transform(label_shape, 0.1f));
134
135 } else {
136 _short_cut_rectangle = align(inside_rectangle, _short_cut_size, alignment::middle_left());
137 _check_rectangle = align(inside_rectangle, _check_size, alignment::middle_right());
138 hilet label_rectangle = aarectangle{
139 point2{_short_cut_rectangle.right() + spacing, 0},
140 point2{_check_rectangle.left() - spacing, context.height()}};
141 this->_on_label_shape = this->_off_label_shape = this->_other_label_shape =
142 box_shape{_label_constraints, label_rectangle, cap_height};
143 }
144 }
145 }
146
147 void draw(widget_draw_context& context) noexcept override
148 {
149 if (*this->mode > widget_mode::invisible and overlaps(context, this->layout)) {
150 draw_button(context);
151 draw_check(context);
152 }
153 _label_widget->draw(context);
154 }
155
156 [[nodiscard]] generator<widget const&> children(bool include_invisible) const noexcept override
157 {
158 co_yield *_label_widget;
159 }
160
161 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept final
162 {
163 hi_axiom(loop::main().on_thread());
164
165 if (*mode >= widget_mode::partial and layout.contains(position)) {
166 return {id, layout.elevation, hitbox_type::button};
167 } else {
168 return {};
169 }
170 }
171
172 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
173 {
174 return *this->mode >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::menu);
175 }
176
177 void activate() noexcept
178 {
179 hi_assert_not_null(delegate);
180 delegate->activate(*this);
181 this->_state_changed();
182 }
183
184 bool handle_event(gui_event const& event) noexcept override
185 {
186 using enum gui_event_type;
187
188 switch (event.type()) {
189 case gui_menu_next:
190 if (*this->mode >= widget_mode::partial and not this->is_last(keyboard_focus_group::menu)) {
191 this->process_event(gui_event::window_set_keyboard_target(
192 nullptr, keyboard_focus_group::menu, keyboard_focus_direction::forward));
193 return true;
194 }
195 break;
196
197 case gui_menu_prev:
198 if (*this->mode >= widget_mode::partial and not this->is_first(keyboard_focus_group::menu)) {
199 this->process_event(gui_event::window_set_keyboard_target(
200 nullptr, keyboard_focus_group::menu, keyboard_focus_direction::backward));
201 return true;
202 }
203 break;
204
205 case gui_activate:
206 if (*this->mode >= widget_mode::partial) {
207 this->activate();
208 this->process_event(gui_event::window_set_keyboard_target(
209 nullptr, keyboard_focus_group::normal, keyboard_focus_direction::forward));
210 this->process_event(gui_event::window_set_keyboard_target(
211 nullptr, keyboard_focus_group::normal, keyboard_focus_direction::backward));
212 return true;
213 }
214 break;
215
216 default:;
217 }
218
219 return super::handle_event(event);
220 }
222private:
224 box_constraints _label_constraints;
225
226 notifier<>::callback_token _delegate_cbt;
227
228 font_book::font_glyph_type _check_glyph;
229 aarectangle _check_glyph_rectangle;
230
231 void draw_button(widget_draw_context& context) noexcept
232 {
233 context.draw_box(
234 this->layout,
235 this->layout.rectangle(),
236 theme<prefix>.background_color(this),
237 theme<prefix>.border_color(this),
238 theme<prefix>.border_width(this),
240 }
241
242 void draw_check(widget_draw_context& context) noexcept
243 {
244 if (this->state != widget_state::off) {
245 context.draw_glyph(
246 this->layout,
247 translate_z(0.1f) * _check_glyph_rectangle,
248 *_check_glyph.font,
249 _check_glyph.glyph,
250 theme<prefix>.fill_color(this));
251 }
252 }
253};
254}} // namespace hi::v1
Defines radio_button_delegate and some default radio_button_delegate delegates.
#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< float, 2 > extent2
A 2D extent.
Definition extent.hpp:502
gui_event_type
GUI event type.
Definition gui_event_type.hpp:21
std::shared_ptr< radio_button_delegate > make_default_radio_button_delegate(auto &&value, auto &&...args) noexcept
Make a shared pointer to a toggle-button delegate.
Definition radio_button_delegate.hpp:113
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
DOXYGEN BUG.
Definition algorithm.hpp:13
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.
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 value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
constexpr value_type & x() noexcept
Access the x element from the point.
Definition point.hpp:111
Definition widget.hpp:26
widget_id id
The numeric identifier of a widget.
Definition widget.hpp:35
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
A radio button delegate controls the state of a radio button widget.
Definition radio_button_delegate.hpp:18
A button that is part of a menu.
Definition radio_menu_button_widget.hpp:31
radio_menu_button_widget(widget *parent, Value &&value, OnValue &&on_value, Attributes &&...attributes) noexcept
Construct a menu button widget with a default button delegate.
Definition radio_menu_button_widget.hpp:72
radio_menu_button_widget(widget *parent, std::shared_ptr< delegate_type > delegate, button_widget_attribute auto &&...attributes) noexcept
Construct a menu button widget.
Definition radio_menu_button_widget.hpp:46
Definition abstract_button_widget.hpp:26
T align(T... args)
T move(T... args)