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