HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scroll_bar_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 "widget.hpp"
8#include "../GUI/draw_context.hpp"
9#include "../observable.hpp"
10#include <memory>
11#include <string>
12#include <array>
13#include <optional>
14#include <future>
15
16namespace tt {
17
18template<bool IsVertical>
19class scroll_bar_widget final : public widget {
20public:
21 using super = widget;
22
23 static constexpr bool is_vertical = IsVertical;
24
25 template<typename Content, typename Aperture, typename Offset>
29 Content &&content,
30 Aperture &&aperture,
31 Offset &&offset) noexcept :
33 content(std::forward<Content>(content)),
34 aperture(std::forward<Aperture>(aperture)),
35 offset(std::forward<Offset>(offset))
36 {
37 _content_callback = this->content.subscribe([this](auto...) {
38 this->window.requestLayout = true;
39 });
40 _aperture_callback = this->aperture.subscribe([this](auto...) {
41 this->window.requestLayout = true;
42 });
43 _offset_callback = this->offset.subscribe([this](auto...) {
44 this->window.requestLayout = true;
45 });
46 }
47
49
50 [[nodiscard]] bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
51 {
52 tt_axiom(gui_system_mutex.recurse_lock_count());
53
54 if (super::update_constraints(display_time_point, need_reconstrain)) {
55 ttlet minimum_length = theme::global->width; // even for vertical bars.
56
57 if constexpr (is_vertical) {
58 _preferred_size = interval_extent2{
59 extent2{theme::global->scroll_bar_thickness, minimum_length},
60 extent2{theme::global->scroll_bar_thickness, std::numeric_limits<float>::max()}};
61 } else {
62 _preferred_size = interval_extent2{
63 extent2{minimum_length, theme::global->scroll_bar_thickness},
64 extent2{std::numeric_limits<float>::max(), theme::global->scroll_bar_thickness}};
65 }
66
67 return true;
68 } else {
69 return false;
70 }
71 }
72
73 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
74 {
75 tt_axiom(gui_system_mutex.recurse_lock_count());
76
77 need_layout |= std::exchange(_request_relayout, false);
78 if (need_layout) {
79 tt_axiom(*content != 0.0f);
80
81 // Calculate the position of the slider.
82 ttlet slider_offset = *offset * travel_vs_hidden_content_ratio();
83
84 if constexpr (is_vertical) {
85 slider_rectangle = aarectangle{rectangle().left(), rectangle().bottom() + slider_offset, rectangle().width(), slider_length()};
86 } else {
87 slider_rectangle =
88 aarectangle{rectangle().left() + slider_offset, rectangle().bottom(), slider_length(), rectangle().height()};
89 }
90 }
91
92 super::update_layout(display_time_point, need_layout);
93 }
94
95 void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
96 {
97 tt_axiom(gui_system_mutex.recurse_lock_count());
98
99 if (overlaps(context, this->_clipping_rectangle) && visible()) {
100 draw_rails(context);
101 draw_slider(context);
102 }
103 super::draw(std::move(context), display_time_point);
104 }
105
106 hit_box hitbox_test(point2 position) const noexcept override
107 {
108 tt_axiom(gui_system_mutex.recurse_lock_count());
109
110 if (visible() && _visible_rectangle.contains(position) && slider_rectangle.contains(position)) {
111 return hit_box{weak_from_this(), _draw_layer};
112 } else {
113 return hit_box{};
114 }
115 }
116
117 [[nodiscard]] bool handle_event(mouse_event const &event) noexcept
118 {
119 ttlet lock = std::scoped_lock(gui_system_mutex);
120 auto handled = super::handle_event(event);
121
122 if (event.cause.leftButton) {
123 handled = true;
124
125 switch (event.type) {
126 using enum mouse_event::Type;
127 case ButtonDown:
128 // Record the original scroll-position before the drag starts.
129 offset_before_drag = *offset;
130 break;
131
132 case Drag: {
133 // The distance the slider has to move relative to the slider position at the
134 // start of the drag.
135 ttlet slider_movement = is_vertical ? event.delta().y() : event.delta().x();
136 ttlet content_movement = slider_movement * hidden_content_vs_travel_ratio();
137 offset = offset_before_drag + content_movement;
138 } break;
139
140 default:;
141 }
142 }
143 return handled;
144 }
145
146 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
147 {
148 return false;
149 }
150
155 [[nodiscard]] bool visible() const noexcept
156 {
157 tt_axiom(gui_system_mutex.recurse_lock_count());
158 return hidden_content() >= 1.0f;
159 }
160
161 [[nodiscard]] color background_color() const noexcept override
162 {
163 return theme::global->fillColor(_semantic_layer);
164 }
165
166 [[nodiscard]] color foreground_color() const noexcept override
167 {
168 if (_hover) {
169 return theme::global->fillColor(_semantic_layer + 2);
170 } else {
171 return theme::global->fillColor(_semantic_layer + 1);
172 }
173 }
174
175private:
176 observable<float> offset;
177 observable<float> aperture;
178 observable<float> content;
179
180 typename decltype(offset)::callback_ptr_type _offset_callback;
181 typename decltype(aperture)::callback_ptr_type _aperture_callback;
182 typename decltype(content)::callback_ptr_type _content_callback;
183
184 aarectangle slider_rectangle;
185
186 float offset_before_drag;
187
188 [[nodiscard]] float rail_length() const noexcept
189 {
190 tt_axiom(gui_system_mutex.recurse_lock_count());
191 return is_vertical ? rectangle().height() : rectangle().width();
192 }
193
194 [[nodiscard]] float slider_length() const noexcept
195 {
196 tt_axiom(gui_system_mutex.recurse_lock_count());
197
198 ttlet content_aperture_ratio = *aperture / *content;
199 return std::max(rail_length() * content_aperture_ratio, theme::global->smallSize * 2.0f);
200 }
201
204 [[nodiscard]] float slider_travel_range() const noexcept
205 {
206 tt_axiom(gui_system_mutex.recurse_lock_count());
207 return rail_length() - slider_length();
208 }
209
212 [[nodiscard]] float hidden_content() const noexcept
213 {
214 tt_axiom(gui_system_mutex.recurse_lock_count());
215 return *content - *aperture;
216 }
217
222 [[nodiscard]] float hidden_content_vs_travel_ratio() const noexcept
223 {
224 tt_axiom(gui_system_mutex.recurse_lock_count());
225
226 ttlet _slider_travel_range = slider_travel_range();
227 return _slider_travel_range != 0.0f ? hidden_content() / _slider_travel_range : 0.0f;
228 }
229
234 [[nodiscard]] float travel_vs_hidden_content_ratio() const noexcept
235 {
236 tt_axiom(gui_system_mutex.recurse_lock_count());
237
238 ttlet _hidden_content = hidden_content();
239 return _hidden_content != 0.0f ? slider_travel_range() / _hidden_content : 0.0f;
240 }
241
242 void draw_rails(draw_context context) noexcept
243 {
244 tt_axiom(gui_system_mutex.recurse_lock_count());
245
246 ttlet corner_shapes =
247 is_vertical ? tt::corner_shapes{rectangle().width() * 0.5f} : tt::corner_shapes{rectangle().height() * 0.5f};
248 context.draw_box(rectangle(), background_color(), corner_shapes);
249 }
250
251 void draw_slider(draw_context context) noexcept
252 {
253 tt_axiom(gui_system_mutex.recurse_lock_count());
254
255 ttlet corner_shapes = is_vertical ? tt::corner_shapes{slider_rectangle.width() * 0.5f} :
256 tt::corner_shapes{slider_rectangle.height() * 0.5f};
257
258 context.draw_box(translate_z(0.1f) * slider_rectangle, foreground_color(), corner_shapes);
259 }
260};
261
262} // 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
bool contains(point2 const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition axis_aligned_rectangle.hpp:222
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 > requestLayout
When set to true the widgets will be layed out.
Definition gui_window.hpp:54
Definition hit_box.hpp:15
Definition mouse_event.hpp:15
A 2D vector using interval arithmetic.
Definition interval_extent2.hpp:18
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
Definition scroll_bar_widget.hpp:19
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition scroll_bar_widget.hpp:146
hit_box hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_bar_widget.hpp:106
void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
Draw the widget.
Definition scroll_bar_widget.hpp:95
bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
Update the constraints of the widget.
Definition scroll_bar_widget.hpp:50
void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
Update the internal layout of the widget.
Definition scroll_bar_widget.hpp:73
bool visible() const noexcept
Is the scrollbar visible.
Definition scroll_bar_widget.hpp:155
bool handle_event(mouse_event const &event) noexcept
Definition scroll_bar_widget.hpp:117
Definition widget.hpp:97
virtual void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
Update the internal layout of the widget.
widget(gui_window &window, std::shared_ptr< abstract_container_widget > parent) noexcept
aarectangle rectangle() const noexcept
Get the rectangle in local coordinates.
Definition widget.hpp:342
virtual bool handle_event(command command) noexcept
Handle command.
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
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 max(T... args)
T move(T... args)