Compare commits

...

11 Commits

32 changed files with 1303 additions and 110 deletions

View File

@ -49,3 +49,5 @@ luna_app(shmem-test.cpp shmem-test)
luna_app(touch.cpp touch)
luna_app(gclient.cpp gclient)
target_link_libraries(gclient PUBLIC ui)
luna_app(taskbar.cpp taskbar)
target_link_libraries(taskbar PUBLIC ui)

View File

@ -1,94 +1,75 @@
#include <errno.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/LocalClient.h>
#include <sys/mman.h>
#include <ui/Canvas.h>
#include <ui/ipc/Server.h>
#include <unistd.h>
#include <ui/App.h>
#include <ui/Layout.h>
struct Window
struct ColorWidget : public ui::Widget
{
ui::Canvas canvas;
int id;
void set_title(os::LocalClient& client, const char* title)
public:
ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second)
{
ui::SetWindowTitleRequest request;
request.window = id;
SET_IPC_STRING(request.title, title);
os::IPC::send_async(client, request);
}
void redraw(os::LocalClient& client)
Result<ui::EventResult> handle_mouse_move(ui::Point) override
{
ui::InvalidateRequest request;
request.window = id;
os::IPC::send_async(client, request);
m_color = m_second_color;
return ui::EventResult::DidHandle;
}
Result<ui::EventResult> handle_mouse_leave(ui::Point) override
{
m_color = m_first_color;
return ui::EventResult::DidHandle;
}
Result<ui::EventResult> handle_mouse_down(ui::Point, int) override
{
return ui::EventResult::DidNotHandle;
}
Result<ui::EventResult> handle_mouse_up(ui::Point, int) override
{
return ui::EventResult::DidNotHandle;
}
Result<void> draw(ui::Canvas& canvas) override
{
canvas.fill(m_color);
return {};
}
private:
ui::Color m_color;
ui::Color m_first_color;
ui::Color m_second_color;
};
Result<void> handle_ipc_client_event(os::LocalClient&, u8)
{
todo();
}
static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rect)
{
int fd = shm_open(path, O_RDWR, 0600);
shm_unlink(path);
if (fd < 0) return err(errno);
usize size = rect.width * rect.height * 4; // 4 bytes per pixel
void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
shm_unlink(path);
close(fd);
return 0;
}
if (outfd) *outfd = fd;
else
close(fd);
return (u32*)p;
}
Result<Window> create_window(os::LocalClient& client, ui::Rect rect)
{
ui::CreateWindowRequest request;
request.rect = rect;
auto response = TRY(os::IPC::send_sync<ui::CreateWindowResponse>(client, request));
u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect));
return Window { ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels }, response.window };
}
Result<int> luna_main(int argc, char** argv)
{
StringView socket_path = "/tmp/wind.sock";
ui::App app;
TRY(app.init(argc, argv));
os::ArgumentParser parser;
parser.add_description("A graphical user interface client."_sv);
parser.add_system_program_info("gclient"_sv);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.parse(argc, argv);
auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 }));
app.set_main_window(window);
auto client = TRY(os::LocalClient::connect(socket_path, false));
window->set_title("Main Window");
window->set_background(ui::CYAN);
Window window = TRY(create_window(*client, ui::Rect { 200, 200, 400, 300 }));
os::println("Created new window with id %d!", window.id);
ui::HorizontalLayout layout;
window->set_main_widget(layout);
sleep(3);
ColorWidget green(ui::GREEN, ui::WHITE);
layout.add_widget(green);
ColorWidget blue(ui::BLUE, ui::GRAY);
layout.add_widget(blue);
window.set_title(*client, "Example Window");
ui::VerticalLayout sublayout;
layout.add_widget(sublayout);
sleep(3);
ColorWidget red(ui::RED, ui::CYAN);
sublayout.add_widget(red);
ColorWidget white(ui::WHITE, ui::GREEN);
sublayout.add_widget(white);
window.canvas.fill(ui::CYAN);
window.redraw(*client);
window->draw();
sleep(3);
return 0;
return app.run();
}

40
apps/taskbar.cpp Normal file
View File

