Luna/gui/libui/src/Window.cpp
apio 140910763e
All checks were successful
Build and test / build (push) Successful in 1m56s
all: Reorder directory structure
Why are command-line utilities stored in "apps"?
And why are apps like "editor" or "terminal" top-level directories?
Command-line utilities now go in "utils".
GUI stuff now goes in "gui".
This includes: libui -> gui/libui, wind -> gui/wind, GUI apps -> gui/apps, editor&terminal -> gui/apps...
System services go in "system".
2024-07-21 13:24:46 +02:00

237 lines
7.5 KiB
C++

/**
* @file Window.cpp
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#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;
auto response = TRY(App::the().client().send_sync<ui::CreateWindowResponse>(request));
auto path = COPY_IPC_STRING(response.shm_path);
u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4, false));
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::SetTitlebarHeightRequest titlebar_request;
titlebar_request.height = height;
titlebar_request.window = response.window;
App::the().client().send_async(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;
shm_request.window = response.window;
App::the().client().send_async(shm_request);
App::the().register_window(move(window), {});
return p;
}
Window::~Window()
{
if (m_canvas.ptr) munmap(m_canvas.ptr, ((usize)m_canvas.width) * ((usize)m_canvas.height) * 4);
}
void Window::set_title(StringView title)
{
ui::SetWindowTitleRequest request;
request.window = m_id;
SET_IPC_STRING(request.title, title.chars());
App::the().client().send_async(request);
m_name = String::from_string_view(title).release_value();
draw();
}
void Window::update()
{
ui::InvalidateRequest request;
request.window = m_id;
App::the().client().send_async(request);
}
void Window::close()
{
App& app = App::the();
ui::CloseWindowRequest request;
request.window = m_id;
app.client().send_async(request);
if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {});
}
void Window::set_special_attributes(WindowAttributes attributes)
{
ui::SetSpecialWindowAttributesRequest request;
request.window = m_id;
request.attributes = attributes;
App::the().client().send_async(request);
}
Result<void> Window::draw()
{
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;
return m_main_widget->handle_mouse_leave();
}
Result<ui::EventResult> Window::handle_mouse_move(ui::Point position)
{
if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_mouse_move(position);
}
Result<ui::EventResult> Window::handle_mouse_buttons(ui::Point position, int buttons)
{
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));
if (rc == ui::EventResult::DidHandle) result = rc;
}
if (m_old_mouse_buttons.has_value())
{
int old_buttons = m_old_mouse_buttons.value();
int diff = old_buttons & ~buttons;
if (diff)
{
auto rc = TRY(m_main_widget->handle_mouse_up(position, diff));
if (rc == ui::EventResult::DidHandle) result = rc;
}
}
m_old_mouse_buttons = buttons;
return result;
}
Result<ui::EventResult> Window::handle_key_event(const ui::KeyEventRequest& request)
{
if (request.pressed)
{
auto* shortcut = m_shortcuts.try_get_ref({ request.code, request.modifiers });
if (shortcut)
{
shortcut->action({ request.code, request.modifiers });
if (shortcut->intercept) return ui::EventResult::DidHandle;
}
}
if (!m_main_widget) return ui::EventResult::DidNotHandle;
return m_main_widget->handle_key_event(request);
}
Result<void> Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept,
os::Function<ui::Shortcut>&& action)
{
TRY(m_shortcuts.try_set(shortcut, { intercept, move(action) }));
return {};
}
}