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
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
63
67
70 observer<semantic_text_style> text_style = semantic_text_style::label;
71
72 observer<alignment> alignment = alignment::top_center();
73
74 attributes_type(attributes_type const&) noexcept = default;
78
81 {
82 set_attributes<0>(std::forward<Attributes>(attributes)...);
83 }
84
85 template<size_t NumLabels>
86 void set_attributes() noexcept
87 {
88 }
89
91 void set_attributes(First&& first, Rest&&...rest) noexcept
92 {
94 if constexpr (NumLabels == 0) {
95 on_label = first;
96 off_label = std::forward<First>(first);
97
98 } else if constexpr (NumLabels == 1) {
100 off_label = std::forward<First>(first);
101
102 } else {
103 hi_static_no_default("Maximum two label attributes (on/off) are allowed on a toolbar-tab-button");
104 }
105 return set_attributes<NumLabels + 1>(std::forward<Rest>(rest)...);
106
107 } else if constexpr (forward_of<First, observer<hi::alignment>>) {
108 alignment = std::forward<First>(first);
109 return set_attributes<NumLabels>(std::forward<Rest>(rest)...);
110
112 text_style = std::forward<First>(first);
113 return set_attributes<NumLabels>(std::forward<Rest>(rest)...);
114
115 } else {
116 hi_static_no_default();
117 }
118 }
119 };
120
121 attributes_type attributes;
122
126
127 hi_num_valid_arguments(consteval static, num_default_delegate_arguments, default_radio_delegate);
128 hi_call_left_arguments(static, make_default_delegate, make_shared_ctad_not_null<default_radio_delegate>);
129 hi_call_right_arguments(static, make_attributes, attributes_type);
130
132 {
133 delegate->deinit(*this);
134 }
135
147 attributes_type attributes,
149 super(parent), attributes(std::move(attributes)), delegate(std::move(delegate))
150 {
151 _on_label_widget = std::make_unique<label_widget>(
152 this, this->attributes.on_label, this->attributes.alignment, this->attributes.text_style);
153 _off_label_widget = std::make_unique<label_widget>(
154 this, this->attributes.off_label, this->attributes.alignment, this->attributes.text_style);
155
156 this->delegate->init(*this);
157 _delegate_cbt = this->delegate->subscribe([&] {
158 set_value(this->delegate->state(*this));
159 });
160 _delegate_cbt();
161 }
162
169 template<typename... Args>
171 requires(num_default_delegate_arguments<Args...>() != 0)
172 :
174 parent,
176 make_default_delegate<num_default_delegate_arguments<Args...>()>(std::forward<Args>(args)...))
177 {
178 }
179
181 {
182 // A toolbar tab button draws a focus line across the whole toolbar
183 // which is beyond it's own clipping rectangle. The parent is the toolbar
184 // so it will include everything that needs to be redrawn.
185 if (parent != nullptr) {
187 } else {
189 }
190 }
191
193 [[nodiscard]] box_constraints update_constraints() noexcept override
194 {
195 _layout = {};
196 _on_label_constraints = _on_label_widget->update_constraints();
197 _off_label_constraints = _off_label_widget->update_constraints();
198
199 _label_constraints = max(_on_label_constraints, _off_label_constraints);
200
201 // On left side a check mark, on right side short-cut. Around the label extra margin.
202 auto const extra_size = extent2{theme().margin<float>() * 2.0f, theme().margin<float>()};
203 return _label_constraints + extra_size;
204 }
205
206 void set_layout(widget_layout const& context) noexcept override
207 {
208 if (compare_store(_layout, context)) {
209 auto const label_rectangle = aarectangle{
210 theme().margin<float>(),
211 0.0f,
212 context.width() - theme().margin<float>() * 2.0f,
213 context.height() - theme().margin<float>()};
214 _on_label_shape = _off_label_shape =
215 box_shape{_label_constraints, label_rectangle, theme().baseline_adjustment()};
216 }
217
218 _on_label_widget->set_mode(value() == widget_value::on ? widget_mode::display : widget_mode::invisible);
219 _off_label_widget->set_mode(value() != widget_value::on ? widget_mode::display : widget_mode::invisible);
220
221 _on_label_widget->set_layout(context.transform(_on_label_shape));
222 _off_label_widget->set_layout(context.transform(_off_label_shape));
223 }
224
225 void draw(draw_context const& context) noexcept override
226 {
227 if (mode() > widget_mode::invisible and overlaps(context, layout())) {
228 draw_toolbar_tab_button(context);
229 _on_label_widget->draw(context);
230 _off_label_widget->draw(context);
231 }
232 }
233
234 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
235 {
236 return mode() >= widget_mode::partial and to_bool(group & hi::keyboard_focus_group::toolbar);
237 }
238
239 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
240 {
241 co_yield *_on_label_widget;
242 co_yield *_off_label_widget;
243 }
244
245 [[nodiscard]] color background_color() const noexcept override
246 {
247 hi_axiom(loop::main().on_thread());
248 if (phase() == widget_phase::pressed) {
249 return theme().color(semantic_color::fill, _layout.layer + 2);
250 } else {
251 return super::background_color();
252 }
253 }
254
255 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
256 {
257 hi_axiom(loop::main().on_thread());
258
259 if (mode() >= widget_mode::partial and layout().contains(position)) {
260 return {id, _layout.elevation, hitbox_type::button};
261 } else {
262 return {};
263 }
264 }
265
266 void activate() noexcept
267 {
268 delegate->activate(*this);
269
270 notifier();
271 }
272
273 bool handle_event(gui_event const& event) noexcept override
274 {
275 hi_axiom(loop::main().on_thread());
276
277 switch (event.type()) {
278 case gui_event_type::gui_activate:
279 if (mode() >= widget_mode::partial) {
280 activate();
281 return true;
282 }
283 break;
284
285 case gui_event_type::mouse_down:
286 if (mode() >= widget_mode::partial and event.mouse().cause.left_button) {
287 set_pressed(true);
288 return true;
289 }
290 break;
291
292 case gui_event_type::mouse_up:
293 if (mode() >= widget_mode::partial and event.mouse().cause.left_button) {
294 set_pressed(false);
295
296 if (layout().rectangle().contains(event.mouse().position)) {
297 handle_event(gui_event_type::gui_activate);
298 }
299 return true;
300 }
301 break;
302
303 default:;
304 }
305
307 }
308 // @endprivatesection
309protected:
310 std::unique_ptr<label_widget> _on_label_widget;
311 box_constraints _on_label_constraints;
312 box_shape _on_label_shape;
313
314 std::unique_ptr<label_widget> _off_label_widget;
315 box_constraints _off_label_constraints;
316 box_shape _off_label_shape;
317
318 callback<void()> _delegate_cbt;
319
320private:
321 box_constraints _label_constraints;
322
323 void draw_toolbar_tab_button(draw_context const& context) noexcept
324 {
325 // Draw the outline of the button across the clipping rectangle to clip the
326 // bottom of the outline.
327 auto const offset = theme().margin<float>() + theme().border_width();
328 auto const outline_rectangle = aarectangle{0, -offset, layout().width(), layout().height() + offset};
329
330 // The focus line will be drawn by the parent widget (toolbar_widget) at 0.5.
331 auto const button_z = focus() ? translate_z(0.6f) : translate_z(0.0f);
332
333 // clang-format off
334 auto button_color = (phase() == widget_phase::hover or value() == widget_value::on) ?
335 theme().color(semantic_color::fill, _layout.layer - 1) :
336 theme().color(semantic_color::fill, _layout.layer);
337 // clang-format on
338
339 auto const corner_radii = hi::corner_radii(0.0f, 0.0f, theme().rounding_radius<float>(), theme().rounding_radius<float>());
340
341 context.draw_box(
342 layout(),
345 focus() ? focus_color() : button_color,
346 theme().border_width(),
348 corner_radii);
349 }
350};
351
352} // namespace v1
353} // namespace hi::v1
Defines widget.
@ rectangle
The gui_event has rectangle data.
widget_mode
The mode that the widget is operating at.
Definition widget_state.hpp:25
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
@ display
The widget is in display-only mode.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
The HikoGUI namespace.
Definition recursive_iterator.hpp:15
border_side
The side where the border is drawn.
Definition draw_context_intf.hpp:28
@ inside
The border is drawn inside the edge of a quad.
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition misc.hpp:53
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:378
Horizontal/Vertical alignment combination.
Definition alignment.hpp:244
The 4 radii of the corners of a quad or rectangle.
Definition corner_radii.hpp:26
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:39
widget_id id
The numeric identifier of a widget.
Definition widget_intf.hpp:30
virtual void request_redraw() const noexcept=0
Request the widget to be redrawn on the next frame.
widget_layout const & layout() const noexcept
Get the current layout for this widget.
Definition widget_intf.hpp:206
widget_intf * parent
Pointer to the parent widget.
Definition widget_intf.hpp:35
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:421
Definition not_null.hpp:22
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(not_null< widget_intf const * > parent, Args &&...args)
Construct a toolbar tab button widget with a default radio delegate.
Definition toolbar_tab_button_widget.hpp:170
not_null< std::shared_ptr< delegate_type > > delegate
The delegate that controls the button widget.
Definition toolbar_tab_button_widget.hpp:125
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition toolbar_tab_button_widget.hpp:180
toolbar_tab_button_widget(not_null< widget_intf const * > parent, attributes_type attributes, not_null< std::shared_ptr< delegate_type > > delegate) noexcept
Construct a toolbar tab button widget.
Definition toolbar_tab_button_widget.hpp:145
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< semantic_text_style > text_style
The text style to button's label.
Definition toolbar_tab_button_widget.hpp:70
observer< label > on_label
The label to show when the button is in the 'on' state.
Definition toolbar_tab_button_widget.hpp:62
An interactive graphical object as part of the user-interface.
Definition widget.hpp:37
void request_redraw() const noexcept override
Request the widget to be redrawn on the next frame.
Definition widget.hpp:135
widget(widget_intf const *parent) noexcept
Definition widget.hpp:49
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:144
True if T is a forwarded type of Forward.
Definition concepts.hpp:136
Definition label_widget.hpp:30
Definition toolbar_tab_button_widget.hpp:21
T move(T... args)