wind+libui: Implement client side decorations

This commit is contained in:
apio 2023-12-27 12:56:40 +01:00
parent 5188def9e5
commit f34dd56375
Signed by: apio
GPG Key ID: B8A7D06E42258954
8 changed files with 151 additions and 88 deletions

View File

@ -9,6 +9,7 @@
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/String.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Mouse.h>
@ -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<Color> m_background {};
Option<int> m_old_mouse_buttons;
bool m_decorated { false };
Result<void> draw_titlebar();
};
}

View File

@ -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;
};
}

View File

@ -8,31 +8,68 @@
*/
#include <luna/String.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <ui/App.h>
#include <ui/Font.h>
#include <ui/Image.h>
#include <ui/Window.h>
#include <ui/ipc/Server.h>
static int titlebar_height()
{
auto font = ui::Font::default_font();
return font->height() + 20;
}
namespace ui
{
Result<Window*> Window::create(Rect rect, WindowType type)
{
auto window = TRY(make_owned<Window>());
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<ui::CreateWindowResponse>(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<void> 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<void> 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<ui::Image> 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<ui::EventResult> Window::handle_mouse_leave()
{
if (!m_main_widget) return ui::EventResult::DidNotHandle;
@ -99,8 +170,19 @@ namespace ui
Result<ui::EventResult> 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));

View File

@ -55,20 +55,13 @@ static Result<void> 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<void> 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<int>(client.windows.size() - 1);
@ -168,6 +161,28 @@ static Result<void> handle_get_screen_rect_message(Client& client)
return {};
}
static Result<void> 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<void> 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);
}
}

View File

@ -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;

View File

@ -7,33 +7,12 @@
LinkedList<Window> 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<ui::Image> 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);
}

View File

@ -12,19 +12,14 @@ struct Window : public LinkedListNode<Window>
{
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();

View File

@ -37,10 +37,9 @@ static void debug(const Vector<OwnedPtr<Client>>& 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 --");