@ -0,0 +1,40 @@
#include <os/Process.h>
#include <ui/App.h>
#include <ui/Button.h>
#include <ui/Container.h>
#include <ui/Image.h>
#include <ui/Layout.h>
Result<int> luna_main(int argc, char** argv)
{
ui::App app;
TRY(app.init(argc, argv));
ui::Rect screen = app.screen_rect();
ui::Rect bar = ui::Rect { ui::Point { 0, screen.height - 50 }, screen.width, 50 };
auto window = TRY(ui::Window::create(bar, false));
app.set_main_window(window);
window->set_background(ui::GRAY);
ui::HorizontalLayout layout(ui::AdjustHeight::Yes, ui::AdjustWidth::No);
window->set_main_widget(layout);
ui::Button button({ 0, 0, 50, 50 });
layout.add_widget(button);
ui::Container container({ 0, 0, 50, 50 }, ui::VerticalAlignment::Center, ui::HorizontalAlignment::Center);
button.set_widget(container);
button.set_action([] {
StringView args[] = { "/usr/bin/gclient" };
os::Process::spawn("/usr/bin/gclient", Slice<StringView> { args, 1 }, false);
});
auto image = TRY(ui::ImageWidget::load("/usr/share/icons/32x32/start-icon.tga"));
container.set_widget(*image);
window->draw();
return app.run();
}

4
base/etc/user/00-taskbar Normal file
View File

@ -0,0 +1,4 @@
Name=taskbar
Description=Start the taskbar.
Command=/usr/bin/taskbar
Restart=true

View File

