diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-05-08 13:53:34 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-05-08 13:53:34 +0200 |
commit | 3ae9fc5d88b7a9cbb382b2a9d7ca2c6f78d0ede1 (patch) | |
tree | 7b6bfa77684920f156e12a0fb043797bc01f1162 /DevTools/VisualBuilder | |
parent | 6b40b081b2e29d048284f614fc1e12a1154aa3e3 (diff) | |
download | serenity-3ae9fc5d88b7a9cbb382b2a9d7ca2c6f78d0ede1.zip |
Move VisualBuilder into a new DevTools directory.
Diffstat (limited to 'DevTools/VisualBuilder')
-rw-r--r-- | DevTools/VisualBuilder/.gitignore | 3 | ||||
-rw-r--r-- | DevTools/VisualBuilder/Makefile | 28 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBForm.cpp | 373 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBForm.h | 60 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBPropertiesWindow.cpp | 24 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBPropertiesWindow.h | 18 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBProperty.cpp | 33 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBProperty.h | 33 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidget.cpp | 197 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidget.h | 73 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidgetPropertyModel.cpp | 72 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidgetPropertyModel.h | 32 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidgetRegistry.cpp | 99 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidgetRegistry.h | 21 | ||||
-rw-r--r-- | DevTools/VisualBuilder/VBWidgetType.h | 16 | ||||
-rw-r--r-- | DevTools/VisualBuilder/main.cpp | 150 |
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; +} |