Переглянути джерело

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.
FrHun 3 роки тому
батько
коміт
8081a8a5de

+ 1 - 2
Userland/Applications/CrashReporter/CrashReporterWindow.gml

@@ -94,8 +94,7 @@
             fixed_width: 150
             fixed_width: 150
         }
         }
 
 
-        // HACK: We need something like Layout::add_spacer() in GML! :^)
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Button {
         @GUI::Button {
             name: "close_button"
             name: "close_button"

+ 1 - 2
Userland/Applications/Run/Run.gml

@@ -41,8 +41,7 @@
         layout: @GUI::HorizontalBoxLayout {}
         layout: @GUI::HorizontalBoxLayout {}
         fixed_height: 22
         fixed_height: 22
 
 
-        // HACK: using an empty widget as a spacer
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Button {
         @GUI::Button {
             name: "ok_button"
             name: "ok_button"

+ 2 - 2
Userland/Applications/Spreadsheet/CondFormatting.gml

@@ -16,7 +16,7 @@
             spacing: 10
             spacing: 10
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Button {
         @GUI::Button {
             name: "add_button"
             name: "add_button"
@@ -30,6 +30,6 @@
             fixed_width: 70
             fixed_width: 70
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
     }
     }
 }
 }

+ 2 - 2
Userland/Applications/Welcome/WelcomeWindow.gml

@@ -85,7 +85,7 @@
                 text: "Next Tip"
                 text: "Next Tip"
             }
             }
 
 
-            @GUI::Widget {}
+            @GUI::Layout::Spacer {}
 
 
             @GUI::HorizontalSeparator {
             @GUI::HorizontalSeparator {
                 fixed_height: 2
                 fixed_height: 2
@@ -107,7 +107,7 @@
             autosize: true
             autosize: true
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Button {
         @GUI::Button {
             name: "close_button"
             name: "close_button"

+ 1 - 2
Userland/Demos/WidgetGallery/DemoWizardPage1.gml

@@ -28,6 +28,5 @@
         }
         }
     }
     }
 
 
-    // Spacer
-    @GUI::Widget {}
+    @GUI::Layout::Spacer {}
 }
 }

+ 1 - 2
Userland/Demos/WidgetGallery/DemoWizardPage2.gml

@@ -14,6 +14,5 @@
         fixed_height: 28
         fixed_height: 28
     }
     }
 
 
-    // Spacer
-    @GUI::Widget {}
+    @GUI::Layout::Spacer {}
 }
 }

+ 14 - 14
Userland/Demos/WidgetGallery/GalleryGML/BasicsTab.gml

