HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scroll_bar_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2020-2022.
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
9#pragma once
10
11#include "../GUI/module.hpp"
12#include "../geometry/module.hpp"
13#include "../observer.hpp"
14#include "../utility/module.hpp"
15#include <memory>
16#include <string>
17#include <array>
18#include <optional>
19#include <future>
20
21namespace hi { inline namespace v1 {
22
32template<axis Axis, fixed_string Name = "">
33class scroll_bar_widget final : public widget {
34public:
35 using super = widget;
36 constexpr static auto prefix = Name / (Axis == axis::horizontal ? "hbar" : "vbar");
37
38 static constexpr hi::axis axis = Axis;
39
40 observer<int> offset;
41 observer<int> aperture;
42 observer<int> content;
43
46 forward_of<observer<int>> auto&& content,
47 forward_of<observer<int>> auto&& aperture,
48 forward_of<observer<int>> auto&& offset) noexcept :
49 widget(parent), content(hi_forward(content)), aperture(hi_forward(aperture)), offset(hi_forward(offset))
50 {
51 _content_cbt = this->content.subscribe([&](auto...) {
52 ++global_counter<"scroll_bar_widget:content:relayout">;
53 process_event({gui_event_type::window_relayout});
54 });
55 _aperture_cbt = this->aperture.subscribe([&](auto...) {
56 ++global_counter<"scroll_bar_widget:aperture:relayout">;
57 process_event({gui_event_type::window_relayout});
58 });
59 _offset_cbt = this->offset.subscribe([&](auto...) {
60 ++global_counter<"scroll_bar_widget:offset:relayout">;
61 process_event({gui_event_type::window_relayout});
62 });
63 }
64
66
67 [[nodiscard]] box_constraints update_constraints() noexcept override
68 {
70 return {};
71 }
72
73 // The theme's width and height are the size of the slider.
74 // The scroll-bars and therefor the scroll-view and scroll-aperture
75 // can never be smaller than the length of the slider of both bars.
76 // Unless those bars are disabled by default.
77 hi_axiom(theme<prefix>.width(this) >= theme<prefix / "slider">.width(this));
78 hi_axiom(theme<prefix>.height(this) >= theme<prefix / "slider">.height(this));
79 if constexpr (axis == axis::vertical) {
80 return {
81 extent2i{theme<prefix>.width(this), theme<prefix>.height(this)},
82 extent2i{theme<prefix>.width(this), theme<prefix>.height(this)},
83 extent2i{theme<prefix>.width(this), large_number_v<int>}};
84 } else {
85 return {
86 extent2i{theme<prefix>.width(this), theme<prefix>.height(this)},
87 extent2i{theme<prefix>.width(this), theme<prefix>.height(this)},
88 extent2i{large_number_v<int>, theme<prefix>.height(this)}};
89 }
90 }
91
92 void set_layout(widget_layout const& context) noexcept override
93 {
94 layout = context;
95
97 _slider_rectangle = {};
98 return;
99 }
100
101 // Calculate the position of the slider.
102 hilet slider_offset = narrow_cast<int>(std::round(*offset * travel_vs_hidden_content_ratio()));
103 if constexpr (axis == axis::vertical) {
104 hilet slider_width = theme<prefix / "slider">.width();
105 hilet x = (context.width() - slider_width) / 2;
106
107 _slider_rectangle = aarectanglei{x, slider_offset, slider_width, slider_length()};
108
109 } else {
110 hilet slider_height = theme < prefix / "slider".height();
111 hilet y = (context.height() - slider_height) / 2;
112
113 _slider_rectangle = aarectanglei{slider_offset, y, slider_length(), slider_height};
114 }
115 }
116
117 [[nodiscard]] bool visible() const noexcept
118 {
119 return *aperture < *content;
120 }
121
122 void draw(widget_draw_context const& context) noexcept override
123 {
124 if (*mode > widget_mode::invisible and overlaps(context, layout) and visible()) {
125 draw_rails(context);
126 draw_slider(context);
127 }
128 }
129
130 hitbox hitbox_test(point2i position) const noexcept override
131 {
132 hi_axiom(loop::main().on_thread());
133
134 if (*mode >= widget_mode::partial and layout.contains(position) and visible() and _slider_rectangle.contains(position)) {
135 return {id, layout.elevation, hitbox_type::scroll_bar};
136 } else {
137 return {};
138 }
139 }
140
141 bool handle_event(gui_event const& event) noexcept override
142 {
143 switch (event.type()) {
144 case gui_event_type::mouse_down:
145 if (event.mouse().cause.left_button) {
146 // Record the original scroll-position before the drag starts.
147 _offset_before_drag = *offset;
148 return true;
149 }
150 break;
151
152 case gui_event_type::mouse_drag:
153 if (event.mouse().cause.left_button) {
154 // The distance the slider has to move relative to the slider position at the
155 // start of the drag.
156 hilet slider_movement =
157 narrow_cast<int>(axis == axis::vertical ? event.drag_delta().y() : event.drag_delta().x());
158 hilet content_movement = narrow_cast<int>(std::round(slider_movement * hidden_content_vs_travel_ratio()));
159 hilet new_offset = _offset_before_drag + content_movement;
160 offset = clamp_offset(new_offset);
161 return true;
162 }
163 break;
164
165 default:;
166 }
167
168 return super::handle_event(event);
169 }
170
171 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
172 {
173 return false;
174 }
175
176private:
177 aarectanglei _slider_rectangle;
178
179 int _offset_before_drag;
180
181 typename decltype(content)::callback_token _content_cbt;
182 typename decltype(aperture)::callback_token _aperture_cbt;
183 typename decltype(offset)::callback_token _offset_cbt;
184
189 [[nodiscard]] int clamp_offset(int new_offset) const noexcept
190 {
191 hilet scrollable_distance = std::max(0, *content - *aperture);
192 return std::clamp(new_offset, 0, scrollable_distance);
193 }
194
195 [[nodiscard]] int rail_length() const noexcept
196 {
197 hi_axiom(loop::main().on_thread());
198 return axis == axis::vertical ? layout.height() : layout.width();
199 }
200
201 [[nodiscard]] int slider_length() const noexcept
202 {
203 hi_axiom(loop::main().on_thread());
204
205 // The minimum length of a slider.
206 auto length = axis == axis::vertical ? theme<prefix / "slider">.height(this) : theme<prefix / "slider">.width(this);
207 hi_axiom(length <= rail_length());
208
209 // Increase the length of a slider based on the content to aperture ratio.
210 if (*content != 0 and *aperture <= *content) {
211 inplace_max(length, *aperture * rail_length() / *content);
212 }
213
214 return length;
215 }
216
219 [[nodiscard]] int slider_travel_range() const noexcept
220 {
221 hi_axiom(loop::main().on_thread());
222 return rail_length() - slider_length();
223 }
224
227 [[nodiscard]] int hidden_content() const noexcept
228 {
229 hi_axiom(loop::main().on_thread());
230 return *content - *aperture;
231 }
232
237 [[nodiscard]] float hidden_content_vs_travel_ratio() const noexcept
238 {
239 hi_axiom(loop::main().on_thread());
240
241 hilet _slider_travel_range = slider_travel_range();
242 return _slider_travel_range != 0 ? narrow_cast<float>(hidden_content()) / _slider_travel_range : 0.0f;
243 }
244
249 [[nodiscard]] float travel_vs_hidden_content_ratio() const noexcept
250 {
251 hi_axiom(loop::main().on_thread());
252
253 hilet _hidden_content = hidden_content();
254 return _hidden_content != 0 ? narrow_cast<float>(slider_travel_range()) / _hidden_content : 0.0f;
255 }
256
257 void draw_rails(widget_draw_context const& context) noexcept
258 {
259 context.draw_box(
260 layout,
261 layout.rectangle(),
262 theme<prefix>.background_color(this),
263 theme<prefix>.border_color(this),
264 theme<prefix>.border_width(this),
265 theme<prefix>.corner_radius(this));
266 }
267
268 void draw_slider(widget_draw_context const& context) noexcept
269 {
270 context.draw_box(
271 layout,
272 translate_z(0.1f) * narrow_cast<aarectangle>(_slider_rectangle),
273 theme<prefix / "slider">.background_color(this),
274 theme<prefix / "slider">.border_color(this),
275 theme<prefix / "slider">.border_width(this),
276 theme<prefix / "slider">.corner_radius(this));
277 }
278};
279
280}} // namespace hi::v1
#define hi_axiom(expression,...)
Specify an axiom; an expression that is true.
Definition assert.hpp:253
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
axis
An enumeration of the 3 axis for 3D geometry.
Definition axis.hpp:18
@ window_relayout
Request that widgets get laid out on the next frame.
@ partial
A widget is partially enabled.
@ collapse
The widget is collapsed.
@ invisible
The widget is invisible.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
auto theme
A tagged global variable to a theme model for a widget's component.
Definition theme_model.hpp:433
constexpr bool contains(point< value_type, 2 > const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition axis_aligned_rectangle.hpp:265
constexpr value_type & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:166
constexpr value_type & height() noexcept
Access the y-as-height element from the extent.
Definition extent.hpp:177
A user interface event.
Definition gui_event.hpp:75
Definition widget.hpp:26
widget_id id
The numeric identifier of a widget.
Definition widget.hpp:35
virtual bool handle_event(gui_event const &event) noexcept
Handle command.
Definition widget.hpp:279
widget * parent
Pointer to the parent widget.
Definition widget.hpp:40
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:53
Draw context for drawing using the HikoGUI shaders.
Definition widget_draw_context.hpp:204
The layout of a widget.
Definition widget_layout.hpp:37
constexpr bool contains(point3i mouse_position) const noexcept
Check if the mouse position is inside the widget.
Definition widget_layout.hpp:126
float elevation
The elevation of the widget above the window.
Definition widget_layout.hpp:72
2D constraints.
Definition box_constraints.hpp:22
Scroll bar widget This widget is used in a pair of a vertical and horizontal scrollbar as a child of ...
Definition scroll_bar_widget.hpp:33
void draw(widget_draw_context const &context) noexcept override
Draw the widget.
Definition scroll_bar_widget.hpp:122
void set_layout(widget_layout const &context) noexcept override
Update the internal layout of the widget.
Definition scroll_bar_widget.hpp:92
box_constraints update_constraints() noexcept override
Update the constraints of the widget.
Definition scroll_bar_widget.hpp:67
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition scroll_bar_widget.hpp:141
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition scroll_bar_widget.hpp:171
hitbox hitbox_test(point2i position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_bar_widget.hpp:130
T max(T... args)
T round(T... args)