From d0c4264608806f501c751eebf79e038515affd9f Mon Sep 17 00:00:00 2001 From: apio Date: Tue, 15 Aug 2023 12:28:47 +0200 Subject: [PATCH] libui: Add basic widget and layout system =D --- apps/gclient.cpp | 47 +++++++++++++++++++++---- libui/CMakeLists.txt | 1 + libui/include/ui/Layout.h | 45 ++++++++++++++++++++++++ libui/include/ui/Widget.h | 65 +++++++++++++++++++++++++++++++++++ libui/include/ui/Window.h | 18 ++++++++++ libui/src/App.cpp | 4 ++- libui/src/Layout.cpp | 72 +++++++++++++++++++++++++++++++++++++++ libui/src/Window.cpp | 13 +++++++ 8 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 libui/include/ui/Layout.h create mode 100644 libui/include/ui/Widget.h create mode 100644 libui/src/Layout.cpp diff --git a/apps/gclient.cpp b/apps/gclient.cpp index 52f224ce..0bdeddaf 100644 --- a/apps/gclient.cpp +++ b/apps/gclient.cpp @@ -1,4 +1,27 @@ #include +#include + +struct ColorWidget : public ui::Widget +{ + public: + ColorWidget(ui::Color color) : m_color(color) + { + } + + Result handle_mousemove(ui::Point) override + { + return ui::EventResult::DidNotHandle; + } + + Result draw(ui::Canvas& canvas) override + { + canvas.fill(m_color); + return {}; + } + + private: + ui::Color m_color; +}; Result luna_main(int argc, char** argv) { @@ -9,13 +32,25 @@ Result luna_main(int argc, char** argv) app.set_main_window(window); window->set_title("Main Window"); - window->canvas().fill(ui::CYAN); - window->update(); + window->set_background(ui::CYAN); - auto* dialog = TRY(ui::Window::create(ui::Rect { 400, 400, 200, 150 })); - dialog->set_title("Error: Unknown Error"); - dialog->canvas().fill(ui::RED); - dialog->update(); + ui::HorizontalLayout layout; + window->set_main_widget(layout); + + ColorWidget green(ui::GREEN); + layout.add_widget(green); + ColorWidget blue(ui::BLUE); + layout.add_widget(blue); + + ui::HorizontalLayout sublayout; + layout.add_widget(sublayout); + + ColorWidget red(ui::RED); + sublayout.add_widget(red); + ColorWidget white(ui::WHITE); + sublayout.add_widget(white); + + window->draw(); return app.run(); } diff --git a/libui/CMakeLists.txt b/libui/CMakeLists.txt index 81270417..ad9cb181 100644 --- a/libui/CMakeLists.txt +++ b/libui/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES src/Image.cpp src/App.cpp src/Window.cpp + src/Layout.cpp ) add_library(ui ${SOURCES}) diff --git a/libui/include/ui/Layout.h b/libui/include/ui/Layout.h new file mode 100644 index 00000000..98ce132e --- /dev/null +++ b/libui/include/ui/Layout.h @@ -0,0 +1,45 @@ +/** + * @file Layout.h + * @author apio (cloudapio.eu) + * @brief Layout widgets to organize content. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include + +namespace ui +{ + enum class AdjustHeight + { + No, + Yes + }; + + enum class AdjustWidth + { + No, + Yes + }; + + class HorizontalLayout final : public Widget + { + public: + HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes); + + Result handle_mousemove(Point position) override; + + Result draw(Canvas& canvas) override; + + Result add_widget(Widget& widget); + + private: + Vector m_widgets; + AdjustHeight m_adjust_height; + AdjustWidth m_adjust_width; + int m_used_width; + }; +} diff --git a/libui/include/ui/Widget.h b/libui/include/ui/Widget.h new file mode 100644 index 00000000..24b1f31f --- /dev/null +++ b/libui/include/ui/Widget.h @@ -0,0 +1,65 @@ +/** + * @file Widget.h + * @author apio (cloudapio.eu) + * @brief Abstract widget class. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include + +namespace ui +{ + class Window; + + enum class EventResult + { + DidHandle, + DidNotHandle, + }; + + class Widget + { + public: + virtual Result handle_mousemove(Point position); + + virtual Result draw(Canvas& canvas); + + void set_window(Window* window, Rect rect, Badge) + { + m_window = window; + m_rect = rect; + } + + void set_parent(Widget* parent) + { + m_parent = parent; + m_window = parent->m_window; + } + + Widget* parent() + { + return m_parent; + } + + Window* window() + { + return m_window; + } + + Rect& rect() + { + return m_rect; + } + + protected: + Widget* m_parent { nullptr }; + Window* m_window; + Rect m_rect { 0, 0, 50, 50 }; + }; +} diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index bf13d32b..ee08b0e6 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace ui { @@ -22,6 +23,18 @@ namespace ui void set_title(StringView title); + void set_background(Color color) + { + m_background = color; + } + + void set_main_widget(Widget& widget) + { + check(!m_main_widget); + widget.set_window(this, m_canvas.rect(), {}); + m_main_widget = &widget; + } + Canvas& canvas() { return m_canvas; @@ -31,6 +44,9 @@ namespace ui void close(); + Result draw(); + Result handle_mousemove(ui::Point position); + int id() const { return m_id; @@ -41,5 +57,7 @@ namespace ui private: int m_id; Canvas m_canvas; + Widget* m_main_widget { nullptr }; + Color m_background { ui::BLACK }; }; } diff --git a/libui/src/App.cpp b/libui/src/App.cpp index 7d934f9f..30ea1ff4 100644 --- a/libui/src/App.cpp +++ b/libui/src/App.cpp @@ -95,7 +95,9 @@ namespace ui case MOUSE_EVENT_REQUEST_ID: { MouseEventRequest request; TRY(m_client->recv_typed(request)); - os::eprintln("ui: Mouse move to (%d, %d)", request.position.x, request.position.y); + auto* window = find_window(request.window); + window->handle_mousemove(request.position); + window->draw(); return {}; } default: fail("Unexpected IPC request from server!"); diff --git a/libui/src/Layout.cpp b/libui/src/Layout.cpp new file mode 100644 index 00000000..9d5d124b --- /dev/null +++ b/libui/src/Layout.cpp @@ -0,0 +1,72 @@ +/** + * @file Layout.cpp + * @author apio (cloudapio.eu) + * @brief Layout widgets to organize content. + * + * @copyright Copyright (c) 2023, the Luna authors. + * + */ + +#include +#include + +namespace ui +{ + HorizontalLayout::HorizontalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width) + : m_adjust_height(adjust_height), m_adjust_width(adjust_width) + { + } + + Result HorizontalLayout::handle_mousemove(Point position) + { + for (auto widget : m_widgets) + { + if (widget->rect().contains(position)) return widget->handle_mousemove(position); + } + + return ui::EventResult::DidNotHandle; + } + + Result HorizontalLayout::draw(Canvas& canvas) + { + for (auto widget : m_widgets) + { + ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height }; + auto subcanvas = canvas.subcanvas(rect); + TRY(widget->draw(subcanvas)); + } + + return {}; + } + + Result HorizontalLayout::add_widget(Widget& widget) + { + TRY(m_widgets.try_append(&widget)); + + if (m_adjust_width == AdjustWidth::No) + { + widget.rect().pos.x = m_rect.pos.x + m_used_width; + m_used_width += widget.rect().width; + } + else + { + int used_width = 0; + div_t result = div(m_rect.width, (int)m_widgets.size()); + for (auto w : m_widgets) + { + w->rect().pos.x = m_rect.pos.x + used_width; + w->rect().width = result.quot; + used_width += result.quot; + } + m_widgets[m_widgets.size() - 1]->rect().width += result.rem; + } + + widget.rect().pos.y = m_rect.pos.y; + + if (m_adjust_height == AdjustHeight::Yes) { widget.rect().height = m_rect.height; } + + widget.set_parent(this); + + return {}; + } +} diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index f1c97b78..be8fcd0f 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -91,4 +91,17 @@ namespace ui app.unregister_window(this, {}); } + + Result Window::draw() + { + if (m_main_widget) return m_main_widget->draw(m_canvas); + update(); + return {}; + } + + Result Window::handle_mousemove(ui::Point position) + { + if (m_main_widget) TRY(m_main_widget->handle_mousemove(position)); + return {}; + } }