浏览代码

LibCore: Make Core::Object properties more dynamic

Instead of everyone overriding save_to() and set_property() and doing
a pretty asymmetric job of implementing the various properties, let's
add a bit of structure here.

Object properties are now represented by a Core::Property. Properties
are registered with a getter and setter (optional) in constructors.
I've added some convenience macros for creating and registering
properties, but this does still feel a bit bulky. We'll have to
iterate on this and see where it goes.
Andreas Kling 4 年之前
父节点
当前提交
e2f32b8f9d

+ 1 - 0
Libraries/LibCore/CMakeLists.txt

@@ -19,6 +19,7 @@ set(SOURCES
     Notifier.cpp
     Object.cpp
     ProcessStatisticsReader.cpp
+    Property.cpp
     puff.cpp
     SocketAddress.cpp
     Socket.cpp

+ 32 - 9
Libraries/LibCore/Object.cpp

@@ -47,6 +47,17 @@ Object::Object(Object* parent, bool is_widget)
     all_objects().append(*this);
     if (m_parent)
         m_parent->add_child(*this);
+
+    REGISTER_READONLY_STRING_PROPERTY("class_name", class_name);
+    REGISTER_STRING_PROPERTY("name", name, set_name);
+
+    register_property(
+        "address", [this] { return FlatPtr(this); },
+        [](auto&) { return false; });
+
+    register_property(
+        "parent", [this] { return FlatPtr(this->parent()); },
+        [](auto&) { return false; });
 }
 
 Object::~Object()
@@ -173,19 +184,26 @@ void Object::deferred_invoke(Function<void(Object&)> invokee)
 
 void Object::save_to(JsonObject& json)
 {
-    json.set("class_name", class_name());
-    json.set("address", (FlatPtr)this);
-    json.set("name", name());
-    json.set("parent", (FlatPtr)parent());
+    for (auto& it : m_properties) {
+        auto& property = it.value;
+        json.set(property->name(), property->get());
+    }
+}
+
+JsonValue Object::property(const StringView& name)
+{
+    auto it = m_properties.find(name);
+    if (it == m_properties.end())
+        return JsonValue();
+    return it->value->get();
 }
 
 bool Object::set_property(const StringView& name, const JsonValue& value)
 {
-    if (name == "name") {
-        set_name(value.to_string());
-        return true;
-    }
-    return false;
+    auto it = m_properties.find(name);
+    if (it == m_properties.end())
+        return false;
+    return it->value->set(value);
 }
 
 bool Object::is_ancestor_of(const Object& other) const
@@ -235,6 +253,11 @@ void Object::decrement_inspector_count(Badge<RPCClient>)
         did_end_inspection();
 }
 