@@ -92,7 +92,7 @@
             @GUI::Widget {
             @GUI::Widget {
                 layout: @GUI::VerticalBoxLayout {}
                 layout: @GUI::VerticalBoxLayout {}
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Button {
                 @GUI::Button {
                     name: "normal_button"
                     name: "normal_button"
@@ -105,7 +105,7 @@
                     enabled: "false"
                     enabled: "false"
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
             }
             }
 
 
             @GUI::VerticalSeparator {}
             @GUI::VerticalSeparator {}
@@ -113,7 +113,7 @@
             @GUI::Widget {
             @GUI::Widget {
                 layout: @GUI::VerticalBoxLayout {}
                 layout: @GUI::VerticalBoxLayout {}
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Button {
                 @GUI::Button {
                     name: "enabled_coolbar_button"
                     name: "enabled_coolbar_button"
@@ -128,7 +128,7 @@
                     button_style: "Coolbar"
                     button_style: "Coolbar"
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
             }
             }
         }
         }
 
 
@@ -144,7 +144,7 @@
                     fixed_width: 60
                     fixed_width: 60
                     layout: @GUI::VerticalBoxLayout {}
                     layout: @GUI::VerticalBoxLayout {}
 
 
-                    @GUI::Widget {}
+                    @GUI::Layout::Spacer {}
 
 
                     @GUI::RadioButton {
                     @GUI::RadioButton {
                         name: "top_radiobutton"
                         name: "top_radiobutton"
@@ -157,16 +157,16 @@
                         text: "Radio 2"
                         text: "Radio 2"
                     }
                     }
 
 
-                    @GUI::Widget {}
+                    @GUI::Layout::Spacer {}
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Widget {
                 @GUI::Widget {
                     fixed_width: 70
                     fixed_width: 70
                     layout: @GUI::VerticalBoxLayout {}
                     layout: @GUI::VerticalBoxLayout {}
 
 
-                    @GUI::Widget {}
+                    @GUI::Layout::Spacer {}
 
 
                     @GUI::CheckBox {
                     @GUI::CheckBox {
                         name: "top_checkbox"
                         name: "top_checkbox"
@@ -179,10 +179,10 @@
                         enabled: false
                         enabled: false
                     }
                     }
 
 
-                    @GUI::Widget {}
+                    @GUI::Layout::Spacer {}
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
             }
             }
 
 
             @GUI::VerticalSeparator {}
             @GUI::VerticalSeparator {}
@@ -190,7 +190,7 @@
             @GUI::Widget {
             @GUI::Widget {
                 layout: @GUI::VerticalBoxLayout {}
                 layout: @GUI::VerticalBoxLayout {}
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Button {
                 @GUI::Button {
                     name: "icon_button"
                     name: "icon_button"
@@ -203,7 +203,7 @@
                     enabled: "false"
                     enabled: "false"
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
             }
             }
         }
         }
     }
     }
@@ -278,7 +278,7 @@
                     }
                     }
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Button {
                 @GUI::Button {
                     name: "font_button"
                     name: "font_button"
@@ -295,7 +295,7 @@
                     text: "Input dialog..."
                     text: "Input dialog..."
                 }
                 }
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
             }
             }
         }
         }
 
 

+ 4 - 4
Userland/Demos/WidgetGallery/GalleryGML/SlidersTab.gml

