mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 07:44:59 +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