+void Object::register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
+{
+    m_properties.set(name, make<Property>(name, move(getter), move(setter)));
+}
+
 const LogStream& operator<<(const LogStream& stream, const Object& object)
 {
     return stream << object.class_name() << '{' << &object << '}';

+ 108 - 2
Libraries/LibCore/Object.h

@@ -27,6 +27,7 @@
 #pragma once
 
 #include <AK/Forward.h>
+#include <AK/HashMap.h>
 #include <AK/IntrusiveList.h>
 #include <AK/Noncopyable.h>
 #include <AK/NonnullRefPtrVector.h>
@@ -34,6 +35,7 @@
 #include <AK/TypeCasts.h>
 #include <AK/Weakable.h>
 #include <LibCore/Forward.h>
+#include <LibCore/Property.h>
 
 namespace Core {
 
@@ -112,8 +114,10 @@ public:
     virtual bool is_action() const { return false; }
     virtual bool is_window() const { return false; }
 
-    virtual void save_to(AK::JsonObject&);
-    virtual bool set_property(const StringView& name, const JsonValue& value);
+    void save_to(AK::JsonObject&);
+
+    bool set_property(const StringView& name, const JsonValue& value);
+    JsonValue property(const StringView& name);
 
     static IntrusiveList<Object, &Object::m_all_objects_list_node>& all_objects();
 
@@ -143,6 +147,8 @@ public:
 protected:
     explicit Object(Object* parent = nullptr, bool is_widget = false);
 
+    void register_property(const String& name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
+
     virtual void timer_event(TimerEvent&);
     virtual void custom_event(CustomEvent&);
 
@@ -158,6 +164,7 @@ private:
     int m_timer_id { 0 };
     unsigned m_inspector_count { 0 };
     bool m_widget { false };
+    HashMap<String, NonnullOwnPtr<Property>> m_properties;
     NonnullRefPtrVector<Object> m_children;
 };
 
@@ -176,4 +183,103 @@ inline void Object::for_each_child_of_type(Callback callback)
 
 const LogStream& operator<<(const LogStream&, const Object&);
 
+#define REGISTER_INT_PROPERTY(property_name, getter, setter) \
+    register_property(                                       \
+        property_name,                                       \
+        [this] { return this->getter(); },                   \
+        [this](auto& value) {                                \
+            this->setter(value.template to_number<int>());   \
+            return true;                                     \
+        });
+
+#define REGISTER_BOOL_PROPERTY(property_name, getter, setter) \
+    register_property(                                        \
+        property_name,                                        \
+        [this] { return this->getter(); },                    \
+        [this](auto& value) {                                 \
+            this->setter(value.to_bool());                    \
+            return true;                                      \
+        });
+
+#define REGISTER_STRING_PROPERTY(property_name, getter, setter) \
+    register_property(                                          \
+        property_name,                                          \
+        [this] { return this->getter(); },                      \
+        [this](auto& value) {                                   \
+            this->setter(value.to_string());                    \
+            return true;                                        \
+        });
+
+#define REGISTER_READONLY_STRING_PROPERTY(property_name, getter) \
+    register_property(                                           \
+        property_name,                                           \
+        [this] { return this->getter(); },                       \
+        nullptr);
+
+#define REGISTER_RECT_PROPERTY(property_name, getter, setter)          \
+    register_property(                                                 \
+        property_name,                                                 \
+        [this] {                                                       \
+            auto rect = this->getter();                                \
+            JsonObject rect_object;                                    \
+            rect_object.set("x", rect.x());                            \
+            rect_object.set("y", rect.y());                            \
+            rect_object.set("width", rect.width());                    \
+            rect_object.set("height", rect.height());                  \
+            return rect_object;                                        \
+        },                                                             \
+        [this](auto& value) {                                          \
+            if (!value.is_object())                                    \
+                return false;                                          \
+            Gfx::IntRect rect;                                         \
+            rect.set_x(value.as_object().get("x").to_i32());           \
+            rect.set_y(value.as_object().get("y").to_i32());           \
+            rect.set_width(value.as_object().get("width").to_i32());   \
+            rect.set_height(value.as_object().get("height").to_i32()); \
+            setter(rect);                                              \
+            return true;                                               \
+        });
+
+#define REGISTER_SIZE_PROPERTY(property_name, getter, setter)          \
+    register_property(                                                 \
+        property_name,                                                 \
+        [this] {                                                       \
+            auto size = this->getter();                                \
+            JsonObject size_object;                                    \
+            size_object.set("width", size.width());                    \
+            size_object.set("height", size.height());                  \
+            return size_object;                                        \
+        },                                                             \
+        [this](auto& value) {                                          \
+            if (!value.is_object())                                    \
+                return false;                                          \
+            Gfx::IntSize size;                                         \
+            size.set_width(value.as_object().get("width").to_i32());   \
+            size.set_height(value.as_object().get("height").to_i32()); \
+            setter(size);                                              \
+            return true;                                               \
+        });
+
+#define REGISTER_SIZE_POLICY_PROPERTY(property_name, getter, setter) \
+    register_property(                                               \
+        property_name, [this]() -> JsonValue {                       \
+        auto policy = this->getter();                                \
+        if (policy == GUI::SizePolicy::Fill)                         \
+            return "Fill";                                           \
+        if (policy == GUI::SizePolicy::Fixed)                        \
+            return "Fixed";                                          \
+        return JsonValue(); },                    \
+        [this](auto& value) {                                        \
+            if (!value.is_string())                                  \
+                return false;                                        \
+            if (value.as_string() == "Fill") {                       \
+                setter(GUI::SizePolicy::Fill);                       \
+                return true;                                         \
+            }                                                        \
+            if (value.as_string() == "Fixed") {                      \
+                setter(GUI::SizePolicy::Fixed);                      \
+                return true;                                         \
+            }                                                        \
+            return false;                                            \
+        });
 }

+ 42 - 0
Libraries/LibCore/Property.cpp

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibCore/Property.h>
+
+namespace Core {
+
+Property::Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter)
+    : m_name(move(name))
+    , m_getter(move(getter))
+    , m_setter(move(setter))
+{
+}
+
+Property::~Property()
+{
+}
+
+}

