26 observer<float> content_width;
27 observer<float> content_height;
28 observer<float> aperture_width;
29 observer<float> aperture_height;
30 observer<float> offset_x;
31 observer<float> offset_y;
41 _content_width_cbt = content_width.subscribe([&](
auto...) {
42 ++global_counter<
"scroll_aperture_widget:content_width:relayout">;
45 _content_height_cbt = content_height.subscribe([&](
auto...) {
46 ++global_counter<
"scroll_aperture_widget:content_height:relayout">;
49 _aperture_width_cbt = aperture_width.subscribe([&](
auto...) {
50 ++global_counter<
"scroll_aperture_widget:aperture_width:relayout">;
53 _aperture_height_cbt = aperture_height.subscribe([&](
auto...) {
54 ++global_counter<
"scroll_aperture_widget:aperture_height:relayout">;
57 _offset_x_cbt = offset_x.subscribe([&](
auto...) {
58 ++global_counter<
"scroll_aperture_widget:offset_x:relayout">;
61 _offset_y_cbt = offset_y.subscribe([&](
auto...) {
62 ++global_counter<
"scroll_aperture_widget:offset_y:relayout">;
67 template<
typename Widget,
typename... Args>
68 Widget& make_widget(Args&&...args)
noexcept
73 auto tmp = std::make_unique<Widget>(
this, std::forward<Args>(args)...);
79 [[nodiscard]]
bool x_axis_scrolls()
const noexcept
81 return *content_width > *aperture_width;
84 [[nodiscard]]
bool y_axis_scrolls()
const noexcept
86 return *content_height > *aperture_height;
90 [[nodiscard]] generator<widget *> children()
const noexcept override
92 co_yield _content.get();
100 hilet content_constraints = _content->set_constraints(context);
102 hilet minimum_size = extent2{
103 content_constraints.margins.left() + content_constraints.minimum.
width() + content_constraints.margins.right(),
104 content_constraints.margins.top() + content_constraints.minimum.height() + content_constraints.margins.bottom()};
105 hilet preferred_size = extent2{
106 content_constraints.margins.left() + content_constraints.preferred.
width() + content_constraints.margins.right(),
107 content_constraints.margins.top() + content_constraints.preferred.height() + content_constraints.margins.bottom()};
108 hilet maximum_size = extent2{
109 content_constraints.margins.left() + content_constraints.maximum.
width() + content_constraints.margins.right(),
110 content_constraints.margins.top() + content_constraints.maximum.height() + content_constraints.margins.bottom()};
112 return _constraints = {minimum_size, preferred_size, maximum_size, margins{}};
115 void set_layout(
widget_layout const& context)
noexcept override
117 hilet content_constraints = _content->constraints();
118 hilet margins = content_constraints.margins;
121 hilet preferred_size = content_constraints.preferred;
123 aperture_width = context.width() - margins.left() - margins.right();
124 aperture_height = context.height() - margins.bottom() - margins.top();
128 content_width = *aperture_width < preferred_size.width() ? preferred_size.width() : *aperture_width;
129 content_height = *aperture_height < preferred_size.height() ? preferred_size.height() : *aperture_height;
133 hilet offset_x_max =
std::max(*content_width - *aperture_width, 0.0f);
134 hilet offset_y_max =
std::max(*content_height - *aperture_height, 0.0f);
135 offset_x = std::clamp(
std::round(*offset_x), 0.0f, offset_x_max);
136 offset_y = std::clamp(
std::round(*offset_y), 0.0f, offset_y_max);
140 _content_rectangle = {-*offset_x + margins.left(), -*offset_y + margins.bottom(), *content_width, *content_height};
144 _content->set_layout(context.transform(_content_rectangle, 1.0f, context.rectangle()));
147 void draw(draw_context
const& context)
noexcept
150 _content->draw(context);
154 [[nodiscard]] hitbox hitbox_test(point3 position)
const noexcept override
159 auto r = _content->hitbox_test_from_parent(position);
161 if (
layout().contains(position)) {
162 r =
std::max(r, hitbox{
this, position});
171 bool handle_event(
gui_event const& event)
noexcept override
175 if (event == gui_event_type::mouse_wheel) {
176 hilet new_offset_x = *offset_x +
event.mouse().wheel_delta.x() * _layout.theme->scale;
177 hilet new_offset_y = *offset_y +
event.mouse().wheel_delta.y() * _layout.theme->scale;
178 hilet max_offset_x =
std::max(0.0f, *content_width - *aperture_width);
179 hilet max_offset_y =
std::max(0.0f, *content_height - *aperture_height);
181 offset_x = std::clamp(new_offset_x, 0.0f, max_offset_x);
182 offset_y = std::clamp(new_offset_y, 0.0f, max_offset_y);
183 ++global_counter<
"scroll_aperture_widget:mouse_wheel:relayout">;
194 auto safe_rectangle = intersect(_layout.rectangle(), _layout.clipping_rectangle);
195 float delta_x = 0.0f;
196 float delta_y = 0.0f;
198 if (safe_rectangle.width() > _layout.theme->margin * 2.0f and
199 safe_rectangle.height() > _layout.theme->margin * 2.0f) {
203 safe_rectangle = safe_rectangle - _layout.theme->margin;
205 if (to_show.right() > safe_rectangle.right()) {
206 delta_x = to_show.right() - safe_rectangle.right();
207 }
else if (to_show.left() < safe_rectangle.left()) {
208 delta_x = to_show.left() - safe_rectangle.left();
211 if (to_show.top() > safe_rectangle.top()) {
212 delta_y = to_show.top() - safe_rectangle.top();
213 }
else if (to_show.bottom() < safe_rectangle.bottom()) {
214 delta_y = to_show.bottom() - safe_rectangle.bottom();
224 parent->
scroll_to_show(bounding_rectangle(_layout.to_parent * translate2(delta_x, delta_y) * to_show));
233 aarectangle _content_rectangle;
235 decltype(content_width)::callback_token _content_width_cbt;
236 decltype(content_height)::callback_token _content_height_cbt;
237 decltype(aperture_width)::callback_token _aperture_width_cbt;
238 decltype(aperture_height)::callback_token _aperture_height_cbt;
239 decltype(offset_x)::callback_token _offset_x_cbt;
240 decltype(offset_y)::callback_token _offset_y_cbt;