1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 22:17:45 +00:00

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.
This commit is contained in:
Andreas Kling 2020-09-15 21:33:37 +02:00
parent 1e96e46a81
commit e2f32b8f9d
23 changed files with 373 additions and 250 deletions

View file

@ -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);
}
}

View file

@ -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:

View file

@ -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");
}
}

View file

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

View file

@ -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));
}
}

View file

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

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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&);

View file

@ -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)

View file

@ -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&)>);