+ 58 - 0
Libraries/LibCore/Property.h

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/JsonValue.h>
+
+namespace Core {
+
+class Property {
+    AK_MAKE_NONCOPYABLE(Property);
+
+public:
+    Property(String name, Function<JsonValue()> getter, Function<bool(const JsonValue&)> setter = nullptr);
+    ~Property();
+
+    bool set(const JsonValue& value)
+    {
+        if (!m_setter)
+            return false;
+        return m_setter(value);
+    }
+
+    JsonValue get() const { return m_getter(); }
+
+    const String& name() const { return m_name; }
+
+private:
+    String m_name;
+    Function<JsonValue()> m_getter;
+    Function<bool(const JsonValue&)> m_setter;
+};
+
+}

+ 5 - 31
Libraries/LibGUI/AbstractButton.cpp

@@ -39,6 +39,11 @@ AbstractButton::AbstractButton(const StringView& text)
     m_auto_repeat_timer->on_timeout = [this] {
         click();
     };
+
+    REGISTER_STRING_PROPERTY("text", text, set_text);
+    REGISTER_BOOL_PROPERTY("checked", is_checked, set_checked);
+    REGISTER_BOOL_PROPERTY("checkable", is_checkable, set_checkable);
+    REGISTER_BOOL_PROPERTY("exclusive", is_exclusive, set_exclusive);
 }
 
 AbstractButton::~AbstractButton()
@@ -185,35 +190,4 @@ void AbstractButton::change_event(Event& event)
     Widget::change_event(event);
 }
 
-void AbstractButton::save_to(JsonObject& json)
-{
-    json.set("text", m_text);
-    json.set("checked", m_checked);
-    json.set("checkable", m_checkable);
-    json.set("exclusive", m_exclusive);
-    Widget::save_to(json);
-}
-
-bool AbstractButton::set_property(const StringView& name, const JsonValue& value)
-{
-    if (name == "text") {
-        set_text(value.to_string());
-        return true;
-    }
-    if (name == "checked") {
-        set_checked(value.to_bool());
-        return true;
-    }
-    if (name == "checkable") {
-        set_checkable(value.to_bool());
-        return true;
-    }
-    if (name == "exclusive") {
-        set_exclusive(value.to_bool());
-        return true;
-    }
-
-    return Widget::set_property(name, value);
-}
-
 }

+ 0 - 3
Libraries/LibGUI/AbstractButton.h

@@ -70,9 +70,6 @@ protected:
     virtual void leave_event(Core::Event&) override;
     virtual void change_event(Event&) override;
 
-    virtual void save_to(JsonObject&) override;
-    virtual bool set_property(const StringView& name, const JsonValue& value) override;
-
     void paint_text(Painter&, const Gfx::IntRect&, const Gfx::Font&, Gfx::TextAlignment);
 
 private:

+ 2 - 7
Libraries/LibGUI/BoxLayout.cpp

