From 9538c06a4581e22b26bdcfcc8bd0217dd373f1d0 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 30 Mar 2019 13:53:30 +0100 Subject: [PATCH] LibGUI: Add a simple GSplitter container widget. This allows you to put multiple widgets in a container and makes the space in between them draggable to resize the two adjacent widgets. --- Applications/FileManager/main.cpp | 5 +- LibGUI/GSplitter.cpp | 99 +++++++++++++++++++++++++++++++ LibGUI/GSplitter.h | 23 +++++++ LibGUI/GWidget.h | 2 +- LibGUI/Makefile | 1 + SharedGraphics/Point.h | 1 + 6 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 LibGUI/GSplitter.cpp create mode 100644 LibGUI/GSplitter.h diff --git a/Applications/FileManager/main.cpp b/Applications/FileManager/main.cpp index dd766b115e..6be5e242ef 100644 --- a/Applications/FileManager/main.cpp +++ b/Applications/FileManager/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -50,9 +51,7 @@ int main(int argc, char** argv) auto* location_textbox = new GTextEditor(GTextEditor::SingleLine, location_toolbar); - // FIXME: Implement an actual GSplitter widget. - auto* splitter = new GWidget(widget); - splitter->set_layout(make(Orientation::Horizontal)); + auto* splitter = new GSplitter(Orientation::Horizontal, widget); auto* tree_view = new GTreeView(splitter); auto file_system_model = GFileSystemModel::create("/", GFileSystemModel::Mode::DirectoriesOnly); tree_view->set_model(file_system_model.copy_ref()); diff --git a/LibGUI/GSplitter.cpp b/LibGUI/GSplitter.cpp new file mode 100644 index 0000000000..e76847d044 --- /dev/null +++ b/LibGUI/GSplitter.cpp @@ -0,0 +1,99 @@ +#include +#include + +GSplitter::GSplitter(Orientation orientation, GWidget* parent) + : GFrame(parent) + , m_orientation(orientation) +{ + set_layout(make(orientation)); + set_fill_with_background_color(true); + set_background_color(Color::LightGray); + layout()->set_spacing(4); +} + +GSplitter::~GSplitter() +{ +} + +void GSplitter::mousedown_event(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left) + return; + m_resizing = true; + int x_or_y = m_orientation == Orientation::Horizontal ? event.x() : event.y(); + GWidget* first_resizee { nullptr }; + GWidget* second_resizee { nullptr }; + int fudge = layout()->spacing(); + for (auto* child : children()) { + if (!child->is_widget()) + continue; + auto& child_widget = *static_cast(child); + int child_start = m_orientation == Orientation::Horizontal ? child_widget.relative_rect().left() : child_widget.relative_rect().top(); + int child_end = m_orientation == Orientation::Horizontal ? child_widget.relative_rect().right() : child_widget.relative_rect().bottom(); + if (x_or_y > child_end && (x_or_y - fudge) <= child_end) + first_resizee = &child_widget; + if (x_or_y < child_start && (x_or_y + fudge) >= child_start) + second_resizee = &child_widget; + } + ASSERT(first_resizee && second_resizee); + m_first_resizee = first_resizee->make_weak_ptr(); + m_second_resizee = second_resizee->make_weak_ptr(); + m_first_resizee_start_size = first_resizee->size(); + m_second_resizee_start_size = second_resizee->size(); + m_resize_origin = event.position(); +} + +void GSplitter::mousemove_event(GMouseEvent& event) +{ + if (!m_resizing) + return; + auto delta = event.position() - m_resize_origin; + if (!m_first_resizee || !m_second_resizee) { + // One or both of the resizees were deleted during an ongoing resize, screw this. + m_resizing = false; + return;; + } + int minimum_size = 0; + auto new_first_resizee_size = m_first_resizee_start_size; + auto new_second_resizee_size = m_second_resizee_start_size; + if (m_orientation == Orientation::Horizontal) { + new_first_resizee_size.set_width(new_first_resizee_size.width() + delta.x()); + new_second_resizee_size.set_width(new_second_resizee_size.width() - delta.x()); + + if (new_first_resizee_size.width() < minimum_size) { + int correction = minimum_size - new_first_resizee_size.width(); + new_first_resizee_size.set_width(new_first_resizee_size.width() + correction); + new_second_resizee_size.set_width(new_second_resizee_size.width() - correction); + } + if (new_second_resizee_size.width() < minimum_size) { + int correction = minimum_size - new_second_resizee_size.width(); + new_second_resizee_size.set_width(new_second_resizee_size.width() + correction); + new_first_resizee_size.set_width(new_first_resizee_size.width() - correction); + } + } else { + new_first_resizee_size.set_height(new_first_resizee_size.height() + delta.y()); + new_second_resizee_size.set_height(new_second_resizee_size.height() - delta.y()); + + if (new_first_resizee_size.height() < minimum_size) { + int correction = minimum_size - new_first_resizee_size.height(); + new_first_resizee_size.set_height(new_first_resizee_size.height() + correction); + new_second_resizee_size.set_height(new_second_resizee_size.height() - correction); + } + if (new_second_resizee_size.height() < minimum_size) { + int correction = minimum_size - new_second_resizee_size.height(); + new_second_resizee_size.set_height(new_second_resizee_size.height() + correction); + new_first_resizee_size.set_height(new_first_resizee_size.height() - correction); + } + } + m_first_resizee->set_preferred_size(new_first_resizee_size); + m_second_resizee->set_preferred_size(new_second_resizee_size); + + invalidate_layout(); +} + +void GSplitter::mouseup_event(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left) + return; + m_resizing = false; +} diff --git a/LibGUI/GSplitter.h b/LibGUI/GSplitter.h new file mode 100644 index 0000000000..f1ecfcf579 --- /dev/null +++ b/LibGUI/GSplitter.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class GSplitter : public GFrame { +public: + GSplitter(Orientation, GWidget* parent); + virtual ~GSplitter() override; + +protected: + virtual void mousedown_event(GMouseEvent&) override; + virtual void mousemove_event(GMouseEvent&) override; + virtual void mouseup_event(GMouseEvent&) override; + +private: + Orientation m_orientation; + bool m_resizing { false }; + Point m_resize_origin; + WeakPtr m_first_resizee; + WeakPtr m_second_resizee; + Size m_first_resizee_start_size; + Size m_second_resizee_start_size; +}; diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index b05bcfa024..a8bf7b2ff9 100644 --- a/LibGUI/GWidget.h +++ b/LibGUI/GWidget.h @@ -135,6 +135,7 @@ public: bool global_cursor_tracking() const; void notify_layout_changed(Badge); + void invalidate_layout(); bool is_visible() const { return m_visible; } void set_visible(bool); @@ -148,7 +149,6 @@ private: void handle_resize_event(GResizeEvent&); void handle_mouseup_event(GMouseEvent&); void do_layout(); - void invalidate_layout(); GWindow* m_window { nullptr }; OwnPtr m_layout; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 372ec0630b..218b344b39 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -56,6 +56,7 @@ LIBGUI_OBJS = \ GFrame.o \ GTreeView.o \ GFileSystemModel.o \ + GSplitter.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) diff --git a/SharedGraphics/Point.h b/SharedGraphics/Point.h index e49d6cb49d..5a00512b7d 100644 --- a/SharedGraphics/Point.h +++ b/SharedGraphics/Point.h @@ -49,6 +49,7 @@ public: } Point operator-() const { return { -m_x, -m_y }; } + Point operator-(const Point& other) const { return { m_x - other.m_x, m_y - other.m_y }; } operator WSAPI_Point() const; String to_string() const { return String::format("[%d,%d]", x(), y()); }