apio
140910763e
All checks were successful
Build and test / build (push) Successful in 1m56s
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".
237 lines
7.5 KiB
C++
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 {};
|
|
}
|
|
}
|