From f34dd5637502e78d6bef2c07fac9303c542abeee Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 27 Dec 2023 12:56:40 +0100 Subject: [PATCH] wind+libui: Implement client side decorations --- libui/include/ui/Window.h | 11 ++++- libui/include/ui/ipc/Server.h | 9 ++++ libui/src/Window.cpp | 92 +++++++++++++++++++++++++++++++++-- wind/IPC.cpp | 34 +++++++++---- wind/Mouse.cpp | 38 +++++---------- wind/Window.cpp | 41 ++-------------- wind/Window.h | 7 +-- wind/main.cpp | 7 ++- 8 files changed, 151 insertions(+), 88 deletions(-) diff --git a/libui/include/ui/Window.h b/libui/include/ui/Window.h index 189f52c6..9c9dfe00 100644 --- a/libui/include/ui/Window.h +++ b/libui/include/ui/Window.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -33,13 +34,13 @@ namespace ui void set_main_widget(Widget& widget) { check(!m_main_widget); - widget.set_window(this, m_canvas.rect(), {}); + widget.set_window(this, m_window_canvas.rect(), {}); m_main_widget = &widget; } Canvas& canvas() { - return m_canvas; + return m_window_canvas; } void update(); @@ -62,8 +63,14 @@ namespace ui private: int m_id; Canvas m_canvas; + Canvas m_titlebar_canvas; + Canvas m_window_canvas; + String m_name; Widget* m_main_widget { nullptr }; Option m_background {}; Option m_old_mouse_buttons; + bool m_decorated { false }; + + Result draw_titlebar(); }; } diff --git a/libui/include/ui/ipc/Server.h b/libui/include/ui/ipc/Server.h index 6eca6461..3f5af6f3 100644 --- a/libui/include/ui/ipc/Server.h +++ b/libui/include/ui/ipc/Server.h @@ -24,6 +24,7 @@ namespace ui INVALIDATE_ID, CLOSE_WINDOW_ID, GET_SCREEN_RECT_ID, + SET_TITLEBAR_RECT_ID, }; enum class WindowType : u8 @@ -78,4 +79,12 @@ namespace ui int _shadow; // Unused. }; + + struct SetTitlebarRectRequest + { + static constexpr u8 ID = SET_TITLEBAR_RECT_ID; + + int window; + ui::Rect titlebar_rect; + }; } diff --git a/libui/src/Window.cpp b/libui/src/Window.cpp index 21b88af6..15ccc31a 100644 --- a/libui/src/Window.cpp +++ b/libui/src/Window.cpp @@ -8,31 +8,68 @@ */ #include +#include #include #include #include #include +#include +#include #include #include +static int titlebar_height() +{ + auto font = ui::Font::default_font(); + return font->height() + 20; +} + namespace ui { Result Window::create(Rect rect, WindowType type) { auto window = TRY(make_owned()); + window->m_name = TRY(String::from_cstring("Window")); + + if (type == ui::WindowType::Normal) + { + int height = titlebar_height(); + rect.height += height; // Make sure we provide the full contents rect that was asked for. + rect.pos.y -= height; // Adjust it so the contents begin at the expected coordinates. + window->m_decorated = true; + } + + rect = rect.normalized(); ui::CreateWindowRequest request; request.rect = rect; - request.type = type; auto response = TRY(os::IPC::send_sync(App::the().client(), request)); auto path = COPY_IPC_STRING(response.shm_path); u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4, false)); - window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }; + Canvas canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }; + window->m_canvas = canvas; window->m_id = response.window; + if (type == ui::WindowType::Normal) + { + int height = titlebar_height(); + window->m_titlebar_canvas = canvas.subcanvas(ui::Rect { 0, 0, canvas.width, height }); + window->m_window_canvas = canvas.subcanvas(ui::Rect { 0, height, canvas.width, canvas.height - height }); + + ui::SetTitlebarRectRequest titlebar_request; + titlebar_request.titlebar_rect = window->m_titlebar_canvas.rect(); + titlebar_request.window = response.window; + os::IPC::send_async(App::the().client(), titlebar_request); + } + else + { + window->m_titlebar_canvas = canvas.subcanvas(ui::Rect { 0, 0, 0, 0 }); + window->m_window_canvas = canvas; + } + Window* p = window.ptr(); ui::RemoveSharedMemoryRequest shm_request; @@ -55,6 +92,9 @@ namespace ui request.window = m_id; SET_IPC_STRING(request.title, title.chars()); os::IPC::send_async(App::the().client(), request); + + m_name = String::from_string_view(title).release_value(); + draw(); } void Window::update() @@ -79,12 +119,43 @@ namespace ui Result Window::draw() { - if (m_background.has_value()) m_canvas.fill(*m_background); - if (m_main_widget) TRY(m_main_widget->draw(m_canvas)); + if (m_background.has_value()) m_window_canvas.fill(*m_background); + if (m_decorated) TRY(draw_titlebar()); + if (m_main_widget) TRY(m_main_widget->draw(m_window_canvas)); update(); return {}; } + static constexpr ui::Color TITLEBAR_COLOR = ui::Color::from_rgb(53, 53, 53); + + // FIXME: Titlebars should be implemented as a separate widget group, to allow for customization and extensibility. + // Additionally, this very specific spaghetti code could be replaced with well-established UI components. + Result Window::draw_titlebar() + { + wchar_t buffer[4096]; + Utf8StringDecoder decoder(m_name.chars()); + decoder.decode(buffer, sizeof(buffer)).release_value(); + + auto font = ui::Font::default_font(); + + m_titlebar_canvas.fill(TITLEBAR_COLOR); + + auto textarea = + m_titlebar_canvas.subcanvas(ui::Rect { 10, 10, m_titlebar_canvas.width - 10, m_titlebar_canvas.height }); + font->render(buffer, ui::WHITE, textarea); + + static SharedPtr g_close_icon; + + if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value(); + + auto close_rect = ui::Rect { m_titlebar_canvas.width - 26, 10, 16, 16 }; + + auto close_area = m_titlebar_canvas.subcanvas(close_rect); + close_area.fill(g_close_icon->pixels(), g_close_icon->width()); + + return {}; + } + Result Window::handle_mouse_leave() { if (!m_main_widget) return ui::EventResult::DidNotHandle; @@ -99,8 +170,19 @@ namespace ui Result Window::handle_mouse_buttons(ui::Point position, int buttons) { - if (!m_main_widget) return ui::EventResult::DidNotHandle; auto result = ui::EventResult::DidNotHandle; + + if (m_decorated && m_titlebar_canvas.rect().contains(position)) + { + // Handle pressing the close button + auto close_rect = ui::Rect { m_titlebar_canvas.width - 26, 10, 16, 16 }; + if (close_rect.contains(position) && (buttons & LEFT)) { close(); } + return ui::EventResult::DidNotHandle; + } + if (m_decorated) position.y -= m_titlebar_canvas.height; + + if (!m_main_widget) return ui::EventResult::DidNotHandle; + if (buttons) { auto rc = TRY(m_main_widget->handle_mouse_down(position, buttons)); diff --git a/wind/IPC.cpp b/wind/IPC.cpp index 7b2222c7..543425bd 100644 --- a/wind/IPC.cpp +++ b/wind/IPC.cpp @@ -55,20 +55,13 @@ static Result handle_create_window_message(Client& client) ui::CreateWindowRequest request; READ_MESSAGE(request); - if (request.type == ui::WindowType::Normal) - { - request.rect.height += - Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for. - request.rect.pos.y -= Window::titlebar_height(); // Adjust it so the contents begin at the expected coordinates. - } - request.rect = request.rect.normalized(); auto name = TRY_OR_IPC_ERROR(String::from_cstring("Window")); auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL))); - auto* window = new (std::nothrow) Window(request.rect, move(name), request.type); + auto* window = new (std::nothrow) Window(request.rect, move(name)); if (!window) { os::IPC::send_error(client.conn, ENOMEM); @@ -81,7 +74,7 @@ static Result handle_create_window_message(Client& client) }); window->pixels = (u32*)TRY_OR_IPC_ERROR( - os::SharedMemory::create(shm_path.view(), window->contents.height * window->contents.width * 4)); + os::SharedMemory::create(shm_path.view(), window->surface.height * window->surface.width * 4)); TRY_OR_IPC_ERROR(client.windows.try_append(window)); int id = static_cast(client.windows.size() - 1); @@ -168,6 +161,28 @@ static Result handle_get_screen_rect_message(Client& client) return {}; } +static Result handle_set_titlebar_rect_message(Client& client) +{ + ui::SetTitlebarRectRequest request; + READ_MESSAGE(request); + + request.titlebar_rect = request.titlebar_rect.normalized(); + + CHECK_WINDOW_ID(request); + + auto* window = client.windows[request.window]; + ui::Rect titlebar_rect = window->surface.absolute(request.titlebar_rect); + + if (!window->surface.contains(titlebar_rect)) + { + os::eprintln("wind: SetTitlebarRect: titlebar rect outside window!"); + return {}; + } + + window->titlebar = request.titlebar_rect; + return {}; +} + namespace wind { Result handle_ipc_message(Client& client, u8 id) @@ -181,6 +196,7 @@ namespace wind case ui::INVALIDATE_ID: return handle_invalidate_message(client); case ui::CLOSE_WINDOW_ID: return handle_close_window_message(client); case ui::GET_SCREEN_RECT_ID: return handle_get_screen_rect_message(client); + case ui::SET_TITLEBAR_RECT_ID: return handle_set_titlebar_rect_message(client); default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL); } } diff --git a/wind/Mouse.cpp b/wind/Mouse.cpp index 070c633c..8da532e2 100644 --- a/wind/Mouse.cpp +++ b/wind/Mouse.cpp @@ -62,30 +62,21 @@ void Mouse::update(const moon::MousePacket& packet) for (Window* window = g_windows.last().value_or(nullptr); window; window = g_windows.previous(window).value_or(nullptr)) { - if (window->surface.absolute(window->close_button).contains(m_position)) + if (window->surface.contains(m_position)) { - ui::WindowCloseRequest request; - request.window = window->id; - auto& client = *window->client; - os::IPC::send_async(client.conn, request); - break; - } - else if (window->surface.absolute(window->titlebar).contains(m_position)) - { - m_dragging_window = window; - m_initial_drag_position = window->surface.relative(m_position); - os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, - window->surface.pos.y, window->surface.width, window->surface.height, - m_initial_drag_position.x, m_initial_drag_position.y); window->focus(); + + if (window->surface.absolute(window->titlebar).contains(m_position)) + { + m_dragging_window = window; + m_initial_drag_position = window->surface.relative(m_position); + os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x, + window->surface.pos.y, window->surface.width, window->surface.height, + m_initial_drag_position.x, m_initial_drag_position.y); + } + break; } - else if (window->surface.absolute(window->contents).contains(m_position)) - { - if (window->type != ui::WindowType::System) window->focus(); - break; // We don't want to continue iterating, otherwise this would take into account windows whose - // titlebar is underneath another window's contents! - } } } @@ -94,14 +85,11 @@ void Mouse::update(const moon::MousePacket& packet) for (Window* window = g_windows.last().value_or(nullptr); window; window = g_windows.previous(window).value_or(nullptr)) { - auto titlebar = window->surface.absolute(window->titlebar); - auto contents = window->surface.absolute(window->contents); - if (titlebar.contains(m_position)) break; - if (contents.contains(m_position)) + if (window->surface.contains(m_position)) { ui::MouseEventRequest request; request.window = window->id; - request.position = contents.relative(m_position); + request.position = window->surface.relative(m_position); request.buttons = packet.buttons; os::IPC::send_async(window->client->conn, request); new_active_window = window; diff --git a/wind/Window.cpp b/wind/Window.cpp index b9b7b769..ce3dd942 100644 --- a/wind/Window.cpp +++ b/wind/Window.cpp @@ -7,33 +7,12 @@ LinkedList g_windows; -static constexpr ui::Color TITLEBAR_COLOR = ui::Color::from_rgb(53, 53, 53); - void Window::draw(ui::Canvas& screen) { dirty = false; auto window = screen.subcanvas(surface); - window.subcanvas(contents).fill(pixels, contents.width); - - wchar_t buffer[4096]; - Utf8StringDecoder decoder(name.chars()); - decoder.decode(buffer, sizeof(buffer)).release_value(); - - auto font = ui::Font::default_font(); - - auto titlebar_canvas = window.subcanvas(titlebar); - titlebar_canvas.fill(TITLEBAR_COLOR); - - auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height }); - font->render(buffer, ui::WHITE, textarea); - - static SharedPtr g_close_icon; - - if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value(); - - auto close_area = window.subcanvas(close_button); - close_area.fill(g_close_icon->pixels(), g_close_icon->width()); + window.fill(pixels, surface.width); } void Window::focus() @@ -43,28 +22,16 @@ void Window::focus() g_windows.append(this); } -Window::Window(ui::Rect r, String&& n, ui::WindowType t) : surface(r), name(move(n)), type(t) +Window::Window(ui::Rect r, String&& n) : surface(r), name(move(n)) { auto font = ui::Font::default_font(); - bool decorated { type == ui::WindowType::Normal }; - if (decorated && surface.width < 36) surface.width = 36; - if (decorated && surface.height < (font->height() + 20)) surface.height = font->height() + 20; - titlebar = decorated ? ui::Rect { 0, 0, surface.width, font->height() + 20 } : ui::Rect { 0, 0, 0, 0 }; - close_button = decorated ? ui::Rect { surface.width - 26, 10, 16, 16 } : ui::Rect { 0, 0, 0, 0 }; - contents = decorated ? ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) } - : ui::Rect { 0, 0, surface.width, surface.height }; + titlebar = ui::Rect { 0, 0, 0, 0 }; g_windows.append(this); } -int Window::titlebar_height() -{ - auto font = ui::Font::default_font(); - return font->height() + 20; -} - Window::~Window() { - usize size = contents.width * contents.height * 4; + usize size = surface.width * surface.height * 4; munmap(pixels, size); } diff --git a/wind/Window.h b/wind/Window.h index c2859047..70d5c26c 100644 --- a/wind/Window.h +++ b/wind/Window.h @@ -12,19 +12,14 @@ struct Window : public LinkedListNode { ui::Rect surface; ui::Rect titlebar; - ui::Rect close_button; - ui::Rect contents; u32* pixels; String name; String shm_path; bool dirty { false }; Client* client; int id; - ui::WindowType type; - static int titlebar_height(); - - Window(ui::Rect, String&&, ui::WindowType); + Window(ui::Rect, String&&); ~Window(); void focus(); diff --git a/wind/main.cpp b/wind/main.cpp index a291db53..78e3dfdf 100644 --- a/wind/main.cpp +++ b/wind/main.cpp @@ -37,10 +37,9 @@ static void debug(const Vector>& clients) for (const auto& window : g_windows) { - os::println("Window of client (fd %d), id %d, %sdecorated, %sdirty (\"%s\") (%d,%d,%d,%d)", - window->client->conn.fd(), window->id, window->type == ui::WindowType::Normal ? "" : "not ", - window->dirty ? "" : "not ", window->name.chars(), window->surface.pos.x, window->surface.pos.y, - window->surface.width, window->surface.height); + os::println("Window of client (fd %d), id %d, %sdirty (\"%s\") (%d,%d,%d,%d)", window->client->conn.fd(), + window->id, window->dirty ? "" : "not ", window->name.chars(), window->surface.pos.x, + window->surface.pos.y, window->surface.width, window->surface.height); } os::println("-- wind: Listing processes --");