@@ -37,6 +37,8 @@ namespace GUI {
 BoxLayout::BoxLayout(Orientation orientation)
     : m_orientation(orientation)
 {
+    register_property(
+        "orientation", [this] { return m_orientation == Gfx::Orientation::Vertical ? "Vertical" : "Horizontal"; }, nullptr);
 }
 
 void BoxLayout::run(Widget& widget)
@@ -174,11 +176,4 @@ void BoxLayout::run(Widget& widget)
             current_y += rect.height() + spacing();
     }
 }
-
-void BoxLayout::save_to(JsonObject& json)
-{
-    Layout::save_to(json);
-    json.set("orientation", m_orientation == Gfx::Orientation::Vertical ? "Vertical" : "Horizontal");
-}
-
 }

+ 0 - 2
Libraries/LibGUI/BoxLayout.h

@@ -44,8 +44,6 @@ public:
 protected:
     explicit BoxLayout(Gfx::Orientation);
 
-    virtual void save_to(JsonObject &) override;
-
 private:
     Gfx::Orientation m_orientation;
 };

+ 39 - 28
Libraries/LibGUI/Layout.cpp

@@ -33,6 +33,45 @@ namespace GUI {
 
 Layout::Layout()
 {
+    REGISTER_INT_PROPERTY("spacing", spacing, set_spacing);
+
+    register_property(
+        "margins",
+        [this] {
+            JsonObject margins_object;
+            margins_object.set("left", m_margins.left());
+            margins_object.set("right", m_margins.right());
+            margins_object.set("top", m_margins.top());
+            margins_object.set("bottom", m_margins.bottom());
+            return margins_object;
+        },
+        [this](auto value) {
+            if (!value.is_array() || value.as_array().size() != 4)
+                return false;
+            int m[4];
+            for (size_t i = 0; i < 4; ++i)
+                m[i] = value.as_array().at(i).to_i32();
+            set_margins({ m[0], m[1], m[2], m[3] });
+            return true;
+        });
+
+    register_property("entries",
+        [this] {
+            JsonArray entries_array;
+            for (auto& entry : m_entries) {
+                JsonObject entry_object;
+                if (entry.type == Entry::Type::Widget) {
+                    entry_object.set("type", "Widget");
+                    entry_object.set("widget", (FlatPtr)entry.widget.ptr());
+                } else if (entry.type == Entry::Type::Spacer) {
+                    entry_object.set("type", "Spacer");
+                } else {
+                    ASSERT_NOT_REACHED();
+                }
+                entries_array.append(move(entry_object));
+            }
+            return entries_array;
+        });
 }
 
 Layout::~Layout()
@@ -121,32 +160,4 @@ void Layout::set_margins(const Margins& margins)
         m_owner->notify_layout_changed({});
 }
 
-void Layout::save_to(JsonObject& json)
-{
-    Core::Object::save_to(json);
-    json.set("spacing", m_spacing);
-
-    JsonObject margins_object;
-    margins_object.set("left", m_margins.left());
-    margins_object.set("right", m_margins.right());
-    margins_object.set("top", m_margins.top());
-    margins_object.set("bottom", m_margins.bottom());
-    json.set("margins", move(margins_object));
-
-    JsonArray entries_array;
-    for (auto& entry : m_entries) {
-        JsonObject entry_object;
-        if (entry.type == Entry::Type::Widget) {
-            entry_object.set("type", "Widget");
-            entry_object.set("widget", (FlatPtr)entry.widget.ptr());
-        } else if (entry.type == Entry::Type::Spacer) {
-            entry_object.set("type", "Spacer");
-        } else {
-            ASSERT_NOT_REACHED();
-        }
-        entries_array.append(move(entry_object));
-    }
-    json.set("entries", move(entries_array));
-}
-
 }

+ 0 - 2
Libraries/LibGUI/Layout.h

@@ -59,8 +59,6 @@ public:
     int spacing() const { return m_spacing; }
     void set_spacing(int);
 
