HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
scroll_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 "widget.hpp"
8#include "scroll_bar_widget.hpp"
9#include "scroll_aperture_widget.hpp"
10#include "scroll_delegate.hpp"
11#include "../GUI/gui_window.hpp"
12#include "../geometry/axis.hpp"
13
14namespace hi::inline v1 {
15
43template<axis Axis = axis::both, bool ControlsWindow = false>
44class scroll_widget final : public widget {
45public:
46 using super = widget;
48
49 static constexpr hi::axis axis = Axis;
50 static constexpr bool controls_window = ControlsWindow;
51
53 {
54 if (auto delegate = _delegate.lock()) {
55 delegate->deinit(*this);
56 }
57 }
58
66 scroll_widget(gui_window& window, widget *parent, std::weak_ptr<delegate_type> delegate = {}) noexcept :
67 super(window, parent), _delegate(std::move(delegate))
68 {
69 hi_axiom(is_gui_thread());
70 hi_axiom(parent);
71
72 // The scroll-widget will not draw itself, only its selected content.
73 semantic_layer = parent->semantic_layer;
74
75 _aperture = std::make_unique<scroll_aperture_widget>(window, this);
76 _horizontal_scroll_bar = std::make_unique<horizontal_scroll_bar_widget>(
77 window, this, _aperture->content_width, _aperture->aperture_width, _aperture->offset_x);
78 _vertical_scroll_bar = std::make_unique<vertical_scroll_bar_widget>(
79 window, this, _aperture->content_height, _aperture->aperture_height, _aperture->offset_y);
80
81 if (auto d = _delegate.lock()) {
82 d->init(*this);
83 }
84 }
85
95 template<typename Widget, typename... Args>
96 Widget& make_widget(Args&&...args) noexcept
97 {
98 return _aperture->make_widget<Widget>(std::forward<Args>(args)...);
99 }
100
102 [[nodiscard]] generator<widget *> children() const noexcept override
103 {
104 co_yield _aperture.get();
105 co_yield _vertical_scroll_bar.get();
106 co_yield _horizontal_scroll_bar.get();
107 }
108
109 widget_constraints const& set_constraints() noexcept override
110 {
111 _layout = {};
112 hilet aperture_constraints = _aperture->set_constraints();
113 hilet horizontal_constraints = _horizontal_scroll_bar->set_constraints();
114 hilet vertical_constraints = _vertical_scroll_bar->set_constraints();
115
116 _constraints = aperture_constraints;
117
118 // When there are scrollbars the widget minimum size becomes basically zero.
119 // However we should at least have enough room to fit in the scroll-bars length-wise.
120 if constexpr (any(axis & axis::horizontal)) {
121 _constraints.minimum.width() = horizontal_constraints.minimum.width();
122 inplace_max(_constraints.preferred.width(), horizontal_constraints.minimum.width());
123 inplace_max(_constraints.maximum.width(), horizontal_constraints.minimum.width());
124 }
125 if constexpr (any(axis & axis::vertical)) {
126 _constraints.minimum.height() = vertical_constraints.minimum.height();
127 inplace_max(_constraints.preferred.height(), vertical_constraints.minimum.height());
128 inplace_max(_constraints.maximum.height(), vertical_constraints.minimum.height());
129 }
130
131 // Make room for the thickness of the scroll-bars.
132 if constexpr (any(axis & axis::horizontal)) {
133 _constraints.minimum.height() += horizontal_constraints.preferred.height();
134 _constraints.preferred.height() += horizontal_constraints.preferred.height();
135 _constraints.maximum.height() += horizontal_constraints.preferred.height();
136 }
137 if constexpr (any(axis & axis::vertical)) {
138 _constraints.minimum.width() += vertical_constraints.preferred.width();
139 _constraints.preferred.width() += vertical_constraints.preferred.width();
140 _constraints.maximum.width() += vertical_constraints.preferred.width();
141 }
142 return _constraints;
143 }
144
145 void set_layout(widget_layout const& layout) noexcept override
146 {
147 if (compare_store(_layout, layout)) {
148 hilet horizontal_visible = _aperture->x_axis_scrolls() and any(axis & axis::horizontal);
149 hilet vertical_visible = _aperture->y_axis_scrolls() and any(axis & axis::vertical);
150 hilet both_visible = horizontal_visible and vertical_visible;
151
152 _horizontal_scroll_bar->mode = horizontal_visible ? widget_mode::enabled : widget_mode::invisible;
153 _vertical_scroll_bar->mode = vertical_visible ? widget_mode::enabled : widget_mode::invisible;
154
155 hilet vertical_scroll_bar_width = _vertical_scroll_bar->constraints().preferred.width();
156 hilet horizontal_scroll_bar_height = _horizontal_scroll_bar->constraints().preferred.height();
157
158 // The aperture size grows to fill the size of the layout.
159 hilet aperture_size = extent2{
160 vertical_visible ? layout.width() - vertical_scroll_bar_width : layout.width(),
161 horizontal_visible ? layout.height() - horizontal_scroll_bar_height : layout.height()};
162 hilet aperture_offset = point2{0.0f, horizontal_visible ? horizontal_scroll_bar_height : 0.0f};
163 _aperture_rectangle = aarectangle{aperture_offset, aperture_size};
164
165 // The length of the scroll-bar is the full length of the widget, or just the length of the aperture depending
166 // if the counter-part scroll-bar is visible.
167 hilet horizontal_scroll_bar_size =
168 extent2{both_visible ? aperture_size.width() : layout.width(), horizontal_scroll_bar_height};
169 hilet vertical_scroll_bar_size =
170 extent2{vertical_scroll_bar_width, both_visible ? aperture_size.height() : layout.height()};
171
172 _vertical_scroll_bar_rectangle = aarectangle{
173 point2{layout.width() - vertical_scroll_bar_size.width(), layout.height() - vertical_scroll_bar_size.height()},
174 vertical_scroll_bar_size};
175
176 _horizontal_scroll_bar_rectangle = aarectangle{point2{0.0f, 0.0f}, horizontal_scroll_bar_size};
177
178 if constexpr (controls_window) {
179 window.set_resize_border_priority(
180 true, not vertical_visible, not horizontal_visible, true);
181 }
182 }
183
184 _aperture->set_layout(layout.transform(_aperture_rectangle));
185 if (*_vertical_scroll_bar->mode > widget_mode::invisible) {
186 _vertical_scroll_bar->set_layout(layout.transform(_vertical_scroll_bar_rectangle));
187 }
188 if (*_horizontal_scroll_bar->mode > widget_mode::invisible) {
189 _horizontal_scroll_bar->set_layout(layout.transform(_horizontal_scroll_bar_rectangle));
190 }
191 }
192
193 void draw(draw_context const& context) noexcept
194 {
195 if (*mode > widget_mode::invisible) {
196 _vertical_scroll_bar->draw(context);
197 _horizontal_scroll_bar->draw(context);
198 _aperture->draw(context);
199 }
200 }
201
202 [[nodiscard]] hitbox hitbox_test(point3 position) const noexcept override
203 {
204 hi_axiom(is_gui_thread());
205
206 if (*mode >= widget_mode::partial) {
207 auto r = _aperture->hitbox_test_from_parent(position);
208 r = _horizontal_scroll_bar->hitbox_test_from_parent(position, r);
209 r = _vertical_scroll_bar->hitbox_test_from_parent(position, r);
210
211 if (layout().contains(position)) {
212 r = std::max(r, hitbox{this, position});
213 }
214 return r;
215
216 } else {
217 return {};
218 }
219 }
220 // @endprivatesection
221private:
223
224 aarectangle _aperture_rectangle;
226
227 aarectangle _horizontal_scroll_bar_rectangle;
229
230 aarectangle _vertical_scroll_bar_rectangle;
232};
233
234template<bool ControlsWindow = false>
235using vertical_scroll_widget = scroll_widget<axis::vertical, ControlsWindow>;
236
237template<bool ControlsWindow = false>
238using horizontal_scroll_widget = scroll_widget<axis::horizontal, ControlsWindow>;
239
240} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
STL namespace.
A return value for a generator-function.
Definition generator.hpp:28
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent.hpp:151
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent.hpp:140
Definition gui_window.hpp:39
The scroll widget allows a content widget to be shown in less space than is required.
Definition scroll_widget.hpp:44
scroll_widget(gui_window &window, widget *parent, std::weak_ptr< delegate_type > delegate={}) noexcept
Constructs an empty scroll widget.
Definition scroll_widget.hpp:66
Widget & make_widget(Args &&...args) noexcept
Add a content widget directly to this scroll widget.
Definition scroll_widget.hpp:96
Definition scroll_delegate.hpp:16
An interactive graphical object as part of the user-interface.
Definition widget.hpp:39
int semantic_layer
The draw layer of the widget.
Definition widget.hpp:81
T max(T... args)