@@ -56,7 +56,7 @@
             margins: [0, 8]
             margins: [0, 8]
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Scrollbar {
         @GUI::Scrollbar {
             name: "enabled_scrollbar"
             name: "enabled_scrollbar"
@@ -67,11 +67,11 @@
             value: 50
             value: 50
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::HorizontalSeparator {}
         @GUI::HorizontalSeparator {}
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Scrollbar {
         @GUI::Scrollbar {
             name: "disabled_scrollbar"
             name: "disabled_scrollbar"
@@ -79,7 +79,7 @@
             fixed_width: -1
             fixed_width: -1
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
     }
     }
 
 
     @GUI::GroupBox {
     @GUI::GroupBox {

+ 1 - 1
Userland/Libraries/LibGUI/FilePickerDialog.gml

@@ -80,7 +80,7 @@
                 fixed_height: 22
                 fixed_height: 22
                 layout: @GUI::HorizontalBoxLayout {}
                 layout: @GUI::HorizontalBoxLayout {}
 
 
-                @GUI::Widget {}
+                @GUI::Layout::Spacer {}
 
 
                 @GUI::Button {
                 @GUI::Button {
                     name: "cancel_button"
                     name: "cancel_button"

+ 42 - 42
Userland/Libraries/LibGUI/GML/Parser.cpp

@@ -42,64 +42,64 @@ static ErrorOr<NonnullRefPtr<Object>> parse_gml_object(Queue<Token>& tokens)
     auto class_name = tokens.dequeue();
     auto class_name = tokens.dequeue();
     object->set_name(class_name.m_view);
     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<Comment> pending_comments;
-    for (;;) {
-        if (peek() == Token::Type::RightCurly) {
-            // End of object
-            break;
-        }
+        NonnullRefPtrVector<Comment> pending_comments;
+        for (;;) {
+            if (peek() == Token::Type::RightCurly) {
+                // End of object
+                break;
+            }
 
 
-        if (peek() == Token::Type::ClassMarker) {
-            // It's a child object.
+            if (peek() == Token::Type::ClassMarker) {
+                // It's a child object.
 
 
-            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_last()));
 
 
-            TRY(object->add_sub_object_child(TRY(parse_gml_object(tokens))));
-        } else if (peek() == Token::Type::Identifier) {
-            // It's a property.
+                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()));
+                while (!pending_comments.is_empty())
+                    TRY(object->add_property_child(pending_comments.take_last()));
 
 
-            auto property_name = tokens.dequeue();
+                auto property_name = tokens.dequeue();
 
 
-            if (property_name.m_view.is_empty())
-                return Error::from_string_literal("Expected non-empty property name"sv);
+                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);
+                if (peek() != Token::Type::Colon)
+                    return Error::from_string_literal("Expected ':'"sv);
 
 
-            tokens.dequeue();
+                tokens.dequeue();
 
 
-            RefPtr<ValueNode> value;
-            if (peek() == Token::Type::ClassMarker)
-                value = TRY(parse_gml_object(tokens));
-            else if (peek() == Token::Type::JsonValue)
-                value = TRY(try_make_ref_counted<JsonValueNode>(TRY(JsonValueNode::from_string(tokens.dequeue().m_view))));
+                RefPtr<ValueNode> value;
+                if (peek() == Token::Type::ClassMarker)
+                    value = TRY(parse_gml_object(tokens));
+                else if (peek() == Token::Type::JsonValue)
+                    value = TRY(try_make_ref_counted<JsonValueNode>(TRY(JsonValueNode::from_string(tokens.dequeue().m_view))));
 
 
-            auto property = TRY(try_make_ref_counted<KeyValuePair>(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<Comment>(tokens.dequeue())));
-        } else {
-            return Error::from_string_literal("Expected child, property, comment, or }}"sv);
+                auto property = TRY(try_make_ref_counted<KeyValuePair>(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<Comment>(tokens.dequeue())));
+            } else {
+                return Error::from_string_literal("Expected child, property, comment, or }}"sv);
+            }
         }
         }
-    }
 
 
-    // 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()));
+        // 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);
+        if (peek() != Token::Type::RightCurly)
+            return Error::from_string_literal("Expected }}"sv);
 
 
-    tokens.dequeue();
+        tokens.dequeue();
+    }
 
 
     return object;
     return object;
 }
 }

+ 1 - 1
Userland/Libraries/LibGUI/PasswordInputDialog.gml

@@ -68,7 +68,7 @@
             }
             }
         }
         }
 
 
-        @GUI::Widget {}
+        @GUI::Layout::Spacer {}
 
 
         @GUI::Widget {
         @GUI::Widget {
             shrink_to_fit: true
             shrink_to_fit: true

+ 25 - 16
Userland/Libraries/LibGUI/Widget.cpp

@@ -1131,27 +1131,36 @@ bool Widget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::O
     object->for_each_child_object_interruptible([&](auto child_data) {
     object->for_each_child_object_interruptible([&](auto child_data) {
         auto class_name = child_data->name();
         auto class_name = child_data->name();
 
 
-        RefPtr<Core::Object> 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;
                 return IterationDecision::Break;
             }
             }
+            this->layout()->add_spacer();
         } else {
         } else {
-            child = unregistered_child_handler(class_name);
-        }
-        if (!child)
-            return IterationDecision::Break;
+            RefPtr<Core::Object> 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<Widget>(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<Widget>(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<TabWidget*>(this)->add_widget(*static_ptr_cast<Widget>(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<TabWidget*>(this)->add_widget(*static_ptr_cast<Widget>(child));
+            }
         }
         }
 
 
         return IterationDecision::Continue;
         return IterationDecision::Continue;

+ 2 - 0
Userland/Services/LoginServer/LoginWindow.gml

@@ -30,6 +30,8 @@
                 text_alignment: "CenterLeft"
                 text_alignment: "CenterLeft"
             }
             }
 
 
+            @GUI::Layout::Spacer {}
+
             @GUI::Button {
             @GUI::Button {
                 name: "log_in"
                 name: "log_in"
                 text: "Log in"
                 text: "Log in"