@ -54,7 +54,7 @@ namespace os
*/
template <typename Client, typename T> Result<void> send_async(Client& client, const T& message)
{
u8 id = T::id;
u8 id = T::ID;
TRY(client.send_typed(id));
TRY(client.send_typed(message));
return {};
@ -90,7 +90,7 @@ namespace os
Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
decltype(handle_ipc_client_event) handler = handle_ipc_client_event)
{
u8 id = T::id;
u8 id = T::ID;
TRY(client.send_typed(id));
TRY(client.send_typed(message));
@ -115,7 +115,7 @@ namespace os
}
}
if (response_id != ResponseType::id)
if (response_id != ResponseType::ID)
{
TRY(handler(client, response_id));
max_other_messages--;

View File

@ -10,6 +10,12 @@ set(SOURCES
src/Rect.cpp
src/Font.cpp
src/Image.cpp
src/App.cpp
src/Window.cpp
src/Layout.cpp
src/Alignment.cpp
src/Container.cpp
src/Button.cpp
)
add_library(ui ${SOURCES})

View File

@ -0,0 +1,30 @@
/**
* @file Alignment.h
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Rect.h>
namespace ui
{
enum class VerticalAlignment
{
Top,
Center,
Bottom
};
enum class HorizontalAlignment
{
Left,
Center,
Right
};
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign);
}

65
libui/include/ui/App.h Normal file
View File

@ -0,0 +1,65 @@
/**
* @file App.h
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/HashMap.h>
#include <os/LocalClient.h>
#include <ui/Window.h>
namespace ui
{
class App
{
public:
App();
~App();
Result<void> init(int, char**);
Result<int> run();
Rect screen_rect();
os::LocalClient& client()
{
return *m_client;
}
void set_should_close(bool b)
{
m_should_close = b;
}
void set_main_window(Window* window)
{
check(!m_main_window);
m_main_window = window;
}
Window* main_window()
{
return m_main_window;
}
Result<void> register_window(OwnedPtr<Window>&& window, Badge<Window>);
void unregister_window(Window* window, Badge<Window>);
Result<void> handle_ipc_event(u8 id);
static App& the();
private:
static App* s_app;
OwnedPtr<os::LocalClient> m_client;
Window* m_main_window { nullptr };
HashMap<int, OwnedPtr<Window>> m_windows;
bool m_should_close { false };
Window* find_window(int id);
};
}

35
libui/include/ui/Button.h Normal file
View File

@ -0,0 +1,35 @@
/**
* @file Button.h
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Widget.h>
namespace ui
{
class Button : public Widget
{
public:
Button(Rect rect);
void set_widget(Widget& widget);
void set_action(void (*action)(void));
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave(Point position) override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
private:
bool m_hovered { false };
bool m_clicked { false };
Widget* m_child;
void (*m_action)(void);
};
}

View File

@ -0,0 +1,34 @@
/**
* @file Container.h
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <ui/Alignment.h>
#include <ui/Widget.h>
namespace ui
{
class Container : public Widget
{
public:
Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign);
void set_widget(Widget& widget);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave(Point position) override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
private:
Widget* m_widget;
VerticalAlignment m_valign;
HorizontalAlignment m_halign;
};
}

View File

@ -11,6 +11,7 @@
#include <luna/Buffer.h>
#include <luna/SharedPtr.h>
#include <os/Path.h>
#include <ui/Widget.h>
namespace ui
{
@ -77,4 +78,19 @@ namespace ui
TGAHeader m_tga_header;
Buffer m_image_data;
};
class ImageWidget final : public Widget
{
public:
static Result<OwnedPtr<ImageWidget>> load(const os::Path& path);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave(Point position) override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
private:
SharedPtr<Image> m_image;
};
}

69
libui/include/ui/Layout.h Normal file
View File

@ -0,0 +1,69 @@
/**
* @file Layout.h
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Vector.h>
#include <ui/Widget.h>
namespace ui
{
enum class AdjustHeight
{
No,
Yes
};
enum class AdjustWidth
{
No,
Yes
};
class HorizontalLayout final : public Widget
{
public:
HorizontalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave(Point position) override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_width;
};
class VerticalLayout final : public Widget
{
public:
VerticalLayout(AdjustHeight adjust_height = AdjustHeight::Yes, AdjustWidth adjust_width = AdjustWidth::Yes);
Result<EventResult> handle_mouse_move(Point position) override;
Result<EventResult> handle_mouse_leave(Point position) override;
Result<EventResult> handle_mouse_down(Point position, int buttons) override;
Result<EventResult> handle_mouse_up(Point position, int buttons) override;
Result<void> draw(Canvas& canvas) override;
Result<void> add_widget(Widget& widget);
private:
Vector<Widget*> m_widgets;
AdjustHeight m_adjust_height;
AdjustWidth m_adjust_width;
int m_used_height;
};
}

21
libui/include/ui/Mouse.h Normal file
View File

@ -0,0 +1,21 @@
/**
* @file Mouse.h
* @author apio (cloudapio.eu)
* @brief Mouse buttons.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <moon/Mouse.h>
namespace ui
{
enum MouseButtons
{
LEFT = moon::Left,
MIDDLE = moon::Middle,
RIGHT = moon::Right,
};
}

68
libui/include/ui/Widget.h Normal file
View File

@ -0,0 +1,68 @@
/**
* @file Widget.h
* @author apio (cloudapio.eu)
* @brief Abstract widget class.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/Result.h>
#include <ui/Canvas.h>
#include <ui/Point.h>
#include <ui/Rect.h>
namespace ui
{
class Window;
enum class EventResult
{
DidHandle,
DidNotHandle,
};
class Widget
{
public:
virtual Result<EventResult> handle_mouse_move(Point position);
virtual Result<EventResult> handle_mouse_down(Point position, int buttons);
virtual Result<EventResult> handle_mouse_up(Point position, int buttons);
virtual Result<EventResult> handle_mouse_leave(Point position);
virtual Result<void> draw(Canvas& canvas);
void set_window(Window* window, Rect rect, Badge<Window>)
{
m_window = window;
m_rect = rect;
}
void set_parent(Widget* parent)
{
m_parent = parent;
m_window = parent->m_window;
}
Widget* parent()
{
return m_parent;
}
Window* window()
{
return m_window;
}
Rect& rect()
{
return m_rect;
}
protected:
Widget* m_parent { nullptr };
Window* m_window;
Rect m_rect { 0, 0, 50, 50 };
};
}

66
libui/include/ui/Window.h Normal file
View File

@ -0,0 +1,66 @@
/**
* @file Window.h
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#pragma once
#include <luna/OwnedPtr.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Mouse.h>
#include <ui/Rect.h>
#include <ui/Widget.h>
namespace ui
{
class Window
{
public:
static Result<Window*> create(Rect rect, bool decorated = true);
void set_title(StringView title);
void set_background(Color color)
{
m_background = color;
}
void set_main_widget(Widget& widget)
{
check(!m_main_widget);
widget.set_window(this, m_canvas.rect(), {});
m_main_widget = &widget;
}
Canvas& canvas()
{
return m_canvas;
}
void update();
void close();
Result<void> draw();
Result<void> handle_mouse_move(ui::Point position);
Result<void> handle_mouse_buttons(ui::Point position, int buttons);
int id() const
{
return m_id;
}
~Window();
private:
int m_id;
Canvas m_canvas;
Widget* m_main_widget { nullptr };
Color m_background { ui::BLACK };
Option<int> m_old_mouse_buttons;
};
}

View File

@ -9,6 +9,7 @@
#pragma once
#include <os/IPC.h>
#include <ui/Point.h>
namespace ui
{
@ -16,13 +17,39 @@ namespace ui
{
IPC_ENUM_CLIENT(ui),
CREATE_WINDOW_RESPONSE_ID,
WINDOW_CLOSE_REQUEST_ID,
MOUSE_EVENT_REQUEST_ID,
GET_SCREEN_RECT_RESPONSE_ID
};
struct CreateWindowResponse
{
static constexpr u8 id = CREATE_WINDOW_RESPONSE_ID;
static constexpr u8 ID = CREATE_WINDOW_RESPONSE_ID;
int window;
IPC_STRING(shm_path);
};
struct WindowCloseRequest
{
static constexpr u8 ID = WINDOW_CLOSE_REQUEST_ID;
int window;
};
struct MouseEventRequest
{
static constexpr u8 ID = MOUSE_EVENT_REQUEST_ID;
int window;
Point position;
int buttons;
};
struct GetScreenRectResponse
{
static constexpr u8 ID = GET_SCREEN_RECT_RESPONSE_ID;
Rect rect;
};
}

View File

@ -21,19 +21,22 @@ namespace ui
CREATE_WINDOW_ID,
SET_WINDOW_TITLE_ID,
INVALIDATE_ID,
CLOSE_WINDOW_ID,
GET_SCREEN_RECT_ID,
};
struct CreateWindowRequest
{
using ResponseType = CreateWindowResponse;
static constexpr u8 id = CREATE_WINDOW_ID;
static constexpr u8 ID = CREATE_WINDOW_ID;
ui::Rect rect;
bool decorated;
};
struct SetWindowTitleRequest
{
static constexpr u8 id = SET_WINDOW_TITLE_ID;
static constexpr u8 ID = SET_WINDOW_TITLE_ID;
int window;
IPC_STRING(title);
@ -41,8 +44,23 @@ namespace ui
struct InvalidateRequest
{
static constexpr u8 id = INVALIDATE_ID;
static constexpr u8 ID = INVALIDATE_ID;
int window;
};
struct CloseWindowRequest
{
static constexpr u8 ID = CLOSE_WINDOW_ID;
int window;
};
struct GetScreenRectRequest
{
using ResponseType = GetScreenRectResponse;
static constexpr u8 ID = GET_SCREEN_RECT_ID;
int _shadow; // Unused.
};
}

40
libui/src/Alignment.cpp Normal file
View File

@ -0,0 +1,40 @@
/**
* @file Alignment.cpp
* @author apio (cloudapio.eu)
* @brief UI component alignment.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Alignment.h>
namespace ui
{
Rect align(Rect container, Rect contained, VerticalAlignment valign, HorizontalAlignment halign)
{
Rect result;
result.width = contained.width;
result.height = contained.height;
result.pos.y = container.pos.y;
result.pos.x = container.pos.x;
switch (valign)
{
case VerticalAlignment::Top: break;
case VerticalAlignment::Center: result.pos.y += (container.height - contained.height) / 2; break;
case VerticalAlignment::Bottom: result.pos.y += container.height - contained.height; break;
default: break;
}
switch (halign)
{
case HorizontalAlignment::Left: break;
case HorizontalAlignment::Center: result.pos.x += (container.width - contained.width) / 2; break;
case HorizontalAlignment::Right: result.pos.x += container.width - contained.width; break;
default: break;
}
return result;
}
}

115
libui/src/App.cpp Normal file
View File

@ -0,0 +1,115 @@
/**
* @file App.cpp
* @author apio (cloudapio.eu)
* @brief UI application event loop.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <os/ArgumentParser.h>
#include <os/File.h>
#include <os/IPC.h>
#include <ui/App.h>
#include <ui/ipc/Client.h>
#include <ui/ipc/Server.h>
Result<void> handle_ipc_client_event(os::LocalClient&, u8 id)
{
return ui::App::the().handle_ipc_event(id);
}
namespace ui
{
App* App::s_app { nullptr };
App::App()
{
s_app = this;
}
App::~App()
{
s_app = nullptr;
}
Result<void> App::init(int argc, char** argv)
{
StringView socket_path = "/tmp/wind.sock";
os::ArgumentParser parser;
parser.add_description("A UI application."_sv);
parser.add_system_program_info(argv[0]);
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
parser.parse(argc, argv);
m_client = TRY(os::LocalClient::connect(socket_path, true));
return {};
}
Result<int> App::run()
{
check(m_main_window);
while (!m_should_close) { TRY(os::IPC::check_for_messages(*m_client)); }
return 0;
}
App& App::the()
{
check(s_app);
return *s_app;
}
Rect App::screen_rect()
{
ui::GetScreenRectRequest request {};
auto response = os::IPC::send_sync<ui::GetScreenRectResponse>(*m_client, request).release_value();
return response.rect;
}
Result<void> App::register_window(OwnedPtr<Window>&& window, Badge<Window>)
{
int id = window->id();
check(TRY(m_windows.try_set(id, move(window))));
return {};
}
void App::unregister_window(Window* window, Badge<Window>)
{
int id = window->id();
check(m_windows.try_remove(id));
}
Window* App::find_window(int id)
{
auto* window = m_windows.try_get_ref(id);
check(window);
return window->ptr();
}
Result<void> App::handle_ipc_event(u8 id)
{
switch (id)
{
case WINDOW_CLOSE_REQUEST_ID: {
WindowCloseRequest request;
TRY(m_client->recv_typed(request));
os::eprintln("ui: Window close request from server! Shall comply.");
auto* window = find_window(request.window);
window->close();
return {};
}
case MOUSE_EVENT_REQUEST_ID: {
MouseEventRequest request;
TRY(m_client->recv_typed(request));
auto* window = find_window(request.window);
window->handle_mouse_move(request.position);
window->handle_mouse_buttons(request.position, request.buttons);
window->draw();
return {};
}
default: fail("Unexpected IPC request from server!");
}
}
}

68
libui/src/Button.cpp Normal file
View File

@ -0,0 +1,68 @@
/**
* @file Button.cpp
* @author apio (cloudapio.eu)
* @brief A clickable component that triggers an action when pressed.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Button.h>
#include <ui/Mouse.h>
namespace ui
{
Button::Button(Rect rect)
{
m_rect = rect;
}
void Button::set_widget(Widget& widget)
{
widget.rect() = m_rect;
m_child = &widget;
widget.set_parent(this);
}
void Button::set_action(void (*action)(void))
{
m_action = action;
}
Result<EventResult> Button::handle_mouse_move(Point position)
{
m_hovered = true;
return m_child->handle_mouse_move(position);
}
Result<EventResult> Button::handle_mouse_leave(Point position)
{
m_hovered = m_clicked = false;
return m_child->handle_mouse_leave(position);
}
Result<EventResult> Button::handle_mouse_down(Point position, int buttons)
{
auto result = TRY(m_child->handle_mouse_down(position, buttons));
if (result == EventResult::DidNotHandle)
{
if (!m_clicked && (buttons == ui::MouseButtons::LEFT))
{
m_clicked = true;
m_action();
}
}
return EventResult::DidHandle;
}
Result<EventResult> Button::handle_mouse_up(Point position, int buttons)
{
if (buttons & ui::MouseButtons::LEFT) m_clicked = false;
return m_child->handle_mouse_up(position, buttons);
}
Result<void> Button::draw(Canvas& canvas)
{
return m_child->draw(canvas);
}
}

55
libui/src/Container.cpp Normal file
View File

@ -0,0 +1,55 @@
/**
* @file Container.cpp
* @author apio (cloudapio.eu)
* @brief A container widget to pad and align objects inside it.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <ui/Container.h>
namespace ui
{
Container::Container(Rect rect, VerticalAlignment valign, HorizontalAlignment halign)
: m_valign(valign), m_halign(halign)
{
m_rect = rect;
}
void Container::set_widget(Widget& widget)
{
m_widget = &widget;
widget.rect() = ui::align(m_rect, widget.rect(), m_valign, m_halign);
widget.set_parent(this);
}
Result<EventResult> Container::handle_mouse_move(Point position)
{
return m_widget->handle_mouse_move(position);
}
Result<EventResult> Container::handle_mouse_leave(Point position)
{
return m_widget->handle_mouse_leave(position);
}
Result<EventResult> Container::handle_mouse_down(Point position, int buttons)
{
return m_widget->handle_mouse_down(position, buttons);
}
Result<EventResult> Container::handle_mouse_up(Point position, int buttons)
{
return m_widget->handle_mouse_up(position, buttons);
}
Result<void> Container::draw(Canvas& canvas)
{
auto rect = ui::Rect { m_widget->rect().pos.x - m_rect.pos.x, m_widget->rect().pos.y - m_rect.pos.y,
m_widget->rect().width, m_widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
return m_widget->draw(subcanvas);
}
}

View File

@ -8,6 +8,7 @@
*/
#include <os/File.h>
#include <ui/Alignment.h>
#include <ui/Image.h>
namespace ui
@ -30,4 +31,38 @@ namespace ui
return image;
}
Result<OwnedPtr<ImageWidget>> ImageWidget::load(const os::Path& path)
{
auto widget = TRY(make_owned<ImageWidget>());
widget->m_image = TRY(Image::load(path));
widget->m_rect = { 0, 0, widget->m_image->width(), widget->m_image->height() };
return widget;
}
Result<EventResult> ImageWidget::handle_mouse_move(Point)
{
return EventResult::DidNotHandle;
}
Result<EventResult> ImageWidget::handle_mouse_leave(Point)
{
return EventResult::DidNotHandle;
}
Result<EventResult> ImageWidget::handle_mouse_up(Point, int)
{
return EventResult::DidNotHandle;
}
Result<EventResult> ImageWidget::handle_mouse_down(Point, int)
{
return EventResult::DidNotHandle;
}
Result<void> ImageWidget::draw(Canvas& canvas)
{
canvas.subcanvas({ 0, 0, m_image->width(), m_image->height() }).fill(m_image->pixels(), m_image->width());
return {};
}
}

