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