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 "widget.hpp"
12#include "../GUI/module.hpp"
13#include "../geometry/module.hpp"
14#include "../observer/module.hpp"
15#include "../utility/module.hpp"
16#include <memory>
17#include <string>
18#include <array>
19#include <optional>
20#include <future>
21
22namespace hi { inline namespace v1 {
23
33template<axis Axis>
34class scroll_bar_widget final : public widget {
35public:
36 using super = widget;
37
38 static constexpr hi::axis axis = Axis;
39
40 observer<float> offset;
41 observer<float> aperture;
42 observer<float> content;
43
46 forward_of<observer<float>> auto&& content,
47 forward_of<observer<float>> auto&& aperture,
48 forward_of<observer<float>> 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">;
54 });
55 _aperture_cbt = this->aperture.subscribe([&](auto...) {
56 ++global_counter<"scroll_bar_widget:aperture:relayout">;
58 });
59 _offset_cbt = this->offset.subscribe([&](auto...) {
60 ++global_counter<"scroll_bar_widget:offset:relayout">;
62 });
63 }
64
66
67 [[nodiscard]] box_constraints update_constraints() noexcept override
68 {
69 _layout = {};
70
72 return {};
73 }
74
75 // The minimum size is twice the length of the slider, which is twice the theme().size()
76 if constexpr (axis == axis::vertical) {
77 return {
78 extent2{theme().icon_size(), theme().size() * 4},
79 extent2{theme().icon_size(), theme().size() * 4},
80 extent2{theme().icon_size(), large_number_v<int>}};
81 } else {
82 return {
83 extent2{theme().size() * 4, theme().icon_size()},
84 extent2{theme().size() * 4, theme().icon_size()},
85 extent2{large_number_v<int>, theme().icon_size()}};
86 }
87 }
88
89 void set_layout(widget_layout const& context) noexcept override
90 {
91 _layout = context;
92
94 _slider_rectangle = {};
95 return;
96 }
97
98 // Calculate the position of the slider.
99 hilet slider_offset = std::round(*offset * travel_vs_hidden_content_ratio());
100 if constexpr (axis == axis::vertical) {
101 _slider_rectangle = aarectangle{0.0f, slider_offset, context.width(), slider_length()};
102 } else {
103 _slider_rectangle = aarectangle{slider_offset, 0.0f, slider_length(), context.height()};
104 }
105 }
106
107 [[nodiscard]] bool visible() const noexcept
108 {
109 return *aperture < *content;
110 }
111
112 void draw(draw_context const& context) noexcept override
113 {
114 if (*mode > widget_mode::invisible and overlaps(context, layout()) and visible()) {
115 draw_rails(context);
116 draw_slider(context);
117 }
118 }
119
120 hitbox hitbox_test(point2 position) const noexcept override
121 {
122 hi_axiom(loop::main().on_thread());
123
124 if (*mode >= widget_mode::partial and layout().contains(position) and visible() and
125 _slider_rectangle.contains(position)) {
126 return {id, _layout.elevation, hitbox_type::scroll_bar};
127 } else {
128 return {};
129 }
130 }
131
132 bool handle_event(gui_event const& event) noexcept override
133 {
134 switch (event.type()) {
135 case gui_event_type::mouse_down:
136 if (event.mouse().cause.left_button) {
137 // Record the original scroll-position before the drag starts.
138 _offset_before_drag = *offset;
139 return true;
140 }
141 break;
142
143 case gui_event_type::mouse_drag:
144 if (event.mouse().cause.left_button) {
145 // The distance the slider has to move relative to the slider position at the
146 // start of the drag.
147 hilet slider_movement = axis == axis::vertical ? event.drag_delta().y() : event.drag_delta().x();
148 hilet content_movement = round_cast<int>(slider_movement * hidden_content_vs_travel_ratio());
149 hilet new_offset = _offset_before_drag + content_movement;
150 offset = clamp_offset(new_offset);
151 return true;
152 }
153 break;
154
155 default:;
156 }
157
158 return super::handle_event(event);
159 }
160
161 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
162 {
163 return false;
164 }
165
166 [[nodiscard]] color background_color() const noexcept override
167 {
168 return theme().color(semantic_color::fill, semantic_layer);
169 }
170
171 [[nodiscard]] color foreground_color() const noexcept override
172 {
173 if (*hover) {
174 return theme().color(semantic_color::fill, semantic_layer + 2);
175 } else {
176 return theme().color(semantic_color::fill, semantic_layer + 1);
177 }
178 }
179
180private:
181 aarectangle _slider_rectangle;
182
183 float _offset_before_drag;
184
185 typename decltype(content)::callback_token _content_cbt;
186 typename decltype(aperture)::callback_token _aperture_cbt;
187 typename decltype(offset)::callback_token _offset_cbt;
188
193 [[nodiscard]] float clamp_offset(float new_offset) const noexcept
194 {
195 hilet scrollable_distance = std::max(0.0f, *content - *aperture);
196 return std::clamp(new_offset, 0.0f, scrollable_distance);
197 }
198
199 [[nodiscard]] float rail_length() const noexcept
200 {
201 hi_axiom(loop::main().on_thread());
202 return axis == axis::vertical ? layout().height() : layout().width();
203 }
204
205 [[nodiscard]] float slider_length() const noexcept
206 {
207 hi_axiom(loop::main().on_thread());
208
209 hilet preferred_length = [&] {
210 if (*content == 0.0f) {
211 return rail_length();
212 } else {
213 return std::round(*aperture * rail_length() / *content);
214 }
215 }();
216
217 return std::clamp(preferred_length, theme().size() * 2.0f, rail_length());
218 }
219
222 [[nodiscard]] float slider_travel_range() const noexcept
223 {
224 hi_axiom(loop::main().on_thread());
225 return rail_length() - slider_length();
226 }
227
230 [[nodiscard]] float hidden_content() const noexcept
231 {
232 hi_axiom(loop::main().on_thread());
233 return *content - *aperture;
234 }
235
240 [[nodiscard]] float hidden_content_vs_travel_ratio() const noexcept
241 {
242 hi_axiom(loop::main().on_thread());
243
244 hilet _slider_travel_range = slider_travel_range();
245 return _slider_travel_range != 0 ? std::round(hidden_content() / _slider_travel_range) : 0.0f;
246 }
247
252 [[nodiscard]] float travel_vs_hidden_content_ratio() const noexcept
253 {
254 hi_axiom(loop::main().on_thread());
255
256 hilet _hidden_content = hidden_content();
257 return _hidden_content != 0 ? slider_travel_range() / _hidden_content : 0.0f;
258 }
259
260 void draw_rails(draw_context const& context) noexcept
261 {
262 hilet corner_radii =
263 axis == axis::vertical ? hi::corner_radii{layout().width() * 0.5f} : hi::corner_radii{layout().height() * 0.5f};
264 context.draw_box(layout(), layout().rectangle(), background_color(), corner_radii);
265 }
266
267 void draw_slider(draw_context const& context) noexcept
268 {
269 hilet corner_radii = axis == axis::vertical ? hi::corner_radii{_slider_rectangle.width() / 2.0f} :
270 hi::corner_radii{_slider_rectangle.height() / 2.0f};
271
272 context.draw_box(
273 layout(), translate_z(0.1f) * _slider_rectangle, foreground_color(), corner_radii);
274 }
275};
276
277}} // namespace hi::v1
Defines widget.
#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.
@ rectangle
The gui_event has rectangle data.
@ 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
This is a RGBA floating point color.
Definition color.hpp:42
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:26
constexpr bool contains(point2 const &rhs) const noexcept
Check if a 2D coordinate is inside the rectangle.
Definition aarectangle.hpp:261
The 4 radii of the corners of a quad or rectangle.
Definition corner_radii.hpp:18
A high-level geometric extent.
Definition extent2.hpp:26
Draw context for drawing using the HikoGUI shaders.
Definition draw_context.hpp:209
A user interface event.
Definition gui_event.hpp:74
widget_id id
The numeric identifier of a widget.
Definition widget_intf.hpp:22
widget_intf * parent
Pointer to the parent widget.
Definition widget_intf.hpp:27
The layout of a widget.
Definition widget_layout.hpp:35
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:34
hitbox hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_bar_widget.hpp:120
void draw(draw_context const &context) noexcept override
Draw the widget.
Definition scroll_bar_widget.hpp:112
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition scroll_bar_widget.hpp:132
void set_layout(widget_layout const &context) noexcept override
Update the internal layout of the widget.
Definition scroll_bar_widget.hpp:89
box_constraints update_constraints() noexcept override
Update the constraints of the widget.
Definition scroll_bar_widget.hpp:67
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition scroll_bar_widget.hpp:161
An interactive graphical object as part of the user-interface.
Definition widget.hpp:36
widget_layout const & layout() const noexcept override
Get the current layout for this widget.
Definition widget.hpp:155
int semantic_layer
The draw layer of the widget.
Definition widget.hpp:65
observer< bool > hover
Mouse cursor is hovering over the widget.
Definition widget.hpp:45
widget(widget *parent) noexcept
bool process_event(gui_event const &event) const noexcept override
Send a event to the window.
Definition widget.hpp:164
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:41
bool handle_event(gui_event const &event) noexcept override
Handle command.
T max(T... args)
T round(T... args)