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/theme.hpp"
9#include "../GUI/mouse_event.hpp"
10#include "../geometry/axis.hpp"
11#include "../observable.hpp"
12#include <memory>
13#include <string>
14#include <array>
15#include <optional>
16#include <future>
17
18namespace tt {
19
20template<axis Axis>
21class scroll_bar_widget final : public widget {
22public:
23 using super = widget;
24
25 static constexpr tt::axis axis = Axis;
26
27 template<typename Content, typename Aperture, typename Offset>
30 Content &&content,
31 Aperture &&aperture,
32 Offset &&offset) noexcept :
34 content(std::forward<Content>(content)),
35 aperture(std::forward<Aperture>(aperture)),
36 offset(std::forward<Offset>(offset))
37 {
38 _content_callback = this->content.subscribe([this](auto...) {
39 this->window.requestLayout = true;
40 });
41 _aperture_callback = this->aperture.subscribe([this](auto...) {
42 this->window.requestLayout = true;
43 });
44 _offset_callback = this->offset.subscribe([this](auto...) {
45 this->window.requestLayout = true;
46 });
47 }
48
50
51 [[nodiscard]] bool constrain(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
52 {
53 tt_axiom(is_gui_thread());
54
55 if (super::constrain(display_time_point, need_reconstrain)) {
56 if constexpr (axis == axis::vertical) {
57 _minimum_size = _preferred_size = {theme::global().icon_size, theme::global().large_size};
58 _maximum_size = {theme::global().icon_size, 32767.0f};
59 } else {
60 _minimum_size = _preferred_size = {theme::global().large_size, theme::global().icon_size};
61 _maximum_size = {32767.0f, theme::global().icon_size};
62 }
63 tt_axiom(_minimum_size <= _preferred_size && _preferred_size <= _maximum_size);
64 return true;
65 } else {
66 return false;
67 }
68 }
69
70 [[nodiscard]] void layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
71 {
72 tt_axiom(is_gui_thread());
73
74 need_layout |= _request_layout.exchange(false);
75 if (need_layout) {
76 tt_axiom(*content != 0.0f);
77
78 // Calculate the position of the slider.
79 ttlet slider_offset = *offset * travel_vs_hidden_content_ratio();
80
81 if constexpr (axis == axis::vertical) {
82 slider_rectangle =
83 aarectangle{rectangle().left(), rectangle().bottom() + slider_offset, rectangle().width(), slider_length()};
84 } else {
85 slider_rectangle =
86 aarectangle{rectangle().left() + slider_offset, rectangle().bottom(), slider_length(), rectangle().height()};
87 }
88 }
89
90 super::layout(display_time_point, need_layout);
91 }
92
93 void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
94 {
95 tt_axiom(is_gui_thread());
96
97 if (overlaps(context, this->_clipping_rectangle) and visible) {
98 draw_rails(context);
99 draw_slider(context);
100 }
101 super::draw(std::move(context), display_time_point);
102 }
103
104 hitbox hitbox_test(point2 position) const noexcept override
105 {
106 tt_axiom(is_gui_thread());
107
108 if (visible and _visible_rectangle.contains(position) and slider_rectangle.contains(position)) {
109 return hitbox{this, draw_layer};
110 } else {
111 return hitbox{};
112 }
113 }
114
115 [[nodiscard]] bool handle_event(mouse_event const &event) noexcept
116 {
117 tt_axiom(is_gui_thread());
118 auto handled = super::handle_event(event);
119
120 if (event.cause.leftButton) {
121 handled = true;
122
123 switch (event.type) {
124 using enum mouse_event::Type;
125 case ButtonDown:
126 // Record the original scroll-position before the drag starts.
127 offset_before_drag = *offset;
128 break;
129
130 case Drag: {
131 // The distance the slider has to move relative to the slider position at the
132 // start of the drag.
133 ttlet slider_movement = axis == axis::vertical ? event.delta().y() : event.delta().x();
134 ttlet content_movement = slider_movement * hidden_content_vs_travel_ratio();
135 offset = offset_before_drag + content_movement;
136 } break;
137
138 default:;
139 }
140 }
141 return handled;
142 }
143
144 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
145 {
146 return false;
147 }
148
149 [[nodiscard]] color background_color() const noexcept override
150 {
151 return theme::global(theme_color::fill, semantic_layer);
152 }
153
154 [[nodiscard]] color foreground_color() const noexcept override
155 {
156 if (_hover) {
157 return theme::global(theme_color::fill, semantic_layer + 2);
158 } else {
159 return theme::global(theme_color::fill, semantic_layer + 1);
160 }
161 }
162
163private:
164 observable<float> offset;
165 observable<float> aperture;
166 observable<float> content;
167
168 typename decltype(offset)::callback_ptr_type _offset_callback;
169 typename decltype(aperture)::callback_ptr_type _aperture_callback;
170 typename decltype(content)::callback_ptr_type _content_callback;
171
172 aarectangle slider_rectangle;
173
174 float offset_before_drag;
175
176 [[nodiscard]] float rail_length() const noexcept
177 {
178 tt_axiom(is_gui_thread());
179 return axis == axis::vertical ? rectangle().height() : rectangle().width();
180 }
181
182 [[nodiscard]] float slider_length() const noexcept
183 {
184 tt_axiom(is_gui_thread());
185
186 ttlet content_aperture_ratio = *aperture / *content;
187 return std::max(rail_length() * content_aperture_ratio, theme::global().size * 2.0f);
188 }
189
192 [[nodiscard]] float slider_travel_range() const noexcept
193 {
194 tt_axiom(is_gui_thread());
195 return rail_length() - slider_length();
196 }
197
200 [[nodiscard]] float hidden_content() const noexcept
201 {
202 tt_axiom(is_gui_thread());
203 return *content - *aperture;
204 }
205
210 [[nodiscard]] float hidden_content_vs_travel_ratio() const noexcept
211 {
212 tt_axiom(is_gui_thread());
213
214 ttlet _slider_travel_range = slider_travel_range();
215 return _slider_travel_range != 0.0f ? hidden_content() / _slider_travel_range : 0.0f;
216 }
217
222 [[nodiscard]] float travel_vs_hidden_content_ratio() const noexcept
223 {
224 tt_axiom(is_gui_thread());
225
226 ttlet _hidden_content = hidden_content();
227 return _hidden_content != 0.0f ? slider_travel_range() / _hidden_content : 0.0f;
228 }
229
230 void draw_rails(draw_context context) noexcept
231 {
232 tt_axiom(is_gui_thread());
233
234 ttlet corner_shapes =
235 axis == axis::vertical ? tt::corner_shapes{rectangle().width() * 0.5f} : tt::corner_shapes{rectangle().height() * 0.5f};
236 context.draw_box(rectangle(), background_color(), corner_shapes);
237 }
238
239 void draw_slider(draw_context context) noexcept
240 {
241 tt_axiom(is_gui_thread());
242
243 ttlet corner_shapes = axis == axis::vertical ? tt::corner_shapes{slider_rectangle.width() * 0.5f} :
244 tt::corner_shapes{slider_rectangle.height() * 0.5f};
245
246 context.draw_box(translate_z(0.1f) * slider_rectangle, foreground_color(), corner_shapes);
247 }
248};
249
250using horizontal_scroll_bar_widget = scroll_bar_widget<axis::horizontal>;
251using vertical_scroll_bar_widget = scroll_bar_widget<axis::vertical>;
252
253} // namespace tt
This is a RGBA floating point color.
Definition color.hpp:36
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:20
bool contains(point2 const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition axis_aligned_rectangle.hpp:224
Definition corner_shapes.hpp:9
Draw context for drawing using the TTauri shaders.
Definition draw_context.hpp:29
Definition gui_window.hpp:36
std::atomic< bool > requestLayout
When set to true the widgets will be laid out.
Definition gui_window.hpp:53
Definition hitbox.hpp:14
Definition mouse_event.hpp:15
callback_ptr_type subscribe(Callback &&callback) noexcept
Subscribe a callback function.
Definition observable.hpp:442
Definition scroll_bar_widget.hpp:21
void 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:70
void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept override
Draw the widget.
Definition scroll_bar_widget.hpp:93
bool constrain(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
Update the constraints of the widget.
Definition scroll_bar_widget.hpp:51
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition scroll_bar_widget.hpp:144
hitbox hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_bar_widget.hpp:104
bool handle_event(mouse_event const &event) noexcept
Definition scroll_bar_widget.hpp:115
An interactive graphical object as part of the user-interface.
Definition widget.hpp:39
widget *const parent
Pointer to the parent widget.
Definition widget.hpp:48
int semantic_layer
The draw layer of the widget.
Definition widget.hpp:92
observable< bool > visible
The widget is visible.
Definition widget.hpp:62
aarectangle rectangle() const noexcept
Get the rectangle in local coordinates.
virtual bool handle_event(command command) noexcept
Handle command.
gui_window & window
Convenient reference to the Window.
Definition widget.hpp:43
virtual void draw(draw_context context, hires_utc_clock::time_point display_time_point) noexcept
Draw the widget.
virtual bool constrain(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept
Update the constraints of the widget.
widget(gui_window &window, widget *parent) noexcept
float draw_layer
The draw layer of the widget.
Definition widget.hpp:76
virtual void layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
Update the internal layout of the widget.
T max(T... args)
T move(T... args)