mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 01:02:45 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									d1d5602132
								
							
						
					
					
						commit
						8081a8a5de
					
				
					 13 changed files with 106 additions and 99 deletions
				
			
		|  | @ -94,8 +94,7 @@ | ||||||
|             fixed_width: 150 |             fixed_width: 150 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // HACK: We need something like Layout::add_spacer() in GML! :^) |         @GUI::Layout::Spacer {} | ||||||
|         @GUI::Widget {} |  | ||||||
| 
 | 
 | ||||||
|         @GUI::Button { |         @GUI::Button { | ||||||
|             name: "close_button" |             name: "close_button" | ||||||
|  |  | ||||||
|  | @ -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::Layout::Spacer {} | ||||||
|         @GUI::Widget {} |  | ||||||
| 
 | 
 | ||||||
|         @GUI::Button { |         @GUI::Button { | ||||||
|             name: "ok_button" |             name: "ok_button" | ||||||
|  |  | ||||||
|  | @ -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 {} | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  |  | ||||||
|  | @ -28,6 +28,5 @@ | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Spacer |     @GUI::Layout::Spacer {} | ||||||
|     @GUI::Widget {} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,6 +14,5 @@ | ||||||
|         fixed_height: 28 |         fixed_height: 28 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Spacer |     @GUI::Layout::Spacer {} | ||||||
|     @GUI::Widget {} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 {} | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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,65 +42,65 @@ 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) |     if (peek() == Token::Type::LeftCurly) { | ||||||
|         return Error::from_string_literal("Expected {{"sv); |  | ||||||
| 
 | 
 | ||||||
|     tokens.dequeue(); |         tokens.dequeue(); | ||||||
| 
 | 
 | ||||||
|     NonnullRefPtrVector<Comment> pending_comments; |         NonnullRefPtrVector<Comment> pending_comments; | ||||||
|     for (;;) { |         for (;;) { | ||||||
|         if (peek() == Token::Type::RightCurly) { |             if (peek() == Token::Type::RightCurly) { | ||||||
|             // End of object
 |                 // End of object
 | ||||||
|             break; |                 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<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); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (peek() == Token::Type::ClassMarker) { |         // Insert any left-over comments as sub object children, as these will be serialized last
 | ||||||
|             // 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()) |         if (peek() != Token::Type::RightCurly) | ||||||
|                 TRY(object->add_sub_object_child(pending_comments.take_first())); |             return Error::from_string_literal("Expected }}"sv); | ||||||
| 
 | 
 | ||||||
|             TRY(object->add_sub_object_child(TRY(parse_gml_object(tokens)))); |         tokens.dequeue(); | ||||||
|         } 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<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); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // 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; |     return object; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @GUI::Widget {} |         @GUI::Layout::Spacer {} | ||||||
| 
 | 
 | ||||||
|         @GUI::Widget { |         @GUI::Widget { | ||||||
|             shrink_to_fit: true |             shrink_to_fit: true | ||||||
|  |  | ||||||
|  | @ -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; |         // It is very questionable if this pseudo object should exist, but it works fine like this for now.
 | ||||||
|         if (auto* registration = Core::ObjectClassRegistration::find(class_name)) { |         if (class_name == "GUI::Layout::Spacer") { | ||||||
|             child = registration->construct(); |             if (!this->layout()) { | ||||||
|             if (!child || !registration->is_derived_from(widget_class)) { |                 dbgln("Specified GUI::Layout::Spacer in GML, but the parent has no Layout."); | ||||||
|                 dbgln("Invalid widget class: '{}'", class_name); |  | ||||||
|                 return IterationDecision::Break; |                 return IterationDecision::Break; | ||||||
|             } |             } | ||||||
|  |             this->layout()->add_spacer(); | ||||||
|         } else { |         } else { | ||||||
|             child = unregistered_child_handler(class_name); |             RefPtr<Core::Object> child; | ||||||
|         } |             if (auto* registration = Core::ObjectClassRegistration::find(class_name)) { | ||||||
|         if (!child) |                 child = registration->construct(); | ||||||
|             return IterationDecision::Break; |                 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.
 | ||||||
|         // 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); | ||||||
|         static_ptr_cast<Widget>(child)->load_from_gml_ast(child_data, unregistered_child_handler); |  | ||||||
| 
 | 
 | ||||||
|         if (is_tab_widget) { |             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.
 |                 // 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); |                 remove_child(*child); | ||||||
|             reinterpret_cast<TabWidget*>(this)->add_widget(*static_ptr_cast<Widget>(child)); |                 reinterpret_cast<TabWidget*>(this)->add_widget(*static_ptr_cast<Widget>(child)); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return IterationDecision::Continue; |         return IterationDecision::Continue; | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 FrHun
						FrHun