HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
menu_button_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2023.
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 "button_delegate.hpp"
13#include "label_widget.hpp"
14#include "../algorithm/algorithm.hpp"
15#include "../l10n/l10n.hpp"
16#include "../observer/observer.hpp"
17#include "../macros.hpp"
18#include <memory>
19#include <string>
20#include <array>
21#include <optional>
22#include <future>
23#include <coroutine>
24
25hi_export_module(hikogui.widgets.menu_button_widget);
26
27hi_export namespace hi { inline namespace v1 {
28
29template<typename Context>
31
36template<std::derived_from<widget> ButtonWidget>
37class menu_button_widget : public widget {
38public:
39 using super = widget;
40 using button_widget_type = ButtonWidget;
41 using button_attributes_type = button_widget_type::attributes_type;
42 using delegate_type = button_widget_type::delegate_type;
43
48
52
55 observer<alignment> alignment = hi::alignment::middle_left();
56
59 observer<semantic_text_style> text_style = semantic_text_style::label;
60
61 attributes_type(attributes_type const&) noexcept = default;
62 attributes_type(attributes_type&&) noexcept = default;
63 attributes_type& operator=(attributes_type const&) noexcept = default;
64 attributes_type& operator=(attributes_type&&) noexcept = default;
65
66 template<menu_button_widget_attribute... Attributes>
67 explicit attributes_type(Attributes&&...attributes) noexcept
68 {
69 set_attributes<0>(std::forward<Attributes>(attributes)...);
70 }
71
72 template<size_t I>
73 void set_attributes() noexcept
74 {
75 }
76
77 template<size_t I, menu_button_widget_attribute First, menu_button_widget_attribute... Rest>
78 void set_attributes(First&& first, Rest&&...rest) noexcept
79 {
81 if constexpr (I == 0) {
82 label = std::forward<First>(first);
83 } else if constexpr (I == 1) {
84 shortcut = std::forward<First>(first);
85 } else {
86 hi_static_no_default();
87 }
88 set_attributes<I + 1>(std::forward<Rest>(rest)...);
89
90 } else if constexpr (forward_of<First, observer<hi::alignment>>) {
91 alignment = std::forward<First>(first);
92 set_attributes<I>(std::forward<Rest>(rest)...);
93
94 } else if constexpr (forward_of<First, observer<hi::semantic_text_style>>) {
95 text_style = std::forward<First>(first);
96 set_attributes<I>(std::forward<Rest>(rest)...);
97
98 } else {
99 hi_static_no_default();
100 }
101 }
102 };
103
104 attributes_type attributes;
105
106 template<typename... Args>
107 [[nodiscard]] consteval static size_t num_default_delegate_arguments() noexcept
108 {
109 return button_widget_type::template num_default_delegate_arguments<Args...>();
110 }
111
112 template<size_t N, typename... Args>
113 [[nodiscard]] static auto make_default_delegate(Args&&...args)
114 {
115 return button_widget_type::template make_default_delegate<N, Args...>(std::forward<Args>(args)...);
116 }
117
118 hi_call_right_arguments(static, make_attributes, attributes_type);
119
120
121 menu_button_widget(
122 widget_intf const* parent,
123 attributes_type attributes,
124 std::shared_ptr<delegate_type> delegate) noexcept :
125 super(parent), attributes(std::move(attributes))
126 {
127 _button_widget = std::make_unique<button_widget_type>(
128 this, button_attributes_type{this->attributes.alignment, keyboard_focus_group::menu}, std::move(delegate));
129 _label_widget =
130 std::make_unique<label_widget>(this, this->attributes.label, this->attributes.alignment, this->attributes.text_style);
131 _shortcut_widget = std::make_unique<label_widget>(
132 this, this->attributes.shortcut, this->attributes.alignment, this->attributes.text_style);
133
134 // Link the state from the button, so that both this widget and the child widget react in the same way.
135 _button_widget->state = state;
136
137 _button_widget_cbt = _button_widget->subscribe([&] {
138 this->request_redraw();
139 this->notifier();
140 });
141
142 _button_widget_cbt();
143 }
144
151 template<typename... Args>
152 menu_button_widget(widget_intf const* parent, Args&&...args)
153 requires(num_default_delegate_arguments<Args...>() != 0)
154 :
156 parent,
157 make_attributes<num_default_delegate_arguments<Args...>()>(std::forward<Args>(args)...),
158 make_default_delegate<num_default_delegate_arguments<Args...>()>(std::forward<Args>(args)...))
159 {
160 }
161
163 [[nodiscard]] box_constraints update_constraints() noexcept override
164 {
165 _layout = {};
166
167 _grid.clear();
168 _grid.add_cell(0, 0, grid_cell_type::button);
169 _grid.add_cell(1, 0, grid_cell_type::label, true);
170 _grid.add_cell(2, 0, grid_cell_type::shortcut);
171
172 for (auto& cell : _grid) {
173 if (cell.value == grid_cell_type::button) {
174 auto constraints = _button_widget->update_constraints();
175 inplace_max(constraints.minimum.width(), theme().size() * 2.0f);
176 inplace_max(constraints.preferred.width(), theme().size() * 2.0f);
177 inplace_max(constraints.maximum.width(), theme().size() * 2.0f);
178 cell.set_constraints(constraints);
179
180 } else if (cell.value == grid_cell_type::label) {
181 cell.set_constraints(_label_widget->update_constraints());
182
183 } else if (cell.value == grid_cell_type::shortcut) {
184 auto constraints = _shortcut_widget->update_constraints();
185 inplace_max(constraints.minimum.width(), theme().size() * 3.0f);
186 inplace_max(constraints.preferred.width(), theme().size() * 3.0f);
187 inplace_max(constraints.maximum.width(), theme().size() * 3.0f);
188 cell.set_constraints(constraints);
189
190 } else {
191 hi_no_default();
192 }
193 }
194
195 auto constraints = _grid.constraints(os_settings::left_to_right());
196 constraints.minimum += extent2{theme().template margin<float>() * 2.0f, theme().template margin<float>() * 2.0f};
197 constraints.preferred += extent2{theme().template margin<float>() * 2.0f, theme().template margin<float>() * 2.0f};
198 constraints.maximum += extent2{theme().template margin<float>() * 2.0f, theme().template margin<float>() * 2.0f};
199 constraints.margins = {};
200 return constraints;
201 }
202
203 void set_layout(widget_layout const& context) noexcept override
204 {
205 if (compare_store(_layout, context)) {
206 auto shape = context.shape;
207 shape.rectangle -= theme().template margin<float>();
208 _grid.set_layout(shape, theme().baseline_adjustment());
209 }
210
211 for (auto const& cell : _grid) {
212 if (cell.value == grid_cell_type::button) {
213 _button_widget->set_layout(context.transform(cell.shape, transform_command::level));
214
215 } else if (cell.value == grid_cell_type::label) {
216 _label_widget->set_layout(context.transform(cell.shape));
217
218 } else if (cell.value == grid_cell_type::shortcut) {
219 _shortcut_widget->set_layout(context.transform(cell.shape));
220
221 } else {
222 hi_no_default();
223 }
224 }
225 }
226
227 void draw(draw_context const& context) noexcept override
228 {
229 if (mode() > widget_mode::invisible and overlaps(context, layout())) {
230 auto outline_color = focus() ? focus_color() : background_color();
231 context.draw_box(
232 layout(), layout().rectangle(), background_color(), outline_color, theme().border_width(), border_side::inside);
233
234 for (auto const& cell : _grid) {
235 if (cell.value == grid_cell_type::button) {
236 _button_widget->draw(context);
237
238 } else if (cell.value == grid_cell_type::label) {
239 _label_widget->draw(context);
240
241 } else if (cell.value == grid_cell_type::shortcut) {
242 _shortcut_widget->draw(context);
243
244 } else {
245 hi_no_default();
246 }
247 }
248 }
249 }
250
251 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
252 {
253 co_yield *_button_widget;
254 co_yield *_label_widget;
255 co_yield *_shortcut_widget;
256 }
257
258 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
259 {
260 hi_axiom(loop::main().on_thread());
261
262 if (mode() >= widget_mode::partial and layout().contains(position)) {
263 // Accept the hitbox of the menu_button_widget on behalf of the button_widget.
264 return {_button_widget->id, _layout.elevation, hitbox_type::button};
265 } else {
266 return {};
267 }
268 }
270protected:
271 enum class grid_cell_type { button, label, shortcut };
272
273 grid_layout<grid_cell_type> _grid;
274
276
277 std::unique_ptr<label_widget> _label_widget;
278 std::unique_ptr<label_widget> _shortcut_widget;
279
280 callback<void()> _button_widget_cbt;
281};
282
283}} // namespace hi::v1
Defines widget.
Defines label_widget.
Defines button_delegate and some default button delegates.
@ rectangle
The gui_event has rectangle data.
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
The HikoGUI namespace.
Definition array_generic.hpp:20
@ 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
@ level
The child widget stays at the same elevation and layer.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Horizontal/Vertical alignment combination.
Definition alignment.hpp:244
Definition widget_intf.hpp:24
notifier< void()> notifier
Notifier which is called after an action is completed by a widget.
Definition widget_intf.hpp:39
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
observer< widget_state > state
The current state of the widget.
Definition widget_intf.hpp:43
A localizable message.
Definition txt.hpp:100
2D constraints.
Definition box_constraints.hpp:25
constexpr reference add_cell(size_t first_column, size_t first_row, size_t last_column, size_t last_row, Value &&value, bool beyond_maximum=false) noexcept
Check if the cell on the grid is already in use.
Definition grid_layout.hpp:1009
constexpr void set_layout(box_shape const &shape, float baseline_adjustment) noexcept
Layout the cells based on the width and height.
Definition grid_layout.hpp:1081
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
Add menu-button around a small-button.
Definition menu_button_widget.hpp:37
menu_button_widget(widget_intf const *parent, Args &&...args)
Construct a widget with a label.
Definition menu_button_widget.hpp:152
Definition menu_button_widget.hpp:44
observer< alignment > alignment
The alignment of the button and on/off/other label.
Definition menu_button_widget.hpp:55
observer< hi::label > shortcut
The label to for the shortcut.
Definition menu_button_widget.hpp:51
observer< semantic_text_style > text_style
The text style to button's label.
Definition menu_button_widget.hpp:59
observer< hi::label > label
The label to show when the button is in the 'on' state.
Definition menu_button_widget.hpp:47
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:141
widget() noexcept
Constructor for creating sub views.
Definition widget.hpp:55
True if T is a forwarded type of Forward.
Definition concepts.hpp:137
Definition label_widget.hpp:30
Definition menu_button_widget.hpp:30
T move(T... args)