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/gui_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 hi::inline v1 {
18
19template<axis Axis>
20class scroll_bar_widget final : public widget {
21public:
22 using super = widget;
23
24 static constexpr hi::axis axis = Axis;
25
26 observable<float> offset;
27 observable<float> aperture;
28 observable<float> content;
29
30 template<typename Content, typename Aperture, typename Offset>
31 scroll_bar_widget(gui_window& window, widget *parent, Content&& content, Aperture&& aperture, Offset&& offset) noexcept :
32 widget(window, parent),
33 content(std::forward<Content>(content)),
34 aperture(std::forward<Aperture>(aperture)),
35 offset(std::forward<Offset>(offset))
36 {
37 // clang-format off
38 _content_cbt = this->content.subscribe([&](auto...){ request_relayout(); });
39 _aperture_cbt = this->aperture.subscribe([&](auto...){ request_relayout(); });
40 _offset_cbt = this->offset.subscribe([&](auto...){ request_relayout(); });
41 // clang-format on
42 }
43
45
46 widget_constraints const& set_constraints() noexcept override
47 {
48 _layout = {};
49
50 // The minimum size is twice the length of the slider, which is twice the theme().size()
51 if constexpr (axis == axis::vertical) {
52 return _constraints = {
53 {theme().icon_size, theme().size * 4.0f},
54 {theme().icon_size, theme().size * 4.0f},
55 {theme().icon_size, 32767.0f}};
56 } else {
57 return _constraints = {
58 {theme().size * 4.0f, theme().icon_size},
59 {theme().size * 4.0f, theme().icon_size},
60 {32767.0f, theme().icon_size}};
61 }
62 }
63
64 void set_layout(widget_layout const& layout) noexcept override
65 {
66 _layout = layout;
67
68 // Calculate the position of the slider.
69 hilet slider_offset = *offset * travel_vs_hidden_content_ratio();
70 if constexpr (axis == axis::vertical) {
71 _slider_rectangle = aarectangle{0.0f, slider_offset, layout.width(), slider_length()};
72 } else {
73 _slider_rectangle = aarectangle{slider_offset, 0.0f, slider_length(), layout.height()};
74 }
75 }
76
77 void draw(draw_context const& context) noexcept override
78 {
79 if (*mode > widget_mode::invisible and overlaps(context, layout())) {
80 draw_rails(context);
81 draw_slider(context);
82 }
83 }
84
85 hitbox hitbox_test(point3 position) const noexcept override
86 {
87 hi_axiom(is_gui_thread());
88
89 if (*mode >= widget_mode::partial and layout().contains(position) and _slider_rectangle.contains(position)) {
90 return {this, position};
91 } else {
92 return {};
93 }
94 }
95
96 bool handle_event(gui_event const& event) noexcept
97 {
98 switch (event.type()) {
99 case gui_event_type::mouse_down:
100 if (event.mouse().cause.left_button) {
101 // Record the original scroll-position before the drag starts.
102 _offset_before_drag = *offset;
103 return true;
104 }
105 break;
106
107 case gui_event_type::mouse_drag:
108 if (event.mouse().cause.left_button) {
109 // The distance the slider has to move relative to the slider position at the
110 // start of the drag.
111 hilet slider_movement = axis == axis::vertical ? event.drag_delta().y() : event.drag_delta().x();
112 hilet content_movement = slider_movement * hidden_content_vs_travel_ratio();
113 hilet new_offset = _offset_before_drag + content_movement;
114 offset = clamp_offset(new_offset);
115 return true;
116 }
117 break;
118
119 default:;
120 }
121
122 return super::handle_event(event);
123 }
124
125 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
126 {
127 return false;
128 }
129
130 [[nodiscard]] color background_color() const noexcept override
131 {
132 return theme().color(semantic_color::fill, semantic_layer);
133 }
134
135 [[nodiscard]] color foreground_color() const noexcept override
136 {
137 if (*hover) {
138 return theme().color(semantic_color::fill, semantic_layer + 2);
139 } else {
140 return theme().color(semantic_color::fill, semantic_layer + 1);
141 }
142 }
143
144private:
145 aarectangle _slider_rectangle;
146
147 float _offset_before_drag;
148
149 typename decltype(content)::token_type _content_cbt;
150 typename decltype(aperture)::token_type _aperture_cbt;
151 typename decltype(offset)::token_type _offset_cbt;
152
157 [[nodiscard]] float clamp_offset(float new_offset) const noexcept
158 {
159 hilet scrollable_distance = std::max(0.0f, *content - *aperture);
160 return std::clamp(new_offset, 0.0f, scrollable_distance);
161 }
162
163 [[nodiscard]] float rail_length() const noexcept
164 {
165 hi_axiom(is_gui_thread());
166 return axis == axis::vertical ? layout().height() : layout().width();
167 }
168
169 [[nodiscard]] float slider_length() const noexcept
170 {
171 hi_axiom(is_gui_thread());
172
173 hilet content_aperture_ratio = *content != 0.0f ? *aperture / *content : 1.0f;
174 hilet rail_length_ = rail_length();
175 return std::clamp(rail_length_ * content_aperture_ratio, theme().size * 2.0f, rail_length_);
176 }
177
180 [[nodiscard]] float slider_travel_range() const noexcept
181 {
182 hi_axiom(is_gui_thread());
183 return rail_length() - slider_length();
184 }
185
188 [[nodiscard]] float hidden_content() const noexcept
189 {
190 hi_axiom(is_gui_thread());
191 return *content - *aperture;
192 }
193
198 [[nodiscard]] float hidden_content_vs_travel_ratio() const noexcept
199 {
200 hi_axiom(is_gui_thread());
201
202 hilet _slider_travel_range = slider_travel_range();
203 return _slider_travel_range != 0.0f ? hidden_content() / _slider_travel_range : 0.0f;
204 }
205
210 [[nodiscard]] float travel_vs_hidden_content_ratio() const noexcept
211 {
212 hi_axiom(is_gui_thread());
213
214 hilet _hidden_content = hidden_content();
215 return _hidden_content != 0.0f ? slider_travel_range() / _hidden_content : 0.0f;
216 }
217
218 void draw_rails(draw_context const& context) noexcept
219 {
220 hilet corner_radii =
221 axis == axis::vertical ? hi::corner_radii{layout().width() * 0.5f} : hi::corner_radii{layout().height() * 0.5f};
222 context.draw_box(layout(), layout().rectangle(), background_color(), corner_radii);
223 }
224
225 void draw_slider(draw_context const& context) noexcept
226 {
227 hilet corner_radii = axis == axis::vertical ? hi::corner_radii{_slider_rectangle.width() * 0.5f} :
228 hi::corner_radii{_slider_rectangle.height() * 0.5f};
229
230 context.draw_box(layout(), translate_z(0.1f) * _slider_rectangle, foreground_color(), corner_radii);
231 }
232};
233
234using horizontal_scroll_bar_widget = scroll_bar_widget<axis::horizontal>;
235using vertical_scroll_bar_widget = scroll_bar_widget<axis::vertical>;
236
237} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
This is a RGBA floating point color.
Definition color.hpp:39
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:20
Draw context for drawing using the HikoGUI shaders.
Definition draw_context.hpp:52
A user interface event.
Definition gui_event.hpp:58
Definition gui_window.hpp:39
Definition hitbox.hpp:16
Definition theme.hpp:21
float size
The size of small square widgets.
Definition theme.hpp:47
float icon_size
Size of icons inside a widget.
Definition theme.hpp:55
An observable value.
Definition observable.hpp:359
Definition scroll_bar_widget.hpp:20
bool handle_event(gui_event const &event) noexcept
Handle command.
Definition scroll_bar_widget.hpp:96
bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
Check if the widget will accept keyboard focus.
Definition scroll_bar_widget.hpp:125
hitbox hitbox_test(point3 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_bar_widget.hpp:85
widget_constraints const & set_constraints() noexcept override
Update the constraints of the widget.
Definition scroll_bar_widget.hpp:46
void set_layout(widget_layout const &layout) noexcept override
Update the internal layout of the widget.
Definition scroll_bar_widget.hpp:64
void draw(draw_context const &context) noexcept override
Draw the widget.
Definition scroll_bar_widget.hpp:77
An interactive graphical object as part of the user-interface.
Definition widget.hpp:39
Definition widget_constraints.hpp:13
Definition widget_layout.hpp:18
T max(T... args)