From b03e1b08b515db786e340ce70a23ae85706c3f84 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 4 Jan 2021 18:17:14 +0100 Subject: [PATCH] LibGUI: Add Widget shrink-to-fit layout flag If this flag is enabled for a widget, it will be automatically sized based on its children. This only works for widgets using a layout. This allows you to put widgets inside each other without having to manually calculate how large the container should be. It's not the perfect API but it's a decent progression in ergonomics. :^) --- Libraries/LibGUI/BoxLayout.cpp | 70 ++++++++++++++++++++++++++++++++++ Libraries/LibGUI/BoxLayout.h | 4 ++ Libraries/LibGUI/Layout.h | 2 + Libraries/LibGUI/Widget.cpp | 10 +++++ Libraries/LibGUI/Widget.h | 4 ++ 5 files changed, 90 insertions(+) diff --git a/Libraries/LibGUI/BoxLayout.cpp b/Libraries/LibGUI/BoxLayout.cpp index 0096732af9..117666a23f 100644 --- a/Libraries/LibGUI/BoxLayout.cpp +++ b/Libraries/LibGUI/BoxLayout.cpp @@ -44,6 +44,70 @@ BoxLayout::BoxLayout(Orientation orientation) "orientation", [this] { return m_orientation == Gfx::Orientation::Vertical ? "Vertical" : "Horizontal"; }, nullptr); } +Gfx::IntSize BoxLayout::preferred_size() const +{ + Gfx::IntSize size; + size.set_primary_size_for_orientation(orientation(), preferred_primary_size()); + size.set_secondary_size_for_orientation(orientation(), preferred_secondary_size()); + return size; +} + +int BoxLayout::preferred_primary_size() const +{ + int size = 0; + + for (auto& entry : m_entries) { + if (!entry.widget) + continue; + int min_size = entry.widget->min_size().primary_size_for_orientation(orientation()); + int max_size = entry.widget->max_size().primary_size_for_orientation(orientation()); + int preferred_primary_size = -1; + if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) { + preferred_primary_size = entry.widget->layout()->preferred_size().primary_size_for_orientation(orientation()); + } + int item_size = max(0, preferred_primary_size); + item_size = max(min_size, item_size); + item_size = min(max_size, item_size); + size += item_size + spacing(); + } + if (size > 0) + size -= spacing(); + + if (orientation() == Gfx::Orientation::Horizontal) + size += margins().left() + margins().right(); + else + size += margins().top() + margins().bottom(); + + if (!size) + return -1; + return size; +} + +int BoxLayout::preferred_secondary_size() const +{ + int size = 0; + for (auto& entry : m_entries) { + if (!entry.widget) + continue; + int min_size = entry.widget->min_size().secondary_size_for_orientation(orientation()); + int preferred_secondary_size = -1; + if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) { + preferred_secondary_size = entry.widget->layout()->preferred_size().secondary_size_for_orientation(orientation()); + size = max(size, preferred_secondary_size); + } + size = max(min_size, size); + } + + if (orientation() == Gfx::Orientation::Horizontal) + size += margins().top() + margins().bottom(); + else + size += margins().left() + margins().right(); + + if (!size) + return -1; + return size; +} + void BoxLayout::run(Widget& widget) { if (m_entries.is_empty()) @@ -71,6 +135,12 @@ void BoxLayout::run(Widget& widget) continue; auto min_size = entry.widget->min_size(); auto max_size = entry.widget->max_size(); + + if (entry.widget->is_shrink_to_fit() && entry.widget->layout()) { + auto preferred_size = entry.widget->layout()->preferred_size(); + min_size = max_size = preferred_size; + } + items.append(Item { entry.widget.ptr(), min_size.primary_size_for_orientation(orientation()), max_size.primary_size_for_orientation(orientation()) }); } diff --git a/Libraries/LibGUI/BoxLayout.h b/Libraries/LibGUI/BoxLayout.h index 021168670c..6f1ac615b5 100644 --- a/Libraries/LibGUI/BoxLayout.h +++ b/Libraries/LibGUI/BoxLayout.h @@ -41,11 +41,15 @@ public: Gfx::Orientation orientation() const { return m_orientation; } virtual void run(Widget&) override; + virtual Gfx::IntSize preferred_size() const override; protected: explicit BoxLayout(Gfx::Orientation); private: + int preferred_primary_size() const; + int preferred_secondary_size() const; + Gfx::Orientation m_orientation; }; diff --git a/Libraries/LibGUI/Layout.h b/Libraries/LibGUI/Layout.h index f5aded5513..2be8e57798 100644 --- a/Libraries/LibGUI/Layout.h +++ b/Libraries/LibGUI/Layout.h @@ -32,6 +32,7 @@ #include #include #include +#include namespace GUI { @@ -49,6 +50,7 @@ public: void remove_widget(Widget&); virtual void run(Widget&) = 0; + virtual Gfx::IntSize preferred_size() const = 0; void notify_adopted(Badge, Widget&); void notify_disowned(Badge, Widget&); diff --git a/Libraries/LibGUI/Widget.cpp b/Libraries/LibGUI/Widget.cpp index 83458fb7c2..3a07da857d 100644 --- a/Libraries/LibGUI/Widget.cpp +++ b/Libraries/LibGUI/Widget.cpp @@ -105,6 +105,8 @@ Widget::Widget() REGISTER_INT_PROPERTY("fixed_height", dummy_fixed_height, set_fixed_height); REGISTER_SIZE_PROPERTY("fixed_size", dummy_fixed_size, set_fixed_size); + REGISTER_BOOL_PROPERTY("shrink_to_fit", is_shrink_to_fit, set_shrink_to_fit); + REGISTER_INT_PROPERTY("x", x, set_x); REGISTER_INT_PROPERTY("y", y, set_y); @@ -1010,4 +1012,12 @@ bool Widget::has_focus_within() const return window->focused_widget() == &effective_focus_widget || is_ancestor_of(*window->focused_widget()); } +void Widget::set_shrink_to_fit(bool b) +{ + if (m_shrink_to_fit == b) + return; + m_shrink_to_fit = b; + invalidate_layout(); +} + } diff --git a/Libraries/LibGUI/Widget.h b/Libraries/LibGUI/Widget.h index fa6ec121f5..05b292b483 100644 --- a/Libraries/LibGUI/Widget.h +++ b/Libraries/LibGUI/Widget.h @@ -297,6 +297,9 @@ public: bool load_from_gml(const StringView&); + void set_shrink_to_fit(bool); + bool is_shrink_to_fit() const { return m_shrink_to_fit; } + protected: Widget(); @@ -367,6 +370,7 @@ private: bool m_enabled { true }; bool m_updates_enabled { true }; bool m_accepts_emoji_input { false }; + bool m_shrink_to_fit { false }; NonnullRefPtr m_palette;