HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scroll_aperture_widget.hpp
Go to the documentation of this file.
1// Copyright Take Vos 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 "../macros.hpp"
13
14namespace hi { inline namespace v1 {
15
24public:
25 using super = widget;
26
27 observer<float> content_width;
28 observer<float> content_height;
29 observer<float> aperture_width;
30 observer<float> aperture_height;
31 observer<float> offset_x;
32 observer<float> offset_y;
33
35 {
36 hi_axiom(loop::main().on_thread());
37 hi_assert_not_null(parent);
38
39 // The aperture-widget will not draw itself, only its selected content.
40 semantic_layer = parent->semantic_layer;
41
42 _content_width_cbt = content_width.subscribe([&](auto...) {
43 ++global_counter<"scroll_aperture_widget:content_width:relayout">;
45 });
46 _content_height_cbt = content_height.subscribe([&](auto...) {
47 ++global_counter<"scroll_aperture_widget:content_height:relayout">;
49 });
50 _aperture_width_cbt = aperture_width.subscribe([&](auto...) {
51 ++global_counter<"scroll_aperture_widget:aperture_width:relayout">;
53 });
54 _aperture_height_cbt = aperture_height.subscribe([&](auto...) {
55 ++global_counter<"scroll_aperture_widget:aperture_height:relayout">;
57 });
58 _offset_x_cbt = offset_x.subscribe([&](auto...) {
59 ++global_counter<"scroll_aperture_widget:offset_x:relayout">;
61 });
62 _offset_y_cbt = offset_y.subscribe([&](auto...) {
63 ++global_counter<"scroll_aperture_widget:offset_y:relayout">;
65 });
66 _minimum_cbt = minimum.subscribe([&](auto...) {
67 ++global_counter<"scroll_aperture_widget:minimum:reconstrain">;
69 });
70 }
71
72 template<typename Widget, typename... Args>
73 Widget& make_widget(Args&&...args) noexcept
74 {
75 hi_axiom(loop::main().on_thread());
76 hi_axiom(_content == nullptr);
77
78 auto tmp = std::make_unique<Widget>(this, std::forward<Args>(args)...);
79 auto& ref = *tmp;
80 _content = std::move(tmp);
81 return ref;
82 }
83
84 [[nodiscard]] bool x_axis_scrolls() const noexcept
85 {
86 return *content_width > *aperture_width;
87 }
88
89 [[nodiscard]] bool y_axis_scrolls() const noexcept
90 {
91 return *content_height > *aperture_height;
92 }
93
95 [[nodiscard]] generator<widget_intf &> children(bool include_invisible) noexcept override
96 {
97 co_yield *_content;
98 }
99
100 [[nodiscard]] box_constraints update_constraints() noexcept override
101 {
102 _layout = {};
103 _content_constraints = _content->update_constraints();
104
105 // The aperture can scroll so its minimum width and height are zero.
106 auto aperture_constraints = _content_constraints;
107 aperture_constraints.minimum = extent2{0, 0};
108
109 return aperture_constraints.internalize_margins().constrain(*minimum, *maximum);
110 }
111
112 void set_layout(widget_layout const& context) noexcept override
113 {
114 if (compare_store(_layout, context)) {
115 aperture_width = context.width() - _content_constraints.margins.left() - _content_constraints.margins.right();
116 aperture_height = context.height() - _content_constraints.margins.bottom() - _content_constraints.margins.top();
117
118 // Start scrolling with the preferred size as minimum, so
119 // that widgets in the content don't get unnecessarily squeezed.
120 content_width = *aperture_width < _content_constraints.preferred.width() ? _content_constraints.preferred.width() :
121 *aperture_width;
122 content_height = *aperture_height < _content_constraints.preferred.height() ?
123 _content_constraints.preferred.height() :
124 *aperture_height;
125 }
126
127 // Make sure the offsets are limited to the scrollable area.
128 hilet offset_x_max = std::max(*content_width - *aperture_width, 0.0f);
129 hilet offset_y_max = std::max(*content_height - *aperture_height, 0.0f);
130 offset_x = std::clamp(*offset_x, 0.0f, offset_x_max);
131 offset_y = std::clamp(*offset_y, 0.0f, offset_y_max);
132
133 // The position of the content rectangle relative to the scroll view.
134 // The size is further adjusted if the either the horizontal or vertical scroll bar is invisible.
135 _content_shape = box_shape{
136 _content_constraints,
138 -*offset_x + _content_constraints.margins.left(),
139 -*offset_y + _content_constraints.margins.bottom(),
140 *content_width,
141 *content_height},
142 theme().baseline_adjustment()};
143
144 // The content needs to be at a higher elevation, so that hitbox check
145 // will work correctly for handling scrolling with mouse wheel.
146 _content->set_layout(context.transform(_content_shape, 1.0f, context.rectangle()));
147 }
148
149 void draw(draw_context const& context) noexcept override
150 {
152 _content->draw(context);
153 }
154 }
155
156 [[nodiscard]] hitbox hitbox_test(point2 position) const noexcept override
157 {
158 hi_axiom(loop::main().on_thread());
159
160 if (*mode >= widget_mode::partial) {
161 auto r = _content->hitbox_test_from_parent(position);
162
163 if (layout().contains(position)) {
164 r = std::max(r, hitbox{id, _layout.elevation});
165 }
166 return r;
167
168 } else {
169 return {};
170 }
171 }
172
173 bool handle_event(gui_event const& event) noexcept override
174 {
175 hi_axiom(loop::main().on_thread());
176
177 if (event == gui_event_type::mouse_wheel) {
178 hilet new_offset_x = *offset_x + std::round(event.mouse().wheel_delta.x() * theme().scale);
179 hilet new_offset_y = *offset_y + std::round(event.mouse().wheel_delta.y() * theme().scale);
180 hilet max_offset_x = std::max(0.0f, *content_width - *aperture_width);
181 hilet max_offset_y = std::max(0.0f, *content_height - *aperture_height);
182
183 offset_x = std::clamp(new_offset_x, 0.0f, max_offset_x);
184 offset_y = std::clamp(new_offset_y, 0.0f, max_offset_y);
185 ++global_counter<"scroll_aperture_widget:mouse_wheel:relayout">;
187 return true;
188 } else {
190 }
191 }
192
193 void scroll_to_show(hi::aarectangle to_show) noexcept override
194 {
195 if (_layout) {
196 auto safe_rectangle = intersect(_layout.rectangle(), _layout.clipping_rectangle);
197 auto delta_x = 0.0f;
198 auto delta_y = 0.0f;
199
200 if (safe_rectangle.width() > theme().margin<float>() * 2.0f and safe_rectangle.height() > theme().margin<float>() * 2.0f) {
201 // This will look visually better, if the selected widget is moved with some margin from
202 // the edge of the scroll widget. The margins of the content do not have anything to do
203 // with the margins that are needed here.
204 safe_rectangle = safe_rectangle - theme().margin<float>();
205
206 if (to_show.right() > safe_rectangle.right()) {
207 delta_x = to_show.right() - safe_rectangle.right();
208 } else if (to_show.left() < safe_rectangle.left()) {
209 delta_x = to_show.left() - safe_rectangle.left();
210 }
211
212 if (to_show.top() > safe_rectangle.top()) {
213 delta_y = to_show.top() - safe_rectangle.top();
214 } else if (to_show.bottom() < safe_rectangle.bottom()) {
215 delta_y = to_show.bottom() - safe_rectangle.bottom();
216 }
217
218 // Scroll the widget
219 offset_x = std::round(offset_x + delta_x);
220 offset_y = std::round(offset_y + delta_y);
221 }
222
223 // There may be recursive scroll view, and they all need to move until the rectangle is visible.
224 if (parent) {
225 parent->scroll_to_show(_layout.to_parent * translate2(delta_x, delta_y) * to_show);
226 }
227
228 } else {
230 }
231 }
233private:
234 box_constraints _content_constraints;
235 box_shape _content_shape;
237 decltype(content_width)::callback_token _content_width_cbt;
238 decltype(content_height)::callback_token _content_height_cbt;
239 decltype(aperture_width)::callback_token _aperture_width_cbt;
240 decltype(aperture_height)::callback_token _aperture_height_cbt;
241 decltype(offset_x)::callback_token _offset_x_cbt;
242 decltype(offset_y)::callback_token _offset_y_cbt;
243 decltype(minimum)::callback_token _minimum_cbt;
244};
245
246}} // namespace hi::v1
Defines widget.
@ window_relayout
Request that widgets get laid out on the next frame.
@ window_reconstrain
Request that widget get constraint on the next frame.
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition misc.hpp:56
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:29
A high-level geometric extent.
Definition extent2.hpp:29
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:104
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:115
Definition translate2.hpp:14
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
virtual void scroll_to_show(hi::aarectangle rectangle) noexcept=0
Scroll to show the given rectangle on the window.
The layout of a widget.
Definition widget_layout.hpp:38
2D constraints.
Definition box_constraints.hpp:25
Definition box_shape.hpp:18
A scroll aperture widget.
Definition scroll_aperture_widget.hpp:23
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< extent2 > minimum
The minimum size this widget is allowed to be.
Definition widget.hpp:79
void scroll_to_show() noexcept
Scroll to show the important part of the widget.
Definition widget_intf.hpp:196
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
observer< extent2 > maximum
The maximum size this widget is allowed to be.
Definition widget.hpp:83
T max(T... args)
T move(T... args)
T round(T... args)