-    virtual void save_to(JsonObject&) override;
-
 protected:
     Layout();
 

+ 14 - 22
Libraries/LibGUI/TabWidget.cpp

@@ -40,6 +40,20 @@ namespace GUI {
 
 TabWidget::TabWidget()
 {
+    REGISTER_INT_PROPERTY("container_padding", container_padding, set_container_padding);
+    REGISTER_BOOL_PROPERTY("uniform_tabs", uniform_tabs, set_uniform_tabs);
+
+    register_property(
+        "text_alignment",
+        [this] { return Gfx::to_string(text_alignment()); },
+        [this](auto& value) {
+            auto alignment = Gfx::text_alignment_from_string(value.to_string());
+            if (alignment.has_value()) {
+                set_text_alignment(alignment.value());
+                return true;
+            }
+            return false;
+        });
 }
 
 TabWidget::~TabWidget()
@@ -396,26 +410,4 @@ void TabWidget::context_menu_event(ContextMenuEvent& context_menu_event)
     }
 }
 
-bool TabWidget::set_property(const StringView& name, const JsonValue& value)
-{
-    if (name == "container_padding") {
-        set_container_padding(value.to_i32());
-        return true;
-    }
-
-    if (name == "uniform_tabs") {
-        set_uniform_tabs(value.to_bool());
-        return true;
-    }
-
-    if (name == "text_alignment") {
-        auto alignment = Gfx::text_alignment_from_string(value.to_string());
-        if (alignment.has_value())
-            set_text_alignment(alignment.value());
-        return true;
-    }
-
-    return Widget::set_property(name, value);
-}
-
 }

+ 1 - 2
Libraries/LibGUI/TabWidget.h

@@ -76,6 +76,7 @@ public:
     void set_text_alignment(Gfx::TextAlignment alignment) { m_text_alignment = alignment; }
     Gfx::TextAlignment text_alignment() const { return m_text_alignment; }
 
+    bool uniform_tabs() const { return m_uniform_tabs; }
     void set_uniform_tabs(bool uniform_tabs) { m_uniform_tabs = uniform_tabs; }
     int uniform_tab_width() const;
 
@@ -86,8 +87,6 @@ public:
     Function<void(Widget&)> on_middle_click;
     Function<void(Widget&, const ContextMenuEvent&)> on_context_menu_request;
 
-    virtual bool set_property(const StringView& name, const JsonValue& value) override;
-
 protected:
     TabWidget();
 

+ 14 - 90
Libraries/LibGUI/Widget.cpp

@@ -110,6 +110,17 @@ Widget::Widget()
     , m_font(Gfx::Font::default_font())
     , m_palette(Application::the()->palette().impl())
 {
+    REGISTER_RECT_PROPERTY("relative_rect", relative_rect, set_relative_rect);
+    REGISTER_BOOL_PROPERTY("fill_with_background_color", fill_with_background_color, set_fill_with_background_color);
+    REGISTER_BOOL_PROPERTY("visible", is_visible, set_visible);
+    REGISTER_BOOL_PROPERTY("focused", is_focused, set_focus);
+    REGISTER_BOOL_PROPERTY("enabled", is_enabled, set_enabled);
+    REGISTER_STRING_PROPERTY("tooltip", tooltip, set_tooltip);
+    REGISTER_SIZE_PROPERTY("preferred_size", preferred_size, set_preferred_size);
+    REGISTER_INT_PROPERTY("preferred_width", preferred_width, set_preferred_width);
+    REGISTER_INT_PROPERTY("preferred_height", preferred_height, set_preferred_height);
+    REGISTER_SIZE_POLICY_PROPERTY("horizontal_size_policy", horizontal_size_policy, set_horizontal_size_policy);
+    REGISTER_SIZE_POLICY_PROPERTY("vertical_size_policy", vertical_size_policy, set_vertical_size_policy);
 }
 
 Widget::~Widget()
