wind+libui: Implement client side decorations
This commit is contained in:
parent
5188def9e5
commit
f34dd56375
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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));
|
||||
|
34
wind/IPC.cpp
34
wind/IPC.cpp
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -62,29 +62,20 @@ 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))
|
||||
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);
|
||||
window->focus();
|
||||
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!
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 --");
|
||||
|
Loading…
Reference in New Issue
Block a user