VisualBuilder: Use real GWidgets instead of pretend VBWidgets.

That first design was the wrong idea. Instead, have VBWidget instantiate
a GWidget of the appropriate type and parent it to the VBForm.
We then use a new "greedy hit-testing" mechanism in GWidget to prevent any
mouse events from reaching the VBForm's children.

To paint the grabbers above the child widgets, I added a slightly hackish
but kind of neat second_paint_event() that is called after a widget has
painted all of his children. :^)
This commit is contained in:
Andreas Kling 2019-04-11 03:34:37 +02:00
parent af070324db
commit c6ffb3e2b8
Notes: sideshowbarker 2024-07-19 14:45:43 +09:00
12 changed files with 94 additions and 87 deletions

View File

@ -1,8 +1,6 @@
OBJS = \
VBForm.o \
VBWidget.o \
VBButtonWidget.o \
VBWidgetFactory.o \
main.o
APP = VisualBuilder

View File

@ -1,17 +0,0 @@
#include "VBButtonWidget.h"
#include <SharedGraphics/StylePainter.h>
#include <LibGUI/GPainter.h>
VBButtonWidget::VBButtonWidget(VBForm& form)
: VBWidget(form)
{
}
VBButtonWidget::~VBButtonWidget()
{
}
void VBButtonWidget::paint(GPainter& painter)
{
StylePainter::paint_button(painter, rect(), ButtonStyle::Normal, false);
}

View File

@ -1,15 +0,0 @@
#pragma once
#include "VBWidget.h"
class VBButtonWidget : public VBWidget {
public:
static Retained<VBButtonWidget> create(VBForm& form) { return adopt(*new VBButtonWidget(form)); }
virtual ~VBButtonWidget() override;
virtual void paint(GPainter&) override;
virtual const char* gwidget_name() const { return "GButton"; }
private:
explicit VBButtonWidget(VBForm&);
};

View File