@@ -774,79 +785,6 @@ void Widget::set_forecolor(const StringView& color_string)
     set_foreground_color(color.value());
 }
 
-void Widget::save_to(AK::JsonObject& json)
-{
-    json.set("relative_rect", relative_rect().to_string());
-    json.set("fill_with_background_color", fill_with_background_color());
-    json.set("tooltip", tooltip());
-    json.set("visible", is_visible());
-    json.set("focused", is_focused());
-    json.set("enabled", is_enabled());
-    json.set("background_color", background_color().to_string());
-    json.set("foreground_color", foreground_color().to_string());
-    json.set("preferred_size", preferred_size().to_string());
-    json.set("size_policy", String::format("[%s,%s]", to_string(horizontal_size_policy()), to_string(vertical_size_policy())));
-    Core::Object::save_to(json);
-}
-
-bool Widget::set_property(const StringView& name, const JsonValue& value)
-{
-    if (name == "fill_with_background_color") {
-        set_fill_with_background_color(value.to_bool());
-        return true;
-    }
-    if (name == "tooltip") {
-        set_tooltip(value.to_string());
-        return true;
-    }
-    if (name == "enable") {
-        set_enabled(value.to_bool());
-        return true;
-    }
-    if (name == "focused") {
-        set_focus(value.to_bool());
-        return true;
-    }
-    if (name == "visible") {
-        set_visible(value.to_bool());
-        return true;
-    }
-
-    if (name == "horizontal_size_policy") {
-        auto string = value.to_string();
-        if (string == "Fill")
-            set_size_policy(Gfx::Orientation::Horizontal, SizePolicy::Fill);
-        else if (string == "Fixed")
-            set_size_policy(Gfx::Orientation::Horizontal, SizePolicy::Fixed);
-        else
-            ASSERT_NOT_REACHED();
-        return true;
-    }
-
-    if (name == "vertical_size_policy") {
-        auto string = value.to_string();
-        if (string == "Fill")
-            set_size_policy(Gfx::Orientation::Vertical, SizePolicy::Fill);
-        else if (string == "Fixed")
-            set_size_policy(Gfx::Orientation::Vertical, SizePolicy::Fixed);
-        else
-            ASSERT_NOT_REACHED();
-        return true;
-    }
-
-    if (name == "preferred_height") {
-        set_preferred_size(preferred_size().width(), value.to_i32());
-        return true;
-    }
-
-    if (name == "preferred_width") {
-        set_preferred_size(value.to_i32(), preferred_size().height());
-        return true;
-    }
-
-    return Core::Object::set_property(name, value);
-}
-
 Vector<Widget*> Widget::child_widgets() const
 {
     Vector<Widget*> widgets;
@@ -975,22 +913,9 @@ bool Widget::load_from_json(const JsonObject& json)
             return false;
         }
 
-        auto spacing = layout.get("spacing");
-        if (spacing.is_number())
-            this->layout()->set_spacing(spacing.to_i32());
-
-        auto margins = layout.get("margins");
-        if (margins.is_array()) {
-            if (margins.as_array().size() != 4) {
-                dbg() << "margins array needs 4 entries";
-                return false;
-            }
-            int m[4];
-            for (size_t i = 0; i < 4; ++i)
-                m[i] = margins.as_array().at(i).to_i32();
-            dbg() << "setting margins " << m[0] << "," << m[1] << "," << m[2] << "," << m[3];
-            this->layout()->set_margins({ m[0], m[1], m[2], m[3] });
-        }
+        layout.for_each_member([&](auto& key, auto& value) {
+            this->layout()->set_property(key, value);
+        });
     }
 
     auto children = json.get("children");
@@ -1045,5 +970,4 @@ Widget* Widget::find_descendant_by_name(const String& name)
     });
     return found_widget;
 }
-
 }

+ 7 - 4
Libraries/LibGUI/Widget.h

