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_vec2{width, height};
81 _preferred_base_line = {};
82 }
83
84 return has_updated_contraints;
85 }
86
87 [[nodiscard]] void update_layout(hires_utc_clock::time_point display_time_point, bool need_layout) noexcept override
88 {
89 tt_axiom(gui_system_mutex.recurse_lock_count());
90 tt_axiom(_content);
91
92 need_layout |= std::exchange(_request_relayout, false);
93 if (need_layout) {
94 // Calculate the width and height of the scroll-bars, make the infinity thin when they don't exist.
95 ttlet vertical_scroll_bar_width =
96 can_scroll_vertically ? _vertical_scroll_bar->preferred_size().minimum().width() : 0.0f;
97 ttlet horizontal_scroll_bar_height =
98 can_scroll_horizontally ? _horizontal_scroll_bar->preferred_size().minimum().height() : 0.0f;
99 ttlet vertical_scroll_bar_height = rectangle().height() - horizontal_scroll_bar_height;
100 ttlet horizontal_scroll_bar_widht = rectangle().width() - vertical_scroll_bar_width;
101
102 // Calculate the rectangles based on the sizes of the scrollbars.
103 ttlet vertical_scroll_bar_rectangle = aarect{
104 rectangle().right() - vertical_scroll_bar_width,
105 rectangle().y() + horizontal_scroll_bar_height,
106 vertical_scroll_bar_width,
107 rectangle().height() - horizontal_scroll_bar_height};
108
109 ttlet horizontal_scroll_bar_rectangle = aarect{
110 rectangle().x(), rectangle().y(), rectangle().width() - vertical_scroll_bar_width, horizontal_scroll_bar_height};
111
112 // Update layout parameters for both scrollbars.
113 if constexpr (can_scroll_horizontally) {
114 _horizontal_scroll_bar->set_layout_parameters(
115 translate2{_window_rectangle} * horizontal_scroll_bar_rectangle, _window_clipping_rectangle);
116 }
117 if constexpr (can_scroll_vertically) {
118 _vertical_scroll_bar->set_layout_parameters(
119 translate2{_window_rectangle} * vertical_scroll_bar_rectangle, _window_clipping_rectangle);
120 }
121
122 auto aperture_x = rectangle().x();
123 auto aperture_y = horizontal_scroll_bar_rectangle.top();
124 auto aperture_width = horizontal_scroll_bar_rectangle.width();
125 auto aperture_height = vertical_scroll_bar_rectangle.height();
126
127 // We can not use the content_rectangle is the window for the content.
128 // We need to calculate the window_content_rectangle, to positions the content after scrolling.
129 _scroll_content_width = can_scroll_horizontally ? _content->preferred_size().minimum().width() : aperture_width;
130 _scroll_content_height = can_scroll_vertically ? _content->preferred_size().minimum().height() : aperture_height;
131
132 _scroll_aperture_width = aperture_width;
133 _scroll_aperture_height = aperture_height;
134
135 ttlet scroll_offset_x_max = std::max(*_scroll_content_width - aperture_width, 0.0f);
136 ttlet scroll_offset_y_max = std::max(*_scroll_content_height - aperture_height, 0.0f);
137
138 _scroll_offset_x = std::clamp(std::round(*_scroll_offset_x), 0.0f, scroll_offset_x_max);
139 _scroll_offset_y = std::clamp(std::round(*_scroll_offset_y), 0.0f, scroll_offset_y_max);
140
141 auto content_x = -*_scroll_offset_x;
142 auto content_y = -*_scroll_offset_y;
143 auto content_width = *_scroll_content_width;
144 auto content_height = *_scroll_content_height;
145
146 if (can_scroll_horizontally && !_horizontal_scroll_bar->visible()) {
147 ttlet delta_height = horizontal_scroll_bar_rectangle.height();
148 aperture_height += delta_height;
149 aperture_y -= delta_height;
150 content_height += delta_height;
151 content_y -= delta_height;
152 }
153
154 if (can_scroll_vertically && !_vertical_scroll_bar->visible()) {
155 ttlet delta_width = vertical_scroll_bar_rectangle.width();
156 aperture_width += delta_width;
157 content_width += delta_width;
158 }
159
160 if constexpr (controls_window) {
161 ttlet has_horizontal_scroll_bar = can_scroll_horizontally && _horizontal_scroll_bar->visible();
162 ttlet has_vertical_scroll_bar = can_scroll_vertically && _vertical_scroll_bar->visible();
163 window.set_resize_border_priority(true, !has_vertical_scroll_bar, !has_horizontal_scroll_bar, true);
164 }
165
166 // Make a clipping rectangle that fits the content_rectangle exactly.
167 ttlet aperture_rectangle = aarect{aperture_x, aperture_y, aperture_width, aperture_height};
168 ttlet window_aperture_clipping_rectangle =
169 intersect(_window_clipping_rectangle, translate2{_window_rectangle} * aperture_rectangle);
170
171 ttlet content_rectangle = aarect{content_x, content_y, content_width, content_height};
172
173 _content->set_layout_parameters(
174 translate2{_window_rectangle} * content_rectangle, window_aperture_clipping_rectangle);
175 }
176
177 super::update_layout(display_time_point, need_layout);
178 }
179
180 [[nodiscard]] hit_box hitbox_test(f32x4 window_position) const noexcept override
181 {
182 ttlet lock = std::scoped_lock(gui_system_mutex);
183 tt_axiom(_content);
184
185 auto r = super::hitbox_test(window_position);
186
187 if (window_clipping_rectangle().contains(window_position)) {
188 // Claim mouse events for scrolling.
189 r = std::max(r, hit_box{weak_from_this(), _draw_layer});
190 }
191
192 return r;
193 }
194
195 template<typename WidgetType = grid_layout_widget, typename... Args>
196 std::shared_ptr<WidgetType> make_widget(Args &&... args) noexcept
197 {
198 ttlet lock = std::scoped_lock(gui_system_mutex);
199
200 auto widget = super::make_widget<WidgetType>(std::forward<Args>(args)...);
201 tt_axiom(!_content);
202 _content = widget;
203 return widget;
204 }
205
206 bool handle_event(mouse_event const &event) noexcept override
207 {
208 ttlet lock = std::scoped_lock(gui_system_mutex);
209 auto handled = super::handle_event(event);
210
211 if (event.type == mouse_event::Type::Wheel) {
212 handled = true;
213 _scroll_offset_x += event.wheelDelta.x();
214 _scroll_offset_y += event.wheelDelta.y();
215 _request_relayout = true;
216 return true;
217 }
218 return handled;
219 }
220
221private:
223 std::shared_ptr<scroll_bar_widget<false>> _horizontal_scroll_bar;
224 std::shared_ptr<scroll_bar_widget<true>> _vertical_scroll_bar;
225
226 observable<float> _scroll_content_width;
227 observable<float> _scroll_content_height;
228 observable<float> _scroll_aperture_width;
229 observable<float> _scroll_aperture_height;
230 observable<float> _scroll_offset_x;
231 observable<float> _scroll_offset_y;
232};
233
234template<bool ControlsWindow = false>
235using vertical_scroll_view_widget = scroll_view_widget<false, true, ControlsWindow>;
236
237template<bool ControlsWindow = false>
238using horizontal_scroll_view_widget = scroll_view_widget<true, false, ControlsWindow>;
239
240} // namespace tt
Definition gui_window.hpp:39
Definition hit_box.hpp:15
Definition mouse_event.hpp:13
A 2D vector using interval arithmetic.
Definition interval_vec2.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:111
hit_box hitbox_test(f32x4 window_position) const noexcept
Find the widget that is under the mouse cursor.
Definition abstract_container_widget.hpp:152
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:96
Definition grid_layout_widget.hpp:16
Definition scroll_view_widget.hpp:14
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:206
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:87
void init() noexcept override
Should be called right after allocating and constructing a widget.
Definition scroll_view_widget.hpp:34
hit_box hitbox_test(f32x4 window_position) const noexcept override
Find the widget that is under the mouse cursor.
Definition scroll_view_widget.hpp:180
Definition widget.hpp:96
widget(gui_window &window, std::shared_ptr< abstract_container_widget > parent) noexcept
virtual bool handle_event(command command) noexcept
Handle command.
gui_window & window
Convenient reference to the Window.
Definition widget.hpp:100
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
virtual aarect window_clipping_rectangle() const noexcept
Get the clipping-rectangle in window coordinates.
Definition widget.hpp:320
aarect rectangle() const noexcept
Get the rectangle in local coordinates.
Definition widget.hpp:340
T max(T... args)
T min(T... args)
T round(T... args)