HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scroll_view_widget.hpp
1// Copyright Take Vos 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 "abstract_container_widget.hpp"
8#include "grid_layout_widget.hpp"
9#include "scroll_bar_widget.hpp"
10
11namespace tt {
12
13template<bool CanScrollHorizontally = true, bool CanScrollVertically = true, bool ControlsWindow = true>
15public:
17
18 static constexpr bool can_scroll_horizontally = CanScrollHorizontally;
19 static constexpr bool can_scroll_vertically = CanScrollVertically;
20 static constexpr bool controls_window = ControlsWindow;
21
23 {
24 if (parent) {
25 // The tab-widget will not draw itself, only its selected content.
26 ttlet lock = std::scoped_lock(gui_system_mutex);
27 _semantic_layer = parent->semantic_layer();
28 }
29 _margin = 0.0f;
30 }
31
33
34 void init() noexcept override
35 {
36 if constexpr (can_scroll_horizontally) {
37 _horizontal_scroll_bar =
38 super::make_widget<scroll_bar_widget<false>>(_scroll_content_width, _scroll_aperture_width, _scroll_offset_x);
39 }
40 if constexpr (can_scroll_vertically) {
41 _vertical_scroll_bar =
42 super::make_widget<scroll_bar_widget<true>>(_scroll_content_height, _scroll_aperture_height, _scroll_offset_y);
43 }
44 }
45
46 [[nodiscard]] bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
47 {
48 tt_axiom(gui_system_mutex.recurse_lock_count());
49 tt_axiom(_content);
50 tt_axiom(!can_scroll_horizontally || _horizontal_scroll_bar);
51 tt_axiom(!can_scroll_vertically || _vertical_scroll_bar);
52
53 auto has_updated_contraints = super::update_constraints(display_time_point, need_reconstrain);
54
55 // Recurse into the selected widget.
56 if (has_updated_contraints) {
57 auto width = _content->preferred_size().width();
58 auto height = _content->preferred_size().height();
59
60 // When there are scrollbars the minimum size is the minimum length of the scrollbar.
61 // The maximum size is the minimum size of the content.
62 if constexpr (can_scroll_horizontally) {
63 // The content could be smaller than the scrollbar.
64 ttlet minimum_width = std::min(width.minimum(), _horizontal_scroll_bar->preferred_size().width().minimum());
65 width = {minimum_width, width.minimum()};
66 }
67 if constexpr (can_scroll_vertically) {
68 ttlet minimum_height = std::min(height.minimum(), _vertical_scroll_bar->preferred_size().height().minimum());
69 height = {minimum_height, height.minimum()};
70 }
71
72 // Make room for the scroll bars.
73 if constexpr (can_scroll_horizontally) {
74 height += _horizontal_scroll_bar->preferred_size().height();
75 }
76 if constexpr (can_scroll_vertically) {
77 width += _vertical_scroll_bar->preferred_size().width();
78 }
79
80 _preferred_size = interval_extent2{width, height};
81 }
82
83 return has_updated_contraints;
84 }
85
86 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
87 {
88 tt_axiom(gui_system_mutex.recurse_lock_count());
89 tt_axiom(_content);
90
91 need_layout |= std::exchange(_request_relayout, false);
92 if (need_layout) {
93 // Calculate the width and height of the scroll-bars, make them infinitesimal thin when they don't exist.
94 ttlet vertical_scroll_bar_width =
95 can_scroll_vertically ? _vertical_scroll_bar->preferred_size().minimum().width() : 0.0f;
96 ttlet horizontal_scroll_bar_height =
97 can_scroll_horizontally ? _horizontal_scroll_bar->preferred_size().minimum().height() : 0.0f;
98 ttlet vertical_scroll_bar_height = height() - horizontal_scroll_bar_height;
99 ttlet horizontal_scroll_bar_width = width() - vertical_scroll_bar_width;
100
101 // Calculate the rectangles based on the sizes of the scrollbars.
102 ttlet vertical_scroll_bar_rectangle = aarectangle{
103 rectangle().right() - vertical_scroll_bar_width,
104 rectangle().bottom() + horizontal_scroll_bar_height,
105 vertical_scroll_bar_width,
106 rectangle().height() - horizontal_scroll_bar_height};
107
108 ttlet horizontal_scroll_bar_rectangle = aarectangle{
109 rectangle().left(), rectangle().bottom(), rectangle().width() - vertical_scroll_bar_width, horizontal_scroll_bar_height};
110
111 // Update layout parameters for both scrollbars.
112 if constexpr (can_scroll_horizontally) {
113 _horizontal_scroll_bar->set_layout_parameters_from_parent(horizontal_scroll_bar_rectangle);
114 }
115 if constexpr (can_scroll_vertically) {
116 _vertical_scroll_bar->set_layout_parameters_from_parent(vertical_scroll_bar_rectangle);
117 }
118
119 auto aperture_x = rectangle().left();
120 auto aperture_y = horizontal_scroll_bar_rectangle.top();
121 auto aperture_width = horizontal_scroll_bar_rectangle.width();
122 auto aperture_height = vertical_scroll_bar_rectangle.height();
123
124 // We can not use the content_rectangle is the window for the content.
125 // We need to calculate the window_content_rectangle, to positions the content after scrolling.
126 _scroll_content_width = can_scroll_horizontally ? _content->preferred_size().minimum().width() : aperture_width;
127 _scroll_content_height = can_scroll_vertically ? _content->preferred_size().minimum().height() : aperture_height;
128
129 _scroll_aperture_width = aperture_width;
130 _scroll_aperture_height = aperture_height;
131
132 ttlet scroll_offset_x_max = std::max(*_scroll_content_width - aperture_width, 0.0f);
133 ttlet scroll_offset_y_max = std::max(*_scroll_content_height - aperture_height, 0.0f);
134
135 _scroll_offset_x = std::clamp(std::round(*_scroll_offset_x), 0.0f, scroll_offset_x_max);
136 _scroll_offset_y = std::clamp(std::round(*_scroll_offset_y), 0.0f, scroll_offset_y_max);
137
138 auto content_x = -*_scroll_offset_x;
139 auto content_y = -*_scroll_offset_y;
140 auto content_width = *_scroll_content_width;
141 auto content_height = *_scroll_content_height;
142
143 if (can_scroll_horizontally && !_horizontal_scroll_bar->visible()) {
144 ttlet delta_height = horizontal_scroll_bar_rectangle.height();
145 aperture_height += delta_height;
146 aperture_y -= delta_height;
147 content_height += delta_height;
148 content_y -= delta_height;
149 }
150
151 if (can_scroll_vertically && !_vertical_scroll_bar->visible()) {
152 ttlet delta_width = vertical_scroll_bar_rectangle.width();
153 aperture_width += delta_width;
154 content_width += delta_width;
155 }
156
157 if constexpr (controls_window) {
158 ttlet has_horizontal_scroll_bar = can_scroll_horizontally && _horizontal_scroll_bar->visible();
159 ttlet has_vertical_scroll_bar = can_scroll_vertically && _vertical_scroll_bar->visible();
160 window.set_resize_border_priority(true, !has_vertical_scroll_bar, !has_horizontal_scroll_bar, true);
161 }
162
163 // Make a clipping rectangle that fits the content_rectangle exactly.
164 ttlet aperture_rectangle = aarectangle{aperture_x, aperture_y, aperture_width, aperture_height};
165 ttlet content_rectangle = aarectangle{content_x, content_y, content_width, content_height};
166
167 _content->set_layout_parameters_from_parent(
168 content_rectangle, aperture_rectangle, _content->draw_layer() - _draw_layer);
169 }
170
171 super::update_layout(display_time_point, need_layout);
172 }
173
174 [[nodiscard]] hit_box hitbox_test(point2 position) const noexcept override
175 {
176 tt_axiom(gui_system_mutex.recurse_lock_count());
177 tt_axiom(_content);
178
179 auto r = super::hitbox_test(position);
180
181 if (_visible_rectangle.contains(position)) {
182 // Claim mouse events for scrolling.
183 r = std::max(r, hit_box{weak_from_this(), _draw_layer});
184 }
185
186 return r;
187 }
188
189 template<typename WidgetType = grid_layout_widget, typename... Args>
190 std::shared_ptr<WidgetType> make_widget(Args &&...args) noexcept
191 {
192 ttlet lock = std::scoped_lock(gui_system_mutex);
193
194 auto widget = super::make_widget<WidgetType>(std::forward<Args>(args)...);
195 tt_axiom(!_content);
196 _content = widget;
197 return widget;
198 }
199
200 bool handle_event(mouse_event const &event) noexcept override
201 {
202 ttlet lock = std::scoped_lock(gui_system_mutex);
203 auto handled = super::handle_event(event);
204
205 if (event.type == mouse_event::Type::Wheel) {
206 handled = true;
207 _scroll_offset_x += event.wheelDelta.x();
208 _scroll_offset_y += event.wheelDelta.y();
209 _request_relayout = true;
210 return true;
211 }
212 return handled;
213 }
214
215private:
217 std::shared_ptr<scroll_bar_widget<false>> _horizontal_scroll_bar;
218 std::shared_ptr<scroll_bar_widget<true>> _vertical_scroll_bar;
219
220 observable<float> _scroll_content_width;
221 observable<float> _scroll_content_height;
222 observable<float> _scroll_aperture_width;
223 observable<float> _scroll_aperture_height;
224 observable<float> _scroll_offset_x;
225 observable<float> _scroll_offset_y;
226};
227
228template<bool ControlsWindow = false>
229using vertical_scroll_view_widget = scroll_view_widget<false, true, ControlsWindow>;
230
231template<bool ControlsWindow = false>
232using horizontal_scroll_view_widget = scroll_view_widget<true, false, ControlsWindow>;
233
234} // namespace tt
Class which represents an axis-aligned rectangle.
Definition axis_aligned_rectangle.hpp:18
Definition gui_window.hpp:37
Definition hit_box.hpp:15
Definition mouse_event.hpp:15
A 2D vector using interval arithmetic.
Definition interval_extent2.hpp:18
Definition observable.hpp:20
int recurse_lock_count() const noexcept
This function should be used in tt_axiom() to check if the lock is held by current thread.
Definition unfair_recursive_mutex.hpp:60
Definition abstract_container_widget.hpp:11
void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept
Update the internal layout of the widget.
Definition abstract_container_widget.hpp:110
hit_box hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition abstract_container_widget.hpp:154
bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept
Update the constraints of the widget.
Definition abstract_container_widget.hpp:95
Definition grid_layout_widget.hpp:16
Definition scroll_view_widget.hpp:14
hit_box hitbox_test(point2 position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_view_widget.hpp:174
bool update_constraints(hires_utc_clock::time_point display_time_point, bool need_reconstrain) noexcept override
Update the constraints of the widget.
Definition scroll_view_widget.hpp:46
bool handle_event(mouse_event const &event) noexcept override
Definition scroll_view_widget.hpp:200
void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
Update the internal layout of the widget.
Definition scroll_view_widget.hpp:86
void init() noexcept override
Should be called right after allocating and constructing a widget.
Definition scroll_view_widget.hpp:34
Definition widget.hpp:97
widget(gui_window &window, std::shared_ptr< abstract_container_widget > parent) noexcept
aarectangle rectangle() const noexcept
Get the rectangle in local coordinates.
Definition widget.hpp:342
virtual bool handle_event(command command) noexcept
Handle command.
gui_window & window
Convenient reference to the Window.
Definition widget.hpp:101
abstract_container_widget const & parent() const noexcept
Get a reference to the parent.
int semantic_layer() const noexcept
The semantic layer of the widget.
Definition widget.hpp:189
T max(T... args)
T min(T... args)
T round(T... args)