diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-02-10 11:07:13 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-02-10 11:07:13 +0100 |
commit | 2def3d8d3ff3c913919cd64b2bf75b77ee57d1cf (patch) | |
tree | b4e92858629468605876ef0e9c48e9391019843e /LibGUI | |
parent | 2cf1dd5b6f10b898f24203c879290d665531a989 (diff) | |
download | serenity-2def3d8d3ff3c913919cd64b2bf75b77ee57d1cf.zip |
LibGUI: Start adding an automatic widget layout system.
My needs are really quite simple, so I'm just going to add what I need
as I go along. The first thing I needed was a simple box layout with
widgets being able to say whether they prefer fixed or fill for both
their vertical and horizontal sizes.
I also made a simple GStatusBar so FileManager can show how many bytes
worth of files are in the current directory.
Diffstat (limited to 'LibGUI')
-rw-r--r-- | LibGUI/GBoxLayout.cpp | 89 | ||||
-rw-r--r-- | LibGUI/GBoxLayout.h | 18 | ||||
-rw-r--r-- | LibGUI/GLayout.cpp | 41 | ||||
-rw-r--r-- | LibGUI/GLayout.h | 31 | ||||
-rw-r--r-- | LibGUI/GStatusBar.cpp | 35 | ||||
-rw-r--r-- | LibGUI/GStatusBar.h | 20 | ||||
-rw-r--r-- | LibGUI/GWidget.cpp | 68 | ||||
-rw-r--r-- | LibGUI/GWidget.h | 26 | ||||
-rw-r--r-- | LibGUI/Makefile | 3 |
9 files changed, 330 insertions, 1 deletions
diff --git a/LibGUI/GBoxLayout.cpp b/LibGUI/GBoxLayout.cpp new file mode 100644 index 0000000000..bc9872bf2d --- /dev/null +++ b/LibGUI/GBoxLayout.cpp @@ -0,0 +1,89 @@ +#include <LibGUI/GBoxLayout.h> +#include <LibGUI/GWidget.h> + +GBoxLayout::GBoxLayout(Orientation orientation) + : m_orientation(orientation) +{ +} + +GBoxLayout::~GBoxLayout() +{ +} + +#if 0 +Size GLayout::compute_preferred_size() const +{ + +} + + +static Size compute_preferred_size(GLayout::Entry& entry) +{ + if (entry.layout) + return entry.layout->compute_preferred_size(); + else { + return entry.widget->preferred_size(); + } +} +#endif + +void GBoxLayout::run(GWidget& widget) +{ + if (m_entries.is_empty()) + return; + + Size available_size = widget.size(); + int number_of_entries_with_fixed_size = 0; + + for (auto& entry : m_entries) { + if (entry.widget && entry.widget->size_policy(orientation()) == SizePolicy::Fixed) { + available_size -= entry.widget->preferred_size(); + ++number_of_entries_with_fixed_size; + } + } + + int number_of_entries_with_automatic_size = m_entries.size() - number_of_entries_with_fixed_size; + + dbgprintf("GBoxLayout: available_size=%d, fixed=%d, fill=%d\n", available_size.height(), number_of_entries_with_fixed_size, number_of_entries_with_automatic_size); + + Size automatic_size; + + if (m_orientation == Orientation::Horizontal) { + automatic_size.set_width(available_size.width() / number_of_entries_with_automatic_size); + automatic_size.set_height(widget.height()); + } else { + automatic_size.set_width(widget.width()); + automatic_size.set_height(available_size.height() / number_of_entries_with_automatic_size); + } + + dbgprintf("GBoxLayout: automatic_size=%s\n", automatic_size.to_string().characters()); + + int current_x = 0; + int current_y = 0; + for (auto& entry : m_entries) { + Rect rect(current_x, current_y, 0, 0); + if (entry.layout) { + // FIXME: Implement recursive layout. + ASSERT_NOT_REACHED(); + } + ASSERT(entry.widget); + if (entry.widget->size_policy(orientation()) == SizePolicy::Fixed) { + rect.set_size(automatic_size); + if (orientation() == Orientation::Vertical) { + rect.set_height(entry.widget->preferred_size().height()); + } else { + rect.set_width(entry.widget->preferred_size().height()); + } + } else { + rect.set_size(automatic_size); + } + + dbgprintf("GBoxLayout: apply, %s{%p} <- %s\n", entry.widget->class_name(), entry.widget.ptr(), rect.to_string().characters()); + entry.widget->set_relative_rect(rect); + + if (orientation() == Orientation::Horizontal) + current_x += rect.width(); + else + current_y += rect.height(); + } +} diff --git a/LibGUI/GBoxLayout.h b/LibGUI/GBoxLayout.h new file mode 100644 index 0000000000..605c7d5d62 --- /dev/null +++ b/LibGUI/GBoxLayout.h @@ -0,0 +1,18 @@ +#pragma once + +#include <LibGUI/GLayout.h> +#include <LibGUI/GWidget.h> + +class GBoxLayout final : public GLayout { +public: + explicit GBoxLayout(Orientation); + virtual ~GBoxLayout() override; + + Orientation orientation() const { return m_orientation; } + + virtual void run(GWidget&) override; + +private: + Orientation m_orientation; +}; + diff --git a/LibGUI/GLayout.cpp b/LibGUI/GLayout.cpp new file mode 100644 index 0000000000..9a16f36580 --- /dev/null +++ b/LibGUI/GLayout.cpp @@ -0,0 +1,41 @@ +#include <LibGUI/GLayout.h> +#include <LibGUI/GWidget.h> + +GLayout::GLayout() +{ +} + +GLayout::~GLayout() +{ +} + +void GLayout::notify_adopted(Badge<GWidget>, GWidget& widget) +{ + if (m_owner.ptr() == &widget) + return; + m_owner = widget.make_weak_ptr(); +} + +void GLayout::notify_disowned(Badge<GWidget>, GWidget& widget) +{ + ASSERT(m_owner.ptr() == &widget); + m_owner.clear(); +} + +void GLayout::add_layout(OwnPtr<GLayout>&& layout) +{ + Entry entry; + entry.layout = move(layout); + m_entries.append(move(entry)); + if (m_owner) + m_owner->notify_layout_changed(Badge<GLayout>()); +} + +void GLayout::add_widget(GWidget& widget) +{ + Entry entry; + entry.widget = widget.make_weak_ptr(); + m_entries.append(move(entry)); + if (m_owner) + m_owner->notify_layout_changed(Badge<GLayout>()); +} diff --git a/LibGUI/GLayout.h b/LibGUI/GLayout.h new file mode 100644 index 0000000000..7a513c8408 --- /dev/null +++ b/LibGUI/GLayout.h @@ -0,0 +1,31 @@ +#pragma once + +#include <AK/Badge.h> +#include <AK/OwnPtr.h> +#include <AK/Vector.h> +#include <AK/WeakPtr.h> + +class GWidget; + +class GLayout { +public: + GLayout(); + virtual ~GLayout(); + + void add_widget(GWidget&); + void add_layout(OwnPtr<GLayout>&&); + + virtual void run(GWidget&) = 0; + + void notify_adopted(Badge<GWidget>, GWidget&); + void notify_disowned(Badge<GWidget>, GWidget&); + +protected: + struct Entry { + WeakPtr<GWidget> widget; + OwnPtr<GLayout> layout; + }; + WeakPtr<GWidget> m_owner; + Vector<Entry> m_entries; +}; + diff --git a/LibGUI/GStatusBar.cpp b/LibGUI/GStatusBar.cpp new file mode 100644 index 0000000000..a8b7232428 --- /dev/null +++ b/LibGUI/GStatusBar.cpp @@ -0,0 +1,35 @@ +#include <LibGUI/GStatusBar.h> +#include <LibGUI/GLabel.h> +#include <LibGUI/GBoxLayout.h> +#include <SharedGraphics/Painter.h> + +GStatusBar::GStatusBar(GWidget* parent) + : GWidget(parent) +{ + set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + set_preferred_size({ 0, 16 }); + set_layout(make<GBoxLayout>(Orientation::Horizontal)); + m_label = new GLabel(this); + m_label->set_fill_with_background_color(false); +} + +GStatusBar::~GStatusBar() +{ +} + +void GStatusBar::set_text(String&& text) +{ + m_label->set_text(move(text)); +} + +String GStatusBar::text() const +{ + return m_label->text(); +} + +void GStatusBar::paint_event(GPaintEvent&) +{ + Painter painter(*this); + painter.fill_rect({ 0, 1, width(), height() - 1 }, Color::LightGray); + painter.draw_line({ 0, 0 }, { width() - 1, 0 }, Color::DarkGray); +} diff --git a/LibGUI/GStatusBar.h b/LibGUI/GStatusBar.h new file mode 100644 index 0000000000..18ccb0ca59 --- /dev/null +++ b/LibGUI/GStatusBar.h @@ -0,0 +1,20 @@ +#pragma once + +#include <LibGUI/GWidget.h> + +class GLabel; + +class GStatusBar : public GWidget { +public: + explicit GStatusBar(GWidget* parent); + virtual ~GStatusBar() override; + + String text() const; + void set_text(String&&); + +private: + virtual const char* class_name() const override { return "GStatusBar"; } + virtual void paint_event(GPaintEvent&) override; + + GLabel* m_label { nullptr }; +}; diff --git a/LibGUI/GWidget.cpp b/LibGUI/GWidget.cpp index 287b9c6e45..d29052a69c 100644 --- a/LibGUI/GWidget.cpp +++ b/LibGUI/GWidget.cpp @@ -2,6 +2,7 @@ #include "GEvent.h" #include "GEventLoop.h" #include "GWindow.h" +#include <LibGUI/GLayout.h> #include <AK/Assertions.h> #include <SharedGraphics/GraphicsBitmap.h> #include <SharedGraphics/Painter.h> @@ -12,6 +13,9 @@ GWidget::GWidget(GWidget* parent) set_font(nullptr); m_background_color = Color::LightGray; m_foreground_color = Color::Black; + + if (parent && parent->layout()) + parent->layout()->add_widget(*this); } GWidget::~GWidget() @@ -42,7 +46,7 @@ void GWidget::event(GEvent& event) m_has_pending_paint_event = false; return handle_paint_event(static_cast<GPaintEvent&>(event)); case GEvent::Resize: - return resize_event(static_cast<GResizeEvent&>(event)); + return handle_resize_event(static_cast<GResizeEvent&>(event)); case GEvent::FocusIn: return focusin_event(event); case GEvent::FocusOut: @@ -87,6 +91,41 @@ void GWidget::handle_paint_event(GPaintEvent& event) } } +void GWidget::set_layout(OwnPtr<GLayout>&& layout) +{ + if (m_layout.ptr() == layout.ptr()) + return; + if (m_layout) + m_layout->notify_disowned(Badge<GWidget>(), *this); + m_layout = move(layout); + if (m_layout) { + m_layout->notify_adopted(Badge<GWidget>(), *this); + do_layout(); + } else { + update(); + } +} + +void GWidget::do_layout() +{ + if (!m_layout) + return; + m_layout->run(*this); + update(); +} + +void GWidget::notify_layout_changed(Badge<GLayout>) +{ + do_layout(); +} + +void GWidget::handle_resize_event(GResizeEvent& event) +{ + if (layout()) + do_layout(); + return resize_event(event); +} + void GWidget::resize_event(GResizeEvent&) { } @@ -216,3 +255,30 @@ bool GWidget::global_cursor_tracking() const return false; return win->global_cursor_tracking_widget() == this; } + +void GWidget::set_preferred_size(const Size& size) +{ + if (m_preferred_size == size) + return; + m_preferred_size = size; + invalidate_layout(); +} + +void GWidget::set_size_policy(SizePolicy horizontal_policy, SizePolicy vertical_policy) +{ + if (m_horizontal_size_policy == horizontal_policy && m_vertical_size_policy == vertical_policy) + return; + m_horizontal_size_policy = horizontal_policy; + m_vertical_size_policy = vertical_policy; + invalidate_layout(); +} + +void GWidget::invalidate_layout() +{ + auto* w = window(); + if (!w) + return; + if (!w->main_widget()) + return; + w->main_widget()->do_layout(); +} diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index cb40a6dfa0..8c64635898 100644 --- a/LibGUI/GWidget.h +++ b/LibGUI/GWidget.h @@ -5,16 +5,32 @@ #include <SharedGraphics/Rect.h> #include <SharedGraphics/Color.h> #include <SharedGraphics/Font.h> +#include <AK/Badge.h> #include <AK/AKString.h> class GraphicsBitmap; +class GLayout; class GWindow; +enum class SizePolicy { Fixed, Fill }; +enum class Orientation { Horizontal, Vertical }; + class GWidget : public GObject { public: explicit GWidget(GWidget* parent = nullptr); virtual ~GWidget() override; + GLayout* layout() { return m_layout.ptr(); } + void set_layout(OwnPtr<GLayout>&&); + + SizePolicy horizontal_size_policy() const { return m_horizontal_size_policy; } + SizePolicy vertical_size_policy() const { return m_vertical_size_policy; } + 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); + + Size preferred_size() const { return m_preferred_size; } + void set_preferred_size(const Size&); + virtual void event(GEvent&) override; virtual void paint_event(GPaintEvent&); virtual void resize_event(GResizeEvent&); @@ -100,16 +116,26 @@ public: void set_global_cursor_tracking(bool); bool global_cursor_tracking() const; + void notify_layout_changed(Badge<GLayout>); + private: void handle_paint_event(GPaintEvent&); + void handle_resize_event(GResizeEvent&); + void do_layout(); + void invalidate_layout(); GWindow* m_window { nullptr }; + OwnPtr<GLayout> m_layout; Rect m_relative_rect; Color m_background_color { 0xffffff }; Color m_foreground_color { 0x000000 }; RetainPtr<Font> m_font; + SizePolicy m_horizontal_size_policy { SizePolicy::Fill }; + SizePolicy m_vertical_size_policy { SizePolicy::Fill }; + Size m_preferred_size; + bool m_has_pending_paint_event { false }; bool m_fill_with_background_color { true }; }; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 9bc857475d..ee4cbe3e6b 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -15,8 +15,11 @@ LIBGUI_OBJS = \ GObject.o \ GTextBox.o \ GScrollBar.o \ + GStatusBar.o \ GWidget.o \ GStyle.o \ + GLayout.o \ + GBoxLayout.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) |