HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
toolbar_tab_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
8
9#pragma once
10
11#include "widget.hpp"
12#include "radio_delegate.hpp"
13#include "../macros.hpp"
14
15hi_export_module(hikogui.widgets.toolbar_tab_button_widget);
16
17hi_export namespace hi {
18inline namespace v1 {
19
20template<typename Context>
22
55public:
56 using super = widget;
57 using delegate_type = radio_delegate;
58
59 struct attributes_type {
63
67
68 observer<alignment> alignment = alignment::top_center();
69
70 attributes_type(attributes_type const&) noexcept = default;
71 attributes_type(attributes_type&&) noexcept = default;
72 attributes_type& operator=(attributes_type const&) noexcept = default;
73 attributes_type& operator=(attributes_type&&) noexcept = default;
74
75 template<toolbar_tab_button_widget_attribute... Attributes>
76 explicit attributes_type(Attributes&&...attributes) noexcept
77 {
78 set_attributes<0>(std::forward<Attributes>(attributes)...);
79 }
80
81 template<size_t NumLabels>
82 void set_attributes() noexcept
83 {
84 }
85
86 template<size_t NumLabels, toolbar_tab_button_widget_attribute First, toolbar_tab_button_widget_attribute... Rest>
87 void set_attributes(First&& first, Rest&&...rest) noexcept
88 {
90 if constexpr (NumLabels == 0) {
91 on_label = first;
93
94 } else if constexpr (NumLabels == 1) {
97
98 } else {
99 hi_static_no_default("Maximum two label attributes (on/off) are allowed on a toolbar-tab-button");
100 }
101 return set_attributes<NumLabels + 1>(std::forward<Rest>(rest)...);
102
103 } else if constexpr (forward_of<First, observer<hi::alignment>>) {
104 alignment = std::forward<First>(first);
105 return set_attributes<NumLabels>(std::forward<Rest>(rest)...);
106
107 } else {
108 hi_static_no_default();
109 }
110 }
111 };
112
113 attributes_type attributes;
114
118
119 hi_num_valid_arguments(consteval static, num_default_delegate_arguments, default_radio_delegate);
120 hi_call_left_arguments(static, make_default_delegate, make_shared_ctad<default_radio_delegate>);
121 hi_call_right_arguments(static, make_attributes, attributes_type);
122
124 {
125 delegate->deinit(*this);
126 }
127
138 attributes_type attributes,
140 super(), attributes(std::move(attributes)), delegate(std::move(delegate))
141 {
142 _on_label_widget = std::make_unique<label_widget>(this->attributes.on_label, this->attributes.alignment);
143 _on_label_widget->set_parent(this);
144
145 _off_label_widget = std::make_unique<label_widget>(this->attributes.off_label, this->attributes.alignment);
146 _off_label_widget->set_parent(this);
147
148 hi_axiom_not_null(this->delegate);
149 this->delegate->init(*this);
150 _delegate_cbt = this->delegate->subscribe([&] {
151 set_value(this->delegate->state(*this));
152 });
153 _delegate_cbt();
154 }
155
162 template<typename... Args>
164 requires(num_default_delegate_arguments<Args...>() != 0)
165 :
167 make_attributes<num_default_delegate_arguments<Args...>()>(std::forward<Args>(args)...),
168 make_default_delegate<num_default_delegate_arguments<Args...>()>(std::forward<Args>(args)...))
169 {
170 }
171
172 void request_redraw() const noexcept override
173 {
174 // A toolbar tab button draws a focus line across the whole toolbar
175 // which is beyond it's own clipping rectangle. The parent is the toolbar
176 // so it will include everything that needs to be redrawn.
177 if (auto *p = parent()) {
178 p->request_redraw();
179 } else {
181 }
182 }
183
185 [[nodiscard]] box_constraints update_constraints() noexcept override
186 {
187 _layout = {};
188 _on_label_constraints = _on_label_widget->update_constraints();
189 _off_label_constraints = _off_label_widget->update_constraints();
190
191 _label_constraints = max(_on_label_constraints, _off_label_constraints);
192
193 // On left side a check mark, on right side short-cut. Around the label extra margin.
194 auto const extra_size = extent2{theme().margin<float>() * 2.0f, theme().margin<float>()};
195 return _label_constraints + extra_size;
196 }
197
198 void set_layout(widget_layout const& context) noexcept override
199 {
200 if (compare_store(_layout, context)) {
201 auto const label_rectangle = aarectangle{
202 theme().margin<float>(),
203 0.0f,
204 context.width() - theme().margin<float>() * 2.0f,
205 context.height() - theme().margin<float>()};
206 _on_label_shape = _off_label_shape =
207 box_shape{_label_constraints, label_rectangle, theme().baseline_adjustment()};
208 }
209
210 _on_label_widget->set_mode(value() == widget_value::on ? widget_mode::display : widget_mode::invisible);
211 _off_label_widget->set_mode(value() != widget_value::on ? widget_mode::display : widget_mode::invisible);
212
213 _on_label_widget->set_layout(context.transform(_on_label_shape));
214 _off_label_widget->set_layout(context.transform(_off_label_shape));
215 }
216
217 void draw(draw_context const& context) noexcept override
218 {
219 if (mode() > widget_mode::invisible and overlaps(context, layout())) {
220 draw_toolbar_tab_button(context);
221 _on_label_widget->draw(context);
222 _off_label_widget->draw(context);
223 }
224 }
225
226 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
227 {
228 return mode() >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::toolbar);
229 }
230
231 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
232 {
233 co_yield *_on_label_widget;
234 co_yield *_off_label_widget;
235 }
236
237 [[nodiscard]] color background_color() const noexcept override
238 {
239 hi_axiom(loop::main().on_thread());
240 if (phase() == widget_phase::pressed) {
241 return theme().fill_color(_layout.layer + 2);
242 } else {
243 return super::background_color();
244 }
245 }
246
247 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
248 {
249 hi_axiom(loop::main().on_thread());
250
251 if (mode() >= widget_mode::partial and layout().contains(position)) {
252 return {id, _layout.elevation, hitbox_type::button};
253 } else {
254 return {};
255 }
256 }
257
258 void activate() noexcept
259 {
260 delegate->activate(*this);
261
262 notifier();
263 }
264
265 bool handle_event(gui_event const& event) noexcept override
266 {
267 hi_axiom(loop::main().on_thread());
268
269 switch (event.type()) {
270 case gui_event_type::gui_activate:
271 if (mode() >= widget_mode::partial) {
272 activate();
273 return true;
274 }
275 break;
276
277 case gui_event_type::mouse_down:
278 if (mode() >= widget_mode::partial and event.mouse().cause.left_button) {
279 set_pressed(true);
280 return true;
281 }
282 break;
283
284 case gui_event_type::mouse_up:
285 if (mode() >= widget_mode::partial and event.mouse().cause.left_button) {
286 set_pressed(false);
287
288 if (layout().rectangle().contains(event.mouse().position)) {
289 handle_event(gui_event_type::gui_activate);
290 }
291 return true;
292 }
293 break;
294
295 default:;
296 }
297
298 return super::handle_event(event);
299 }
300 // @endprivatesection
301protected:
302 std::unique_ptr<label_widget> _on_label_widget;
303 box_constraints _on_label_constraints;
304 box_shape _on_label_shape;
305
306 std::unique_ptr<label_widget> _off_label_widget;
307 box_constraints _off_label_constraints;
308 box_shape _off_label_shape;
309
310 callback<void()> _delegate_cbt;
311
312private:
313 box_constraints _label_constraints;
314
315 void draw_toolbar_tab_button(draw_context const& context) noexcept
316 {
317 // Draw the outline of the button across the clipping rectangle to clip the
318 // bottom of the outline.
319 auto const offset = theme().margin<float>() + theme().border_width();
320 auto const outline_rectangle = aarectangle{0, -offset, layout().width(), layout().height() + offset};
321
322 // The focus line will be drawn by the parent widget (toolbar_widget) at 0.5.
323 auto const button_z = focus() ? translate_z(0.6f) : translate_z(0.0f);
324
325 // clang-format off
326 auto button_color = (phase() == widget_phase::hover or value() == widget_value::on) ?
327 theme().fill_color(_layout.layer - 1) :
328 theme().fill_color(_layout.layer);
329 // clang-format on
330
331 auto const corner_radii = hi::corner_radii(0.0f, 0.0f, theme().rounding_radius<float>(), theme().rounding_radius<float>());
332
333 context.draw_box(
334 layout(),
335 button_z * outline_rectangle,
336 button_color,
337 focus() ? focus_color() : button_color,
338 theme().border_width(),
340 corner_radii);
341 }
342};
343
344} // namespace v1
345} // namespace hi::v1
Defines widget.
@ rectangle
The gui_event has rectangle data.
Definition gui_event_variant.hpp:44
@ partial
A widget is partially enabled.
Definition widget_state.hpp:73
@ invisible
The widget is invisible.
Definition widget_state.hpp:41
@ display
The widget is in display-only mode.
Definition widget_state.hpp:55
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:21
The HikoGUI API version 1.
Definition array_generic.hpp:22
@ color
A color value was modified.
Definition style_modify_mask.hpp:27
@ inside
The border is drawn inside the edge of a quad.
Definition draw_context_intf.hpp:35
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition misc.hpp:53
A high-level geometric extent.
Definition extent2.hpp:32
notifier< void()> notifier
Notifier which is called after an action is completed by a widget.
Definition widget_intf.hpp:45
widget_id id
The numeric identifier of a widget.
Definition widget_intf.hpp:31
widget_layout const & layout() const noexcept
Get the current layout for this widget.
Definition widget_intf.hpp:241
widget_intf * parent() const noexcept
Pointer to the parent widget.
Definition widget_intf.hpp:113
A localizable message.
Definition txt.hpp:100
2D constraints.
Definition box_constraints.hpp:25
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
void reset() noexcept
Reset the observer.
Definition observer_intf.hpp:423
A radio delegate controls the state of a radio button widget.
Definition radio_delegate.hpp:16
A default radio button delegate.
Definition radio_delegate.hpp:56
A graphical control element that allows the user to choose only one of a predefined set of mutually e...
Definition toolbar_tab_button_widget.hpp:54
toolbar_tab_button_widget(Args &&...args)
Construct a toolbar tab button widget with a default radio delegate.
Definition toolbar_tab_button_widget.hpp:163
std::shared_ptr< delegate_type > delegate
The delegate that controls the button widget.
Definition toolbar_tab_button_widget.hpp:117
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition toolbar_tab_button_widget.hpp:172
toolbar_tab_button_widget(attributes_type attributes, std::shared_ptr< delegate_type > delegate) noexcept
Construct a toolbar tab button widget.
Definition toolbar_tab_button_widget.hpp:137
Definition toolbar_tab_button_widget.hpp:59
observer< label > off_label
The label to show when the button is in the 'off' state.
Definition toolbar_tab_button_widget.hpp:66
observer< label > on_label
The label to show when the button is in the 'on' state.
Definition toolbar_tab_button_widget.hpp:62
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition widget.hpp:136
widget() noexcept
Constructor for creating sub views.
Definition widget.hpp:50
box_constraints update_constraints() noexcept override
Update the constraints of the widget.
Definition widget.hpp:110
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:145
True if T is a forwarded type of Forward.
Definition concepts.hpp:137
Definition label_widget.hpp:30
Definition toolbar_tab_button_widget.hpp:21
T forward(T... args)
T move(T... args)