Serenity Operating System
1/*
2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/RefPtr.h>
9#include <LibGUI/Layout.h>
10#include <LibGUI/ScrollableContainerWidget.h>
11
12REGISTER_WIDGET(GUI, ScrollableContainerWidget)
13
14namespace GUI {
15
16ScrollableContainerWidget::ScrollableContainerWidget()
17{
18 REGISTER_BOOL_PROPERTY("scrollbars_enabled", is_scrollbars_enabled, set_scrollbars_enabled);
19 REGISTER_BOOL_PROPERTY("should_hide_unnecessary_scrollbars", should_hide_unnecessary_scrollbars, set_should_hide_unnecessary_scrollbars);
20}
21
22void ScrollableContainerWidget::did_scroll()
23{
24 AbstractScrollableWidget::did_scroll();
25 update_widget_position();
26}
27
28void ScrollableContainerWidget::update_widget_position()
29{
30 if (!m_widget)
31 return;
32 m_widget->move_to(-horizontal_scrollbar().value() + content_margins().left(), -vertical_scrollbar().value() + content_margins().top());
33}
34
35void ScrollableContainerWidget::update_widget_size()
36{
37 if (!m_widget)
38 return;
39 m_widget->do_layout();
40 if (m_widget->is_shrink_to_fit() && m_widget->layout()) {
41 auto new_size = Widget::content_size();
42 auto preferred_size = m_widget->effective_preferred_size();
43 if (preferred_size.width().is_int())
44 new_size.set_width(preferred_size.width().as_int());
45 if (preferred_size.height().is_int())
46 new_size.set_height(preferred_size.height().as_int());
47 m_widget->resize(new_size);
48 set_content_size(new_size);
49 } else {
50 auto inner_size = Widget::content_size();
51 auto min_size = m_widget->effective_min_size();
52 auto new_size = Gfx::Size {
53 max(inner_size.width(), MUST(min_size.width().shrink_value())),
54 max(inner_size.height(), MUST(min_size.height().shrink_value()))
55 };
56 m_widget->resize(new_size);
57 set_content_size(new_size);
58 }
59}
60
61void ScrollableContainerWidget::update_widget_min_size()
62{
63 if (!m_widget)
64 set_min_content_size({});
65 else
66 set_min_content_size(Gfx::IntSize(m_widget->effective_min_size().replace_component_if_matching_with(SpecialDimension::Shrink, UISize { 0, 0 })));
67}
68
69void ScrollableContainerWidget::resize_event(GUI::ResizeEvent& event)
70{
71 AbstractScrollableWidget::resize_event(event);
72 update_widget_size();
73 update_widget_position();
74}
75
76void ScrollableContainerWidget::layout_relevant_change_occurred()
77{
78 update_widget_min_size();
79 update_scrollbar_visibility();
80 update_scrollbar_ranges();
81 update_widget_size();
82 update_widget_position();
83 update();
84}
85
86void ScrollableContainerWidget::set_widget(GUI::Widget* widget)
87{
88 if (m_widget == widget)
89 return;
90
91 if (m_widget)
92 remove_child(*m_widget);
93
94 m_widget = widget;
95
96 if (m_widget) {
97 add_child(*m_widget);
98 m_widget->move_to_back();
99 }
100 update_widget_min_size();
101 update_widget_size();
102 update_widget_position();
103}
104
105ErrorOr<void> ScrollableContainerWidget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node const> ast, UnregisteredChildHandler unregistered_child_handler)
106{
107 if (is<GUI::GML::GMLFile>(ast.ptr()))
108 return load_from_gml_ast(static_cast<GUI::GML::GMLFile const&>(*ast).main_class(), unregistered_child_handler);
109
110 VERIFY(is<GUI::GML::Object>(ast.ptr()));
111 auto const& object = static_cast<GUI::GML::Object const&>(*ast);
112
113 object.for_each_property([&](auto key, auto value) {
114 set_property(key, value);
115 });
116
117 auto content_widget_value = object.get_property("content_widget"sv);
118 if (!content_widget_value.is_null() && !is<GUI::GML::Object>(content_widget_value.ptr())) {
119 return Error::from_string_literal("ScrollableContainerWidget content_widget is not an object");
120 }
121
122 auto has_children = false;
123 object.for_each_child_object([&](auto) { has_children = true; });
124 if (has_children) {
125 return Error::from_string_literal("Children specified for ScrollableContainerWidget, but only 1 widget as content_widget is supported");
126 }
127
128 if (!content_widget_value.is_null() && is<GUI::GML::Object>(content_widget_value.ptr())) {
129 auto const& content_widget = static_cast<GUI::GML::Object const&>(*content_widget_value);
130 auto class_name = content_widget.name();
131
132 RefPtr<Core::Object> child;
133 if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
134 child = TRY(registration->construct());
135 } else {
136 child = TRY(unregistered_child_handler(class_name));
137 }
138 if (!child)
139 return Error::from_string_literal("Unable to construct a Widget class for ScrollableContainerWidget content_widget property");
140 auto widget_ptr = verify_cast<GUI::Widget>(child.ptr());
141 set_widget(widget_ptr);
142 TRY(static_cast<Widget&>(*child).load_from_gml_ast(content_widget, unregistered_child_handler));
143 }
144
145 return {};
146}
147
148}