LibGUI: Add a GStackWidget for many widgets sharing a single location.

Call set_active_widget(GWidget*) to put a new widget on top.
This commit is contained in:
Andreas Kling 2019-03-15 16:12:06 +01:00
parent ab92252ee6
commit 497300c492
10 changed files with 157 additions and 5 deletions

View file

@ -1,7 +1,7 @@
#include <LibGUI/GBoxLayout.h>
#include <LibGUI/GWidget.h>
//#define GBOXLAYOUT_DEBUG
#define GBOXLAYOUT_DEBUG
#ifdef GBOXLAYOUT_DEBUG
#include <stdio.h>
@ -41,14 +41,19 @@ void GBoxLayout::run(GWidget& widget)
Size available_size = widget.size();
int number_of_entries_with_fixed_size = 0;
int number_of_visible_entries = 0;
for (auto& entry : m_entries) {
if (!entry.widget->is_visible())
continue;
++number_of_visible_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;
int number_of_entries_with_automatic_size = number_of_visible_entries - number_of_entries_with_fixed_size;
#ifdef GBOXLAYOUT_DEBUG
printf("GBoxLayout: available_size=%s, fixed=%d, fill=%d\n", available_size.to_string().characters(), number_of_entries_with_fixed_size, number_of_entries_with_automatic_size);
@ -74,6 +79,8 @@ void GBoxLayout::run(GWidget& widget)
int current_y = margins().top();
for (auto& entry : m_entries) {
if (!entry.widget->is_visible())
continue;
Rect rect(current_x, current_y, 0, 0);
if (entry.layout) {
// FIXME: Implement recursive layout.

12
LibGUI/GEvent.cpp Normal file
View file

@ -0,0 +1,12 @@
#include <LibGUI/GEvent.h>
#include <LibGUI/GObject.h>
GChildEvent::GChildEvent(Type type, GObject& child)
: GEvent(type)
, m_child(child.make_weak_ptr())
{
}
GChildEvent::~GChildEvent()
{
}

View file

@ -4,8 +4,11 @@
#include <SharedGraphics/Rect.h>
#include <AK/AKString.h>
#include <AK/Types.h>
#include <AK/WeakPtr.h>
#include <Kernel/KeyCode.h>
class GObject;
class GEvent {
public:
enum Type {
@ -31,6 +34,8 @@ public:
FocusIn,
FocusOut,
WindowCloseRequest,
ChildAdded,
ChildRemoved,
};
GEvent() { }
@ -170,3 +175,15 @@ public:
private:
int m_timer_id;
};
class GChildEvent final : public GEvent {
public:
GChildEvent(Type, GObject& child);
~GChildEvent();
GObject* child() { return m_child.ptr(); }
const GObject* child() const { return m_child.ptr(); }
private:
WeakPtr<GObject> m_child;
};

View file

@ -28,6 +28,9 @@ void GObject::event(GEvent& event)
case GEvent::DeferredDestroy:
delete this;
break;
case GEvent::ChildAdded:
case GEvent::ChildRemoved:
return child_event(static_cast<GChildEvent&>(event));
case GEvent::Invalid:
ASSERT_NOT_REACHED();
break;
@ -39,6 +42,7 @@ void GObject::event(GEvent& event)
void GObject::add_child(GObject& object)
{
m_children.append(&object);
GEventLoop::main().post_event(*this, make<GChildEvent>(GEvent::ChildAdded, object));
}
void GObject::remove_child(GObject& object)
@ -46,6 +50,7 @@ void GObject::remove_child(GObject& object)
for (ssize_t i = 0; i < m_children.size(); ++i) {
if (m_children[i] == &object) {
m_children.remove(i);
GEventLoop::main().post_event(*this, make<GChildEvent>(GEvent::ChildRemoved, object));
return;
}
}
@ -55,6 +60,10 @@ void GObject::timer_event(GTimerEvent&)
{
}
void GObject::child_event(GChildEvent&)
{
}
void GObject::start_timer(int ms)
{
if (m_timer_id) {

View file

@ -4,6 +4,7 @@
#include <AK/Weakable.h>
class GEvent;
class GChildEvent;
class GTimerEvent;
class GObject : public Weakable<GObject> {
@ -29,12 +30,14 @@ public:
void delete_later();
private:
virtual bool is_widget() const { return false; }
protected:
virtual void timer_event(GTimerEvent&);
virtual void child_event(GChildEvent&);
private:
GObject* m_parent { nullptr };
int m_timer_id { 0 };
Vector<GObject*> m_children;
};

62
LibGUI/GStackWidget.cpp Normal file
View file

@ -0,0 +1,62 @@
#include <LibGUI/GStackWidget.h>
#include <LibGUI/GBoxLayout.h>
GStackWidget::GStackWidget(GWidget* parent)
: GWidget(parent)
{
set_fill_with_background_color(true);
set_background_color(Color::Red);
}
GStackWidget::~GStackWidget()
{
}
void GStackWidget::set_active_widget(GWidget* widget)
{
dbgprintf("XXX: GStackWidget: set_active_widget %p\n", widget);
if (widget == m_active_widget)
return;
if (m_active_widget)
m_active_widget->set_visible(false);
m_active_widget = widget;
if (m_active_widget) {
m_active_widget->set_relative_rect(rect());
m_active_widget->set_visible(true);
}
}
void GStackWidget::resize_event(GResizeEvent& event)
{
if (!m_active_widget)
return;
m_active_widget->set_relative_rect({ { }, event.size() });
}
void GStackWidget::child_event(GChildEvent& event)
{
if (!event.child() || !event.child()->is_widget())
return;
auto& child = static_cast<GWidget&>(*event.child());
if (event.type() == GEvent::ChildAdded) {
dbgprintf("XXX: GStackWidget: did_add_child %p\n", &child);
if (!m_active_widget) {
set_active_widget(&child);
} else {
child.set_visible(false);
}
} else if (event.type() == GEvent::ChildRemoved) {
dbgprintf("XXX: GStackWidget: did_remove_child %p\n", &child);
if (m_active_widget == &child) {
GWidget* new_active_widget = nullptr;
for (auto* new_child : children()) {
if (new_child->is_widget()) {
new_active_widget = static_cast<GWidget*>(new_child);
break;
}
}
set_active_widget(new_active_widget);
}
}
}

21
LibGUI/GStackWidget.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <LibGUI/GWidget.h>
class GStackWidget : public GWidget {
public:
explicit GStackWidget(GWidget* parent);
virtual ~GStackWidget() override;
GWidget* active_widget() const { return m_active_widget; }
void set_active_widget(GWidget*);
protected:
virtual void child_event(GChildEvent&) override;
virtual void resize_event(GResizeEvent&) override;
private:
virtual const char* class_name() const override { return "GStackWidget"; }
GWidget* m_active_widget { nullptr };
};

View file

@ -91,6 +91,8 @@ void GWidget::handle_paint_event(GPaintEvent& event)
paint_event(event);
for (auto* ch : children()) {
auto* child = (GWidget*)ch;
if (!child->is_visible())
continue;
if (child->relative_rect().intersects(event.rect())) {
auto local_rect = event.rect();
local_rect.intersect(child->relative_rect());
@ -303,3 +305,14 @@ void GWidget::invalidate_layout()
return;
w->main_widget()->do_layout();
}
void GWidget::set_visible(bool visible)
{
if (visible == m_visible)
return;
m_visible = visible;
if (auto* parent = parent_widget())
parent->invalidate_layout();
if (m_visible)
update();
}

View file

@ -122,7 +122,12 @@ public:
void notify_layout_changed(Badge<GLayout>);
bool is_visible() const { return m_visible; }
void set_visible(bool);
private:
virtual bool is_widget() const final { return true; }
void handle_paint_event(GPaintEvent&);
void handle_resize_event(GResizeEvent&);
void do_layout();
@ -141,4 +146,5 @@ private:
Size m_preferred_size;
bool m_fill_with_background_color { false };
bool m_visible { true };
};

View file

@ -35,6 +35,8 @@ LIBGUI_OBJS = \
GTextEditor.o \
GClipboard.o \
GSortingProxyTableModel.o \
GStackWidget.o \
GEvent.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)