summaryrefslogtreecommitdiff
path: root/LibGUI
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-02-10 11:07:13 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-02-10 11:07:13 +0100
commit2def3d8d3ff3c913919cd64b2bf75b77ee57d1cf (patch)
treeb4e92858629468605876ef0e9c48e9391019843e /LibGUI
parent2cf1dd5b6f10b898f24203c879290d665531a989 (diff)
downloadserenity-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.cpp89
-rw-r--r--LibGUI/GBoxLayout.h18
-rw-r--r--LibGUI/GLayout.cpp41
-rw-r--r--LibGUI/GLayout.h31
-rw-r--r--LibGUI/GStatusBar.cpp35
-rw-r--r--LibGUI/GStatusBar.h20
-rw-r--r--LibGUI/GWidget.cpp68
-rw-r--r--LibGUI/GWidget.h26
-rw-r--r--LibGUI/Makefile3
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)