192
libui/src/Layout.cpp Normal file
View File

@ -0,0 +1,192 @@
/**
* @file Layout.cpp
* @author apio (cloudapio.eu)
* @brief Layout widgets to organize content.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <stdlib.h>
#include <ui/Layout.h>
namespace ui
{
HorizontalLayout::HorizontalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> HorizontalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave(position));
}
return result;
}
Result<EventResult> HorizontalLayout::handle_mouse_leave(Point position)
{
for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position));
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> HorizontalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<void> HorizontalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> HorizontalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_width == AdjustWidth::No)
{
widget.rect().pos.x = m_rect.pos.x + m_used_width;
m_used_width += widget.rect().width;
}
else
{
int used_width = 0;
div_t result = div(m_rect.width, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.x = m_rect.pos.x + used_width;
w->rect().width = result.quot;
used_width += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().width += result.rem;
}
widget.rect().pos.y = m_rect.pos.y;
if (m_adjust_height == AdjustHeight::Yes) { widget.rect().height = m_rect.height; }
widget.set_parent(this);
return {};
}
VerticalLayout::VerticalLayout(AdjustHeight adjust_height, AdjustWidth adjust_width)
: m_adjust_height(adjust_height), m_adjust_width(adjust_width)
{
}
Result<EventResult> VerticalLayout::handle_mouse_move(Point position)
{
EventResult result = ui::EventResult::DidNotHandle;
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) result = TRY(widget->handle_mouse_move(position));
else
TRY(widget->handle_mouse_leave(position));
}
return result;
}
Result<EventResult> VerticalLayout::handle_mouse_leave(Point position)
{
for (auto widget : m_widgets) TRY(widget->handle_mouse_leave(position));
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_mouse_up(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_up(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<EventResult> VerticalLayout::handle_mouse_down(Point position, int buttons)
{
for (auto widget : m_widgets)
{
if (widget->rect().contains(position)) return widget->handle_mouse_down(position, buttons);
}
return ui::EventResult::DidNotHandle;
}
Result<void> VerticalLayout::draw(Canvas& canvas)
{
for (auto widget : m_widgets)
{
ui::Rect rect = { m_rect.relative(widget->rect().pos), widget->rect().width, widget->rect().height };
auto subcanvas = canvas.subcanvas(rect);
TRY(widget->draw(subcanvas));
}
return {};
}
Result<void> VerticalLayout::add_widget(Widget& widget)
{
TRY(m_widgets.try_append(&widget));
if (m_adjust_height == AdjustHeight::No)
{
widget.rect().pos.y = m_rect.pos.y + m_used_height;
m_used_height += widget.rect().height;
}
else
{
int used_height = 0;
div_t result = div(m_rect.height, (int)m_widgets.size());
for (auto w : m_widgets)
{
w->rect().pos.y = m_rect.pos.y + used_height;
w->rect().height = result.quot;
used_height += result.quot;
}
m_widgets[m_widgets.size() - 1]->rect().height += result.rem;
}
widget.rect().pos.x = m_rect.pos.x;
if (m_adjust_width == AdjustWidth::Yes) { widget.rect().width = m_rect.width; }
widget.set_parent(this);
return {};
}
}

124
libui/src/Window.cpp Normal file
View File

@ -0,0 +1,124 @@
/**
* @file Window.cpp
* @author apio (cloudapio.eu)
* @brief UI windows.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <ui/App.h>
#include <ui/Window.h>
#include <ui/ipc/Server.h>
#include <unistd.h>
static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rect)
{
int fd = shm_open(path, O_RDWR, 0600);
shm_unlink(path);
if (fd < 0) return err(errno);
usize size = rect.width * rect.height * 4; // 4 bytes per pixel
void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
shm_unlink(path);
close(fd);
return 0;
}
if (outfd) *outfd = fd;
else
close(fd);
return (u32*)p;
}
namespace ui
{
Result<Window*> Window::create(Rect rect, bool decorated)
{
auto window = TRY(make_owned<Window>());
ui::CreateWindowRequest request;
request.rect = rect;
request.decorated = decorated;
auto response = TRY(os::IPC::send_sync<ui::CreateWindowResponse>(App::the().client(), request));
u32* pixels = TRY(create_shm_region(response.shm_path, nullptr, rect));
window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels };
window->m_id = response.window;
Window* p = window.ptr();
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());
os::IPC::send_async(App::the().client(), request);
}
void Window::update()
{
ui::InvalidateRequest request;
request.window = m_id;
os::IPC::send_async(App::the().client(), request);
}
void Window::close()
{
App& app = App::the();
ui::CloseWindowRequest request;
request.window = m_id;
os::IPC::send_async(app.client(), request);
if (this == app.main_window()) app.set_should_close(true);
app.unregister_window(this, {});
}
Result<void> Window::draw()
{
m_canvas.fill(m_background);
if (m_main_widget) return m_main_widget->draw(m_canvas);
update();
return {};
}
Result<void> Window::handle_mouse_move(ui::Point position)
{
if (!m_main_widget) return {};
TRY(m_main_widget->handle_mouse_move(position));
return {};
}
Result<void> Window::handle_mouse_buttons(ui::Point position, int buttons)
{
if (!m_main_widget) return {};
if (buttons) TRY(m_main_widget->handle_mouse_down(position, buttons));
if (m_old_mouse_buttons.has_value())
{
int old_buttons = m_old_mouse_buttons.value();
int diff = old_buttons & ~buttons;
if (diff) TRY(m_main_widget->handle_mouse_up(position, diff));
}
m_old_mouse_buttons = buttons;
return {};
}
}

View File

@ -1,4 +1,5 @@
#include "IPC.h"
#include "Screen.h"
#include <errno.h>
#include <luna/Alignment.h>
#include <luna/String.h>
@ -64,7 +65,7 @@ static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rec
if (rc.error() == EAGAIN) \
{ \
client.rpc_in_progress = true; \
client.rpc_id = decltype(request)::id; \
client.rpc_id = decltype(request)::ID; \
return {}; \
} \
else \
@ -72,17 +73,32 @@ static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rec
} \
} while (0)
#define CHECK_WINDOW_ID(request) \
do { \
if ((usize)request.window >= client.windows.size() || !client.windows[request.window]) \
{ \
os::eprintln("wind: Window id is invalid!"); \
return {}; \
} \
} while (0)
static Result<void> handle_create_window_message(Client& client)
{
ui::CreateWindowRequest request;
READ_MESSAGE(request);
request.rect = request.rect.normalized();
request.rect.height += Window::titlebar_height(); // Make sure we provide the full contents rect that was asked for.
if (request.decorated)
{
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.
}
auto name = TRY(String::from_cstring("Window"));
auto* window = new (std::nothrow) Window(request.rect, move(name));
auto* window = new (std::nothrow) Window(request.rect, move(name), request.decorated);
if (!window)
{
os::IPC::send_error(client.conn, ENOMEM);
@ -96,6 +112,9 @@ static Result<void> handle_create_window_message(Client& client)
TRY_OR_IPC_ERROR(client.windows.try_append(window));
int id = static_cast<int>(client.windows.size() - 1);
window->client = &client;
window->id = id;
ui::CreateWindowResponse response;
response.window = id;
SET_IPC_STRING(response.shm_path, shm_path.chars());
@ -112,11 +131,7 @@ static Result<void> handle_set_window_title_message(Client& client)
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
if ((usize)request.window >= client.windows.size())
{
os::eprintln("wind: Window id out of range!");
return {};
}
CHECK_WINDOW_ID(request);
client.windows[request.window]->name = move(name);
@ -128,17 +143,40 @@ static Result<void> handle_invalidate_message(Client& client)
ui::InvalidateRequest request;
READ_MESSAGE(request);
if ((usize)request.window >= client.windows.size())
{
os::eprintln("wind: Window id out of range!");
return {};
}
CHECK_WINDOW_ID(request);
client.windows[request.window]->dirty = true;
return {};
}
static Result<void> handle_close_window_message(Client& client)
{
ui::CloseWindowRequest request;
READ_MESSAGE(request);
CHECK_WINDOW_ID(request);
auto* window = client.windows[request.window];
client.windows[request.window] = nullptr;
g_windows.remove(window);
delete window;
return {};
}
static Result<void> handle_get_screen_rect_message(Client& client)
{
ui::GetScreenRectRequest request;
READ_MESSAGE(request); // Kinda pointless, but required.
ui::GetScreenRectResponse response;
response.rect = Screen::the().canvas().rect();
os::IPC::send_async(client.conn, response);
return {};
}
namespace wind
{
Result<void> handle_ipc_message(Client& client, u8 id)
@ -149,6 +187,8 @@ namespace wind
case ui::CREATE_WINDOW_ID: return handle_create_window_message(client);
case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client);
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);
default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL);
}
}

View File

@ -1,6 +1,9 @@
#include "Mouse.h"
#include "Client.h"
#include <os/File.h>
#include <os/IPC.h>
#include <ui/Image.h>
#include <ui/ipc/Client.h>
static SharedPtr<ui::Image> g_mouse_cursor;
@ -51,9 +54,10 @@ void Mouse::update(const moon::MousePacket& packet)
{
if (window->surface.absolute(window->close_button).contains(m_position))
{
// Close button pressed
g_windows.remove(window);
delete window;
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))
@ -74,4 +78,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))
{
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))
{
ui::MouseEventRequest request;
request.window = window->id;
request.position = contents.relative(m_position);
request.buttons = packet.buttons;
os::IPC::send_async(window->client->conn, request);
break;
}
}
}

View File

@ -5,7 +5,9 @@
#include <sys/mman.h>
#include <unistd.h>
Result<Screen> Screen::open()
Screen Screen::s_the;
Result<void> Screen::open()
{
int fd = ::open("/dev/fb0", O_RDWR);
if (fd < 0) return err(errno);
@ -23,7 +25,9 @@ Result<Screen> Screen::open()
screen.m_canvas = ui::Canvas::create((u8*)p, width, height);
screen.m_size = width * height * BYTES_PER_PIXEL;
return screen;
s_the = screen;
return {};
}
void Screen::sync()

View File

@ -7,7 +7,7 @@ constexpr int BYTES_PER_PIXEL = 4;
class Screen
{
public:
static Result<Screen> open();
static Result<void> open();
ui::Canvas& canvas()
{
@ -19,9 +19,16 @@ class Screen
return m_size;
}
static Screen& the()
{
return s_the;
}
void sync();
private:
ui::Canvas m_canvas;
int m_size;
static Screen s_the;
};

View File

@ -41,14 +41,15 @@ void Window::focus()
g_windows.append(this);
}
Window::Window(ui::Rect r, String&& n) : surface(r), name(move(n))
Window::Window(ui::Rect r, String&& n, bool d) : surface(r), name(move(n)), decorated(d)
{
auto font = ui::Font::default_font();
if (surface.width < 36) surface.width = 36;
if (surface.height < (font->height() + 20)) surface.height = font->height() + 20;
titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 };
close_button = ui::Rect { surface.width - 26, 10, 16, 16 };
contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) };
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 };
g_windows.append(this);
}

View File

@ -5,6 +5,8 @@
#include <ui/Color.h>
#include <ui/Rect.h>
struct Client;
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
@ -14,10 +16,13 @@ struct Window : public LinkedListNode<Window>
u32* pixels;
String name;
bool dirty { false };
Client* client;
int id;
bool decorated;
static int titlebar_height();
Window(ui::Rect, String&&);
Window(ui::Rect, String&&, bool);
~Window();
void focus();

View File

@ -49,7 +49,8 @@ Result<int> luna_main(int argc, char** argv)
keyboard->set_buffer(os::File::NotBuffered);
keyboard->set_close_on_exec();
auto screen = TRY(Screen::open());
TRY(Screen::open());
auto& screen = Screen::the();
Mouse mouse_pointer { screen.canvas() };
@ -127,12 +128,15 @@ Result<int> luna_main(int argc, char** argv)
auto client = clients.remove_at(i);
client.conn.disconnect();
for (auto& window : client.windows)
{
if (window)
{
g_windows.remove(window);
delete window;
}
}
}
}
if (fds[2].revents & POLLIN)
{
auto client = TRY(server->accept());