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 = 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
56 [[nodiscard]] aarect window_clipping_rectangle() const noexcept override
57 {
58 tt_axiom(gui_system_mutex.recurse_lock_count());
59 return this->parent().window_clipping_rectangle();
60 }
61
62 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
63 {
64 tt_axiom(gui_system_mutex.recurse_lock_count());
65 return is_toolbar(group) && *this->enabled;
66 }
67
68 [[nodiscard]] bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
69 {
70 tt_axiom(gui_system_mutex.recurse_lock_count());
71
72 if (super::update_constraints(display_time_point, need_reconstrain)) {
73 _label_stencil = stencil::make_unique(alignment::top_center, *label, theme::global->labelStyle);
74
75 ttlet minimum_height = _label_stencil->preferred_extent().height();
76 ttlet minimum_width = _label_stencil->preferred_extent().width() + 2.0f * theme::global->margin;
77
78 this->_preferred_size = {
79 f32x4{minimum_width, minimum_height},
81 this->_preferred_base_line = relative_base_line{vertical_alignment::middle, -theme::global->margin};
82 return true;
83 } else {
84 return false;
85 }
86 }
87
88 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
89 {
90 tt_axiom(gui_system_mutex.recurse_lock_count());
91
92 need_layout |= std::exchange(this->_request_relayout, false);
93 if (need_layout) {
94 // A tab button widget draws beyond its clipping rectangle.
95 this->window.request_redraw(this->window_clipping_rectangle());
96
97 ttlet offset = theme::global->margin + theme::global->borderWidth;
98 _button_rectangle = aarect{
99 this->rectangle().x(),
100 this->rectangle().y() - offset,
101 this->rectangle().width(),
102 this->rectangle().height() + offset};
103
104 _label_stencil->set_layout_parameters(this->rectangle(), this->base_line());
105 }
106
107 super::update_layout(display_time_point, need_layout);
108 }
109
110 void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
111 {
112 tt_axiom(gui_system_mutex.recurse_lock_count());
113
114 if (overlaps(context, this->window_clipping_rectangle())) {
115 draw_button(context);
116 draw_label(context);
117 draw_focus_line(context);
118 }
119
120 super::draw(std::move(context), display_time_point);
121 }
122
123 [[nodiscard]] bool handle_event(tt::command command) noexcept override
124 {
125 switch (command) {
126 case command::gui_toolbar_next:
127 if (!this->is_last(keyboard_focus_group::toolbar)) {
128 this->window.update_keyboard_target(
129 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::forward);
130 }
131 return true;
132
133 case command::gui_toolbar_prev:
134 if (!this->is_first(keyboard_focus_group::toolbar)) {
135 this->window.update_keyboard_target(
136 this->shared_from_this(), keyboard_focus_group::toolbar, keyboard_focus_direction::backward);
137 }
138 return true;
139
140 default:;
141 }
142
143 return super::handle_event(command);
144 }
145
146private:
147 typename decltype(label)::callback_ptr_type _label_callback;
148 aarect _button_rectangle;
149 std::unique_ptr<stencil> _label_stencil;
150
151 void draw_focus_line(draw_context const &context) noexcept
152 {
153 if (this->_focus && this->window.active && *this->value == this->true_value) {
154 ttlet &parent_ = this->parent();
155
156 // Draw the focus line over the full width of the window at the bottom
157 // of the toolbar.
158 auto parent_context = parent_.make_draw_context(context);
159
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 parent_context.transform = translate3{0.0f, 0.0f, 1.7f} * parent_context.transform;
163
164 parent_context.fill_color = theme::global->accentColor;
165 parent_context.draw_filled_quad(
166 aarect{parent_.rectangle().x(), parent_.rectangle().y(), parent_.rectangle().width(), 1.0f});
167 }
168 }
169
170 void draw_button(draw_context context) noexcept
171 {
172 tt_axiom(gui_system_mutex.recurse_lock_count());
173 if (this->_focus && this->window.active) {
174 // The focus line will be placed at 0.7.
175 context.transform = translate3{0.0f, 0.0f, 0.8f} * context.transform;
176 } else {
177 context.transform = translate3{0.0f, 0.0f, 0.6f} * context.transform;
178 }
179
180 // Override the clipping rectangle to match the toolbar rectangle exactly
181 // so that the bottom border of the tab button is not drawn.
182 context.clipping_rectangle = this->parent().window_rectangle();
183
184 if (this->_hover || *this->value == this->true_value) {
185 context.fill_color = theme::global->fillColor(this->_semantic_layer - 1);
186 context.line_color = context.fill_color;
187 } else {
188 context.fill_color = theme::global->fillColor(this->_semantic_layer);
189 context.line_color = context.fill_color;
190 }
191
192 if (this->_focus && this->window.active) {
193 context.line_color = theme::global->accentColor;
194 }
195
196 context.corner_shapes = f32x4{0.0f, 0.0f, theme::global->roundingRadius, theme::global->roundingRadius};
197 context.draw_box_with_border_inside(_button_rectangle);
198 }
199
200 void draw_label(draw_context context) noexcept
201 {
202 tt_axiom(gui_system_mutex.recurse_lock_count());
203
204 context.transform = translate3{0.0f, 0.0f, 0.9f} * context.transform;
205
206 if (*this->enabled) {
207 context.line_color = theme::global->labelStyle.color;
208 }
209
210 _label_stencil->draw(context, true);
211 }
212};
213
214} // namespace tt
Definition alignment.hpp:104
Definition translate.hpp:14
Draw context for drawing using the TTauri shaders.
Definition draw_context.hpp:33
Definition gui_window.hpp:39
std::atomic< bool > active
Definition gui_window.hpp:71
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.
void request_redraw(aarect rectangle=aarect::infinity()) noexcept
Request a rectangle on the window to be redrawn.
Definition gui_window.hpp:115
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:55
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:68
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:62
bool handle_event(tt::command command) noexcept override
Handle command.
Definition toolbar_tab_button_widget.hpp:123
void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
Draw the widget.
Definition toolbar_tab_button_widget.hpp:110
aarect window_clipping_rectangle() const noexcept override
The tab button widget will draw beyond the normal clipping rectangle.
Definition toolbar_tab_button_widget.hpp:56
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:88
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
gui_window & window
Convenient reference to the Window.
Definition widget.hpp:100
virtual void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept
Draw the widget.
Definition widget.hpp:460
float base_line() const noexcept
Get the base-line in local coordinates.
Definition widget.hpp:350
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.
virtual aarect window_clipping_rectangle() const noexcept
Get the clipping-rectangle in window coordinates.
Definition widget.hpp:320
aarect window_rectangle() const noexcept
Get the rectangle in window coordinates.
Definition widget.hpp:310
aarect rectangle() const noexcept
Get the rectangle in local coordinates.
Definition widget.hpp:340
T infinity(T... args)
T move(T... args)