mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibGUI: Move GML parsing and formatting to new AST
This commit introduces a couple of connected changes that are hard to untangle, unfortunately: - Parse GML into the AST instead of JSON - Change the load_from_json API on Widget to load_from_gml_ast - Remove this same API from Core::Object as it isn't used outside of LibGUI and was a workaround for the object registration detection; by verifying the objects we're getting and casting we can remove this constraint. - Format GML by calling the formating APIs on the AST itself; remove GMLFormatter.cpp as it's not needed anymore. After this change, GML formatting already respects comments :^)
This commit is contained in:
parent
1806b297b6
commit
41ef4f11dc
Notes:
sideshowbarker
2024-07-17 19:39:15 +09:00
Author: https://github.com/kleinesfilmroellchen Commit: https://github.com/SerenityOS/serenity/commit/41ef4f11dcd Pull-request: https://github.com/SerenityOS/serenity/pull/12281 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/awesomekling Reviewed-by: https://github.com/linusg
10 changed files with 127 additions and 236 deletions
|
@ -181,8 +181,6 @@ public:
|
|||
void increment_inspector_count(Badge<InspectorServerConnection>);
|
||||
void decrement_inspector_count(Badge<InspectorServerConnection>);
|
||||
|
||||
virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*)(const String&)) { return false; }
|
||||
|
||||
protected:
|
||||
explicit Object(Object* parent = nullptr);
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ set(SOURCES
|
|||
GitCommitSyntaxHighlighter.cpp
|
||||
GlyphMapWidget.cpp
|
||||
GML/AutocompleteProvider.cpp
|
||||
GML/Formatter.cpp
|
||||
GML/Lexer.cpp
|
||||
GML/Parser.cpp
|
||||
GML/SyntaxHighlighter.cpp
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Formatter.h"
|
||||
#include "Parser.h"
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
|
||||
namespace GUI::GML {
|
||||
|
||||
static String format_gml_object(const JsonObject& node, size_t indentation = 0, bool is_inline = false)
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
auto indent = [&builder](size_t indentation) {
|
||||
for (size_t i = 0; i < indentation; ++i)
|
||||
builder.append(" ");
|
||||
};
|
||||
|
||||
struct Property {
|
||||
String key;
|
||||
JsonValue value;
|
||||
};
|
||||
Vector<Property> properties;
|
||||
node.for_each_member([&](auto& key, auto& value) {
|
||||
if (key != "class" && key != "layout" && key != "children")
|
||||
properties.append({ key, value });
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (!is_inline)
|
||||
indent(indentation);
|
||||
builder.append('@');
|
||||
builder.append(node.get("class").as_string());
|
||||
builder.append(" {\n");
|
||||
|
||||
for (auto& property : properties) {
|
||||
indent(indentation + 1);
|
||||
builder.append(property.key);
|
||||
builder.append(": ");
|
||||
if (property.value.is_array()) {
|
||||
// custom array serialization as AK's doesn't pretty-print
|
||||
// objects and arrays (we only care about arrays (for now))
|
||||
builder.append("[");
|
||||
auto first = true;
|
||||
property.value.as_array().for_each([&](auto& value) {
|
||||
if (!first)
|
||||
builder.append(", ");
|
||||
first = false;
|
||||
value.serialize(builder);
|
||||
});
|
||||
builder.append("]");
|
||||
} else {
|
||||
property.value.serialize(builder);
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
if (node.has("layout")) {
|
||||
auto layout = node.get("layout").as_object();
|
||||
if (!properties.is_empty())
|
||||
builder.append("\n");
|
||||
indent(indentation + 1);
|
||||
builder.append("layout: ");
|
||||
builder.append(format_gml_object(move(layout), indentation + 1, true));
|
||||
}
|
||||
|
||||
if (node.has("children")) {
|
||||
auto children = node.get("children").as_array();
|
||||
auto first = properties.is_empty() && !node.has("layout");
|
||||
children.for_each([&](auto& value) {
|
||||
if (!first)
|
||||
builder.append("\n");
|
||||
first = false;
|
||||
builder.append(format_gml_object(value.as_object(), indentation + 1));
|
||||
});
|
||||
}
|
||||
|
||||
indent(indentation);
|
||||
builder.append("}\n");
|
||||
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
String format_gml(StringView string)
|
||||
{
|
||||
// FIXME: Preserve comments somehow, they're not contained
|
||||
// in the JSON object returned by parse_gml()
|
||||
auto ast = parse_gml(string);
|
||||
if (ast.is_null())
|
||||
return {};
|
||||
VERIFY(ast.is_object());
|
||||
return format_gml_object(ast.as_object());
|
||||
}
|
||||
|
||||
}
|
|
@ -7,9 +7,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGUI/GML/AST.h>
|
||||
#include <LibGUI/GML/Parser.h>
|
||||
|
||||
namespace GUI::GML {
|
||||
|
||||
String format_gml(StringView);
|
||||
inline String format_gml(StringView string)
|
||||
{
|
||||
auto ast = parse_gml(string);
|
||||
if (ast.is_error())
|
||||
return {};
|
||||
return ast.value()->to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,18 +6,20 @@
|
|||
*/
|
||||
|
||||
#include "Parser.h"
|
||||
#include "AST.h"
|
||||
#include "Lexer.h"
|
||||
#include <AK/Error.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <AK/RefPtr.h>
|
||||
|
||||
namespace GUI::GML {
|
||||
|
||||
static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
|
||||
static ErrorOr<NonnullRefPtr<Object>> parse_gml_object(Queue<Token>& tokens)
|
||||
{
|
||||
JsonObject object;
|
||||
JsonArray children;
|
||||
auto object = TRY(try_make_ref_counted<Object>());
|
||||
|
||||
auto peek = [&] {
|
||||
if (tokens.is_empty())
|
||||
|
@ -25,23 +27,21 @@ static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
|
|||
return tokens.head().m_type;
|
||||
};
|
||||
|
||||
while (peek() == Token::Type::Comment)
|
||||
tokens.dequeue();
|
||||
|
||||
if (peek() != Token::Type::ClassMarker) {
|
||||
dbgln("Expected class marker");
|
||||
return {};
|
||||
while (peek() == Token::Type::Comment) {
|
||||
dbgln("found comment {}", tokens.head().m_view);
|
||||
TRY(object->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
|
||||
}
|
||||
|
||||
if (peek() != Token::Type::ClassMarker)
|
||||
return Error::from_string_literal("Expected class marker"sv);
|
||||
|
||||
tokens.dequeue();
|
||||
|
||||
if (peek() != Token::Type::ClassName) {
|
||||
dbgln("Expected class name");
|
||||
return {};
|
||||
}
|
||||
if (peek() != Token::Type::ClassName)
|
||||
return Error::from_string_literal("Expected class name"sv);
|
||||
|
||||
auto class_name = tokens.dequeue();
|
||||
object.set("class", JsonValue(class_name.m_view));
|
||||
object->set_name(class_name.m_view);
|
||||
|
||||
if (peek() != Token::Type::LeftCurly) {
|
||||
// Empty object
|
||||
|
@ -57,72 +57,44 @@ static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
|
|||
|
||||
if (peek() == Token::Type::ClassMarker) {
|
||||
// It's a child object.
|
||||
auto value = parse_core_object(tokens);
|
||||
if (!value.has_value()) {
|
||||
dbgln("Parsing child object failed");
|
||||
return {};
|
||||
}
|
||||
if (!value.value().is_object()) {
|
||||
dbgln("Expected child to be Core::Object");
|
||||
return {};
|
||||
}
|
||||
children.append(value.release_value());
|
||||
TRY(object->add_child(TRY(parse_gml_object(tokens))));
|
||||
} else if (peek() == Token::Type::Identifier) {
|
||||
// It's a property.
|
||||
auto property_name = tokens.dequeue();
|
||||
|
||||
if (property_name.m_view.is_empty()) {
|
||||
dbgln("Expected non-empty property name");
|
||||
return {};
|
||||
}
|
||||
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) {
|
||||
dbgln("Expected ':'");
|
||||
return {};
|
||||
}
|
||||
tokens.dequeue();
|
||||
|
||||
JsonValue value;
|
||||
if (peek() == Token::Type::ClassMarker) {
|
||||
auto parsed_value = parse_core_object(tokens);
|
||||
if (!parsed_value.has_value())
|
||||
return {};
|
||||
if (!parsed_value.value().is_object()) {
|
||||
dbgln("Expected property to be Core::Object");
|
||||
return {};
|
||||
}
|
||||
value = parsed_value.release_value();
|
||||
} else if (peek() == Token::Type::JsonValue) {
|
||||
auto value_string = tokens.dequeue();
|
||||
auto parsed_value = JsonValue::from_string(value_string.m_view);
|
||||
if (parsed_value.is_error()) {
|
||||
dbgln("Expected property to be JSON value");
|
||||
return {};
|
||||
}
|
||||
value = parsed_value.release_value();
|
||||
}
|
||||
object.set(property_name.m_view, move(value));
|
||||
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_child(property));
|
||||
|
||||
} else if (peek() == Token::Type::Comment) {
|
||||
tokens.dequeue();
|
||||
TRY(object->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
|
||||
} else {
|
||||
dbgln("Expected child, property, comment, or }}");
|
||||
return {};
|
||||
return Error::from_string_literal("Expected child, property, comment, or }}"sv);
|
||||
}
|
||||
}
|
||||
|
||||
if (peek() != Token::Type::RightCurly) {
|
||||
dbgln("Expected }}");
|
||||
return {};
|
||||
}
|
||||
tokens.dequeue();
|
||||
if (peek() != Token::Type::RightCurly)
|
||||
return Error::from_string_literal("Expected }}"sv);
|
||||
|
||||
if (!children.is_empty())
|
||||
object.set("children", move(children));
|
||||
tokens.dequeue();
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
JsonValue parse_gml(StringView string)
|
||||
ErrorOr<NonnullRefPtr<GMLFile>> parse_gml(StringView string)
|
||||
{
|
||||
auto lexer = Lexer(string);
|
||||
|
||||
|
@ -130,12 +102,23 @@ JsonValue parse_gml(StringView string)
|
|||
for (auto& token : lexer.lex())
|
||||
tokens.enqueue(token);
|
||||
|
||||
auto root = parse_core_object(tokens);
|
||||
auto file = TRY(try_make_ref_counted<GMLFile>());
|
||||
|
||||
if (!root.has_value())
|
||||
return JsonValue();
|
||||
auto peek = [&] {
|
||||
if (tokens.is_empty())
|
||||
return Token::Type::Unknown;
|
||||
return tokens.head().m_type;
|
||||
};
|
||||
|
||||
return root.release_value();
|
||||
while (peek() == Token::Type::Comment)
|
||||
TRY(file->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
|
||||
|
||||
TRY(file->add_child(TRY(parse_gml_object(tokens))));
|
||||
|
||||
while (!tokens.is_empty())
|
||||
TRY(file->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <LibGUI/GML/AST.h>
|
||||
|
||||
namespace GUI::GML {
|
||||
|
||||
JsonValue parse_gml(StringView);
|
||||
ErrorOr<NonnullRefPtr<GMLFile>> parse_gml(StringView);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibGUI/Layout.h>
|
||||
#include <LibGUI/ScrollableContainerWidget.h>
|
||||
|
||||
|
@ -85,48 +86,46 @@ void ScrollableContainerWidget::set_widget(GUI::Widget* widget)
|
|||
update_widget_position();
|
||||
}
|
||||
|
||||
bool ScrollableContainerWidget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
|
||||
bool ScrollableContainerWidget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
|
||||
{
|
||||
json.for_each_member([&](auto& key, auto& value) {
|
||||
if (is<GUI::GML::GMLFile>(ast.ptr()))
|
||||
return load_from_gml_ast(static_ptr_cast<GUI::GML::GMLFile>(ast)->main_class(), unregistered_child_handler);
|
||||
|
||||
VERIFY(is<GUI::GML::Object>(ast.ptr()));
|
||||
auto object = static_ptr_cast<GUI::GML::Object>(ast);
|
||||
|
||||
object->for_each_property([&](auto key, auto value) {
|
||||
set_property(key, value);
|
||||
});
|
||||
|
||||
auto layout_value = json.get("layout");
|
||||
if (!layout_value.is_null()) {
|
||||
dbgln("Layout specified in ScrollableContainerWidget, this is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto content_widget_value = json.get("content_widget");
|
||||
if (!content_widget_value.is_null() && !content_widget_value.is_object()) {
|
||||
auto content_widget_value = object->get_property("content_widget"sv);
|
||||
if (!content_widget_value.is_null() && !is<Object>(content_widget_value.ptr())) {
|
||||
dbgln("content widget is not an object");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!json.get("children").is_null()) {
|
||||
auto has_children = false;
|
||||
object->for_each_child_object([&](auto) { has_children = true; });
|
||||
if (has_children) {
|
||||
dbgln("children specified for ScrollableContainerWidget, but only 1 widget as content_widget is supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (content_widget_value.is_object()) {
|
||||
auto& content_widget = content_widget_value.as_object();
|
||||
auto class_name = content_widget.get("class");
|
||||
if (!class_name.is_string()) {
|
||||
dbgln("Invalid content widget class name");
|
||||
return false;
|
||||
}
|
||||
if (!content_widget_value.is_null() && is<Object>(content_widget_value.ptr())) {
|
||||
auto content_widget = static_ptr_cast<GUI::GML::Object>(content_widget_value);
|
||||
auto class_name = content_widget->name();
|
||||
|
||||
RefPtr<Core::Object> child;
|
||||
if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
|
||||
if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
|
||||
child = registration->construct();
|
||||
} else {
|
||||
child = unregistered_child_handler(class_name.as_string());
|
||||
child = unregistered_child_handler(class_name);
|
||||
}
|
||||
if (!child)
|
||||
return false;
|
||||
auto widget_ptr = verify_cast<GUI::Widget>(child.ptr());
|
||||
set_widget(widget_ptr);
|
||||
child->load_from_json(content_widget, unregistered_child_handler);
|
||||
static_ptr_cast<Widget>(child)->load_from_gml_ast(content_widget.release_nonnull(), unregistered_child_handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ protected:
|
|||
private:
|
||||
void update_widget_size();
|
||||
void update_widget_position();
|
||||
virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
|
||||
virtual bool load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
|
||||
|
||||
ScrollableContainerWidget();
|
||||
|
||||
|
|
|
@ -6,11 +6,15 @@
|
|||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibGUI/GML/AST.h>
|
||||
#include <LibGUI/GML/Parser.h>
|
||||
#include <LibGUI/Layout.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
|
@ -1067,32 +1071,36 @@ bool Widget::load_from_gml(StringView gml_string)
|
|||
bool Widget::load_from_gml(StringView gml_string, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
|
||||
{
|
||||
auto value = GML::parse_gml(gml_string);
|
||||
if (!value.is_object())
|
||||
if (value.is_error()) {
|
||||
// FIXME: We don't report the error, so at least print it.
|
||||
dbgln("Error while parsing GML: {}", value.error());
|
||||
return false;
|
||||
return load_from_json(value.as_object(), unregistered_child_handler);
|
||||
}
|
||||
return load_from_gml_ast(value.release_value(), unregistered_child_handler);
|
||||
}
|
||||
|
||||
bool Widget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
|
||||
bool Widget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
|
||||
{
|
||||
json.for_each_member([&](auto& key, auto& value) {
|
||||
if (is<GUI::GML::GMLFile>(ast.ptr()))
|
||||
return load_from_gml_ast(static_ptr_cast<GUI::GML::GMLFile>(ast)->main_class(), unregistered_child_handler);
|
||||
|
||||
VERIFY(is<GUI::GML::Object>(ast.ptr()));
|
||||
auto object = static_ptr_cast<GUI::GML::Object>(ast);
|
||||
|
||||
object->for_each_property([&](auto key, auto value) {
|
||||
set_property(key, value);
|
||||
});
|
||||
|
||||
auto layout_value = json.get("layout");
|
||||
if (!layout_value.is_null() && !layout_value.is_object()) {
|
||||
dbgln("layout is not an object");
|
||||
return false;
|
||||
}
|
||||
if (layout_value.is_object()) {
|
||||
auto& layout = layout_value.as_object();
|
||||
auto class_name = layout.get("class");
|
||||
auto layout = object->layout_object();
|
||||
if (!layout.is_null()) {
|
||||
auto class_name = layout->name();
|
||||
if (class_name.is_null()) {
|
||||
dbgln("Invalid layout class name");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& layout_class = *Core::ObjectClassRegistration::find("GUI::Layout");
|
||||
if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
|
||||
if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
|
||||
auto layout = registration->construct();
|
||||
if (!layout || !registration->is_derived_from(layout_class)) {
|
||||
dbgln("Invalid layout class: '{}'", class_name.to_string());
|
||||
|
@ -1104,40 +1112,32 @@ bool Widget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unreg
|
|||
return false;
|
||||
}
|
||||
|
||||
layout.for_each_member([&](auto& key, auto& value) {
|
||||
layout->for_each_property([&](auto key, auto value) {
|
||||
this->layout()->set_property(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
auto& widget_class = *Core::ObjectClassRegistration::find("GUI::Widget");
|
||||
auto children = json.get("children");
|
||||
if (children.is_array()) {
|
||||
for (auto& child_json_value : children.as_array().values()) {
|
||||
if (!child_json_value.is_object())
|
||||
return false;
|
||||
auto& child_json = child_json_value.as_object();
|
||||
auto class_name = child_json.get("class");
|
||||
if (!class_name.is_string()) {
|
||||
dbgln("No class name in entry");
|
||||
return false;
|
||||
}
|
||||
object->for_each_child_object_interruptible([&](auto child_data) {
|
||||
auto class_name = child_data->name();
|
||||
|
||||
RefPtr<Core::Object> child;
|
||||
if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
|
||||
child = registration->construct();
|
||||
if (!child || !registration->is_derived_from(widget_class)) {
|
||||
dbgln("Invalid widget class: '{}'", class_name.to_string());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
child = unregistered_child_handler(class_name.as_string());
|
||||
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;
|
||||
}
|
||||
if (!child)
|
||||
return false;
|
||||
add_child(*child);
|
||||
child->load_from_json(child_json, unregistered_child_handler);
|
||||
} else {
|
||||
child = unregistered_child_handler(class_name);
|
||||
}
|
||||
}
|
||||
if (!child)
|
||||
return IterationDecision::Break;
|
||||
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);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
|
||||
#include <AK/EnumBits.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibCore/Object.h>
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibGUI/FocusPolicy.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGUI/GML/AST.h>
|
||||
#include <LibGUI/Margins.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
|
@ -299,6 +301,9 @@ public:
|
|||
|
||||
bool has_pending_drop() const;
|
||||
|
||||
// In order for others to be able to call this, it needs to be public.
|
||||
virtual bool load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&));
|
||||
|
||||
protected:
|
||||
Widget();
|
||||
|
||||
|
@ -354,8 +359,6 @@ private:
|
|||
void focus_previous_widget(FocusSource, bool siblings_only);
|
||||
void focus_next_widget(FocusSource, bool siblings_only);
|
||||
|
||||
virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
|
||||
|
||||
// HACK: These are used as property getters for the fixed_* size property aliases.
|
||||
int dummy_fixed_width() { return 0; }
|
||||
int dummy_fixed_height() { return 0; }
|
||||
|
|
Loading…
Reference in a new issue