HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
menu_item_widget.hpp
1// Copyright Take Vos 2020-2021.
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
5#pragma once
6
7#include "abstract_button_widget.hpp"
8#include "toolbar_widget.hpp"
9#include "../stencils/label_stencil.hpp"
10#include "../graphic_path.hpp"
11#include "../GUI/draw_context.hpp"
12#include <memory>
13#include <string>
14#include <array>
15#include <variant>
16
17namespace tt {
18
19
47template<typename T>
49public:
51 using value_type = typename super::value_type;
52
54
55 template<typename Value = observable<value_type>>
59 value_type true_value,
60 Value &&value = {}) noexcept :
61 super(window, parent, std::move(true_value), std::forward<Value>(value)),
62 _parent_is_toolbar(parent->is_toolbar())
63 {
64 // menu item buttons hug the container-border and neighbor widgets.
65 this->_margin = 0.0f;
66 }
67
68 void init() noexcept override
69 {
71 _label_callback = this->label.subscribe([this](auto...) {
72 this->_request_reconstrain = true;
73 });
74 }
75
78 void set_show_check_mark(bool flag) noexcept
79 {
80 tt_axiom(gui_system_mutex.recurse_lock_count());
81 this->_show_check_mark = flag;
82 this->_request_reconstrain = true;
83 }
84
94 [[nodiscard]] bool show_check_mark() const noexcept
95 {
96 tt_axiom(gui_system_mutex.recurse_lock_count());
97 return _show_check_mark;
98 }
99
102 void set_show_icon(bool flag) noexcept
103 {
104 tt_axiom(gui_system_mutex.recurse_lock_count());
105 this->_show_icon = flag;
106 this->_request_reconstrain = true;
107 }
108
121 [[nodiscard]] bool show_icon() const noexcept
122 {
123 tt_axiom(gui_system_mutex.recurse_lock_count());
124 return _show_icon;
125 }
126
129 void set_show_short_cut(bool flag) noexcept
130 {
131 tt_axiom(gui_system_mutex.recurse_lock_count());
132 this->_show_short_cut = flag;
133 this->_request_reconstrain = true;
134 }
135
147 [[nodiscard]] bool show_short_cut() const noexcept
148 {
149 tt_axiom(gui_system_mutex.recurse_lock_count());
150 return _show_short_cut;
151 }
152
153 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
154 {
155 tt_axiom(gui_system_mutex.recurse_lock_count());
156 if (_parent_is_toolbar) {
157 return is_toolbar(group) && *this->enabled;
158 } else {
159 return is_menu(group) && *this->enabled;
160 }
161 }
162
163 [[nodiscard]] bool handle_event(tt::command command) noexcept override
164 {
165 switch (command) {
166 case command::gui_menu_next:
167 if (!_parent_is_toolbar && !this->is_last(keyboard_focus_group::menu)) {
168 this->window.update_keyboard_target(
169 this->shared_from_this(), keyboard_focus_group::menu, keyboard_focus_direction::forward);
170 return true;
171 }
172 break;
173
174 case command::gui_menu_prev:
175 if (!_parent_is_toolbar && !this->is_first(keyboard_focus_group::menu)) {
176 this->window.update_keyboard_target(
177 this->shared_from_this(), keyboard_focus_group::menu, keyboard_focus_direction::backward);
178 return true;
179 }
180 break;
181
182 case command::gui_toolbar_next:
183 if (_parent_is_toolbar && !this->is_last(keyboard_focus_group::toolbar)) {
184 this->window.update_keyboard_target(
185 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::forward);
186 return true;
187 }
188 break;
189
190 case command::gui_toolbar_prev:
191 if (_parent_is_toolbar && !this->is_first(keyboard_focus_group::toolbar)) {
192 this->window.update_keyboard_target(
193 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::backward);
194 return true;
195 }
196 break;
197
198 case command::gui_activate:
199 case command::gui_enter:
200 if (!_parent_is_toolbar) {
201 ttlet direction =
202 command == command::gui_enter ? keyboard_focus_direction::forward : keyboard_focus_direction::backward;
203
204 // We need to find the backward widget of the parent, before the menu is closed.
205 ttlet focus_widget_after_commit = this->window.widget->find_next_widget(
206 this->shared_from_this(), keyboard_focus_group::normal, direction);
207
208 ttlet handled = super::handle_event(command::gui_activate);
209 tt_axiom(handled);
210
211 this->window.update_keyboard_target(focus_widget_after_commit);
212 return handled;
213 }
214 break;
215
216 default:;
217 }
218
219 return super::handle_event(command);
220 }
221
222 [[nodiscard]] bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
223 {
224 tt_axiom(gui_system_mutex.recurse_lock_count());
225
226 if (super::update_constraints(display_time_point, need_reconstrain)) {
227 _label_stencil = stencil::make_unique(alignment::middle_left, *label, theme::global->labelStyle);
228 _label_stencil->set_show_icon(_show_icon);
229
230 _check_mark_stencil = stencil::make_unique(alignment::middle_center, elusive_icon::Ok);
231
232 auto width = _label_stencil->preferred_extent().width() + theme::global->margin * 2.0f;
233 if (_show_check_mark) {
234 width += theme::global->small_icon_size + theme::global->margin;
235 }
236 if (_show_short_cut) {
237 width += theme::global->margin + theme::global->small_icon_size * 3.0f;
238 }
239
240 ttlet height = _label_stencil->preferred_extent().height() + theme::global->margin * 2.0f;
241 this->_preferred_size = {
243 return true;
244 } else {
245 return false;
246 }
247 }
248
249 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
250 {
251 tt_axiom(gui_system_mutex.recurse_lock_count());
252
253 need_layout |= std::exchange(this->_request_relayout, false);
254 if (need_layout) {
255 ttlet check_mark_x = this->rectangle().left() + theme::global->margin;
256 ttlet check_mark_width = theme::global->small_icon_size;
257 ttlet check_mark_height = theme::global->small_icon_size;
258 ttlet check_mark_y = this->rectangle().middle() - check_mark_height * 0.5f;
259 ttlet check_mark_rectangle = aarectangle{check_mark_x, check_mark_y, check_mark_width, check_mark_height};
260 _check_mark_stencil->set_layout_parameters(check_mark_rectangle);
261
262 ttlet short_cut_width = theme::global->small_icon_size * 3.0f;
263 ttlet short_cut_height = this->rectangle().height();
264 ttlet short_cut_x = this->rectangle().right() - theme::global->margin - short_cut_width;
265 ttlet short_cut_y = this->rectangle().bottom();
266 ttlet short_cut_rectangle = aarectangle{short_cut_x, short_cut_y, short_cut_width, short_cut_height};
267
268 ttlet label_height = this->rectangle().height();
269 ttlet label_y = this->rectangle().bottom();
270 auto label_width = this->rectangle().width() - theme::global->margin * 2.0f;
271 auto label_x = this->rectangle().left() + theme::global->margin;
272 if (_show_check_mark) {
273 label_width -= (check_mark_width + theme::global->margin);
274 label_x += (check_mark_width + theme::global->margin);
275 }
276 if (_show_short_cut) {
277 label_width -= (theme::global->margin + short_cut_width);
278 }
279 ttlet label_rectangle = aarectangle{label_x, label_y, label_width, label_height};
280
281 _label_stencil->set_layout_parameters(label_rectangle);
282 }
283 super::update_layout(display_time_point, need_layout);
284 }
285
286 void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
287 {
288 tt_axiom(gui_system_mutex.recurse_lock_count());
289
290 if (overlaps(context, this->_clipping_rectangle)) {
291 draw_background(context);
292 draw_check_mark(context);
293 draw_label(context);
294 }
295
296 super::draw(std::move(context), display_time_point);
297 }
298
299 [[nodiscard]] color focus_color() const noexcept override
300 {
301 if (this->_focus && this->window.active) {
302 return super::focus_color();
303 } else {
304 return color::transparent();
305 }
306 }
307
308private:
309 typename decltype(label)::callback_ptr_type _label_callback;
310 std::unique_ptr<label_stencil> _label_stencil;
311 std::unique_ptr<image_stencil> _check_mark_stencil;
312
313 bool _parent_is_toolbar;
314 bool _show_check_mark = false;
315 bool _show_icon = false;
316 bool _show_short_cut = false;
317
318 void draw_background(draw_context context) noexcept
319 {
320 tt_axiom(gui_system_mutex.recurse_lock_count());
321 context.draw_box_with_border_inside(this->rectangle(), this->background_color(), this->focus_color());
322 }
323
324 void draw_label(draw_context context) noexcept
325 {
326 _label_stencil->draw(context, this->label_color(), translate_z(0.1f));
327 }
328
329 void draw_check_mark(draw_context context) noexcept
330 {
331 if (this->value == this->true_value) {
332 _check_mark_stencil->draw(context, this->accent_color(), translate_z(0.1f));
333 }
334 }
335};
336
337} // namespace tt
This is a RGBA floating point color.
Definition color.hpp:39
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:18
float middle() const noexcept
The middle on the y-axis between bottom and top.
Definition axis_aligned_rectangle.hpp:194
Draw context for drawing using the TTauri shaders.
Definition draw_context.hpp:33
Definition gui_window.hpp:37
std::atomic< bool > active
Definition gui_window.hpp:69
std::shared_ptr< window_widget > widget
The widget covering the complete window.
Definition gui_window.hpp:93
void update_keyboard_target(std::shared_ptr< tt::widget > widget, keyboard_focus_group group=keyboard_focus_group::normal) noexcept
Change the keyboard focus to the given widget.
A localized text + icon label.
Definition label.hpp:76
Definition observable.hpp:20
int recurse_lock_count() const noexcept
This function should be used in tt_axiom() to check if the lock is held by current thread.
Definition unfair_recursive_mutex.hpp:60
An abstract button widget.
Definition abstract_button_widget.hpp:16
bool handle_event(command command) noexcept
Handle command.
Definition abstract_button_widget.hpp:53
Menu item widget.
Definition menu_item_widget.hpp:48
bool show_icon() const noexcept
Whether the text in the label will align to an optional icon in the label.
Definition menu_item_widget.hpp:121
void set_show_check_mark(bool flag) noexcept
Set the show_check_mark() flag.
Definition menu_item_widget.hpp:78
bool handle_event(tt::command command) noexcept override
Handle command.
Definition menu_item_widget.hpp:163
void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
Update the internal layout of the widget.
Definition menu_item_widget.hpp:249
bool show_check_mark() const noexcept
Whether the label aligns to an optional check-mark.
Definition menu_item_widget.hpp:94
void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
Draw the widget.
Definition menu_item_widget.hpp:286
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition menu_item_widget.hpp:153
bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
Update the constraints of the widget.
Definition menu_item_widget.hpp:222
void set_show_short_cut(bool flag) noexcept
Set the show_short_cut() flag.
Definition menu_item_widget.hpp:129
void set_show_icon(bool flag) noexcept
Set the show_icon() flag.
Definition menu_item_widget.hpp:102
void init() noexcept override
Should be called right after allocating and constructing a widget.
Definition menu_item_widget.hpp:68
bool show_short_cut() const noexcept
Whether the menu item should make space for an optional short-cut.
Definition menu_item_widget.hpp:147
bool is_last(keyboard_focus_group group) const noexcept
Is this widget the last widget in the parent container.
virtual void init() noexcept
Should be called right after allocating and constructing a widget.
Definition widget.hpp:119
virtual void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
Update the internal layout of the widget.
observable< bool > enabled
The widget is enabled.
Definition widget.hpp:105
aarectangle rectangle() const noexcept
Get the rectangle in local coordinates.
Definition widget.hpp:342
gui_window & window
Convenient reference to the Window.
Definition widget.hpp:101
virtual void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept
Draw the widget.
Definition widget.hpp:462
bool is_first(keyboard_focus_group group) const noexcept
Is this widget the first widget in the parent container.
virtual bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept
Update the constraints of the widget.
abstract_container_widget const & parent() const noexcept
Get a reference to the parent.
T infinity(T... args)
T move(T... args)