@ -1,23 +1,24 @@
#include "VBForm.h"
#include "VBWidget.h"
#include "VBWidgetFactory.h"
#include <LibGUI/GPainter.h>
VBForm::VBForm(const String& name, GWidget* parent)
: GWidget(parent)
, m_name(name)
{
set_fill_with_background_color(true);
set_background_color(Color::LightGray);
set_greedy_for_hits(true);
auto box1 = VBWidget::create(*this);
box1->set_rect({ 10, 10, 61, 41 });
auto box1 = VBWidget::create(WidgetType::GSpinBox, *this);
box1->set_rect({ 10, 10, 61, 21 });
m_widgets.append(move(box1));
auto box2 = VBWidget::create(*this);
auto box2 = VBWidget::create(WidgetType::GTextEditor, *this);
box2->set_rect({ 100, 100, 161, 141 });
m_widgets.append(move(box2));
auto button1 = VBWidgetFactory::create("GButton", *this);
auto button1 = VBWidget::create(WidgetType::GButton, *this);
button1->set_rect({ 200, 50, 101, 21 });
m_widgets.append(move(button1));
}
@ -36,9 +37,14 @@ void VBForm::paint_event(GPaintEvent& event)
painter.set_pixel({ x, y }, Color::Black);
}
}
}
void VBForm::second_paint_event(GPaintEvent& event)
{
GPainter painter(*this);
painter.add_clip_rect(event.rect());
for (auto& widget : m_widgets) {
widget->paint(painter);
if (widget->is_selected()) {
for_each_direction([&] (Direction direction) {
painter.fill_rect(widget->grabber_rect(direction), Color::Black);

View File

@ -9,6 +9,9 @@ public:
explicit VBForm(const String& name, GWidget* parent = nullptr);
virtual ~VBForm() override;
String name() const { return m_name; }
void set_name(const String& name) { m_name = name; }
bool is_selected(const VBWidget&) const;
VBWidget* widget_at(const Point&);
@ -17,6 +20,7 @@ public:
protected:
virtual void paint_event(GPaintEvent&) override;
virtual void second_paint_event(GPaintEvent&) override;
virtual void mousedown_event(GMouseEvent&) override;
virtual void mousemove_event(GMouseEvent&) override;
virtual void mouseup_event(GMouseEvent&) override;

View File

@ -1,16 +1,51 @@
#include "VBWidget.h"
#include "VBForm.h"
#include <LibGUI/GPainter.h>
#include <LibGUI/GLabel.h>
#include <LibGUI/GButton.h>
#include <LibGUI/GSpinBox.h>
#include <LibGUI/GTextEditor.h>
VBWidget::VBWidget(VBForm& form)
: m_form(form)
static GWidget* build_gwidget(WidgetType type, GWidget* parent)
{
switch (type) {
case WidgetType::GWidget:
return new GWidget(parent);
case WidgetType::GLabel:
return new GLabel(parent);
case WidgetType::GButton:
return new GButton(parent);
case WidgetType::GSpinBox:
return new GSpinBox(parent);
case WidgetType::GTextEditor:
return new GTextEditor(GTextEditor::Type::MultiLine, parent);
default:
ASSERT_NOT_REACHED();
return nullptr;
}
}
VBWidget::VBWidget(WidgetType type, VBForm& form)
: m_type(type)
, m_form(form)
{
m_gwidget = build_gwidget(type, &form);
}
VBWidget::~VBWidget()
{
}
Rect VBWidget::rect() const
{
return m_gwidget->relative_rect();
}
void VBWidget::set_rect(const Rect& rect)
{
m_gwidget->set_relative_rect(rect);
}
bool VBWidget::is_selected() const
{
return m_form.is_selected(*this);
@ -22,21 +57,21 @@ Rect VBWidget::grabber_rect(Direction direction) const
int half_grabber_size = grabber_size / 2;
switch (direction) {
case Direction::Left:
return { m_rect.x() - half_grabber_size, m_rect.center().y() - half_grabber_size, grabber_size, grabber_size };
return { rect().x() - half_grabber_size, rect().center().y() - half_grabber_size, grabber_size, grabber_size };
case Direction::UpLeft:
return { m_rect.x() - half_grabber_size, m_rect.y() - half_grabber_size, grabber_size, grabber_size };
return { rect().x() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
case Direction::Up:
return { m_rect.center().x() - half_grabber_size, m_rect.y() - half_grabber_size, grabber_size, grabber_size };
return { rect().center().x() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
case Direction::UpRight:
return { m_rect.right() - half_grabber_size, m_rect.y() - half_grabber_size, grabber_size, grabber_size };
return { rect().right() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
case Direction::Right:
return { m_rect.right() - half_grabber_size, m_rect.center().y() - half_grabber_size, grabber_size, grabber_size };
return { rect().right() - half_grabber_size, rect().center().y() - half_grabber_size, grabber_size, grabber_size };
case Direction::DownLeft:
return { m_rect.x() - half_grabber_size, m_rect.bottom() - half_grabber_size, grabber_size, grabber_size };
return { rect().x() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
case Direction::Down:
return { m_rect.center().x() - half_grabber_size, m_rect.bottom() - half_grabber_size, grabber_size, grabber_size };
return { rect().center().x() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
case Direction::DownRight:
return { m_rect.right() - half_grabber_size, m_rect.bottom() - half_grabber_size, grabber_size, grabber_size };
return { rect().right() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
default:
ASSERT_NOT_REACHED();
}
@ -51,9 +86,3 @@ Direction VBWidget::grabber_at(const Point& position) const
});
return found_grabber;
}
void VBWidget::paint(GPainter& painter)
{
painter.fill_rect(m_rect, Color::White);
painter.draw_rect(m_rect, Color::Black);
}

View File

@ -6,6 +6,7 @@
#include <AK/Weakable.h>
class GPainter;
class GWidget;
class VBForm;
enum class Direction { None, Left, UpLeft, Up, UpRight, Right, DownRight, Down, DownLeft };
@ -22,27 +23,35 @@ inline void for_each_direction(Callback callback)
callback(Direction::DownLeft);
}
enum class WidgetType {
None,
GWidget,
GButton,
GLabel,
GSpinBox,
GTextEditor,
};
class VBWidget : public Retainable<VBWidget>, public Weakable<VBWidget> {
public:
static Retained<VBWidget> create(VBForm& form) { return adopt(*new VBWidget(form)); }
virtual ~VBWidget();
static Retained<VBWidget> create(WidgetType type, VBForm& form) { return adopt(*new VBWidget(type, form)); }
~VBWidget();
bool is_selected() const;
Rect rect() const { return m_rect; }
void set_rect(const Rect& rect) { m_rect = rect; }
Rect rect() const;
void set_rect(const Rect&);
Rect grabber_rect(Direction) const;
Direction grabber_at(const Point&) const;
virtual void paint(GPainter&);
virtual const char* gwidget_name() const { return "GWidget"; }
GWidget* gwidget() { return m_gwidget; }
protected:
explicit VBWidget(VBForm&);
VBWidget(WidgetType, VBForm&);
private:
WidgetType m_type { WidgetType::None };
VBForm& m_form;
Rect m_rect;
GWidget* m_gwidget { nullptr };
};

View File

@ -1,9 +0,0 @@
#include "VBWidgetFactory.h"
#include "VBButtonWidget.h"
Retained<VBWidget> VBWidgetFactory::create(const String& widget_name, VBForm& form)
{
if (widget_name == "GButton")
return VBButtonWidget::create(form);
return VBWidget::create(form);
}

View File

@ -1,12 +0,0 @@
#pragma once
#include <AK/AKString.h>
#include <AK/Retained.h>
class VBForm;
class VBWidget;
class VBWidgetFactory {
public:
static Retained<VBWidget> create(const String& widget_name, VBForm&);
};

View File

@ -46,7 +46,7 @@ int main(int argc, char** argv)
app.set_menubar(move(menubar));
auto* window = new GWindow;
window->set_title("Form1");
window->set_title(form1->name());
window->set_rect(20, 200, 640, 400);
window->set_main_widget(form1);
window->set_should_exit_event_loop_on_close(true);

View File

@ -115,6 +115,7 @@ void GWidget::handle_paint_event(GPaintEvent& event)
child->event(local_event);
}
}
second_paint_event(event);
}
void GWidget::set_layout(OwnPtr<GLayout>&& layout)
@ -207,6 +208,10 @@ void GWidget::paint_event(GPaintEvent&)
{
}
void GWidget::second_paint_event(GPaintEvent&)
{
}
void GWidget::show_event(GShowEvent&)
{
}
@ -282,6 +287,8 @@ Rect GWidget::screen_relative_rect() const
GWidget::HitTestResult GWidget::hit_test(int x, int y)
{
if (is_greedy_for_hits())
return { this, x, y };
for (int i = children().size() - 1; i >= 0; --i) {
if (!children()[i]->is_widget())
continue;

View File

@ -56,6 +56,9 @@ public:
virtual void leave_event(CEvent&);
virtual void child_event(CChildEvent&) override;
// This is called after children have been painted.
virtual void second_paint_event(GPaintEvent&);
Rect relative_rect() const { return m_relative_rect; }
Point relative_position() const { return m_relative_rect.location(); }
@ -147,6 +150,9 @@ public:
bool spans_entire_window_horizontally() const;
bool is_greedy_for_hits() const { return m_greedy_for_hits; }
void set_greedy_for_hits(bool b) { m_greedy_for_hits = b; }
private:
virtual bool is_widget() const final { return true; }
@ -173,6 +179,7 @@ private:
bool m_fill_with_background_color { false };
bool m_visible { true };
bool m_greedy_for_hits { false };
CElapsedTimer m_click_clock;
};