@@ -109,11 +109,18 @@ public:
     SizePolicy size_policy(Orientation orientation) { return orientation == Orientation::Horizontal ? m_horizontal_size_policy : m_vertical_size_policy; }
     void set_size_policy(SizePolicy horizontal_policy, SizePolicy vertical_policy);
     void set_size_policy(Orientation, SizePolicy);
+    void set_horizontal_size_policy(SizePolicy policy) { set_size_policy(policy, vertical_size_policy()); }
+    void set_vertical_size_policy(SizePolicy policy) { set_size_policy(horizontal_size_policy(), policy); }
 
     Gfx::IntSize preferred_size() const { return m_preferred_size; }
     void set_preferred_size(const Gfx::IntSize&);
     void set_preferred_size(int width, int height) { set_preferred_size({ width, height }); }
 
+    int preferred_width() const { return preferred_size().width(); }
+    int preferred_height() const { return preferred_size().height(); }
+    void set_preferred_width(int w) { set_preferred_size(w, preferred_height()); }
+    void set_preferred_height(int h) { set_preferred_size(preferred_width(), h); }
+
     bool has_tooltip() const { return !m_tooltip.is_empty(); }
     String tooltip() const { return m_tooltip; }
     void set_tooltip(const StringView&);
@@ -261,8 +268,6 @@ public:
     virtual bool is_radio_button() const { return false; }
     virtual bool is_abstract_button() const { return false; }
 
-    virtual void save_to(AK::JsonObject&) override;
-
     void do_layout();
 
     Gfx::Palette palette() const;
@@ -317,8 +322,6 @@ protected:
     virtual void did_begin_inspection() override;
     virtual void did_end_inspection() override;
 
-    virtual bool set_property(const StringView& name, const JsonValue& value) override;
-
 private:
     void handle_paint_event(PaintEvent&);
     void handle_resize_event(ResizeEvent&);

+ 18 - 14
Libraries/LibGUI/Window.cpp

@@ -65,6 +65,24 @@ Window::Window(Core::Object* parent)
     all_windows->set(this);
     m_rect_when_windowless = { -5000, -5000, 140, 140 };
     m_title_when_windowless = "GUI::Window";
+
+    register_property(
+        "title",
+        [this] { return title(); },
+        [this](auto& value) {
+            set_title(value.to_string());
+            return true;
+        });
+
+    register_property("visible", [this] { return is_visible(); });
+    register_property("active", [this] { return is_active(); });
+
+    REGISTER_BOOL_PROPERTY("minimizable", is_minimizable, set_minimizable);
+    REGISTER_BOOL_PROPERTY("resizable", is_resizable, set_resizable);
+    REGISTER_BOOL_PROPERTY("fullscreen", is_fullscreen, set_fullscreen);
+    REGISTER_RECT_PROPERTY("rect", rect, set_rect);
+    REGISTER_SIZE_PROPERTY("base_size", base_size, set_base_size);
+    REGISTER_SIZE_PROPERTY("size_increment", size_increment, set_size_increment);
 }
 
 Window::~Window()
@@ -724,20 +742,6 @@ Vector<Widget*> Window::focusable_widgets() const
     return collected_widgets;
 }
 
