/** * @file Window.cpp * @author apio (cloudapio.eu) * @brief UI windows. * * @copyright Copyright (c) 2023, the Luna authors. * */ #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; auto response = TRY(App::the().client().send_sync(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 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 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; return m_main_widget->handle_mouse_leave(); } Result Window::handle_mouse_move(ui::Point position) { if (!m_main_widget) return ui::EventResult::DidNotHandle; return m_main_widget->handle_mouse_move(position); } Result 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 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 Window::add_keyboard_shortcut(ui::Shortcut shortcut, bool intercept, os::Function&& action) { TRY(m_shortcuts.try_set(shortcut, { intercept, move(action) })); return {}; } }