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(touch.cpp touch)
|
||||||
luna_app(gclient.cpp gclient)
|
luna_app(gclient.cpp gclient)
|
||||||
target_link_libraries(gclient PUBLIC ui)
|
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 <ui/App.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <ui/Layout.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>
|
|
||||||
|
|
||||||
struct Window
|
struct ColorWidget : public ui::Widget
|
||||||
{
|
{
|
||||||
ui::Canvas canvas;
|
public:
|
||||||
int id;
|
ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second)
|
||||||
|
|
||||||
void set_title(os::LocalClient& client, const char* title)
|
|
||||||
{
|
{
|
||||||
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;
|
m_color = m_second_color;
|
||||||
request.window = id;
|
return ui::EventResult::DidHandle;
|
||||||
os::IPC::send_async(client, request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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;
|
auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 }));
|
||||||
parser.add_description("A graphical user interface client."_sv);
|
app.set_main_window(window);
|
||||||
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 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 }));
|
ui::HorizontalLayout layout;
|
||||||
os::println("Created new window with id %d!", window.id);
|
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->draw();
|
||||||
window.redraw(*client);
|
|
||||||
|
|
||||||
sleep(3);
|
return app.run();
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
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)
|
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(id));
|
||||||
TRY(client.send_typed(message));
|
TRY(client.send_typed(message));
|
||||||
return {};
|
return {};
|
||||||
@ -90,7 +90,7 @@ namespace os
|
|||||||
Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
|
Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
|
||||||
decltype(handle_ipc_client_event) handler = handle_ipc_client_event)
|
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(id));
|
||||||
TRY(client.send_typed(message));
|
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));
|
TRY(handler(client, response_id));
|
||||||
max_other_messages--;
|
max_other_messages--;
|
||||||
|
@ -10,6 +10,12 @@ set(SOURCES
|
|||||||
src/Rect.cpp
|
src/Rect.cpp
|
||||||
src/Font.cpp
|
src/Font.cpp
|
||||||
src/Image.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})
|
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/Buffer.h>
|
||||||
#include <luna/SharedPtr.h>
|
#include <luna/SharedPtr.h>
|
||||||
#include <os/Path.h>
|
#include <os/Path.h>
|
||||||
|
#include <ui/Widget.h>
|
||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
@ -77,4 +78,19 @@ namespace ui
|
|||||||
TGAHeader m_tga_header;
|
TGAHeader m_tga_header;
|
||||||
Buffer m_image_data;
|
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
|
#pragma once
|
||||||
#include <os/IPC.h>
|
#include <os/IPC.h>
|
||||||
|
#include <ui/Point.h>
|
||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
@ -16,13 +17,39 @@ namespace ui
|
|||||||
{
|
{
|
||||||
IPC_ENUM_CLIENT(ui),
|
IPC_ENUM_CLIENT(ui),
|
||||||
CREATE_WINDOW_RESPONSE_ID,
|
CREATE_WINDOW_RESPONSE_ID,
|
||||||
|
WINDOW_CLOSE_REQUEST_ID,
|
||||||
|
MOUSE_EVENT_REQUEST_ID,
|
||||||
|
GET_SCREEN_RECT_RESPONSE_ID
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CreateWindowResponse
|
struct CreateWindowResponse
|
||||||
{
|
{
|
||||||
static constexpr u8 id = CREATE_WINDOW_RESPONSE_ID;
|
static constexpr u8 ID = CREATE_WINDOW_RESPONSE_ID;
|
||||||
|
|
||||||
int window;
|
int window;
|
||||||
IPC_STRING(shm_path);
|
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,
|
CREATE_WINDOW_ID,
|
||||||
SET_WINDOW_TITLE_ID,
|
SET_WINDOW_TITLE_ID,
|
||||||
INVALIDATE_ID,
|
INVALIDATE_ID,
|
||||||
|
CLOSE_WINDOW_ID,
|
||||||
|
GET_SCREEN_RECT_ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CreateWindowRequest
|
struct CreateWindowRequest
|
||||||
{
|
{
|
||||||
using ResponseType = CreateWindowResponse;
|
using ResponseType = CreateWindowResponse;
|
||||||
static constexpr u8 id = CREATE_WINDOW_ID;
|
static constexpr u8 ID = CREATE_WINDOW_ID;
|
||||||
|
|
||||||
ui::Rect rect;
|
ui::Rect rect;
|
||||||
|
bool decorated;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SetWindowTitleRequest
|
struct SetWindowTitleRequest
|
||||||
{
|
{
|
||||||
static constexpr u8 id = SET_WINDOW_TITLE_ID;
|
static constexpr u8 ID = SET_WINDOW_TITLE_ID;
|
||||||
|
|
||||||
int window;
|
int window;
|
||||||
IPC_STRING(title);
|
IPC_STRING(title);
|
||||||
@ -41,8 +44,23 @@ namespace ui
|
|||||||
|
|
||||||
struct InvalidateRequest
|
struct InvalidateRequest
|
||||||
{
|
{
|
||||||
static constexpr u8 id = INVALIDATE_ID;
|
static constexpr u8 ID = INVALIDATE_ID;
|
||||||
|
|
||||||
int window;
|
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 <os/File.h>
|
||||||
|
#include <ui/Alignment.h>
|
||||||
#include <ui/Image.h>
|
#include <ui/Image.h>
|
||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
@ -30,4 +31,38 @@ namespace ui
|
|||||||
|
|
||||||
return image;
|
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 "IPC.h"
|
||||||
|
#include "Screen.h"
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <luna/Alignment.h>
|
#include <luna/Alignment.h>
|
||||||
#include <luna/String.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) \
|
if (rc.error() == EAGAIN) \
|
||||||
{ \
|
{ \
|
||||||
client.rpc_in_progress = true; \
|
client.rpc_in_progress = true; \
|
||||||
client.rpc_id = decltype(request)::id; \
|
client.rpc_id = decltype(request)::ID; \
|
||||||
return {}; \
|
return {}; \
|
||||||
} \
|
} \
|
||||||
else \
|
else \
|
||||||
@ -72,17 +73,32 @@ static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rec
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} 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)
|
static Result<void> handle_create_window_message(Client& client)
|
||||||
{
|
{
|
||||||
ui::CreateWindowRequest request;
|
ui::CreateWindowRequest request;
|
||||||
READ_MESSAGE(request);
|
READ_MESSAGE(request);
|
||||||
|
|
||||||
request.rect = request.rect.normalized();
|
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 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)
|
if (!window)
|
||||||
{
|
{
|
||||||
os::IPC::send_error(client.conn, ENOMEM);
|
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));
|
TRY_OR_IPC_ERROR(client.windows.try_append(window));
|
||||||
int id = static_cast<int>(client.windows.size() - 1);
|
int id = static_cast<int>(client.windows.size() - 1);
|
||||||
|
|
||||||
|
window->client = &client;
|
||||||
|
window->id = id;
|
||||||
|
|
||||||
ui::CreateWindowResponse response;
|
ui::CreateWindowResponse response;
|
||||||
response.window = id;
|
response.window = id;
|
||||||
SET_IPC_STRING(response.shm_path, shm_path.chars());
|
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);
|
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
|
||||||
|
|
||||||
if ((usize)request.window >= client.windows.size())
|
CHECK_WINDOW_ID(request);
|
||||||
{
|
|
||||||
os::eprintln("wind: Window id out of range!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
client.windows[request.window]->name = move(name);
|
client.windows[request.window]->name = move(name);
|
||||||
|
|
||||||
@ -128,17 +143,40 @@ static Result<void> handle_invalidate_message(Client& client)
|
|||||||
ui::InvalidateRequest request;
|
ui::InvalidateRequest request;
|
||||||
READ_MESSAGE(request);
|
READ_MESSAGE(request);
|
||||||
|
|
||||||
if ((usize)request.window >= client.windows.size())
|
CHECK_WINDOW_ID(request);
|
||||||
{
|
|
||||||
os::eprintln("wind: Window id out of range!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
client.windows[request.window]->dirty = true;
|
client.windows[request.window]->dirty = true;
|
||||||
|
|
||||||
return {};
|
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
|
namespace wind
|
||||||
{
|
{
|
||||||
Result<void> handle_ipc_message(Client& client, u8 id)
|
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::CREATE_WINDOW_ID: return handle_create_window_message(client);
|
||||||
case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_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::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);
|
default: os::eprintln("wind: Invalid IPC message from client!"); return err(EINVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#include "Mouse.h"
|
#include "Mouse.h"
|
||||||
|
#include "Client.h"
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <ui/Image.h>
|
#include <ui/Image.h>
|
||||||
|
#include <ui/ipc/Client.h>
|
||||||
|
|
||||||
static SharedPtr<ui::Image> g_mouse_cursor;
|
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))
|
if (window->surface.absolute(window->close_button).contains(m_position))
|
||||||
{
|
{
|
||||||
// Close button pressed
|
ui::WindowCloseRequest request;
|
||||||
g_windows.remove(window);
|
request.window = window->id;
|
||||||
delete window;
|
auto& client = *window->client;
|
||||||
|
os::IPC::send_async(client.conn, request);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (window->surface.absolute(window->titlebar).contains(m_position))
|
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 <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
Result<Screen> Screen::open()
|
Screen Screen::s_the;
|
||||||
|
|
||||||
|
Result<void> Screen::open()
|
||||||
{
|
{
|
||||||
int fd = ::open("/dev/fb0", O_RDWR);
|
int fd = ::open("/dev/fb0", O_RDWR);
|
||||||
if (fd < 0) return err(errno);
|
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_canvas = ui::Canvas::create((u8*)p, width, height);
|
||||||
screen.m_size = width * height * BYTES_PER_PIXEL;
|
screen.m_size = width * height * BYTES_PER_PIXEL;
|
||||||
|
|
||||||
return screen;
|
s_the = screen;
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::sync()
|
void Screen::sync()
|
||||||
|
@ -7,7 +7,7 @@ constexpr int BYTES_PER_PIXEL = 4;
|
|||||||
class Screen
|
class Screen
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static Result<Screen> open();
|
static Result<void> open();
|
||||||
|
|
||||||
ui::Canvas& canvas()
|
ui::Canvas& canvas()
|
||||||
{
|
{
|
||||||
@ -19,9 +19,16 @@ class Screen
|
|||||||
return m_size;
|
return m_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Screen& the()
|
||||||
|
{
|
||||||
|
return s_the;
|
||||||
|
}
|
||||||
|
|
||||||
void sync();
|
void sync();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ui::Canvas m_canvas;
|
ui::Canvas m_canvas;
|
||||||
int m_size;
|
int m_size;
|
||||||
|
|
||||||
|
static Screen s_the;
|
||||||
};
|
};
|
||||||
|
@ -41,14 +41,15 @@ void Window::focus()
|
|||||||
g_windows.append(this);
|
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();
|
auto font = ui::Font::default_font();
|
||||||
if (surface.width < 36) surface.width = 36;
|
if (decorated && surface.width < 36) surface.width = 36;
|
||||||
if (surface.height < (font->height() + 20)) surface.height = font->height() + 20;
|
if (decorated && surface.height < (font->height() + 20)) surface.height = font->height() + 20;
|
||||||
titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 };
|
titlebar = decorated ? ui::Rect { 0, 0, surface.width, font->height() + 20 } : ui::Rect { 0, 0, 0, 0 };
|
||||||
close_button = ui::Rect { surface.width - 26, 10, 16, 16 };
|
close_button = decorated ? ui::Rect { surface.width - 26, 10, 16, 16 } : ui::Rect { 0, 0, 0, 0 };
|
||||||
contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) };
|
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);
|
g_windows.append(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
#include <ui/Color.h>
|
#include <ui/Color.h>
|
||||||
#include <ui/Rect.h>
|
#include <ui/Rect.h>
|
||||||
|
|
||||||
|
struct Client;
|
||||||
|
|
||||||
struct Window : public LinkedListNode<Window>
|
struct Window : public LinkedListNode<Window>
|
||||||
{
|
{
|
||||||
ui::Rect surface;
|
ui::Rect surface;
|
||||||
@ -14,10 +16,13 @@ struct Window : public LinkedListNode<Window>
|
|||||||
u32* pixels;
|
u32* pixels;
|
||||||
String name;
|
String name;
|
||||||
bool dirty { false };
|
bool dirty { false };
|
||||||
|
Client* client;
|
||||||
|
int id;
|
||||||
|
bool decorated;
|
||||||
|
|
||||||
static int titlebar_height();
|
static int titlebar_height();
|
||||||
|
|
||||||
Window(ui::Rect, String&&);
|
Window(ui::Rect, String&&, bool);
|
||||||
~Window();
|
~Window();
|
||||||
|
|
||||||
void focus();
|
void focus();
|
||||||
|
@ -49,7 +49,8 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
keyboard->set_buffer(os::File::NotBuffered);
|
keyboard->set_buffer(os::File::NotBuffered);
|
||||||
keyboard->set_close_on_exec();
|
keyboard->set_close_on_exec();
|
||||||
|
|
||||||
auto screen = TRY(Screen::open());
|
TRY(Screen::open());
|
||||||
|
auto& screen = Screen::the();
|
||||||
|
|
||||||
Mouse mouse_pointer { screen.canvas() };
|
Mouse mouse_pointer { screen.canvas() };
|
||||||
|
|
||||||
@ -127,12 +128,15 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
auto client = clients.remove_at(i);
|
auto client = clients.remove_at(i);
|
||||||
client.conn.disconnect();
|
client.conn.disconnect();
|
||||||
for (auto& window : client.windows)
|
for (auto& window : client.windows)
|
||||||
|
{
|
||||||
|
if (window)
|
||||||
{
|
{
|
||||||
g_windows.remove(window);
|
g_windows.remove(window);
|
||||||
delete window;
|
delete window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (fds[2].revents & POLLIN)
|
if (fds[2].revents & POLLIN)
|
||||||
{
|
{
|
||||||
auto client = TRY(server->accept());
|
auto client = TRY(server->accept());
|
||||||
|
Loading…
Reference in New Issue
Block a user