-void Window::save_to(AK::JsonObject& json)
-{
-    json.set("title", title());
-    json.set("visible", is_visible());
-    json.set("active", is_active());
-    json.set("minimizable", is_minimizable());
-    json.set("resizable", is_resizable());
-    json.set("fullscreen", is_fullscreen());
-    json.set("rect", rect().to_string());
-    json.set("base_size", base_size().to_string());
-    json.set("size_increment", size_increment().to_string());
-    Core::Object::save_to(json);
-}
-
 void Window::set_fullscreen(bool fullscreen)
 {
     if (m_fullscreen == fullscreen)

+ 0 - 2
Libraries/LibGUI/Window.h

@@ -177,8 +177,6 @@ public:
 
     Vector<Widget*> focusable_widgets() const;
 
-    virtual void save_to(AK::JsonObject&) override;
-
     void schedule_relayout();
 
     static void for_each_window(Badge<WindowServerConnection>, Function<void(Window&)>);

+ 26 - 18
Libraries/LibGfx/TextAlignment.h

@@ -31,13 +31,18 @@
 
 namespace Gfx {
 
+#define GFX_ENUMERATE_TEXT_ALIGNMENTS(M) \
+    M(TopLeft)                           \
+    M(CenterLeft)                        \
+    M(Center)                            \
+    M(CenterRight)                       \
+    M(TopRight)                          \
+    M(BottomRight)
+
 enum class TextAlignment {
-    TopLeft,
-    CenterLeft,
-    Center,
-    CenterRight,
-    TopRight,
-    BottomRight,
+#define __ENUMERATE(x) x,
+    GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
 };
 
 inline bool is_right_text_alignment(TextAlignment alignment)
@@ -54,18 +59,21 @@ inline bool is_right_text_alignment(TextAlignment alignment)
 
 inline Optional<TextAlignment> text_alignment_from_string(const StringView& string)
 {
-    if (string == "TopLeft")
-        return TextAlignment::TopLeft;
-    if (string == "CenterLeft")
-        return TextAlignment::CenterLeft;
-    if (string == "Center")
-        return TextAlignment::Center;
-    if (string == "CenterRight")
-        return TextAlignment::CenterRight;
-    if (string == "TopRight")
-        return TextAlignment::TopRight;
-    if (string == "BottomRight")
-        return TextAlignment::BottomRight;
+#define __ENUMERATE(x) \
+    if (string == #x)  \
+        return TextAlignment::x;
+    GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
+    return {};
+}
+
+inline const char* to_string(TextAlignment text_alignment)
+{
+#define __ENUMERATE(x)                      \
+    if (text_alignment == TextAlignment::x) \
+        return #x;
+    GFX_ENUMERATE_TEXT_ALIGNMENTS(__ENUMERATE)
+#undef __ENUMERATE
     return {};
 }
 

+ 2 - 2
Libraries/LibLine/Editor.h

@@ -274,8 +274,8 @@ private:
         Retry
     };
 
-    // ^Core::Object
-    virtual void save_to(JsonObject&) override;
+    // FIXME: Port to Core::Property
+    void save_to(JsonObject&);
 
     struct KeyCallback {
         KeyCallback(Function<bool(Editor&)> cb)

+ 0 - 7
Libraries/LibWeb/Loader/ResourceLoader.cpp

@@ -205,11 +205,4 @@ bool ResourceLoader::is_port_blocked(int port)
     return false;
 }
 
-void ResourceLoader::save_to(JsonObject& object)
-{
-    Object::save_to(object);
-    object.set("pending_loads", m_pending_loads);
-    object.set("user_agent", m_user_agent);
-}
-
 }

+ 0 - 2
Libraries/LibWeb/Loader/ResourceLoader.h

@@ -59,8 +59,6 @@ private:
     ResourceLoader();
     static bool is_port_blocked(int port);
 
-    virtual void save_to(JsonObject&) override;
-
     int m_pending_loads { 0 };
 
     RefPtr<Protocol::Client> m_protocol_client;

+ 2 - 1
Services/SystemServer/Service.h

@@ -42,7 +42,8 @@ public:
 
     static Service* find_by_pid(pid_t);
 
-    void save_to(AK::JsonObject&) override;
+    // FIXME: Port to Core::Property
+    void save_to(AK::JsonObject&);
 
 private:
     Service(const Core::ConfigFile&, const StringView& name);

+ 2 - 2
Shell/Shell.h

@@ -194,8 +194,8 @@ private:
     Shell();
     virtual ~Shell() override;
 
-    // ^Core::Object
-    virtual void save_to(JsonObject&) override;
+    // FIXME: Port to Core::Property
+    void save_to(JsonObject&);
     void bring_cursor_to_beginning_of_a_line() const;
 
     void cache_path();