From 8081a8a5de9ea54b18e21da9786652942f5b8439 Mon Sep 17 00:00:00 2001 From: FrHun <28605587+frhun@users.noreply.github.com> Date: Tue, 4 Jan 2022 18:15:15 +0100 Subject: [PATCH] LibGUI: Add layout spacer support to GML This is a bit of a hack, but it is an easy way to finally get spacers into GML. This will translate well if spacers are later to become child objects of the continer widget. --- .../CrashReporter/CrashReporterWindow.gml | 3 +- Userland/Applications/Run/Run.gml | 3 +- .../Spreadsheet/CondFormatting.gml | 4 +- .../Applications/Welcome/WelcomeWindow.gml | 4 +- .../Demos/WidgetGallery/DemoWizardPage1.gml | 3 +- .../Demos/WidgetGallery/DemoWizardPage2.gml | 3 +- .../WidgetGallery/GalleryGML/BasicsTab.gml | 28 ++--- .../WidgetGallery/GalleryGML/SlidersTab.gml | 8 +- .../Libraries/LibGUI/FilePickerDialog.gml | 2 +- Userland/Libraries/LibGUI/GML/Parser.cpp | 102 +++++++++--------- .../Libraries/LibGUI/PasswordInputDialog.gml | 2 +- Userland/Libraries/LibGUI/Widget.cpp | 41 ++++--- Userland/Services/LoginServer/LoginWindow.gml | 2 + 13 files changed, 106 insertions(+), 99 deletions(-) diff --git a/Userland/Applications/CrashReporter/CrashReporterWindow.gml b/Userland/Applications/CrashReporter/CrashReporterWindow.gml index f4457761107..59928c04d23 100644 --- a/Userland/Applications/CrashReporter/CrashReporterWindow.gml +++ b/Userland/Applications/CrashReporter/CrashReporterWindow.gml @@ -94,8 +94,7 @@ fixed_width: 150 } - // HACK: We need something like Layout::add_spacer() in GML! :^) - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "close_button" diff --git a/Userland/Applications/Run/Run.gml b/Userland/Applications/Run/Run.gml index 5f2fd0ca20f..0ac27a4b686 100644 --- a/Userland/Applications/Run/Run.gml +++ b/Userland/Applications/Run/Run.gml @@ -41,8 +41,7 @@ layout: @GUI::HorizontalBoxLayout {} fixed_height: 22 - // HACK: using an empty widget as a spacer - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "ok_button" diff --git a/Userland/Applications/Spreadsheet/CondFormatting.gml b/Userland/Applications/Spreadsheet/CondFormatting.gml index 853b39a8ebd..934573a1b85 100644 --- a/Userland/Applications/Spreadsheet/CondFormatting.gml +++ b/Userland/Applications/Spreadsheet/CondFormatting.gml @@ -16,7 +16,7 @@ spacing: 10 } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "add_button" @@ -30,6 +30,6 @@ fixed_width: 70 } - @GUI::Widget {} + @GUI::Layout::Spacer {} } } diff --git a/Userland/Applications/Welcome/WelcomeWindow.gml b/Userland/Applications/Welcome/WelcomeWindow.gml index 6c7ebae2e33..618b58aa735 100644 --- a/Userland/Applications/Welcome/WelcomeWindow.gml +++ b/Userland/Applications/Welcome/WelcomeWindow.gml @@ -85,7 +85,7 @@ text: "Next Tip" } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::HorizontalSeparator { fixed_height: 2 @@ -107,7 +107,7 @@ autosize: true } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "close_button" diff --git a/Userland/Demos/WidgetGallery/DemoWizardPage1.gml b/Userland/Demos/WidgetGallery/DemoWizardPage1.gml index 39509577bb0..6aea235524f 100644 --- a/Userland/Demos/WidgetGallery/DemoWizardPage1.gml +++ b/Userland/Demos/WidgetGallery/DemoWizardPage1.gml @@ -28,6 +28,5 @@ } } - // Spacer - @GUI::Widget {} + @GUI::Layout::Spacer {} } diff --git a/Userland/Demos/WidgetGallery/DemoWizardPage2.gml b/Userland/Demos/WidgetGallery/DemoWizardPage2.gml index fac8a0c2786..542509ca82f 100644 --- a/Userland/Demos/WidgetGallery/DemoWizardPage2.gml +++ b/Userland/Demos/WidgetGallery/DemoWizardPage2.gml @@ -14,6 +14,5 @@ fixed_height: 28 } - // Spacer - @GUI::Widget {} + @GUI::Layout::Spacer {} } diff --git a/Userland/Demos/WidgetGallery/GalleryGML/BasicsTab.gml b/Userland/Demos/WidgetGallery/GalleryGML/BasicsTab.gml index e9ba1fa4ca8..bc3cf3538e4 100644 --- a/Userland/Demos/WidgetGallery/GalleryGML/BasicsTab.gml +++ b/Userland/Demos/WidgetGallery/GalleryGML/BasicsTab.gml @@ -92,7 +92,7 @@ @GUI::Widget { layout: @GUI::VerticalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "normal_button" @@ -105,7 +105,7 @@ enabled: "false" } - @GUI::Widget {} + @GUI::Layout::Spacer {} } @GUI::VerticalSeparator {} @@ -113,7 +113,7 @@ @GUI::Widget { layout: @GUI::VerticalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "enabled_coolbar_button" @@ -128,7 +128,7 @@ button_style: "Coolbar" } - @GUI::Widget {} + @GUI::Layout::Spacer {} } } @@ -144,7 +144,7 @@ fixed_width: 60 layout: @GUI::VerticalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::RadioButton { name: "top_radiobutton" @@ -157,16 +157,16 @@ text: "Radio 2" } - @GUI::Widget {} + @GUI::Layout::Spacer {} } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Widget { fixed_width: 70 layout: @GUI::VerticalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::CheckBox { name: "top_checkbox" @@ -179,10 +179,10 @@ enabled: false } - @GUI::Widget {} + @GUI::Layout::Spacer {} } - @GUI::Widget {} + @GUI::Layout::Spacer {} } @GUI::VerticalSeparator {} @@ -190,7 +190,7 @@ @GUI::Widget { layout: @GUI::VerticalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "icon_button" @@ -203,7 +203,7 @@ enabled: "false" } - @GUI::Widget {} + @GUI::Layout::Spacer {} } } } @@ -278,7 +278,7 @@ } } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "font_button" @@ -295,7 +295,7 @@ text: "Input dialog..." } - @GUI::Widget {} + @GUI::Layout::Spacer {} } } diff --git a/Userland/Demos/WidgetGallery/GalleryGML/SlidersTab.gml b/Userland/Demos/WidgetGallery/GalleryGML/SlidersTab.gml index 790c89b06bd..098736b78c8 100644 --- a/Userland/Demos/WidgetGallery/GalleryGML/SlidersTab.gml +++ b/Userland/Demos/WidgetGallery/GalleryGML/SlidersTab.gml @@ -56,7 +56,7 @@ margins: [0, 8] } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Scrollbar { name: "enabled_scrollbar" @@ -67,11 +67,11 @@ value: 50 } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::HorizontalSeparator {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Scrollbar { name: "disabled_scrollbar" @@ -79,7 +79,7 @@ fixed_width: -1 } - @GUI::Widget {} + @GUI::Layout::Spacer {} } @GUI::GroupBox { diff --git a/Userland/Libraries/LibGUI/FilePickerDialog.gml b/Userland/Libraries/LibGUI/FilePickerDialog.gml index 94d09891bf6..61034bbce00 100644 --- a/Userland/Libraries/LibGUI/FilePickerDialog.gml +++ b/Userland/Libraries/LibGUI/FilePickerDialog.gml @@ -80,7 +80,7 @@ fixed_height: 22 layout: @GUI::HorizontalBoxLayout {} - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Button { name: "cancel_button" diff --git a/Userland/Libraries/LibGUI/GML/Parser.cpp b/Userland/Libraries/LibGUI/GML/Parser.cpp index 169f24c8b9a..394774a5ab9 100644 --- a/Userland/Libraries/LibGUI/GML/Parser.cpp +++ b/Userland/Libraries/LibGUI/GML/Parser.cpp @@ -42,65 +42,65 @@ static ErrorOr> parse_gml_object(Queue& tokens) auto class_name = tokens.dequeue(); object->set_name(class_name.m_view); - if (peek() != Token::Type::LeftCurly) - return Error::from_string_literal("Expected {{"sv); + if (peek() == Token::Type::LeftCurly) { - tokens.dequeue(); + tokens.dequeue(); - NonnullRefPtrVector pending_comments; - for (;;) { - if (peek() == Token::Type::RightCurly) { - // End of object - break; + NonnullRefPtrVector pending_comments; + for (;;) { + if (peek() == Token::Type::RightCurly) { + // End of object + break; + } + + if (peek() == Token::Type::ClassMarker) { + // It's a child object. + + while (!pending_comments.is_empty()) + TRY(object->add_sub_object_child(pending_comments.take_last())); + + TRY(object->add_sub_object_child(TRY(parse_gml_object(tokens)))); + } else if (peek() == Token::Type::Identifier) { + // It's a property. + + while (!pending_comments.is_empty()) + TRY(object->add_property_child(pending_comments.take_last())); + + auto property_name = tokens.dequeue(); + + if (property_name.m_view.is_empty()) + return Error::from_string_literal("Expected non-empty property name"sv); + + if (peek() != Token::Type::Colon) + return Error::from_string_literal("Expected ':'"sv); + + tokens.dequeue(); + + RefPtr value; + if (peek() == Token::Type::ClassMarker) + value = TRY(parse_gml_object(tokens)); + else if (peek() == Token::Type::JsonValue) + value = TRY(try_make_ref_counted(TRY(JsonValueNode::from_string(tokens.dequeue().m_view)))); + + auto property = TRY(try_make_ref_counted(property_name.m_view, value.release_nonnull())); + TRY(object->add_property_child(property)); + } else if (peek() == Token::Type::Comment) { + pending_comments.append(TRY(Node::from_token(tokens.dequeue()))); + } else { + return Error::from_string_literal("Expected child, property, comment, or }}"sv); + } } - if (peek() == Token::Type::ClassMarker) { - // It's a child object. + // Insert any left-over comments as sub object children, as these will be serialized last + while (!pending_comments.is_empty()) + TRY(object->add_sub_object_child(pending_comments.take_first())); - while (!pending_comments.is_empty()) - TRY(object->add_sub_object_child(pending_comments.take_first())); + if (peek() != Token::Type::RightCurly) + return Error::from_string_literal("Expected }}"sv); - TRY(object->add_sub_object_child(TRY(parse_gml_object(tokens)))); - } else if (peek() == Token::Type::Identifier) { - // It's a property. - - while (!pending_comments.is_empty()) - TRY(object->add_property_child(pending_comments.take_first())); - - auto property_name = tokens.dequeue(); - - if (property_name.m_view.is_empty()) - return Error::from_string_literal("Expected non-empty property name"sv); - - if (peek() != Token::Type::Colon) - return Error::from_string_literal("Expected ':'"sv); - - tokens.dequeue(); - - RefPtr value; - if (peek() == Token::Type::ClassMarker) - value = TRY(parse_gml_object(tokens)); - else if (peek() == Token::Type::JsonValue) - value = TRY(try_make_ref_counted(TRY(JsonValueNode::from_string(tokens.dequeue().m_view)))); - - auto property = TRY(try_make_ref_counted(property_name.m_view, value.release_nonnull())); - TRY(object->add_property_child(property)); - } else if (peek() == Token::Type::Comment) { - pending_comments.append(TRY(Node::from_token(tokens.dequeue()))); - } else { - return Error::from_string_literal("Expected child, property, comment, or }}"sv); - } + tokens.dequeue(); } - // Insert any left-over comments as sub object children, as these will be serialized last - while (!pending_comments.is_empty()) - TRY(object->add_sub_object_child(pending_comments.take_first())); - - if (peek() != Token::Type::RightCurly) - return Error::from_string_literal("Expected }}"sv); - - tokens.dequeue(); - return object; } diff --git a/Userland/Libraries/LibGUI/PasswordInputDialog.gml b/Userland/Libraries/LibGUI/PasswordInputDialog.gml index 7d32d977e87..5bc2b750926 100644 --- a/Userland/Libraries/LibGUI/PasswordInputDialog.gml +++ b/Userland/Libraries/LibGUI/PasswordInputDialog.gml @@ -68,7 +68,7 @@ } } - @GUI::Widget {} + @GUI::Layout::Spacer {} @GUI::Widget { shrink_to_fit: true diff --git a/Userland/Libraries/LibGUI/Widget.cpp b/Userland/Libraries/LibGUI/Widget.cpp index 251bb6d010d..d613aed0bdf 100644 --- a/Userland/Libraries/LibGUI/Widget.cpp +++ b/Userland/Libraries/LibGUI/Widget.cpp @@ -1131,27 +1131,36 @@ bool Widget::load_from_gml_ast(NonnullRefPtr ast, RefPtrfor_each_child_object_interruptible([&](auto child_data) { auto class_name = child_data->name(); - RefPtr child; - if (auto* registration = Core::ObjectClassRegistration::find(class_name)) { - child = registration->construct(); - if (!child || !registration->is_derived_from(widget_class)) { - dbgln("Invalid widget class: '{}'", class_name); + // It is very questionable if this pseudo object should exist, but it works fine like this for now. + if (class_name == "GUI::Layout::Spacer") { + if (!this->layout()) { + dbgln("Specified GUI::Layout::Spacer in GML, but the parent has no Layout."); return IterationDecision::Break; } + this->layout()->add_spacer(); } else { - child = unregistered_child_handler(class_name); - } - if (!child) - return IterationDecision::Break; + RefPtr child; + if (auto* registration = Core::ObjectClassRegistration::find(class_name)) { + child = registration->construct(); + if (!child || !registration->is_derived_from(widget_class)) { + dbgln("Invalid widget class: '{}'", class_name); + return IterationDecision::Break; + } + } else { + child = unregistered_child_handler(class_name); + } + if (!child) + return IterationDecision::Break; + add_child(*child); - add_child(*child); - // This is possible as we ensure that Widget is a base class above. - static_ptr_cast(child)->load_from_gml_ast(child_data, unregistered_child_handler); + // This is possible as we ensure that Widget is a base class above. + static_ptr_cast(child)->load_from_gml_ast(child_data, unregistered_child_handler); - if (is_tab_widget) { - // FIXME: We need to have the child added before loading it so that it can access us. But the TabWidget logic requires the child to not be present yet. - remove_child(*child); - reinterpret_cast(this)->add_widget(*static_ptr_cast(child)); + if (is_tab_widget) { + // FIXME: We need to have the child added before loading it so that it can access us. But the TabWidget logic requires the child to not be present yet. + remove_child(*child); + reinterpret_cast(this)->add_widget(*static_ptr_cast(child)); + } } return IterationDecision::Continue; diff --git a/Userland/Services/LoginServer/LoginWindow.gml b/Userland/Services/LoginServer/LoginWindow.gml index a1d2749596c..e3219ebe106 100644 --- a/Userland/Services/LoginServer/LoginWindow.gml +++ b/Userland/Services/LoginServer/LoginWindow.gml @@ -30,6 +30,8 @@ text_alignment: "CenterLeft" } + @GUI::Layout::Spacer {} + @GUI::Button { name: "log_in" text: "Log in"