45 static_assert(Axis == axis::horizontal or Axis == axis::vertical or Axis == axis::both);
50 static constexpr tt::axis axis = Axis;
51 static constexpr bool controls_window = ControlsWindow;
63 tt_axiom(is_gui_thread());
71 _request_layout =
true;
74 _scroll_content_width.subscribe(_layout_callback);
75 _scroll_content_height.subscribe(_layout_callback);
76 _scroll_aperture_width.subscribe(_layout_callback);
77 _scroll_aperture_height.subscribe(_layout_callback);
78 _scroll_offset_x.subscribe(_layout_callback);
79 _scroll_offset_y.subscribe(_layout_callback);
91 template<
typename Widget,
typename... Args>
94 tt_axiom(is_gui_thread());
95 tt_axiom(not _content);
97 auto &
widget = super::make_widget<Widget>(std::forward<Args>(args)...);
103 void init() noexcept
override
107 _horizontal_scroll_bar =
108 &super::make_widget<horizontal_scroll_bar_widget>(_scroll_content_width, _scroll_aperture_width, _scroll_offset_x);
109 _vertical_scroll_bar =
110 &super::make_widget<vertical_scroll_bar_widget>(_scroll_content_height, _scroll_aperture_height, _scroll_offset_y);
112 if (
auto delegate = _delegate.lock()) {
113 delegate->init(*
this);
117 void deinit() noexcept
override
119 if (
auto delegate = _delegate.lock()) {
120 delegate->deinit(*
this);
125 [[nodiscard]]
float margin() const noexcept
override
130 [[nodiscard]]
bool constrain(utc_nanoseconds display_time_point,
bool need_reconstrain)
noexcept override
132 tt_axiom(is_gui_thread());
135 auto has_updated_contraints =
super::constrain(display_time_point, need_reconstrain);
138 if (has_updated_contraints) {
147 if constexpr (any(axis & axis::horizontal)) {
153 if constexpr (any(axis & axis::vertical)) {
160 if constexpr (any(axis & axis::horizontal)) {
165 if constexpr (any(axis & axis::vertical)) {
171 tt_axiom(_minimum_size <= _preferred_size && _preferred_size <= _maximum_size);
172 return has_updated_contraints;
175 [[nodiscard]]
void layout(utc_nanoseconds display_time_point,
bool need_layout)
noexcept override
177 tt_axiom(is_gui_thread());
180 need_layout |= _request_layout.exchange(
false);
187 ttlet height_adjustment = _horizontal_scroll_bar->
visible ? horizontal_scroll_bar_height : 0.0f;
188 ttlet width_adjustment = _vertical_scroll_bar->
visible ? vertical_scroll_bar_width : 0.0f;
190 ttlet vertical_scroll_bar_rectangle = aarectangle{
191 width() - vertical_scroll_bar_width, height_adjustment, vertical_scroll_bar_width, height() - height_adjustment};
193 ttlet horizontal_scroll_bar_rectangle =
194 aarectangle{0.0f, 0.0f, width() - width_adjustment, horizontal_scroll_bar_height};
196 _vertical_scroll_bar->set_layout_parameters_from_parent(vertical_scroll_bar_rectangle);
197 _horizontal_scroll_bar->set_layout_parameters_from_parent(horizontal_scroll_bar_rectangle);
199 _aperture_rectangle = aarectangle{0.0f, height_adjustment, width() - width_adjustment, height() - height_adjustment};
206 _scroll_aperture_width = _aperture_rectangle.width();
207 _scroll_aperture_height = _aperture_rectangle.height();
209 ttlet scroll_offset_x_max =
std::max(_scroll_content_width - _scroll_aperture_width, 0.0f);
210 ttlet scroll_offset_y_max =
std::max(_scroll_content_height - _scroll_aperture_height, 0.0f);
212 _scroll_offset_x = std::clamp(
std::round(*_scroll_offset_x), 0.0f, scroll_offset_x_max);
213 _scroll_offset_y = std::clamp(
std::round(*_scroll_offset_y), 0.0f, scroll_offset_y_max);
216 ttlet content_size = extent2{
217 std::max(*_scroll_content_width, _aperture_rectangle.width()),
218 std::max(*_scroll_content_height, _aperture_rectangle.height())};
222 ttlet content_rectangle = aarectangle{
223 -_scroll_offset_x, -_scroll_offset_y - height_adjustment, content_size.width(), content_size.height()};
226 _content->set_layout_parameters_from_parent(
229 if constexpr (controls_window) {
230 window.set_resize_border_priority(
231 true, not _vertical_scroll_bar->
visible, not _horizontal_scroll_bar->
visible,
true);
238 [[nodiscard]] hitbox hitbox_test(point2 position)
const noexcept override
240 tt_axiom(is_gui_thread());
245 if (_visible_rectangle.contains(position)) {
253 bool handle_event(mouse_event
const &event)
noexcept override
255 tt_axiom(is_gui_thread());
258 if (event.type == mouse_event::Type::Wheel) {
260 _scroll_offset_x +=
event.wheelDelta.x();
261 _scroll_offset_y +=
event.wheelDelta.y();
262 _request_layout =
true;
270 auto rectangle_ = aarectangle{
rectangle};
272 float delta_x = 0.0f;
273 if (rectangle_.right() > _aperture_rectangle.right()) {
274 delta_x = rectangle_.right() - _aperture_rectangle.right();
275 }
else if (rectangle_.left() < _aperture_rectangle.left()) {
276 delta_x = rectangle_.left() - _aperture_rectangle.left();
279 float delta_y = 0.0f;
280 if (rectangle_.top() > _aperture_rectangle.top()) {
281 delta_y = rectangle_.top() - _aperture_rectangle.top();
282 }
else if (rectangle_.bottom() < _aperture_rectangle.bottom()) {
283 delta_y = rectangle_.bottom() - _aperture_rectangle.bottom();
286 _scroll_offset_x += delta_x;
287 _scroll_offset_y += delta_y;
297 widget *_content =
nullptr;
298 horizontal_scroll_bar_widget *_horizontal_scroll_bar =
nullptr;
299 vertical_scroll_bar_widget *_vertical_scroll_bar =
nullptr;
301 observable<float> _scroll_content_width;
302 observable<float> _scroll_content_height;
303 observable<float> _scroll_aperture_width;
304 observable<float> _scroll_aperture_height;
305 observable<float> _scroll_offset_x;
306 observable<float> _scroll_offset_y;
307 observable<float>::callback_ptr_type _layout_callback;
309 aarectangle _aperture_rectangle;
316 ttlet content_size = _content->preferred_size();
318 if (content_size <= size()) {
319 return {
false,
false};
320 }
else if (content_size.width() - _vertical_scroll_bar->preferred_size().width() <= width()) {
321 return {
false,
true};
322 }
else if (content_size.height() - _horizontal_scroll_bar->preferred_size().height() <= height()) {
323 return {
true,
false};