summaryrefslogtreecommitdiff
path: root/DevTools/VisualBuilder
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-05-08 13:53:34 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-05-08 13:53:34 +0200
commit3ae9fc5d88b7a9cbb382b2a9d7ca2c6f78d0ede1 (patch)
tree7b6bfa77684920f156e12a0fb043797bc01f1162 /DevTools/VisualBuilder
parent6b40b081b2e29d048284f614fc1e12a1154aa3e3 (diff)
downloadserenity-3ae9fc5d88b7a9cbb382b2a9d7ca2c6f78d0ede1.zip
Move VisualBuilder into a new DevTools directory.
Diffstat (limited to 'DevTools/VisualBuilder')
-rw-r--r--DevTools/VisualBuilder/.gitignore3
-rw-r--r--DevTools/VisualBuilder/Makefile28
-rw-r--r--DevTools/VisualBuilder/VBForm.cpp373
-rw-r--r--DevTools/VisualBuilder/VBForm.h60
-rw-r--r--DevTools/VisualBuilder/VBPropertiesWindow.cpp24
-rw-r--r--DevTools/VisualBuilder/VBPropertiesWindow.h18
-rw-r--r--DevTools/VisualBuilder/VBProperty.cpp33
-rw-r--r--DevTools/VisualBuilder/VBProperty.h33
-rw-r--r--DevTools/VisualBuilder/VBWidget.cpp197
-rw-r--r--DevTools/VisualBuilder/VBWidget.h73
-rw-r--r--DevTools/VisualBuilder/VBWidgetPropertyModel.cpp72
-rw-r--r--DevTools/VisualBuilder/VBWidgetPropertyModel.h32
-rw-r--r--DevTools/VisualBuilder/VBWidgetRegistry.cpp99
-rw-r--r--DevTools/VisualBuilder/VBWidgetRegistry.h21
-rw-r--r--DevTools/VisualBuilder/VBWidgetType.h16
-rw-r--r--DevTools/VisualBuilder/main.cpp150
16 files changed, 1232 insertions, 0 deletions
diff --git a/DevTools/VisualBuilder/.gitignore b/DevTools/VisualBuilder/.gitignore
new file mode 100644
index 0000000000..f1b8cdb6c9
--- /dev/null
+++ b/DevTools/VisualBuilder/.gitignore
@@ -0,0 +1,3 @@
+*.o
+*.d
+VisualBuilder
diff --git a/DevTools/VisualBuilder/Makefile b/DevTools/VisualBuilder/Makefile
new file mode 100644
index 0000000000..27f8dcdc5d
--- /dev/null
+++ b/DevTools/VisualBuilder/Makefile
@@ -0,0 +1,28 @@
+include ../../Makefile.common
+
+OBJS = \
+ VBForm.o \
+ VBWidget.o \
+ VBWidgetRegistry.o \
+ VBWidgetPropertyModel.o \
+ VBProperty.o \
+ VBPropertiesWindow.o \
+ main.o
+
+APP = VisualBuilder
+
+DEFINES += -DUSERLAND
+
+all: $(APP)
+
+$(APP): $(OBJS)
+ $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lgui -lcore -lc
+
+.cpp.o:
+ @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+-include $(OBJS:%.o=%.d)
+
+clean:
+ @echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
+
diff --git a/DevTools/VisualBuilder/VBForm.cpp b/DevTools/VisualBuilder/VBForm.cpp
new file mode 100644
index 0000000000..63c4886f30
--- /dev/null
+++ b/DevTools/VisualBuilder/VBForm.cpp
@@ -0,0 +1,373 @@
+#include "VBForm.h"
+#include "VBWidget.h"
+#include "VBProperty.h"
+#include <LibGUI/GPainter.h>
+#include <LibGUI/GMenu.h>
+#include <LibGUI/GAction.h>
+#include <LibGUI/GMessageBox.h>
+#include <LibCore/CFile.h>
+
+static VBForm* s_current;
+VBForm* VBForm::current()
+{
+ return s_current;
+}
+
+VBForm::VBForm(const String& name, GWidget* parent)
+ : GWidget(parent)
+ , m_name(name)
+{
+ s_current = this;
+ set_fill_with_background_color(true);
+ set_background_color(Color::LightGray);
+ set_greedy_for_hits(true);
+
+ auto box1 = VBWidget::create(VBWidgetType::GSpinBox, *this);
+ box1->set_rect({ 10, 10, 81, 21 });
+ m_widgets.append(move(box1));
+
+ auto box2 = VBWidget::create(VBWidgetType::GTextEditor, *this);
+ box2->set_rect({ 100, 100, 161, 161 });
+ m_widgets.append(move(box2));
+
+ auto button1 = VBWidget::create(VBWidgetType::GButton, *this);
+ button1->set_rect({ 200, 50, 81, 21 });
+ m_widgets.append(move(button1));
+
+ auto groupbox1 = VBWidget::create(VBWidgetType::GGroupBox, *this);
+ groupbox1->set_rect({ 300, 150, 161, 51 });
+ m_widgets.append(move(groupbox1));
+
+ m_context_menu = make<GMenu>("Context menu");
+ m_context_menu->add_action(GAction::create("Move to front", [this] (auto&) {
+ if (auto* widget = single_selected_widget())
+ widget->gwidget()->move_to_front();
+ }));
+ m_context_menu->add_action(GAction::create("Move to back", [this] (auto&) {
+ if (auto* widget = single_selected_widget())
+ widget->gwidget()->move_to_back();
+ }));
+ m_context_menu->add_action(GAction::create("Delete", [this] (auto&) {
+ delete_selected_widgets();
+ }));
+}
+
+void VBForm::context_menu_event(GContextMenuEvent& event)
+{
+ m_context_menu->popup(event.screen_position());
+}
+
+void VBForm::insert_widget(VBWidgetType type)
+{
+ auto widget = VBWidget::create(type, *this);
+ widget->set_rect({ m_next_insertion_position, { m_grid_size * 10 + 1, m_grid_size * 5 + 1 } });
+ m_next_insertion_position.move_by(m_grid_size, m_grid_size);
+ m_widgets.append(move(widget));
+}
+
+VBForm::~VBForm()
+{
+}
+
+void VBForm::paint_event(GPaintEvent& event)
+{
+ GPainter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ for (int y = 0; y < height(); y += m_grid_size) {
+ for (int x = 0; x < width(); x += m_grid_size) {
+ painter.set_pixel({ x, y }, Color::from_rgb(0x404040));
+ }
+ }
+}
+
+void VBForm::second_paint_event(GPaintEvent& event)
+{
+ GPainter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ for (auto& widget : m_widgets) {
+ if (widget->is_selected()) {
+ for_each_direction([&] (Direction direction) {
+ painter.fill_rect(widget->grabber_rect(direction), Color::Black);
+ });
+ }
+ }
+}
+
+bool VBForm::is_selected(const VBWidget& widget) const
+{
+ // FIXME: Fix HashTable and remove this const_cast.
+ return m_selected_widgets.contains(const_cast<VBWidget*>(&widget));
+}
+
+VBWidget* VBForm::widget_at(const Point& position)
+{
+ auto* gwidget = child_at(position);
+ if (!gwidget)
+ return nullptr;
+ return m_gwidget_map.get(gwidget);
+}
+
+void VBForm::grabber_mousedown_event(GMouseEvent& event, Direction grabber)
+{
+ m_transform_event_origin = event.position();
+ for_each_selected_widget([] (auto& widget) { widget.capture_transform_origin_rect(); });
+ m_resize_direction = grabber;
+}
+
+void VBForm::keydown_event(GKeyEvent& event)
+{
+ if (event.key() == KeyCode::Key_Delete) {
+ delete_selected_widgets();
+ return;
+ }
+ if (event.key() == KeyCode::Key_Tab) {
+ if (m_widgets.is_empty())
+ return;
+ if (m_selected_widgets.is_empty()) {
+ set_single_selected_widget(m_widgets.first());
+ update();
+ return;
+ }
+ int selected_widget_index = 0;
+ for (; selected_widget_index < m_widgets.size(); ++selected_widget_index) {
+ if (m_widgets[selected_widget_index] == *m_selected_widgets.begin())
+ break;
+ }
+ ++selected_widget_index;
+ if (selected_widget_index == m_widgets.size())
+ selected_widget_index = 0;
+ set_single_selected_widget(m_widgets[selected_widget_index]);
+ update();
+ return;
+ }
+ if (!m_selected_widgets.is_empty()) {
+ switch (event.key()) {
+ case KeyCode::Key_Up:
+ update();
+ for_each_selected_widget([this] (auto& widget) { widget.gwidget()->move_by(0, -m_grid_size); });
+ break;
+ case KeyCode::Key_Down:
+ update();
+ for_each_selected_widget([this] (auto& widget) { widget.gwidget()->move_by(0, m_grid_size); });
+ break;
+ case KeyCode::Key_Left:
+ update();
+ for_each_selected_widget([this] (auto& widget) { widget.gwidget()->move_by(-m_grid_size, 0); });
+ break;
+ case KeyCode::Key_Right:
+ update();
+ for_each_selected_widget([this] (auto& widget) { widget.gwidget()->move_by(m_grid_size, 0); });
+ break;
+ }
+ return;
+ }
+}
+
+void VBForm::set_single_selected_widget(VBWidget* widget)
+{
+ if (!widget) {
+ if (!m_selected_widgets.is_empty()) {
+ m_selected_widgets.clear();
+ on_widget_selected(nullptr);
+ update();
+ }
+ return;
+ }
+ m_selected_widgets.clear();
+ m_selected_widgets.set(widget);
+ on_widget_selected(m_selected_widgets.size() == 1 ? widget : nullptr);
+ update();
+}
+
+void VBForm::add_to_selection(VBWidget& widget)
+{
+ m_selected_widgets.set(&widget);
+ update();
+}
+
+void VBForm::remove_from_selection(VBWidget& widget)
+{
+ m_selected_widgets.remove(&widget);
+ update();
+}
+
+void VBForm::mousedown_event(GMouseEvent& event)
+{
+ if (m_resize_direction == Direction::None) {
+ bool hit_grabber = false;
+ for_each_selected_widget([&] (auto& widget) {
+ auto grabber = widget.grabber_at(event.position());
+ if (grabber != Direction::None) {
+ hit_grabber = true;
+ return grabber_mousedown_event(event, grabber);
+ }
+ });
+ if (hit_grabber)
+ return;
+ }
+ auto* widget = widget_at(event.position());
+ if (!widget) {
+ set_single_selected_widget(nullptr);
+ return;
+ }
+ if (event.button() == GMouseButton::Left || event.button() == GMouseButton::Right) {
+ m_transform_event_origin = event.position();
+ if (event.modifiers() == Mod_Ctrl)
+ remove_from_selection(*widget);
+ else if (event.modifiers() == Mod_Shift)
+ add_to_selection(*widget);
+ else if (!m_selected_widgets.contains(widget))
+ set_single_selected_widget(widget);
+ for_each_selected_widget([] (auto& widget) { widget.capture_transform_origin_rect(); });
+ on_widget_selected(single_selected_widget());
+ }
+}
+
+void VBForm::mousemove_event(GMouseEvent& event)
+{
+ if (event.buttons() & GMouseButton::Left) {
+ if (m_resize_direction == Direction::None) {
+ update();
+ auto delta = event.position() - m_transform_event_origin;
+ for_each_selected_widget([&] (auto& widget) {
+ auto new_rect = widget.transform_origin_rect().translated(delta);
+ new_rect.set_x(new_rect.x() - (new_rect.x() % m_grid_size));
+ new_rect.set_y(new_rect.y() - (new_rect.y() % m_grid_size));
+ widget.set_rect(new_rect);
+ });
+ return;
+ }
+ int diff_x = event.x() - m_transform_event_origin.x();
+ int diff_y = event.y() - m_transform_event_origin.y();
+
+ int change_x = 0;
+ int change_y = 0;
+ int change_w = 0;
+ int change_h = 0;
+
+ switch (m_resize_direction) {
+ case Direction::DownRight:
+ change_w = diff_x;
+ change_h = diff_y;
+ break;
+ case Direction::Right:
+ change_w = diff_x;
+ break;
+ case Direction::UpRight:
+ change_w = diff_x;
+ change_y = diff_y;
+ change_h = -diff_y;
+ break;
+ case Direction::Up:
+ change_y = diff_y;
+ change_h = -diff_y;
+ break;
+ case Direction::UpLeft:
+ change_x = diff_x;
+ change_w = -diff_x;
+ change_y = diff_y;
+ change_h = -diff_y;
+ break;
+ case Direction::Left:
+ change_x = diff_x;
+ change_w = -diff_x;
+ break;
+ case Direction::DownLeft:
+ change_x = diff_x;
+ change_w = -diff_x;
+ change_h = diff_y;
+ break;
+ case Direction::Down:
+ change_h = diff_y;
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ update();
+ for_each_selected_widget([&] (auto& widget) {
+ auto new_rect = widget.transform_origin_rect();
+ Size minimum_size { 5, 5 };
+ new_rect.set_x(new_rect.x() + change_x);
+ new_rect.set_y(new_rect.y() + change_y);
+ new_rect.set_width(max(minimum_size.width(), new_rect.width() + change_w));
+ new_rect.set_height(max(minimum_size.height(), new_rect.height() + change_h));
+ new_rect.set_x(new_rect.x() - (new_rect.x() % m_grid_size));
+ new_rect.set_y(new_rect.y() - (new_rect.y() % m_grid_size));
+ new_rect.set_width(new_rect.width() - (new_rect.width() % m_grid_size) + 1);
+ new_rect.set_height(new_rect.height() - (new_rect.height() % m_grid_size) + 1);
+ widget.set_rect(new_rect);
+ });
+ }
+}
+
+void VBForm::write_to_file(const String& path)
+{
+ CFile file(path);
+ if (!file.open(CIODevice::WriteOnly)) {
+ GMessageBox box(String::format("Could not open '%s' for writing", path.characters()), "Error", window());
+ box.exec();
+ return;
+ }
+ file.printf("[Form]\n");
+ file.printf("Name=%s\n", m_name.characters());
+ file.printf("\n");
+ int i = 0;
+ for (auto& widget : m_widgets) {
+ file.printf("[Widget %d]\n", i++);
+ widget->for_each_property([&] (auto& property) {
+ file.printf("%s=%s\n", property.name().characters(), property.value().to_string().characters());
+ });
+ file.printf("\n");
+ }
+}
+
+void VBForm::dump()
+{
+ dbgprintf("[Form]\n");
+ dbgprintf("Name=%s\n", m_name.characters());
+ dbgprintf("\n");
+ int i = 0;
+ for (auto& widget : m_widgets) {
+ dbgprintf("[Widget %d]\n", i++);
+ widget->for_each_property([] (auto& property) {
+ dbgprintf("%s=%s\n", property.name().characters(), property.value().to_string().characters());
+ });
+ dbgprintf("\n");
+ }
+}
+
+void VBForm::mouseup_event(GMouseEvent& event)
+{
+ if (event.button() == GMouseButton::Left) {
+ m_transform_event_origin = { };
+ m_resize_direction = Direction::None;
+ }
+}
+
+void VBForm::delete_selected_widgets()
+{
+ Vector<VBWidget*> to_delete;
+ for_each_selected_widget([&] (auto& widget) {
+ to_delete.append(&widget);
+ });
+ for (auto& widget : to_delete)
+ m_widgets.remove_first_matching([&widget] (auto& entry) { return entry == widget; } );
+ on_widget_selected(single_selected_widget());
+}
+
+template<typename Callback>
+void VBForm::for_each_selected_widget(Callback callback)
+{
+ for (auto& widget : m_selected_widgets)
+ callback(*widget);
+}
+
+VBWidget* VBForm::single_selected_widget()
+{
+ if (m_selected_widgets.size() != 1)
+ return nullptr;
+ return *m_selected_widgets.begin();
+}
diff --git a/DevTools/VisualBuilder/VBForm.h b/DevTools/VisualBuilder/VBForm.h
new file mode 100644
index 0000000000..bae9553233
--- /dev/null
+++ b/DevTools/VisualBuilder/VBForm.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <LibGUI/GWidget.h>
+#include <AK/Vector.h>
+#include "VBWidget.h"
+
+class VBForm : public GWidget {
+ friend class VBWidget;
+public:
+ explicit VBForm(const String& name, GWidget* parent = nullptr);
+ virtual ~VBForm() override;
+
+ static VBForm* current();
+
+ 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&);
+
+ void set_should_snap_to_grip(bool snap) { m_should_snap_to_grid = snap; }
+ bool should_snap_to_grid() const { return m_should_snap_to_grid; }
+
+ void insert_widget(VBWidgetType);
+
+ Function<void(VBWidget*)> on_widget_selected;
+
+ void write_to_file(const String& path);
+ void dump();
+
+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;
+ virtual void context_menu_event(GContextMenuEvent&) override;
+ virtual void keydown_event(GKeyEvent&) override;
+
+private:
+ void grabber_mousedown_event(GMouseEvent&, Direction grabber);
+ void set_single_selected_widget(VBWidget*);
+ void add_to_selection(VBWidget&);
+ void remove_from_selection(VBWidget&);
+ void delete_selected_widgets();
+ template<typename Callback> void for_each_selected_widget(Callback);
+
+ VBWidget* single_selected_widget();
+
+ String m_name;
+ int m_grid_size { 5 };
+ bool m_should_snap_to_grid { true };
+ Vector<Retained<VBWidget>> m_widgets;
+ HashMap<GWidget*, VBWidget*> m_gwidget_map;
+ HashTable<VBWidget*> m_selected_widgets;
+ Point m_transform_event_origin;
+ Point m_next_insertion_position;
+ Direction m_resize_direction { Direction::None };
+ OwnPtr<GMenu> m_context_menu;
+};
diff --git a/DevTools/VisualBuilder/VBPropertiesWindow.cpp b/DevTools/VisualBuilder/VBPropertiesWindow.cpp
new file mode 100644
index 0000000000..cfa8b836a6
--- /dev/null
+++ b/DevTools/VisualBuilder/VBPropertiesWindow.cpp
@@ -0,0 +1,24 @@
+#include "VBPropertiesWindow.h"
+#include <LibGUI/GWidget.h>
+#include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GTableView.h>
+#include <LibGUI/GTextBox.h>
+
+VBPropertiesWindow::VBPropertiesWindow()
+{
+ set_title("Properties");
+ set_rect(780, 200, 240, 280);
+
+ auto* widget = new GWidget;
+ widget->set_fill_with_background_color(true);
+ widget->set_layout(make<GBoxLayout>(Orientation::Vertical));
+ set_main_widget(widget);
+
+ m_table_view = new GTableView(widget);
+ m_table_view->set_headers_visible(false);
+ m_table_view->set_editable(true);
+}
+
+VBPropertiesWindow::~VBPropertiesWindow()
+{
+}
diff --git a/DevTools/VisualBuilder/VBPropertiesWindow.h b/DevTools/VisualBuilder/VBPropertiesWindow.h
new file mode 100644
index 0000000000..8dd27dd189
--- /dev/null
+++ b/DevTools/VisualBuilder/VBPropertiesWindow.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <LibGUI/GWindow.h>
+
+class GTableView;
+class GTextBox;
+
+class VBPropertiesWindow final : public GWindow {
+public:
+ VBPropertiesWindow();
+ virtual ~VBPropertiesWindow() override;
+
+ GTableView& table_view() { return *m_table_view; }
+ const GTableView& table_view() const { return *m_table_view; }
+
+private:
+ GTableView* m_table_view { nullptr };
+};
diff --git a/DevTools/VisualBuilder/VBProperty.cpp b/DevTools/VisualBuilder/VBProperty.cpp
new file mode 100644
index 0000000000..1ce4ba0dca
--- /dev/null
+++ b/DevTools/VisualBuilder/VBProperty.cpp
@@ -0,0 +1,33 @@
+#include "VBProperty.h"
+#include "VBWidget.h"
+
+VBProperty::VBProperty(VBWidget& widget, const String& name, const GVariant& value)
+ : m_widget(widget)
+ , m_name(name)
+ , m_value(value)
+{
+}
+
+VBProperty::VBProperty(VBWidget& widget, const String& name, Function<GVariant(const GWidget&)>&& getter, Function<void(GWidget&, const GVariant&)>&& setter)
+ : m_widget(widget)
+ , m_name(name)
+ , m_getter(move(getter))
+ , m_setter(move(setter))
+{
+ ASSERT(m_getter);
+ ASSERT(m_setter);
+}
+
+VBProperty::~VBProperty()
+{
+}
+
+void VBProperty::set_value(const GVariant& value)
+{
+ if (m_value == value)
+ return;
+ m_value = value;
+ if (m_setter)
+ m_setter(*m_widget.gwidget(), value);
+ m_widget.property_did_change();
+}
diff --git a/DevTools/VisualBuilder/VBProperty.h b/DevTools/VisualBuilder/VBProperty.h
new file mode 100644
index 0000000000..9d70c66c8e
--- /dev/null
+++ b/DevTools/VisualBuilder/VBProperty.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/Function.h>
+#include <LibGUI/GVariant.h>
+
+class GWidget;
+class VBWidget;
+
+class VBProperty {
+ friend class VBWidget;
+public:
+ VBProperty(VBWidget&, const String& name, const GVariant& value);
+ VBProperty(VBWidget&, const String& name, Function<GVariant(const GWidget&)>&& getter, Function<void(GWidget&, const GVariant&)>&& setter);
+ ~VBProperty();
+
+ String name() const { return m_name; }
+ const GVariant& value() const { return m_value; }
+ void set_value(const GVariant&);
+
+ bool is_readonly() const { return m_readonly; }
+ void set_readonly(bool b) { m_readonly = b; }
+
+ void sync();
+
+private:
+ VBWidget& m_widget;
+ String m_name;
+ GVariant m_value;
+ Function<GVariant(const GWidget&)> m_getter;
+ Function<void(GWidget&, const GVariant&)> m_setter;
+ bool m_readonly { false };
+};
diff --git a/DevTools/VisualBuilder/VBWidget.cpp b/DevTools/VisualBuilder/VBWidget.cpp
new file mode 100644
index 0000000000..83e92167de
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidget.cpp
@@ -0,0 +1,197 @@
+#include "VBWidget.h"
+#include "VBForm.h"
+#include "VBProperty.h"
+#include "VBWidgetRegistry.h"
+#include "VBWidgetPropertyModel.h"
+#include <LibGUI/GPainter.h>
+#include <LibGUI/GLabel.h>
+#include <LibGUI/GButton.h>
+#include <LibGUI/GScrollBar.h>
+#include <LibGUI/GSpinBox.h>
+#include <LibGUI/GTextEditor.h>
+#include <LibGUI/GGroupBox.h>
+#include <LibGUI/GCheckBox.h>
+#include <LibGUI/GProgressBar.h>
+#include <LibGUI/GSlider.h>
+
+VBWidget::VBWidget(VBWidgetType type, VBForm& form)
+ : m_type(type)
+ , m_form(form)
+ , m_property_model(VBWidgetPropertyModel::create(*this))
+{
+ m_gwidget = VBWidgetRegistry::build_gwidget(*this, type, &form, m_properties);
+ m_form.m_gwidget_map.set(m_gwidget, this);
+ setup_properties();
+}
+
+VBWidget::~VBWidget()
+{
+ m_form.m_gwidget_map.remove(m_gwidget);
+ m_form.m_selected_widgets.remove(this);
+ delete m_gwidget;
+}
+
+Rect VBWidget::rect() const
+{
+ return m_gwidget->relative_rect();
+}
+
+void VBWidget::set_rect(const Rect& rect)
+{
+ if (rect == m_gwidget->relative_rect())
+ return;
+ m_gwidget->set_relative_rect(rect);
+ synchronize_properties();
+}
+
+bool VBWidget::is_selected() const
+{
+ return m_form.is_selected(*this);
+}
+
+Rect VBWidget::grabber_rect(Direction direction) const
+{
+ int grabber_size = 5;
+ int half_grabber_size = grabber_size / 2;
+ switch (direction) {
+ case Direction::Left:
+ return { rect().x() - half_grabber_size, rect().center().y() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::UpLeft:
+ return { rect().x() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::Up:
+ return { rect().center().x() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::UpRight:
+ return { rect().right() - half_grabber_size, rect().y() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::Right:
+ return { rect().right() - half_grabber_size, rect().center().y() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::DownLeft:
+ return { rect().x() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::Down:
+ return { rect().center().x() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
+ case Direction::DownRight:
+ return { rect().right() - half_grabber_size, rect().bottom() - half_grabber_size, grabber_size, grabber_size };
+ default:
+ ASSERT_NOT_REACHED();
+ }
+}
+
+Direction VBWidget::grabber_at(const Point& position) const
+{
+ Direction found_grabber = Direction::None;
+ for_each_direction([&] (Direction direction) {
+ if (grabber_rect(direction).contains(position))
+ found_grabber = direction;
+ });
+ return found_grabber;
+}
+
+void VBWidget::for_each_property(Function<void(VBProperty&)> callback)
+{
+ for (auto& it : m_properties) {
+ callback(*it);
+ }
+}
+
+void VBWidget::add_property(const String& name, Function<GVariant(const GWidget&)>&& getter, Function<void(GWidget&, const GVariant&)>&& setter)
+{
+ auto& prop = property(name);
+ prop.m_getter = move(getter);
+ prop.m_setter = move(setter);
+}
+
+#define VB_ADD_PROPERTY(gclass, name, getter, setter, variant_type) \
+ add_property(name, \
+ [] (auto& widget) -> GVariant { return ((const gclass&)widget).getter(); }, \
+ [] (auto& widget, auto& value) { ((gclass&)widget).setter(value.to_ ## variant_type()); } \
+ )
+
+void VBWidget::setup_properties()
+{
+ VB_ADD_PROPERTY(GWidget, "width", width, set_width, int);
+ VB_ADD_PROPERTY(GWidget, "height", height, set_height, int);
+ VB_ADD_PROPERTY(GWidget, "x", x, set_x, int);
+ VB_ADD_PROPERTY(GWidget, "y", y, set_y, int);
+ VB_ADD_PROPERTY(GWidget, "visible", is_visible, set_visible, bool);
+ VB_ADD_PROPERTY(GWidget, "enabled", is_enabled, set_enabled, bool);
+ VB_ADD_PROPERTY(GWidget, "tooltip", tooltip, set_tooltip, string);
+ VB_ADD_PROPERTY(GWidget, "backcolor", background_color, set_background_color, color);
+ VB_ADD_PROPERTY(GWidget, "forecolor", foreground_color, set_foreground_color, color);
+ VB_ADD_PROPERTY(GWidget, "autofill", fill_with_background_color, set_fill_with_background_color, bool);
+
+ if (m_type == VBWidgetType::GLabel) {
+ VB_ADD_PROPERTY(GLabel, "text", text, set_text, string);
+ }
+
+ if (m_type == VBWidgetType::GButton) {
+ VB_ADD_PROPERTY(GButton, "caption", caption, set_caption, string);
+ }
+
+ if (m_type == VBWidgetType::GGroupBox) {
+ VB_ADD_PROPERTY(GGroupBox, "name", name, set_name, string);
+ }
+
+ if (m_type == VBWidgetType::GScrollBar) {
+ VB_ADD_PROPERTY(GScrollBar, "min", min, set_min, int);
+ VB_ADD_PROPERTY(GScrollBar, "max", max, set_max, int);
+ VB_ADD_PROPERTY(GScrollBar, "value", value, set_value, int);
+ VB_ADD_PROPERTY(GScrollBar, "step", step, set_step, int);
+ }
+
+ if (m_type == VBWidgetType::GSpinBox) {
+ VB_ADD_PROPERTY(GSpinBox, "min", min, set_min, int);
+ VB_ADD_PROPERTY(GSpinBox, "max", max, set_max, int);
+ VB_ADD_PROPERTY(GSpinBox, "value", value, set_value, int);
+ }
+
+ if (m_type == VBWidgetType::GProgressBar) {
+ VB_ADD_PROPERTY(GProgressBar, "min", min, set_min, int);
+ VB_ADD_PROPERTY(GProgressBar, "max", max, set_max, int);
+ VB_ADD_PROPERTY(GProgressBar, "value", value, set_value, int);
+ }
+
+ if (m_type == VBWidgetType::GSlider) {
+ VB_ADD_PROPERTY(GSlider, "min", min, set_min, int);
+ VB_ADD_PROPERTY(GSlider, "max", max, set_max, int);
+ VB_ADD_PROPERTY(GSlider, "value", value, set_value, int);
+ }
+
+ if (m_type == VBWidgetType::GTextEditor) {
+ VB_ADD_PROPERTY(GTextEditor, "text", text, set_text, string);
+ VB_ADD_PROPERTY(GTextEditor, "ruler_visible", is_ruler_visible, set_ruler_visible, bool);
+ }
+
+ if (m_type == VBWidgetType::GCheckBox) {
+ VB_ADD_PROPERTY(GCheckBox, "caption", caption, set_caption, string);
+ VB_ADD_PROPERTY(GCheckBox, "checked", is_checked, set_checked, bool);
+ }
+}
+
+void VBWidget::synchronize_properties()
+{
+ for (auto& prop : m_properties) {
+ if (prop->m_getter)
+ prop->m_value = prop->m_getter(*gwidget());
+ }
+
+ m_property_model->update();
+}
+
+VBProperty& VBWidget::property(const String& name)
+{
+ for (auto& prop : m_properties) {
+ if (prop->name() == name)
+ return *prop;
+ }
+ m_properties.append(make<VBProperty>(*this, name, GVariant()));
+ return *m_properties.last();
+}
+
+void VBWidget::property_did_change()
+{
+ m_form.update();
+}
+
+void VBWidget::capture_transform_origin_rect()
+{
+ m_transform_origin_rect = rect();
+}
diff --git a/DevTools/VisualBuilder/VBWidget.h b/DevTools/VisualBuilder/VBWidget.h
new file mode 100644
index 0000000000..afd240b081
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidget.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <SharedGraphics/Rect.h>
+#include <AK/Retainable.h>
+#include <AK/Retained.h>
+#include <AK/Weakable.h>
+#include <AK/HashMap.h>
+#include <AK/Function.h>
+#include "VBWidgetType.h"
+
+class GPainter;
+class GVariant;
+class GWidget;
+class VBForm;
+class VBProperty;
+class VBWidgetPropertyModel;
+
+enum class Direction { None, Left, UpLeft, Up, UpRight, Right, DownRight, Down, DownLeft };
+template<typename Callback>
+inline void for_each_direction(Callback callback)
+{
+ callback(Direction::Left);
+ callback(Direction::UpLeft);
+ callback(Direction::Up);
+ callback(Direction::UpRight);
+ callback(Direction::Right);
+ callback(Direction::DownRight);
+ callback(Direction::Down);
+ callback(Direction::DownLeft);
+}
+
+class VBWidget : public Retainable<VBWidget>, public Weakable<VBWidget> {
+ friend class VBWidgetPropertyModel;
+public:
+ static Retained<VBWidget> create(VBWidgetType type, VBForm& form) { return adopt(*new VBWidget(type, form)); }
+ ~VBWidget();
+
+ bool is_selected() const;
+
+ Rect rect() const;
+ void set_rect(const Rect&);
+
+ Rect grabber_rect(Direction) const;
+ Direction grabber_at(const Point&) const;
+
+ GWidget* gwidget() { return m_gwidget; }
+
+ VBProperty& property(const String&);
+
+ void for_each_property(Function<void(VBProperty&)>);
+
+ VBWidgetPropertyModel& property_model() { return *m_property_model; }
+
+ void setup_properties();
+ void synchronize_properties();
+
+ void property_did_change();
+
+ Rect transform_origin_rect() const { return m_transform_origin_rect; }
+ void capture_transform_origin_rect();
+
+private:
+ VBWidget(VBWidgetType, VBForm&);
+
+ void add_property(const String& name, Function<GVariant(const GWidget&)>&& getter, Function<void(GWidget&, const GVariant&)>&& setter);
+
+ VBWidgetType m_type { VBWidgetType::None };
+ VBForm& m_form;
+ GWidget* m_gwidget { nullptr };
+ Vector<OwnPtr<VBProperty>> m_properties;
+ Retained<VBWidgetPropertyModel> m_property_model;
+ Rect m_transform_origin_rect;
+};
diff --git a/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp b/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp
new file mode 100644
index 0000000000..0a4935c660
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidgetPropertyModel.cpp
@@ -0,0 +1,72 @@
+#include "VBWidgetPropertyModel.h"
+#include "VBWidget.h"
+#include "VBProperty.h"
+#include <SharedGraphics/Font.h>
+
+VBWidgetPropertyModel::VBWidgetPropertyModel(VBWidget& widget)
+ : m_widget(widget)
+{
+}
+
+VBWidgetPropertyModel::~VBWidgetPropertyModel()
+{
+}
+
+int VBWidgetPropertyModel::row_count(const GModelIndex&) const
+{
+ return m_widget.m_properties.size();
+}
+
+String VBWidgetPropertyModel::column_name(int column) const
+{
+ switch (column) {
+ case Column::Name: return "Name";
+ case Column::Value: return "Value";
+ default: ASSERT_NOT_REACHED();
+ }
+}
+
+GModel::ColumnMetadata VBWidgetPropertyModel::column_metadata(int column) const
+{
+ UNUSED_PARAM(column);
+ if (column == Column::Name)
+ return { 110, TextAlignment::CenterLeft, &Font::default_bold_font() };
+ return { 90, TextAlignment::CenterLeft };
+}
+
+GVariant VBWidgetPropertyModel::data(const GModelIndex& index, Role role) const
+{
+ if (role == Role::Display) {
+ auto& property = *m_widget.m_properties[index.row()];
+ switch (index.column()) {
+ case Column::Name: return property.name();
+ case Column::Value: return property.value();
+ }
+ ASSERT_NOT_REACHED();
+ }
+ if (role == Role::ForegroundColor) {
+ auto& property = *m_widget.m_properties[index.row()];
+ switch (index.column()) {
+ case Column::Name: return Color::Black;
+ case Column::Value: return property.is_readonly() ? Color(Color::MidGray) : Color(Color::Black);
+ }
+ ASSERT_NOT_REACHED();
+ }
+ return { };
+}
+
+void VBWidgetPropertyModel::set_data(const GModelIndex& index, const GVariant& value)
+{
+ ASSERT(index.column() == Column::Value);
+ auto& property = *m_widget.m_properties[index.row()];
+ ASSERT(!property.is_readonly());
+ property.set_value(value);
+}
+
+bool VBWidgetPropertyModel::is_editable(const GModelIndex& index) const
+{
+ if (index.column() != Column::Value)
+ return false;
+ auto& property = *m_widget.m_properties[index.row()];
+ return !property.is_readonly();
+}
diff --git a/DevTools/VisualBuilder/VBWidgetPropertyModel.h b/DevTools/VisualBuilder/VBWidgetPropertyModel.h
new file mode 100644
index 0000000000..b84bffc697
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidgetPropertyModel.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <LibGUI/GModel.h>
+
+class VBWidget;
+class VBProperty;
+
+class VBWidgetPropertyModel : public GModel {
+public:
+ enum Column {
+ Name = 0,
+ Value,
+ __Count
+ };
+
+ static Retained<VBWidgetPropertyModel> create(VBWidget& widget) { return adopt(*new VBWidgetPropertyModel(widget)); }
+ virtual ~VBWidgetPropertyModel() override;
+
+ virtual int row_count(const GModelIndex&) const override;
+ virtual int column_count(const GModelIndex&) const override { return Column::__Count; }
+ virtual String column_name(int column) const override;
+ virtual ColumnMetadata column_metadata(int column) const override;
+ virtual GVariant data(const GModelIndex&, Role = Role::Display) const override;
+ virtual void update() override { did_update(); }
+ virtual bool is_editable(const GModelIndex&) const override;
+ virtual void set_data(const GModelIndex&, const GVariant&) override;
+
+private:
+ explicit VBWidgetPropertyModel(VBWidget&);
+
+ VBWidget& m_widget;
+};
diff --git a/DevTools/VisualBuilder/VBWidgetRegistry.cpp b/DevTools/VisualBuilder/VBWidgetRegistry.cpp
new file mode 100644
index 0000000000..3bde68ec3d
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidgetRegistry.cpp
@@ -0,0 +1,99 @@
+#include "VBWidgetRegistry.h"
+#include "VBProperty.h"
+#include <LibGUI/GLabel.h>
+#include <LibGUI/GButton.h>
+#include <LibGUI/GSpinBox.h>
+#include <LibGUI/GTextEditor.h>
+#include <LibGUI/GProgressBar.h>
+#include <LibGUI/GCheckBox.h>
+#include <LibGUI/GScrollBar.h>
+#include <LibGUI/GGroupBox.h>
+#include <LibGUI/GSlider.h>
+
+static String to_class_name(VBWidgetType type)
+{
+ switch (type) {
+ case VBWidgetType::GWidget: return "GWidget";
+ case VBWidgetType::GButton: return "GButton";
+ case VBWidgetType::GLabel: return "GLabel";
+ case VBWidgetType::GSpinBox: return "GSpinBox";
+ case VBWidgetType::GTextEditor: return "GTextEditor";
+ case VBWidgetType::GProgressBar: return "GProgressBar";
+ case VBWidgetType::GCheckBox: return "GCheckBox";
+ case VBWidgetType::GScrollBar: return "GScrollBar";
+ case VBWidgetType::GGroupBox: return "GGroupBox";
+ case VBWidgetType::GSlider: return "GSlider";
+ default: ASSERT_NOT_REACHED();
+ }
+}
+
+static GWidget* build_gwidget(VBWidgetType type, GWidget* parent)
+{
+ switch (type) {
+ case VBWidgetType::GWidget:
+ return new GWidget(parent);
+ case VBWidgetType::GScrollBar:
+ return new GScrollBar(Orientation::Vertical, parent);
+ case VBWidgetType::GGroupBox:
+ return new GGroupBox("groupbox_1", parent);
+ case VBWidgetType::GLabel: {
+ auto* label = new GLabel(parent);
+ label->set_fill_with_background_color(true);
+ label->set_text("label_1");
+ return label;
+ }
+ case VBWidgetType::GButton: {
+ auto* button = new GButton(parent);
+ button->set_caption("button_1");
+ return button;
+ }
+ case VBWidgetType::GSpinBox: {
+ auto* box = new GSpinBox(parent);
+ box->set_range(0, 100);
+ box->set_value(0);
+ return box;
+ }
+ case VBWidgetType::GTextEditor: {
+ auto* editor = new GTextEditor(GTextEditor::Type::MultiLine, parent);
+ editor->set_ruler_visible(false);
+ return editor;
+ }
+ case VBWidgetType::GProgressBar: {
+ auto* bar = new GProgressBar(parent);
+ bar->set_format(GProgressBar::Format::NoText);
+ bar->set_range(0, 100);
+ bar->set_value(50);
+ return bar;
+ }
+ case VBWidgetType::GSlider: {
+ auto* slider = new GSlider(parent);
+ slider->set_range(0, 100);
+ slider->set_value(50);
+ return slider;
+ }
+ case VBWidgetType::GCheckBox: {
+ auto* box = new GCheckBox(parent);
+ box->set_caption("checkbox_1");
+ return box;
+ }
+ default:
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+}
+
+GWidget* VBWidgetRegistry::build_gwidget(VBWidget& widget, VBWidgetType type, GWidget* parent, Vector<OwnPtr<VBProperty>>& properties)
+{
+ auto* gwidget = ::build_gwidget(type, parent);
+ auto add_readonly_property = [&] (const String& name, const GVariant& value) {
+ auto property = make<VBProperty>(widget, name, value);
+ property->set_readonly(true);
+ properties.append(move(property));
+ };
+ auto add_property = [&] (const String& name, Function<GVariant(const GWidget&)>&& getter, Function<void(GWidget&, const GVariant&)>&& setter) {
+ auto property = make<VBProperty>(widget, name, move(getter), move(setter));
+ properties.append(move(property));
+ };
+ add_readonly_property("class", to_class_name(type));
+ return gwidget;
+}
diff --git a/DevTools/VisualBuilder/VBWidgetRegistry.h b/DevTools/VisualBuilder/VBWidgetRegistry.h
new file mode 100644
index 0000000000..d2af1f941e
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidgetRegistry.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "VBWidgetType.h"
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/AKString.h>
+
+class GWidget;
+class VBProperty;
+class VBWidget;
+
+class VBWidgetRegistry {
+public:
+ template<typename Callback> static void for_each_widget_type(Callback callback)
+ {
+ for (unsigned i = 1; i < (unsigned)VBWidgetType::__Count; ++i)
+ callback((VBWidgetType)i);
+ }
+
+ static GWidget* build_gwidget(VBWidget&, VBWidgetType, GWidget* parent, Vector<OwnPtr<VBProperty>>&);
+};
diff --git a/DevTools/VisualBuilder/VBWidgetType.h b/DevTools/VisualBuilder/VBWidgetType.h
new file mode 100644
index 0000000000..1d0487a478
--- /dev/null
+++ b/DevTools/VisualBuilder/VBWidgetType.h
@@ -0,0 +1,16 @@
+#pragma once
+
+enum class VBWidgetType {
+ None = 0,
+ GWidget,
+ GButton,
+ GLabel,
+ GSpinBox,
+ GTextEditor,
+ GProgressBar,
+ GCheckBox,
+ GScrollBar,
+ GGroupBox,
+ GSlider,
+ __Count
+};
diff --git a/DevTools/VisualBuilder/main.cpp b/DevTools/VisualBuilder/main.cpp
new file mode 100644
index 0000000000..03b32576c3
--- /dev/null
+++ b/DevTools/VisualBuilder/main.cpp
@@ -0,0 +1,150 @@
+#include <LibGUI/GWindow.h>
+#include <LibGUI/GWidget.h>
+#include <LibGUI/GBoxLayout.h>
+#include <LibGUI/GApplication.h>
+#include <LibGUI/GMenuBar.h>
+#include <LibGUI/GAction.h>
+#include <LibGUI/GButton.h>
+#include <LibGUI/GTableView.h>
+#include "VBForm.h"
+#include "VBWidget.h"
+#include "VBWidgetPropertyModel.h"
+#include "VBPropertiesWindow.h"
+#include <unistd.h>
+#include <stdio.h>
+#include <signal.h>
+#include <fcntl.h>
+
+static GWindow* make_toolbox_window();
+
+int main(int argc, char** argv)
+{
+ GApplication app(argc, argv);
+
+ auto* propbox = new VBPropertiesWindow;
+
+ auto* form1 = new VBForm("Form1");
+ form1->on_widget_selected = [propbox] (VBWidget* widget) {
+ propbox->table_view().set_model(widget ? &widget->property_model() : nullptr);
+ };
+
+ auto menubar = make<GMenuBar>();
+ auto app_menu = make<GMenu>("Visual Builder");
+ app_menu->add_action(GAction::create("Quit", { Mod_Alt, Key_F4 }, [] (const GAction&) {
+ GApplication::the().quit(0);
+ return;
+ }));
+ menubar->add_menu(move(app_menu));
+
+ auto file_menu = make<GMenu>("File");
+ file_menu->add_action(GAction::create("Dump Form", [&] (auto&) {
+ form1->dump();
+ }));
+ file_menu->add_action(GAction::create("Save Form...", { Mod_Ctrl, Key_S }, [form1] (auto&) {
+ form1->write_to_file("/tmp/form.frm");
+ }));
+ menubar->add_menu(move(file_menu));
+
+ auto edit_menu = make<GMenu>("Edit");
+ menubar->add_menu(move(edit_menu));
+
+ auto help_menu = make<GMenu>("Help");
+ help_menu->add_action(GAction::create("About", [] (const GAction&) {
+ dbgprintf("FIXME: Implement Help/About\n");
+ }));
+ menubar->add_menu(move(help_menu));
+
+ app.set_menubar(move(menubar));
+
+ auto* window = new GWindow;
+ window->set_title(form1->name());
+ window->set_rect(120, 200, 640, 400);
+ window->set_main_widget(form1);
+ window->set_should_exit_event_loop_on_close(true);
+ window->show();
+
+ auto* toolbox = make_toolbox_window();
+ toolbox->show();
+
+ propbox->show();
+
+ return app.exec();
+}
+
+GWindow* make_toolbox_window()
+{
+ auto* window = new GWindow;
+ window->set_title("Widgets");
+ window->set_rect(20, 200, 80, 300);
+
+ auto* widget = new GWidget;
+ widget->set_fill_with_background_color(true);
+ widget->set_layout(make<GBoxLayout>(Orientation::Vertical));
+ window->set_main_widget(widget);
+
+ auto* label_button = new GButton(widget);
+ label_button->set_tooltip("GLabel");
+ label_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/label.png"));
+ label_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GLabel);
+ };
+
+ auto* button_button = new GButton(widget);
+ button_button->set_tooltip("GButton");
+ button_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/button.png"));
+ button_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GButton);
+ };
+ auto* spinbox_button = new GButton(widget);
+ spinbox_button->set_tooltip("GSpinBox");
+ spinbox_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/spinbox.png"));
+ spinbox_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GSpinBox);
+ };
+ auto* editor_button = new GButton(widget);
+ editor_button->set_tooltip("GTextEditor");
+ editor_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/textbox.png"));
+ editor_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GTextEditor);
+ };
+ auto* progress_bar_button = new GButton(widget);
+ progress_bar_button->set_tooltip("GProgressBar");
+ progress_bar_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/progressbar.png"));
+ progress_bar_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GProgressBar);
+ };
+ auto* slider_button = new GButton(widget);
+ slider_button->set_tooltip("GSlider");
+ slider_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/slider.png"));
+ slider_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GSlider);
+ };
+ auto* checkbox_button = new GButton(widget);
+ checkbox_button->set_tooltip("GCheckBox");
+ checkbox_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/checkbox.png"));
+ checkbox_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GCheckBox);
+ };
+ auto* scrollbar_button = new GButton(widget);
+ scrollbar_button->set_tooltip("GScrollBar");
+ scrollbar_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/scrollbar.png"));
+ scrollbar_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GScrollBar);
+ };
+ auto* groupbox_button = new GButton(widget);
+ groupbox_button->set_tooltip("GGroupBox");
+ groupbox_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/vbwidgets/groupbox.png"));
+ groupbox_button->on_click = [] (GButton&) {
+ if (auto* form = VBForm::current())
+ form->insert_widget(VBWidgetType::GGroupBox);
+ };
+ return window;
+}