HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
toolbar_tab_button_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_radio_button_widget.hpp"
8#include "../GUI/draw_context.hpp"
9#include "../observable.hpp"
10#include "../label.hpp"
11#include <memory>
12#include <string>
13#include <array>
14#include <optional>
15#include <future>
16
17namespace tt {
18
19template<typename T>
21public:
23 using value_type = typename super::value_type;
24
26
27 template<typename Value, typename Label = observable<tt::label>>
31 value_type true_value,
32 Value &&value,
33 Label &&label = {}) noexcept :
34 abstract_radio_button_widget<T>(window, parent, std::move(true_value), std::forward<Value>(value)),
35 label(std::forward<Label>(label))
36 {
37 this->_width_resistance = 2;
38 }
39
42 {
43 }
44
46
47 void init() noexcept override
48 {
49 _label_callback = label.subscribe([this](auto...) {
50 this->_request_reconstrain = true;
51 });
52 }
53
54 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
55 {
56 tt_axiom(gui_system_mutex.recurse_lock_count());
57 return is_toolbar(group) && *this->enabled;
58 }
59
60 [[nodiscard]] bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
61 {
62 tt_axiom(gui_system_mutex.recurse_lock_count());
63
64 if (super::update_constraints(display_time_point, need_reconstrain)) {
65 _label_stencil = stencil::make_unique(alignment::top_center, *label, theme::global->labelStyle);
66
67 ttlet minimum_height = _label_stencil->preferred_extent().height();
68 ttlet minimum_width = _label_stencil->preferred_extent().width() + 2.0f * theme::global->margin;
69
70 this->_preferred_size = {
71 extent2{minimum_width, minimum_height},
73 return true;
74 } else {
75 return false;
76 }
77 }
78
79 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
80 {
81 tt_axiom(gui_system_mutex.recurse_lock_count());
82
83 need_layout |= std::exchange(this->_request_relayout, false);
84 if (need_layout) {
85 // A tab button widget draws beyond its clipping rectangle.
86 this->request_redraw();
87
88 ttlet offset = theme::global->margin + theme::global->borderWidth;
89 _button_rectangle = aarectangle{
90 this->rectangle().left(),
91 this->rectangle().bottom() - offset,
92 this->rectangle().width(),
93 this->rectangle().height() + offset};
94
95 _label_stencil->set_layout_parameters(this->rectangle(), this->base_line());
96 }
97
98 super::update_layout(display_time_point, need_layout);
99 }
100
101 void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
102 {
103 tt_axiom(gui_system_mutex.recurse_lock_count());
104
105 if (overlaps(context, this->_clipping_rectangle)) {
106 draw_button(context);
107 draw_label(context);
108 }
109
110 draw_focus_line(context);
111
112 super::draw(std::move(context), display_time_point);
113 }
114
115 [[nodiscard]] bool handle_event(tt::command command) noexcept override
116 {
117 switch (command) {
118 case command::gui_toolbar_next:
119 if (!this->is_last(keyboard_focus_group::toolbar)) {
120 this->window.update_keyboard_target(
121 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::forward);
122 }
123 return true;
124
125 case command::gui_toolbar_prev:
126 if (!this->is_first(keyboard_focus_group::toolbar)) {
127 this->window.update_keyboard_target(
128 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::backward);
129 }
130 return true;
131
132 default:;
133 }
134
135 return super::handle_event(command);
136 }
137
138private:
139 typename decltype(label)::callback_ptr_type _label_callback;
140 aarectangle _button_rectangle;
141 std::unique_ptr<stencil> _label_stencil;
142
143 void draw_focus_line(draw_context context) noexcept
144 {
145 if (this->_focus && this->window.active && *this->value == this->true_value) {
146 ttlet &parent_ = this->parent();
147 ttlet parent_rectangle = aarectangle{this->_parent_to_local * parent_.rectangle()};
148
149 // Create a line, on the bottom of the toolbar over the full width.
150 ttlet line_rectangle = aarectangle{
151 parent_rectangle.left(),
152 parent_rectangle.bottom(),
153 parent_rectangle.width(),
154 theme::global->borderWidth
155 };
156
157 context.set_clipping_rectangle(line_rectangle);
158
159 if (overlaps(context, line_rectangle)) {
160 // Draw the line above every other direct child of the toolbar, and between
161 // the selected-tab (0.6) and unselected-tabs (0.8).
162 context.draw_filled_quad(translate_z(0.7f) * line_rectangle, this->focus_color());
163 }
164 }
165 }
166
167 void draw_button(draw_context context) noexcept
168 {
169 tt_axiom(gui_system_mutex.recurse_lock_count());
170
171 // Override the clipping rectangle to match the toolbar rectangle exactly
172 // so that the bottom border of the tab button is not drawn.
173 context.set_clipping_rectangle(aarectangle{this->_parent_to_local * this->parent().clipping_rectangle()});
174
175 // The focus line will be placed at 0.7.
176 ttlet button_z = (this->_focus && this->window.active) ? translate_z(0.8f) : translate_z(0.6f);
177
178 auto button_color = (this->_hover || *this->value == this->true_value) ?
179 theme::global->fillColor(this->_semantic_layer - 1) :
180 theme::global->fillColor(this->_semantic_layer);
181
182 ttlet corner_shapes = tt::corner_shapes{0.0f, 0.0f, theme::global->roundingRadius, theme::global->roundingRadius};
183 context.draw_box_with_border_inside(
184 button_z * _button_rectangle,
185 button_color,
186 (this->_focus && this->window.active) ? this->focus_color() : button_color,
187 corner_shapes);
188 }
189
190 void draw_label(draw_context context) noexcept
191 {
192 tt_axiom(gui_system_mutex.recurse_lock_count());
193 _label_stencil->draw(context, this->label_color(), translate_z(0.9f));
194 }
195};
196
197} // namespace tt
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:18
Definition corner_shapes.hpp:9
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
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
bool handle_event(command command) noexcept
Handle command.
Definition abstract_button_widget.hpp:53
An abstract radio button widget.
Definition abstract_radio_button_widget.hpp:15
Definition toolbar_tab_button_widget.hpp:20
bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
Update the constraints of the widget.
Definition toolbar_tab_button_widget.hpp:60
void init() noexcept override
Should be called right after allocating and constructing a widget.
Definition toolbar_tab_button_widget.hpp:47
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition toolbar_tab_button_widget.hpp:54
bool handle_event(tt::command command) noexcept override
Handle command.
Definition toolbar_tab_button_widget.hpp:115
void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
Draw the widget.
Definition toolbar_tab_button_widget.hpp:101
void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
Update the internal layout of the widget.
Definition toolbar_tab_button_widget.hpp:79
bool is_last(keyboard_focus_group group) const noexcept
Is this widget the last widget in the parent container.
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 float base_line() const noexcept
Return the base-line where the text should be located.
Definition widget.hpp:351
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)