Compare commits
11 Commits
60f9c0e5a3
...
945d838166
Author | SHA1 | Date | |
---|---|---|---|
945d838166 | |||
742fb4d8a6 | |||
e115274cc4 | |||
f71cb4ee09 | |||
4c7828f0eb | |||
d78d76ad16 | |||
d0c4264608 | |||
0a39628bb1 | |||
02c72e15d5 | |||
c99c2e4fe3 | |||
57761df341 |
@ -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)
|
||||
|
129
apps/gclient.cpp
129
apps/gclient.cpp
@ -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
40
apps/taskbar.cpp
Normal 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
4
base/etc/user/00-taskbar
Normal file
@ -0,0 +1,4 @@
|
||||
Name=taskbar
|
||||
Description=Start the taskbar.
|
||||
Command=/usr/bin/taskbar
|
||||
Restart=true
|
@ -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--;
|
||||
|
@ -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})
|
||||
|
30
libui/include/ui/Alignment.h
Normal file
30
libui/include/ui/Alignment.h
Normal 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
65
libui/include/ui/App.h
Normal 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
35
libui/include/ui/Button.h
Normal 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);
|
||||
};
|
||||
}
|
34
libui/include/ui/Container.h
Normal file
34
libui/include/ui/Container.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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
69
libui/include/ui/Layout.h
Normal 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
21
libui/include/ui/Mouse.h
Normal 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
68
libui/include/ui/Widget.h
Normal 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
66
libui/include/ui/Window.h
Normal 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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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
40
libui/src/Alignment.cpp
Normal 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
115
libui/src/App.cpp
Normal 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
68
libui/src/Button.cpp
Normal 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
55
libui/src/Container.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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
192
libui/src/Layout.cpp
Normal 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
124
libui/src/Window.cpp
Normal 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 {};
|
||||
}
|
||||
}
|
66
wind/IPC.cpp
66
wind/IPC.cpp
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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() };
|
||||
|
||||
@ -128,8 +129,11 @@ Result<int> luna_main(int argc, char** argv)
|
||||
client.conn.disconnect();
|
||||
for (auto& window : client.windows)
|
||||
{
|
||||
g_windows.remove(window);
|
||||
delete window;
|
||||
if (window)
|
||||
{
|
||||
g_windows.remove(window);
|
||||
delete window;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user