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