Compare commits
58 Commits
462c48001f
...
945d838166
Author | SHA1 | Date | |
---|---|---|---|
945d838166 | |||
742fb4d8a6 | |||
e115274cc4 | |||
f71cb4ee09 | |||
4c7828f0eb | |||
d78d76ad16 | |||
d0c4264608 | |||
0a39628bb1 | |||
02c72e15d5 | |||
c99c2e4fe3 | |||
57761df341 | |||
60f9c0e5a3 | |||
397ed396d8 | |||
4bcea9b141 | |||
ac5dd05d2e | |||
a2fd838f5d | |||
56269f5187 | |||
2c2c6fbc2d | |||
753a56d14c | |||
b614fa04de | |||
561d737f6c | |||
c0ca99bc7c | |||
e3c210b437 | |||
0f46868cce | |||
b53df986ca | |||
b2c5598ebd | |||
9722e9def8 | |||
4382c3686b | |||
ebd44b72ec | |||
c49b8e48f4 | |||
853d0421a0 | |||
2ed9e1aa93 | |||
16f058f4df | |||
3509c93ef4 | |||
742f5810c2 | |||
02ecdd65eb | |||
8d97a83acf | |||
61636792cd | |||
81a2d0a039 | |||
2533d39bd4 | |||
f371db3049 | |||
1492c5be96 | |||
85c045e16b | |||
c8232b97d7 | |||
7ed2bc83fe | |||
07e27d4d9c | |||
e108d613cd | |||
a8266f72cb | |||
2ea609df93 | |||
25d0a4ad4c | |||
2f28ecd027 | |||
fb5fdfc290 | |||
069525ecff | |||
c49ac8391e | |||
e30acc855a | |||
eba68703b2 | |||
c219dc926a | |||
a1fcb87086 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -4,7 +4,13 @@ build/
|
||||
initrd/boot/moon
|
||||
env-local.sh
|
||||
initrd/bin/**
|
||||
base/usr/**
|
||||
base/usr/bin/**
|
||||
base/usr/include/**
|
||||
base/usr/lib/**
|
||||
base/usr/share/pkgdb/**
|
||||
!base/usr/share/fonts/*
|
||||
base/usr/share/**
|
||||
base/usr/x86_64-luna/**
|
||||
.fakeroot
|
||||
kernel/config.cmake
|
||||
ports/out/
|
||||
|
@ -45,8 +45,10 @@ endif()
|
||||
|
||||
add_subdirectory(libluna)
|
||||
add_subdirectory(libos)
|
||||
add_subdirectory(libui)
|
||||
add_subdirectory(libc)
|
||||
add_subdirectory(kernel)
|
||||
add_subdirectory(apps)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(shell)
|
||||
add_subdirectory(wind)
|
||||
|
@ -47,3 +47,7 @@ luna_app(socket-client.cpp socket-client)
|
||||
luna_app(input.cpp input)
|
||||
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)
|
||||
|
75
apps/gclient.cpp
Normal file
75
apps/gclient.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include <ui/App.h>
|
||||
#include <ui/Layout.h>
|
||||
|
||||
struct ColorWidget : public ui::Widget
|
||||
{
|
||||
public:
|
||||
ColorWidget(ui::Color first, ui::Color second) : m_color(first), m_first_color(first), m_second_color(second)
|
||||
{
|
||||
}
|
||||
|
||||
Result<ui::EventResult> handle_mouse_move(ui::Point) override
|
||||
{
|
||||
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<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
ui::App app;
|
||||
TRY(app.init(argc, argv));
|
||||
|
||||
auto* window = TRY(ui::Window::create(ui::Rect { 200, 200, 400, 300 }));
|
||||
app.set_main_window(window);
|
||||
|
||||
window->set_title("Main Window");
|
||||
window->set_background(ui::CYAN);
|
||||
|
||||
ui::HorizontalLayout layout;
|
||||
window->set_main_widget(layout);
|
||||
|
||||
ColorWidget green(ui::GREEN, ui::WHITE);
|
||||
layout.add_widget(green);
|
||||
ColorWidget blue(ui::BLUE, ui::GRAY);
|
||||
layout.add_widget(blue);
|
||||
|
||||
ui::VerticalLayout sublayout;
|
||||
layout.add_widget(sublayout);
|
||||
|
||||
ColorWidget red(ui::RED, ui::CYAN);
|
||||
sublayout.add_widget(red);
|
||||
ColorWidget white(ui::WHITE, ui::GREEN);
|
||||
sublayout.add_widget(white);
|
||||
|
||||
window->draw();
|
||||
|
||||
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();
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
Name=motd
|
||||
Description=Show the message of the day to the user.
|
||||
Command=/usr/bin/cat /etc/motd
|
||||
Wait=true
|
@ -1,6 +0,0 @@
|
||||
Name=listen
|
||||
Description=Start a Unix domain socket test server.
|
||||
Command=/usr/bin/socket-test
|
||||
StandardOutput=/dev/uart0
|
||||
StandardError=/dev/uart0
|
||||
Restart=true
|
@ -1,4 +1,6 @@
|
||||
Name=login
|
||||
Description=Start the command-line login program.
|
||||
Command=/usr/bin/login
|
||||
Description=Start the display server.
|
||||
Command=/usr/bin/wind --user=selene
|
||||
StandardOutput=/dev/uart0
|
||||
StandardError=/dev/uart0
|
||||
Restart=true
|
||||
|
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
|
3
base/etc/user/01-gclient
Normal file
3
base/etc/user/01-gclient
Normal file
@ -0,0 +1,3 @@
|
||||
Name=gclient
|
||||
Description=Sample user application.
|
||||
Command=/usr/bin/gclient
|
BIN
base/usr/share/cursors/default.tga
Normal file
BIN
base/usr/share/cursors/default.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1004 B |
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Bold.psf
Normal file
Binary file not shown.
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
BIN
base/usr/share/fonts/Tamsyn-Regular.psf
Normal file
Binary file not shown.
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
BIN
base/usr/share/icons/16x16/app-close.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -43,8 +43,8 @@ static void process_mouse_event(u8 data)
|
||||
packet.buttons = 0;
|
||||
|
||||
u8 flags = g_mouse_packet[0];
|
||||
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -packet.xdelta;
|
||||
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -packet.ydelta;
|
||||
if (flags & PS2_MOUSE_X_SIGN) packet.xdelta = -(256 - packet.xdelta);
|
||||
if (flags & PS2_MOUSE_Y_SIGN) packet.ydelta = -(256 - packet.ydelta);
|
||||
|
||||
if (flags & PS2_MOUSE_MIDDLE_BTN) packet.buttons |= moon::MouseButton::Middle;
|
||||
if (flags & PS2_MOUSE_RIGHT_BTN) packet.buttons |= moon::MouseButton::Right;
|
||||
|
@ -65,6 +65,12 @@ class Socket : public VFS::FileInode
|
||||
m_metadata.nlinks--;
|
||||
}
|
||||
|
||||
virtual bool can_accept_connections() const = 0;
|
||||
|
||||
virtual bool can_read_data() const = 0;
|
||||
|
||||
virtual bool peer_disconnected() const = 0;
|
||||
|
||||
virtual ~Socket() = default;
|
||||
|
||||
protected:
|
||||
|
@ -17,6 +17,21 @@ class UnixSocket : public Socket
|
||||
return (m_state == Connected || m_state == Reset) && !m_data.size();
|
||||
}
|
||||
|
||||
bool can_read_data() const override
|
||||
{
|
||||
return (m_state == Connected || m_state == Reset) && m_data.size();
|
||||
}
|
||||
|
||||
bool can_accept_connections() const override
|
||||
{
|
||||
return !m_listen_queue.is_empty();
|
||||
}
|
||||
|
||||
bool peer_disconnected() const override
|
||||
{
|
||||
return m_state == Reset;
|
||||
}
|
||||
|
||||
Result<usize> send(const u8*, usize, int) override;
|
||||
Result<usize> recv(u8*, usize, int) const override;
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "Pledge.h"
|
||||
#include "fs/VFS.h"
|
||||
#include "memory/MemoryManager.h"
|
||||
#include "net/Socket.h"
|
||||
#include "sys/Syscall.h"
|
||||
#include "thread/Scheduler.h"
|
||||
#include <bits/poll.h>
|
||||
@ -45,11 +46,31 @@ Result<u64> sys_poll(Registers*, SyscallArgs args)
|
||||
auto& inode = inodes[i];
|
||||
if (!inode) continue;
|
||||
|
||||
if (kfds[i].events & POLLIN && !inode->will_block_if_read())
|
||||
if (kfds[i].events & POLLIN)
|
||||
{
|
||||
if (inode->type() == VFS::InodeType::Socket)
|
||||
{
|
||||
auto socket = (Socket*)inode.ptr();
|
||||
if (socket->can_read_data() || socket->can_accept_connections())
|
||||
{
|
||||
fds_with_events++;
|
||||
kfds[i].revents |= POLLIN;
|
||||
}
|
||||
if (socket->peer_disconnected())
|
||||
{
|
||||
fds_with_events++;
|
||||
kfds[i].revents |= POLLHUP;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inode->will_block_if_read())
|
||||
{
|
||||
fds_with_events++;
|
||||
kfds[i].revents |= POLLIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fds_with_events && (timeout > 0 || infinite))
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define POLLIN (1 << 0)
|
||||
#define POLLERR (1 << 1)
|
||||
#define POLLNVAL (1 << 2)
|
||||
#define POLLHUP (1 << 3)
|
||||
|
||||
typedef __u64_t nfds_t;
|
||||
|
||||
|
@ -11,6 +11,9 @@ class Buffer
|
||||
Buffer(Buffer&& other);
|
||||
Buffer(const Buffer& other) = delete; // For now.
|
||||
|
||||
Buffer& operator=(Buffer&&);
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
static Result<Buffer> create_sized(usize size);
|
||||
|
||||
Result<void> try_resize(usize new_size);
|
||||
|
@ -16,7 +16,7 @@ template <typename T, usize Size> class CircularQueue
|
||||
{
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
bool is_empty() const
|
||||
{
|
||||
return m_tail.load() == m_head.load();
|
||||
}
|
||||
@ -76,7 +76,7 @@ template <typename T> class DynamicCircularQueue
|
||||
if (m_data) free_impl(m_data);
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
bool is_empty() const
|
||||
{
|
||||
return m_tail.load() == m_head.load();
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ Buffer::Buffer(Buffer&& other) : m_data(other.data()), m_size(other.size())
|
||||
other.m_data = nullptr;
|
||||
}
|
||||
|
||||
Buffer& Buffer::operator=(Buffer&& other)
|
||||
{
|
||||
if (&other == this) return *this;
|
||||
if (m_data) free_impl(m_data);
|
||||
m_data = other.m_data;
|
||||
m_size = other.m_size;
|
||||
other.m_data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
if (m_data) free_impl(m_data);
|
||||
|
@ -14,6 +14,9 @@ set(SOURCES
|
||||
src/Mode.cpp
|
||||
src/Prompt.cpp
|
||||
src/Security.cpp
|
||||
src/LocalServer.cpp
|
||||
src/LocalClient.cpp
|
||||
src/IPC.cpp
|
||||
)
|
||||
|
||||
add_library(os ${SOURCES})
|
||||
|
157
libos/include/os/IPC.h
Normal file
157
libos/include/os/IPC.h
Normal file
@ -0,0 +1,157 @@
|
||||
/**
|
||||
* @file IPC.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Inter-process communication primitives.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <os/LocalClient.h>
|
||||
#include <os/LocalServer.h>
|
||||
|
||||
#define IPC_ENUM_SERVER(name) __##name##_SERVER_ERROR = 0
|
||||
#define IPC_ENUM_CLIENT(name) __##name##_CLIENT_ERROR = 0
|
||||
|
||||
/**
|
||||
* @brief Called to handle IPC events (client-side).
|
||||
*
|
||||
* @param conn The connection object being used.
|
||||
* @param id The ID of the message.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
extern Result<void> handle_ipc_client_event(os::LocalClient& conn, u8 id);
|
||||
|
||||
/**
|
||||
* @brief Called to handle IPC events (server-side).
|
||||
*
|
||||
* @param conn The connection object being used.
|
||||
* @param id The ID of the message.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
extern Result<void> handle_ipc_server_event(os::LocalServer::Client& conn, u8 id);
|
||||
|
||||
namespace os
|
||||
{
|
||||
namespace IPC
|
||||
{
|
||||
static constexpr usize IPC_STRING_LENGTH = 256;
|
||||
|
||||
#define IPC_STRING(name) char name[os::IPC::IPC_STRING_LENGTH];
|
||||
#define COPY_IPC_STRING(name) \
|
||||
TRY(String::from_string_view(StringView::from_fixed_size_cstring(name, os::IPC::IPC_STRING_LENGTH)))
|
||||
#define SET_IPC_STRING(name, value) strlcpy(name, value, os::IPC::IPC_STRING_LENGTH)
|
||||
|
||||
/**
|
||||
* @brief Sends an IPC message without waiting for a reply.
|
||||
*
|
||||
* @tparam Client The type of the client interface being used to communicate.
|
||||
* @tparam T The type of the message.
|
||||
* @param client The connection object being used to communicate.
|
||||
* @param message The IPC message.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename Client, typename T> Result<void> send_async(Client& client, const T& message)
|
||||
{
|
||||
u8 id = T::ID;
|
||||
TRY(client.send_typed(id));
|
||||
TRY(client.send_typed(message));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends an error result to the IPC connection, indicating that an operation could not be performed.
|
||||
*
|
||||
* @tparam Client The type of the client interface being used to communicate.
|
||||
* @param client The connection object being used to communicate.
|
||||
* @param error The error code.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename Client> Result<void> send_error(Client& client, int error)
|
||||
{
|
||||
u8 id = 0;
|
||||
TRY(client.send_typed(id));
|
||||
TRY(client.send_typed(error));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends an IPC message and waits for a reply (client-only).
|
||||
*
|
||||
* @tparam ResponseType The type of the response.
|
||||
* @tparam T The type of the message.
|
||||
* @param client The connection object being used to communicate.
|
||||
* @param message The IPC message.
|
||||
* @param handler The function used to handle messages that do not match the reply.
|
||||
* @return Result<ResponseType> An error, or the response.
|
||||
*/
|
||||
template <typename ResponseType, typename T>
|
||||
Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
|
||||
decltype(handle_ipc_client_event) handler = handle_ipc_client_event)
|
||||
{
|
||||
u8 id = T::ID;
|
||||
TRY(client.send_typed(id));
|
||||
TRY(client.send_typed(message));
|
||||
|
||||
// We allow receiving 5 messages of different types, but if those have passed and we still don't have a
|
||||
// reply, fail with ENOMSG.
|
||||
int max_other_messages = 5;
|
||||
|
||||
while (max_other_messages)
|
||||
{
|
||||
u8 response_id;
|
||||
auto rc = client.recv_typed(response_id);
|
||||
if (rc.has_error() && rc.error() == EAGAIN) continue;
|
||||
|
||||
if (response_id == 0) // Error result
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int code;
|
||||
rc = client.recv_typed(code);
|
||||
if (rc.has_error() && rc.error() == EAGAIN) continue;
|
||||
return err(code);
|
||||
}
|
||||
}
|
||||
|
||||
if (response_id != ResponseType::ID)
|
||||
{
|
||||
TRY(handler(client, response_id));
|
||||
max_other_messages--;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
ResponseType response;
|
||||
rc = client.recv_typed(response);
|
||||
if (rc.has_error() && rc.error() == EAGAIN) continue;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return err(ENOMSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check for new IPC messages on a connection and handle them appropriately.
|
||||
*
|
||||
* @param client The client connection.
|
||||
* @param handler The function used to handle messages.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
Result<void> check_for_messages(os::LocalClient& client,
|
||||
decltype(handle_ipc_client_event) handler = handle_ipc_client_event);
|
||||
|
||||
/**
|
||||
* @brief Check for new IPC messages on a connection and handle them appropriately.
|
||||
*
|
||||
* @param server The server connection.
|
||||
* @param handler The function used to handle messages.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
Result<void> check_for_messages(os::LocalServer::Client& server,
|
||||
decltype(handle_ipc_server_event) handler = handle_ipc_server_event);
|
||||
}
|
||||
}
|
99
libos/include/os/LocalClient.h
Normal file
99
libos/include/os/LocalClient.h
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file LocalClient.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief UNIX local domain client class.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/OwnedPtr.h>
|
||||
#include <luna/StringView.h>
|
||||
|
||||
namespace os
|
||||
{
|
||||
/**
|
||||
* @brief A client used to connect to a local server socket.
|
||||
*/
|
||||
class LocalClient
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new client object and connect it to a local server.
|
||||
*
|
||||
* @param path The path of the server socket to connect to.
|
||||
* @param blocking Whether the client should block if no data is available and recv() is called.
|
||||
* @return Result<OwnedPtr<LocalClient>> An error, or a new client object.
|
||||
*/
|
||||
static Result<OwnedPtr<LocalClient>> connect(StringView path, bool blocking);
|
||||
|
||||
/**
|
||||
* @brief Return the underlying socket file descriptor used by this object.
|
||||
*
|
||||
* @return int The file descriptor.
|
||||
*/
|
||||
int fd() const
|
||||
{
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read arbitrary data from the server. The call will block if there is no data and this object has not
|
||||
* been created as non-blocking.
|
||||
*
|
||||
* @param buf The buffer to read data into.
|
||||
* @param length The maximum amount of bytes to read.
|
||||
* @return Result<usize> An error, or the number of bytes read.
|
||||
*/
|
||||
Result<usize> recv(u8* buf, usize length);
|
||||
|
||||
/**
|
||||
* @brief Read an object from the server. The call will block if there is no data and this object has not been
|
||||
* created as non-blocking.
|
||||
*
|
||||
* @tparam T The type of the object.
|
||||
* @param out A reference to the object to read data into.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename T> Result<void> recv_typed(T& out)
|
||||
{
|
||||
TRY(recv((u8*)&out, sizeof(T)));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send arbitrary data to the server.
|
||||
*
|
||||
* @param buf The buffer to send data from.
|
||||
* @param length The amount of bytes to send.
|
||||
* @return Result<usize> An error, or the number of bytes actually sent.
|
||||
*/
|
||||
Result<usize> send(const u8* buf, usize length);
|
||||
|
||||
/**
|
||||
* @brief Send an object to the server.
|
||||
*
|
||||
* @tparam T The type of the object.
|
||||
* @param out A reference to the object to send data from.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename T> Result<void> send_typed(const T& out)
|
||||
{
|
||||
TRY(send((const u8*)&out, sizeof(T)));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect from the attached server.
|
||||
*
|
||||
* This will make any further reads on this connection return ECONNRESET, and will make this object invalid.
|
||||
*/
|
||||
void disconnect();
|
||||
|
||||
~LocalClient();
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
}
|
142
libos/include/os/LocalServer.h
Normal file
142
libos/include/os/LocalServer.h
Normal file
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @file LocalServer.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief UNIX local domain server class.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/OwnedPtr.h>
|
||||
#include <luna/Result.h>
|
||||
#include <luna/StringView.h>
|
||||
|
||||
namespace os
|
||||
{
|
||||
/**
|
||||
* @brief A local domain server, used to communicate between processes on the same machine.
|
||||
*/
|
||||
class LocalServer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new server object and bind it to a local address.
|
||||
*
|
||||
* @param path The path to use for the server socket.
|
||||
* @param blocking Whether the server should block if no connections are available when calling accept().
|
||||
* @return Result<OwnedPtr<LocalServer>> An error, or a new server object.
|
||||
*/
|
||||
static Result<OwnedPtr<LocalServer>> create(StringView path, bool blocking);
|
||||
|
||||
/**
|
||||
* @brief Activate the server and start listening for connections.
|
||||
*
|
||||
* @param backlog The number of unaccepted connections to keep.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
Result<void> listen(int backlog);
|
||||
|
||||
/**
|
||||
* @brief Return the underlying socket file descriptor used by this object.
|
||||
*
|
||||
* @return int The file descriptor.
|
||||
*/
|
||||
int fd() const
|
||||
{
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An interface to communicate with clients connected to a local server.
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Read arbitrary data from the client. The call will block if there is no data and the parent server
|
||||
* object has not been created as non-blocking.
|
||||
*
|
||||
* @param buf The buffer to read data into.
|
||||
* @param length The maximum amount of bytes to read.
|
||||
* @return Result<usize> An error, or the number of bytes read.
|
||||
*/
|
||||
Result<usize> recv(u8* buf, usize length);
|
||||
|
||||
/**
|
||||
* @brief Read an object from the client. The call will block if there is no data and the parent server
|
||||
* object has not been created as non-blocking.
|
||||
*
|
||||
* @tparam T The type of the object.
|
||||
* @param out A reference to the object to read data into.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename T> Result<void> recv_typed(T& out)
|
||||
{
|
||||
TRY(recv((u8*)&out, sizeof(T)));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send arbitrary data to the client.
|
||||
*
|
||||
* @param buf The buffer to send data from.
|
||||
* @param length The amount of bytes to send.
|
||||
* @return Result<usize> An error, or the number of bytes actually sent.
|
||||
*/
|
||||
Result<usize> send(const u8* buf, usize length);
|
||||
|
||||
/**
|
||||
* @brief Send an object to the client.
|
||||
*
|
||||
* @tparam T The type of the object.
|
||||
* @param out A reference to the object to send data from.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
template <typename T> Result<void> send_typed(const T& out)
|
||||
{
|
||||
TRY(send((const u8*)&out, sizeof(T)));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect from the attached client.
|
||||
*
|
||||
* This will make any further reads on the client return ECONNRESET, and will make this object invalid.
|
||||
*/
|
||||
void disconnect();
|
||||
|
||||
/**
|
||||
* @brief Return the underlying socket file descriptor used by this object.
|
||||
*
|
||||
* @return int The file descriptor.
|
||||
*/
|
||||
int fd() const
|
||||
{
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
Client(Client&& other);
|
||||
Client(int fd);
|
||||
~Client();
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Accept a new incoming connection and return a handle to it. If there are no incoming connections,
|
||||
* accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN
|
||||
* (if the object was created with blocking=false).
|
||||
*
|
||||
* @return Result<Client> An error, or a handle to the new connection.
|
||||
*/
|
||||
Result<Client> accept();
|
||||
|
||||
~LocalServer();
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
bool m_blocking;
|
||||
};
|
||||
}
|
39
libos/src/IPC.cpp
Normal file
39
libos/src/IPC.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file IPC.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Inter-process communication primitives.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <os/IPC.h>
|
||||
|
||||
namespace os::IPC
|
||||
{
|
||||
Result<void> check_for_messages(os::LocalClient& client, decltype(handle_ipc_client_event) handler)
|
||||
{
|
||||
u8 id;
|
||||
auto rc = client.recv_typed(id);
|
||||
if (rc.has_error())
|
||||
{
|
||||
if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block.
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
return handler(client, id);
|
||||
}
|
||||
|
||||
Result<void> check_for_messages(os::LocalServer::Client& client, decltype(handle_ipc_server_event) handler)
|
||||
{
|
||||
u8 id;
|
||||
auto rc = client.recv_typed(id);
|
||||
if (rc.has_error())
|
||||
{
|
||||
if (rc.error() == EAGAIN) return {}; // No messages, and the caller does not want us to block.
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
return handler(client, id);
|
||||
}
|
||||
}
|
68
libos/src/LocalClient.cpp
Normal file
68
libos/src/LocalClient.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file LocalClient.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief UNIX local domain client class.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <os/LocalClient.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace os
|
||||
{
|
||||
Result<OwnedPtr<LocalClient>> LocalClient::connect(StringView path, bool blocking)
|
||||
{
|
||||
auto client = TRY(make_owned<LocalClient>());
|
||||
|
||||
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) return err(errno);
|
||||
|
||||
struct sockaddr_un un;
|
||||
un.sun_family = AF_UNIX;
|
||||
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
|
||||
|
||||
if (::connect(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
|
||||
{
|
||||
close(sockfd);
|
||||
return err(errno);
|
||||
}
|
||||
|
||||
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
|
||||
|
||||
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
client->m_fd = sockfd;
|
||||
return client;
|
||||
}
|
||||
|
||||
LocalClient::~LocalClient()
|
||||
{
|
||||
close(m_fd);
|
||||
}
|
||||
|
||||
Result<usize> LocalClient::recv(u8* buf, usize length)
|
||||
{
|
||||
ssize_t nread = read(m_fd, buf, length);
|
||||
if (nread < 0) return err(errno);
|
||||
return nread;
|
||||
}
|
||||
|
||||
Result<usize> LocalClient::send(const u8* buf, usize length)
|
||||
{
|
||||
ssize_t nwrite = write(m_fd, buf, length);
|
||||
if (nwrite < 0) return err(errno);
|
||||
return nwrite;
|
||||
}
|
||||
|
||||
void LocalClient::disconnect()
|
||||
{
|
||||
close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
101
libos/src/LocalServer.cpp
Normal file
101
libos/src/LocalServer.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file LocalServer.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief UNIX local domain server class.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <os/FileSystem.h>
|
||||
#include <os/LocalServer.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace os
|
||||
{
|
||||
Result<OwnedPtr<LocalServer>> LocalServer::create(StringView path, bool blocking)
|
||||
{
|
||||
auto server = TRY(make_owned<LocalServer>());
|
||||
|
||||
(void)os::FileSystem::remove(path); // We explicitly ignore any error here, either it doesn't exist (which is
|
||||
// fine), or it cannot be removed, which will make bind() fail later.
|
||||
|
||||
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) return err(errno);
|
||||
|
||||
struct sockaddr_un un;
|
||||
un.sun_family = AF_UNIX;
|
||||
strncpy(un.sun_path, path.chars(), sizeof(un.sun_path));
|
||||
|
||||
if (bind(sockfd, (struct sockaddr*)&un, sizeof(un)) < 0)
|
||||
{
|
||||
close(sockfd);
|
||||
return err(errno);
|
||||
}
|
||||
|
||||
if (!blocking) { fcntl(sockfd, F_SETFL, O_NONBLOCK); }
|
||||
server->m_blocking = blocking;
|
||||
|
||||
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
server->m_fd = sockfd;
|
||||
return server;
|
||||
}
|
||||
|
||||
Result<void> LocalServer::listen(int backlog)
|
||||
{
|
||||
if (::listen(m_fd, backlog) < 0) return err(errno);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<LocalServer::Client> LocalServer::accept()
|
||||
{
|
||||
int fd = ::accept(m_fd, nullptr, nullptr);
|
||||
if (fd < 0) return err(errno);
|
||||
if (!m_blocking) fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
return Client { fd };
|
||||
}
|
||||
|
||||
LocalServer::~LocalServer()
|
||||
{
|
||||
close(m_fd);
|
||||
}
|
||||
|
||||
LocalServer::Client::Client(Client&& other) : m_fd(other.m_fd)
|
||||
{
|
||||
other.m_fd = -1;
|
||||
}
|
||||
|
||||
LocalServer::Client::Client(int fd) : m_fd(fd)
|
||||
{
|
||||
}
|
||||
|
||||
LocalServer::Client::~Client()
|
||||
{
|
||||
if (m_fd >= 0) close(m_fd);
|
||||
}
|
||||
|
||||
Result<usize> LocalServer::Client::recv(u8* buf, usize length)
|
||||
{
|
||||
ssize_t nread = read(m_fd, buf, length);
|
||||
if (nread < 0) return err(errno);
|
||||
return nread;
|
||||
}
|
||||
|
||||
Result<usize> LocalServer::Client::send(const u8* buf, usize length)
|
||||
{
|
||||
ssize_t nwrite = write(m_fd, buf, length);
|
||||
if (nwrite < 0) return err(errno);
|
||||
return nwrite;
|
||||
}
|
||||
|
||||
void LocalServer::Client::disconnect()
|
||||
{
|
||||
close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
25
libui/CMakeLists.txt
Normal file
25
libui/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
# The UI and graphics library for Luna.
|
||||
|
||||
file(GLOB HEADERS include/ui/*.h)
|
||||
|
||||
set(SOURCES
|
||||
${HEADERS}
|
||||
include/ui/ipc/Server.h
|
||||
include/ui/ipc/Client.h
|
||||
src/Canvas.cpp
|
||||
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})
|
||||
target_compile_options(ui PRIVATE ${COMMON_FLAGS} -fno-threadsafe-statics)
|
||||
target_include_directories(ui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/)
|
||||
target_include_directories(ui PUBLIC ${LUNA_BASE}/usr/include)
|
||||
target_link_libraries(ui PUBLIC os)
|
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);
|
||||
};
|
||||
}
|
73
libui/include/ui/Canvas.h
Normal file
73
libui/include/ui/Canvas.h
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file Canvas.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Drawable surfaces.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Result.h>
|
||||
#include <luna/Types.h>
|
||||
#include <ui/Color.h>
|
||||
#include <ui/Point.h>
|
||||
#include <ui/Rect.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief A drawable surface.
|
||||
*/
|
||||
struct Canvas
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int stride;
|
||||
u8* ptr;
|
||||
|
||||
/**
|
||||
* @brief Create a new Canvas object.
|
||||
*
|
||||
* @param ptr The memory to use for the canvas. It must be of at least width * height * 4 bytes of length.
|
||||
* @param width The width of the canvas.
|
||||
* @param height The height of the canvas.
|
||||
* @return Canvas The new Canvas object.
|
||||
*/
|
||||
static Canvas create(u8* ptr, int width, int height);
|
||||
|
||||
/**
|
||||
* @brief Return a new Canvas that represents a subsection of the current one.
|
||||
*
|
||||
* @param rect The dimensions of the new canvas. If these exceed the bounds of the current canvas, they will be
|
||||
* clamped.
|
||||
* @return Canvas The new Canvas object.
|
||||
*/
|
||||
Canvas subcanvas(Rect rect);
|
||||
|
||||
/**
|
||||
* @brief Return the dimensions of the current canvas.
|
||||
*
|
||||
* @return Rect This canvas's dimensions, as a Rect object.
|
||||
*/
|
||||
Rect rect()
|
||||
{
|
||||
return Rect { .pos = { 0, 0 }, .width = width, .height = height };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fill the entire canvas with one color.
|
||||
*
|
||||
* @param color The color to use.
|
||||
*/
|
||||
void fill(Color color);
|
||||
|
||||
/**
|
||||
* @brief Fill the canvas with pixels.
|
||||
*
|
||||
* @param pixels The array of pixels (must be at least width*height).
|
||||
* @param stride The number of pixels to skip to go to the next line.
|
||||
*/
|
||||
void fill(u32* pixels, int stride);
|
||||
};
|
||||
};
|
113
libui/include/ui/Color.h
Normal file
113
libui/include/ui/Color.h
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @file Color.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief RGBA colors.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Types.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief A 32-bit ARGB color.
|
||||
*/
|
||||
struct Color
|
||||
{
|
||||
union {
|
||||
u32 raw;
|
||||
u8 colors[4];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return the blue value of this color.
|
||||
*
|
||||
* @return constexpr u8 The blue value.
|
||||
*/
|
||||
constexpr u8 red() const
|
||||
{
|
||||
return colors[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the green value of this color.
|
||||
*
|
||||
* @return constexpr u8 The green value.
|
||||
*/
|
||||
constexpr u8 green() const
|
||||
{
|
||||
return colors[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the blue value of this color.
|
||||
*
|
||||
* @return constexpr u8 The blue value.
|
||||
*/
|
||||
constexpr u8 blue() const
|
||||
{
|
||||
return colors[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the alpha value of this color.
|
||||
*
|
||||
* @return constexpr u8 The alpha value.
|
||||
*/
|
||||
constexpr u8 alpha() const
|
||||
{
|
||||
return colors[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new color from a 32-bit ARGB integer.
|
||||
*
|
||||
* @param raw The integer representing the color.
|
||||
* @return constexpr Color The new color.
|
||||
*/
|
||||
static constexpr Color from_u32(u32 raw)
|
||||
{
|
||||
return Color { .raw = raw };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new color from its separate RGBA values (from 0 to 255).
|
||||
*
|
||||
* @param red The red value.
|
||||
* @param green The green value.
|
||||
* @param blue The blue value.
|
||||
* @param alpha The alpha value.
|
||||
* @return constexpr Color The new color.
|
||||
*/
|
||||
static constexpr Color from_rgba(u8 red, u8 green, u8 blue, u8 alpha)
|
||||
{
|
||||
return Color { .colors = { blue, green, red, alpha } };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new color from its separate RGB values (from 0 to 255).
|
||||
*
|
||||
* @param red The red value.
|
||||
* @param green The green value.
|
||||
* @param blue The blue value.
|
||||
* @return constexpr Color The new color.
|
||||
*/
|
||||
static constexpr Color from_rgb(u8 red, u8 green, u8 blue)
|
||||
{
|
||||
return from_rgba(red, green, blue, 0xff);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Color WHITE = Color::from_rgb(0xff, 0xff, 0xff);
|
||||
static constexpr Color BLACK = Color::from_rgb(0x00, 0x00, 0x00);
|
||||
static constexpr Color GRAY = Color::from_rgb(0x80, 0x80, 0x80);
|
||||
|
||||
static constexpr Color BLUE = Color::from_rgb(0x00, 0x00, 0xff);
|
||||
static constexpr Color GREEN = Color::from_rgb(0x00, 0xff, 0x00);
|
||||
static constexpr Color RED = Color::from_rgb(0xff, 0x00, 0x00);
|
||||
|
||||
static constexpr Color CYAN = Color::from_rgb(0x00, 0xff, 0xff);
|
||||
};
|
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;
|
||||
};
|
||||
}
|
120
libui/include/ui/Font.h
Normal file
120
libui/include/ui/Font.h
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* @file Font.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief PSF font loading and rendering.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Buffer.h>
|
||||
#include <luna/SharedPtr.h>
|
||||
#include <os/Path.h>
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
#define PSF_FONT_MAGIC 0x864ab572
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief A class holding PSF font data, used for direct rendering of glyphs into a canvas.
|
||||
*/
|
||||
class Font : public Shareable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief An enum used to select a font weight when loading a font.
|
||||
*/
|
||||
enum FontWeight
|
||||
{
|
||||
Regular,
|
||||
Bold,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Load a Font object from a font file.
|
||||
*
|
||||
* @param path The full path to the font file.
|
||||
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
|
||||
*/
|
||||
static Result<SharedPtr<Font>> load(const os::Path& path);
|
||||
|
||||
/**
|
||||
* @brief Load a system font by name.
|
||||
*
|
||||
* @param name The name of the font to load (the default system font is "Tamsyn").
|
||||
* @param weight The weight of the font (regular or bold).
|
||||
* @return Result<SharedPtr<Font>> An error, or the loaded Font object.
|
||||
*/
|
||||
static Result<SharedPtr<Font>> load_builtin(StringView name, FontWeight weight);
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the system's default font.
|
||||
*
|
||||
* @return SharedPtr<Font> The default font.
|
||||
*/
|
||||
static SharedPtr<Font> default_font();
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the system's default bold font.
|
||||
*
|
||||
* @return SharedPtr<Font> The default bold font.
|
||||
*/
|
||||
static SharedPtr<Font> default_bold_font();
|
||||
|
||||
/**
|
||||
* @brief Render a single Unicode code point onto a canvas, using this font's glyphs.
|
||||
*
|
||||
* @param codepoint The code point to render.
|
||||
* @param color The color to draw the code point in.
|
||||
* @param canvas The canvas to use.
|
||||
*/
|
||||
void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas);
|
||||
|
||||
/**
|
||||
* @brief Render a Unicode text string onto a canvas, using this font's glyphs.
|
||||
*
|
||||
* @param text The string to render (must be null-terminated).
|
||||
* @param color The color to draw the code point in.
|
||||
* @param canvas The canvas to use.
|
||||
*/
|
||||
void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas);
|
||||
|
||||
/**
|
||||
* @brief Return the width of this font's glyphs.
|
||||
*
|
||||
* @return int The width.
|
||||
*/
|
||||
int width() const
|
||||
{
|
||||
return m_psf_header.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the height of this font's glyphs.
|
||||
*
|
||||
* @return int The height.
|
||||
*/
|
||||
int height() const
|
||||
{
|
||||
return m_psf_header.height;
|
||||
}
|
||||
|
||||
private:
|
||||
struct PSFHeader
|
||||
{
|
||||
u32 magic;
|
||||
u32 version; // zero
|
||||
u32 headersize;
|
||||
u32 flags; // 0 if there's no unicode table
|
||||
u32 numglyph;
|
||||
u32 bytesperglyph;
|
||||
int height;
|
||||
int width;
|
||||
};
|
||||
|
||||
PSFHeader m_psf_header;
|
||||
Buffer m_font_data;
|
||||
};
|
||||
};
|
96
libui/include/ui/Image.h
Normal file
96
libui/include/ui/Image.h
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @file Image.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief TGA image loading and rendering.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Buffer.h>
|
||||
#include <luna/SharedPtr.h>
|
||||
#include <os/Path.h>
|
||||
#include <ui/Widget.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief An image in the TGA file format.
|
||||
*/
|
||||
class Image : public Shareable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Load a new TGA image from a file.
|
||||
*
|
||||
* @param path The path to open.
|
||||
* @return Result<SharedPtr<Image>> An error, or a new Image object.
|
||||
*/
|
||||
static Result<SharedPtr<Image>> load(const os::Path& path);
|
||||
|
||||
/**
|
||||
* @brief Return the array of pixels contained in the image.
|
||||
*
|
||||
* @return u32* The array of pixels.
|
||||
*/
|
||||
u32* pixels()
|
||||
{
|
||||
return (u32*)m_image_data.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the width of the image.
|
||||
*
|
||||
* @return u16 The width.
|
||||
*/
|
||||
u16 width()
|
||||
{
|
||||
return m_tga_header.w;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the height of the image.
|
||||
*
|
||||
* @return u16 The height.
|
||||
*/
|
||||
u16 height()
|
||||
{
|
||||
return m_tga_header.h;
|
||||
}
|
||||
|
||||
private:
|
||||
struct [[gnu::packed]] TGAHeader
|
||||
{
|
||||
u8 idlen;
|
||||
u8 colormap;
|
||||
u8 encoding;
|
||||
u16 cmaporig, cmaplen;
|
||||
u8 cmapent;
|
||||
u16 x;
|
||||
u16 y;
|
||||
u16 w;
|
||||
u16 h;
|
||||
u8 bpp;
|
||||
u8 pixeltype;
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
22
libui/include/ui/Point.h
Normal file
22
libui/include/ui/Point.h
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file Point.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief 2D space points.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief A point in 2D space.
|
||||
*/
|
||||
struct Point
|
||||
{
|
||||
int x { 0 };
|
||||
int y { 0 };
|
||||
};
|
||||
}
|
81
libui/include/ui/Rect.h
Normal file
81
libui/include/ui/Rect.h
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @file Rect.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief A simple 2D rectangle representation.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <ui/Point.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/**
|
||||
* @brief A simple rectangle.
|
||||
*/
|
||||
struct Rect
|
||||
{
|
||||
Point pos;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
/**
|
||||
* @brief Check if a point is contained in this rectangle.
|
||||
*
|
||||
* @param point The point to check.
|
||||
* @return true The point is contained inside the rectangle.
|
||||
* @return false The point is not contained inside the rectangle.
|
||||
*/
|
||||
bool contains(Point point);
|
||||
|
||||
/**
|
||||
* @brief Check if another rectangle is contained in this one.
|
||||
*
|
||||
* @param point The rectangle to check.
|
||||
* @return true The other rectangle is contained inside this one.
|
||||
* @return false The other rectangle is not contained inside this one.
|
||||
*/
|
||||
bool contains(Rect rect);
|
||||
|
||||
/**
|
||||
* @brief Normalize a point to fit inside this rectangle.
|
||||
*
|
||||
* @param point The original point.
|
||||
* @return Point The normalized point.
|
||||
*/
|
||||
Point normalize(Point point);
|
||||
|
||||
/**
|
||||
* @brief Transform an absolute position to a position relative to this rectangle.
|
||||
*
|
||||
* @param pos The original absolute position.
|
||||
* @return Point The position relative to this rectangle.
|
||||
*/
|
||||
Point relative(Point pos);
|
||||
|
||||
/**
|
||||
* @brief Transform a position relative to this rectangle to an absolute position.
|
||||
*
|
||||
* @param pos The original relative position.
|
||||
* @return Point The absolute position.
|
||||
*/
|
||||
Point absolute(Point pos);
|
||||
|
||||
/**
|
||||
* @brief Transform another rectangle relative to this one to an absolute rectangle.
|
||||
*
|
||||
* @param rect The original relative rectangle.
|
||||
* @return Point The absolute rectangle.
|
||||
*/
|
||||
Rect absolute(Rect rect);
|
||||
|
||||
/**
|
||||
* @brief Return a copy of this rectangle with no negative values (normalized to 0).
|
||||
*
|
||||
* @return Rect The new rectangle.
|
||||
*/
|
||||
Rect normalized();
|
||||
};
|
||||
}
|
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;
|
||||
};
|
||||
}
|
55
libui/include/ui/ipc/Client.h
Normal file
55
libui/include/ui/ipc/Client.h
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @file ipc/Client.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief IPC message definitions for UI messages sent to the client.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <os/IPC.h>
|
||||
#include <ui/Point.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
enum ClientMessages : u8
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
66
libui/include/ui/ipc/Server.h
Normal file
66
libui/include/ui/ipc/Server.h
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @file ipc/Server.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief IPC message definitions for UI messages sent to the server.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <os/IPC.h>
|
||||
#include <ui/Color.h>
|
||||
#include <ui/Rect.h>
|
||||
#include <ui/ipc/Client.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
enum ServerMessages : u8
|
||||
{
|
||||
IPC_ENUM_SERVER(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;
|
||||
|
||||
ui::Rect rect;
|
||||
bool decorated;
|
||||
};
|
||||
|
||||
struct SetWindowTitleRequest
|
||||
{
|
||||
static constexpr u8 ID = SET_WINDOW_TITLE_ID;
|
||||
|
||||
int window;
|
||||
IPC_STRING(title);
|
||||
};
|
||||
|
||||
struct InvalidateRequest
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
62
libui/src/Canvas.cpp
Normal file
62
libui/src/Canvas.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file Canvas.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Drawable surfaces.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
Canvas Canvas::create(u8* ptr, int width, int height)
|
||||
{
|
||||
return Canvas { .width = width, .height = height, .stride = width, .ptr = ptr };
|
||||
}
|
||||
|
||||
Canvas Canvas::subcanvas(Rect rect)
|
||||
{
|
||||
if (rect.pos.x < 0) rect.pos.x = 0;
|
||||
if (rect.pos.y < 0) rect.pos.y = 0;
|
||||
if (rect.pos.x + rect.width > width) rect.width = width - rect.pos.x;
|
||||
if (rect.pos.y + rect.height > height) rect.height = height - rect.pos.y;
|
||||
|
||||
u8* p = ptr + rect.pos.x * sizeof(Color) + (rect.pos.y * sizeof(Color) * stride);
|
||||
|
||||
return Canvas { .width = rect.width, .height = rect.height, .stride = stride, .ptr = p };
|
||||
}
|
||||
|
||||
void Canvas::fill(Color color)
|
||||
{
|
||||
u8* p = ptr;
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
u32* colorp = (u32*)p;
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
*colorp = color.raw;
|
||||
colorp++;
|
||||
}
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::fill(u32* pixels, int _stride)
|
||||
{
|
||||
u8* p = ptr;
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
u32* colorp = (u32*)p;
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
u32 pix = pixels[j];
|
||||
if (Color::from_u32(pix).alpha() == 0xff) *colorp = pix;
|
||||
colorp++;
|
||||
}
|
||||
pixels += _stride;
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
|
||||
}
|
121
libui/src/Font.cpp
Normal file
121
libui/src/Font.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @file Font.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief PSF font loading and rendering.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/String.h>
|
||||
#include <os/File.h>
|
||||
#include <ui/Font.h>
|
||||
|
||||
constexpr static int BYTES_PER_PIXEL = (int)sizeof(ui::Color);
|
||||
|
||||
namespace ui
|
||||
{
|
||||
Result<SharedPtr<Font>> Font::load(const os::Path& path)
|
||||
{
|
||||
auto font = TRY(make_shared<Font>());
|
||||
|
||||
auto file = TRY(os::File::open(path, os::File::ReadOnly));
|
||||
|
||||
TRY(file->read_typed(font->m_psf_header));
|
||||
|
||||
if (font->m_psf_header.magic != PSF_FONT_MAGIC)
|
||||
{
|
||||
os::eprintln("ui::Font::load(%s) failed: font magic does not match PSF2 magic", path.name().chars());
|
||||
return err(ENOTSUP);
|
||||
}
|
||||
|
||||
if (font->m_psf_header.version != 0)
|
||||
{
|
||||
os::eprintln("ui::Font::load(%s) failed: font version is unsupported", path.name().chars());
|
||||
return err(ENOTSUP);
|
||||
}
|
||||
|
||||
if (font->m_psf_header.flags)
|
||||
{
|
||||
os::eprintln("ui::Font::load(%s) warning: font has a unicode table, which we're ignoring",
|
||||
path.name().chars());
|
||||
// todo(); // Font has a unicode table, oh no!
|
||||
}
|
||||
|
||||
font->m_font_data = TRY(file->read_all()); // Read the rest of the file into the font data buffer.
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
Result<SharedPtr<Font>> Font::load_builtin(StringView name, FontWeight weight)
|
||||
{
|
||||
auto path = TRY(String::format("/usr/share/fonts/%s-%s.psf"_sv, name.chars(),
|
||||
weight == FontWeight::Bold ? "Bold" : "Regular"));
|
||||
|
||||
return load(path.view());
|
||||
}
|
||||
|
||||
SharedPtr<Font> Font::default_font()
|
||||
{
|
||||
static SharedPtr<ui::Font> s_default_font = {};
|
||||
if (!s_default_font) s_default_font = load("/usr/share/fonts/Tamsyn-Regular.psf").release_value();
|
||||
return s_default_font;
|
||||
}
|
||||
|
||||
SharedPtr<Font> Font::default_bold_font()
|
||||
{
|
||||
static SharedPtr<ui::Font> s_default_bold_font = {};
|
||||
if (!s_default_bold_font) s_default_bold_font = load("/usr/share/fonts/Tamsyn-Bold.psf").release_value();
|
||||
return s_default_bold_font;
|
||||
}
|
||||
|
||||
void Font::render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas)
|
||||
{
|
||||
const wchar_t str[] = { codepoint, 0 };
|
||||
render(str, color, canvas);
|
||||
}
|
||||
|
||||
void Font::render(const wchar_t* text, ui::Color color, ui::Canvas& canvas)
|
||||
{
|
||||
usize len = wcslen(text);
|
||||
|
||||
int height = m_psf_header.height;
|
||||
int width = m_psf_header.width;
|
||||
int last_char_width = width;
|
||||
|
||||
if (canvas.width < (m_psf_header.width * static_cast<int>(len)))
|
||||
{
|
||||
len = (canvas.width / width) + 1;
|
||||
last_char_width = canvas.width % width;
|
||||
}
|
||||
|
||||
if (canvas.height < height) height = canvas.height;
|
||||
|
||||
const int bytes_per_line = (m_psf_header.width + 7) / 8;
|
||||
|
||||
for (usize i = 0; i < len; i++)
|
||||
{
|
||||
if (i + 1 == len) width = last_char_width;
|
||||
wchar_t codepoint = text[i];
|
||||
|
||||
u8* glyph =
|
||||
m_font_data.data() + (codepoint > 0 && codepoint < (wchar_t)m_psf_header.numglyph ? codepoint : 0) *
|
||||
m_psf_header.bytesperglyph;
|
||||
|
||||
u32 offset = (u32)i * m_psf_header.width * BYTES_PER_PIXEL;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
u32 line = offset;
|
||||
int mask = 1 << (m_psf_header.width - 1);
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
if (*((u32*)glyph) & mask) *(u32*)(canvas.ptr + line) = color.raw;
|
||||
mask >>= 1;
|
||||
line += BYTES_PER_PIXEL;
|
||||
}
|
||||
glyph += bytes_per_line;
|
||||
offset += canvas.stride * BYTES_PER_PIXEL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
libui/src/Image.cpp
Normal file
68
libui/src/Image.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file Image.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief TGA image loading and rendering.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <os/File.h>
|
||||
#include <ui/Alignment.h>
|
||||
#include <ui/Image.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
Result<SharedPtr<Image>> Image::load(const os::Path& path)
|
||||
{
|
||||
auto image = TRY(make_shared<Image>());
|
||||
auto file = TRY(os::File::open(path, os::File::ReadOnly));
|
||||
|
||||
TRY(file->read_typed(image->m_tga_header));
|
||||
|
||||
if (image->m_tga_header.encoding != 2) todo();
|
||||
if (image->m_tga_header.bpp != 32) todo();
|
||||
|
||||
Buffer image_id;
|
||||
TRY(file->read(image_id, image->m_tga_header.idlen));
|
||||
|
||||
TRY(file->read(image->m_image_data,
|
||||
image->m_tga_header.w * image->m_tga_header.h * (image->m_tga_header.bpp / 8)));
|
||||
|
||||
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 {};
|
||||
}
|
||||
}
|
62
libui/src/Rect.cpp
Normal file
62
libui/src/Rect.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file Rect.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief A simple 2D rectangle representation.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ui/Rect.h>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
bool Rect::contains(Point point)
|
||||
{
|
||||
return (point.x >= pos.x) && (point.y >= pos.y) && (point.x <= (pos.x + width)) &&
|
||||
(point.y <= (pos.y + height));
|
||||
}
|
||||
|
||||
bool Rect::contains(Rect rect)
|
||||
{
|
||||
if (!contains(rect.pos)) return false;
|
||||
Point rel = relative(rect.pos);
|
||||
if ((rel.x + rect.width) > width) return false;
|
||||
if ((rel.y + rect.height) > height) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Point Rect::normalize(Point point)
|
||||
{
|
||||
if (point.x < pos.x) point.x = pos.x;
|
||||
if (point.y < pos.y) point.y = pos.y;
|
||||
if (point.x > pos.x + width) point.x = pos.x + width;
|
||||
if (point.y > pos.y + height) point.y = pos.y + height;
|
||||
return point;
|
||||
}
|
||||
|
||||
Point Rect::relative(Point point)
|
||||
{
|
||||
point = normalize(point);
|
||||
point.x -= pos.x;
|
||||
point.y -= pos.y;
|
||||
return point;
|
||||
}
|
||||
|
||||
Point Rect::absolute(Point point)
|
||||
{
|
||||
point.x += pos.x;
|
||||
point.y += pos.y;
|
||||
return point;
|
||||
}
|
||||
|
||||
Rect Rect::absolute(Rect rect)
|
||||
{
|
||||
return Rect { absolute(rect.pos), rect.width, rect.height };
|
||||
}
|
||||
|
||||
Rect Rect::normalized()
|
||||
{
|
||||
return Rect { ui::Point { pos.x < 0 ? 0 : pos.x, pos.y < 0 ? 0 : pos.y }, width, height };
|
||||
}
|
||||
};
|
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 {};
|
||||
}
|
||||
}
|
@ -8,10 +8,12 @@ cd $LUNA_ROOT
|
||||
mkdir -p $LUNA_BASE
|
||||
mkdir -p $LUNA_BASE/usr/include
|
||||
mkdir -p $LUNA_BASE/usr/include/luna
|
||||
mkdir -p $LUNA_BASE/usr/include/ui
|
||||
mkdir -p $LUNA_BASE/usr/include/os
|
||||
mkdir -p $LUNA_BASE/usr/include/moon
|
||||
|
||||
cp --preserve=timestamps -RT libc/include/ $LUNA_BASE/usr/include
|
||||
cp --preserve=timestamps -RT libluna/include/luna/ $LUNA_BASE/usr/include/luna
|
||||
cp --preserve=timestamps -RT libui/include/ui/ $LUNA_BASE/usr/include/ui
|
||||
cp --preserve=timestamps -RT libos/include/os/ $LUNA_BASE/usr/include/os
|
||||
cp --preserve=timestamps -RT kernel/src/api/ $LUNA_BASE/usr/include/moon
|
||||
|
@ -4,7 +4,7 @@ source $(dirname $0)/env.sh
|
||||
|
||||
cd $LUNA_ROOT
|
||||
|
||||
FOLDERS=(kernel libc libos libluna apps shell tests)
|
||||
FOLDERS=(kernel libc libos libui libluna apps shell tests)
|
||||
|
||||
SOURCES=($(find ${FOLDERS[@]} -type f -name "*.cpp"))
|
||||
SOURCES+=($(find ${FOLDERS[@]} -type f -name "*.h"))
|
||||
|
19
wind/CMakeLists.txt
Normal file
19
wind/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
Screen.h
|
||||
Screen.cpp
|
||||
Mouse.h
|
||||
Mouse.cpp
|
||||
Window.h
|
||||
Window.cpp
|
||||
IPC.cpp
|
||||
IPC.h
|
||||
Client.h
|
||||
)
|
||||
|
||||
add_executable(wind ${SOURCES})
|
||||
target_compile_options(wind PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings -fno-threadsafe-statics)
|
||||
add_dependencies(wind libc)
|
||||
target_include_directories(wind PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_link_libraries(wind PRIVATE os ui)
|
||||
install(TARGETS wind DESTINATION ${LUNA_BASE}/usr/bin)
|
11
wind/Client.h
Normal file
11
wind/Client.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "Window.h"
|
||||
#include <os/LocalServer.h>
|
||||
|
||||
struct Client
|
||||
{
|
||||
os::LocalServer::Client conn;
|
||||
Vector<Window*> windows;
|
||||
bool rpc_in_progress { false };
|
||||
u8 rpc_id { 0 };
|
||||
};
|
211
wind/IPC.cpp
Normal file
211
wind/IPC.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include "IPC.h"
|
||||
#include "Screen.h"
|
||||
#include <errno.h>
|
||||
#include <luna/Alignment.h>
|
||||
#include <luna/String.h>
|
||||
#include <os/File.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define TRY_OR_IPC_ERROR(expr) \
|
||||
({ \
|
||||
auto _expr_rc = (expr); \
|
||||
if (!_expr_rc.has_value()) \
|
||||
{ \
|
||||
delete window; \
|
||||
os::IPC::send_error(client.conn, _expr_rc.error()); \
|
||||
return {}; \
|
||||
} \
|
||||
_expr_rc.release_value(); \
|
||||
})
|
||||
|
||||
static Result<u32*> create_shm_region(const char* path, int* outfd, ui::Rect rect)
|
||||
{
|
||||
int fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (fd < 0)
|
||||
{
|
||||
os::eprintln("wind: could not create shared memory region: shm_open failed (%s) - %s", path, strerror(errno));
|
||||
return err(errno);
|
||||
}
|
||||
|
||||
usize size = align_up<PAGE_SIZE>(rect.width * rect.height * 4); // 4 bytes per pixel
|
||||
|
||||
if (ftruncate(fd, size) < 0)
|
||||
{
|
||||
os::eprintln("wind: could not create shared memory region: ftruncate failed (%d, %zu) - %s", fd, size,
|
||||
strerror(errno));
|
||||
shm_unlink(path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (p == MAP_FAILED)
|
||||
{
|
||||
os::eprintln("wind: could not create shared memory region: mmap failed (%zu, %d) - %s", size, fd,
|
||||
strerror(errno));
|
||||
shm_unlink(path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (outfd) *outfd = fd;
|
||||
else
|
||||
close(fd);
|
||||
return (u32*)p;
|
||||
}
|
||||
|
||||
#define READ_MESSAGE(request) \
|
||||
do { \
|
||||
auto rc = client.conn.recv_typed(request); \
|
||||
if (rc.has_error()) \
|
||||
{ \
|
||||
if (rc.error() == EAGAIN) \
|
||||
{ \
|
||||
client.rpc_in_progress = true; \
|
||||
client.rpc_id = decltype(request)::ID; \
|
||||
return {}; \
|
||||
} \
|
||||
else \
|
||||
return rc.release_error(); \
|
||||
} \
|
||||
} 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();
|
||||
|
||||
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), request.decorated);
|
||||
if (!window)
|
||||
{
|
||||
os::IPC::send_error(client.conn, ENOMEM);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto shm_path = TRY_OR_IPC_ERROR(String::format("/wind-shm-%d-%lu"_sv, client.conn.fd(), time(NULL)));
|
||||
|
||||
window->pixels = TRY_OR_IPC_ERROR(create_shm_region(shm_path.chars(), nullptr, window->contents));
|
||||
|
||||
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());
|
||||
os::IPC::send_async(client.conn, response);
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_set_window_title_message(Client& client)
|
||||
{
|
||||
ui::SetWindowTitleRequest request;
|
||||
READ_MESSAGE(request);
|
||||
|
||||
auto name = COPY_IPC_STRING(request.title);
|
||||
|
||||
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
|
||||
client.windows[request.window]->name = move(name);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> handle_invalidate_message(Client& client)
|
||||
{
|
||||
ui::InvalidateRequest request;
|
||||
READ_MESSAGE(request);
|
||||
|
||||
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)
|
||||
{
|
||||
client.rpc_in_progress = false;
|
||||
switch (id)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Result<void> handle_ipc(Client& client)
|
||||
{
|
||||
if (client.rpc_in_progress) return handle_ipc_message(client, client.rpc_id);
|
||||
|
||||
u8 id;
|
||||
auto rc = client.conn.recv_typed(id);
|
||||
if (rc.has_error())
|
||||
{
|
||||
if (rc.error() == EAGAIN) { return {}; }
|
||||
else
|
||||
return rc.release_error();
|
||||
}
|
||||
|
||||
return handle_ipc_message(client, id);
|
||||
}
|
||||
}
|
10
wind/IPC.h
Normal file
10
wind/IPC.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "Client.h"
|
||||
#include <ui/ipc/Server.h>
|
||||
|
||||
namespace wind
|
||||
{
|
||||
Result<void> handle_ipc_message(Client& client, u8 id);
|
||||
|
||||
Result<void> handle_ipc(Client& client);
|
||||
}
|
98
wind/Mouse.cpp
Normal file
98
wind/Mouse.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#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;
|
||||
|
||||
Mouse::Mouse(ui::Canvas& screen)
|
||||
{
|
||||
m_position.x = screen.width / 2;
|
||||
m_position.y = screen.height / 2;
|
||||
m_screen_rect = screen.rect();
|
||||
|
||||
g_mouse_cursor = ui::Image::load("/usr/share/cursors/default.tga").value_or({});
|
||||
}
|
||||
|
||||
void Mouse::draw(ui::Canvas& screen)
|
||||
{
|
||||
if (!g_mouse_cursor) return;
|
||||
auto canvas = screen.subcanvas(ui::Rect { m_position, g_mouse_cursor->width(), g_mouse_cursor->height() });
|
||||
canvas.fill(g_mouse_cursor->pixels(), g_mouse_cursor->width());
|
||||
}
|
||||
|
||||
void Mouse::update(const moon::MousePacket& packet)
|
||||
{
|
||||
m_position.x += packet.xdelta;
|
||||
m_position.y -= packet.ydelta;
|
||||
m_position = m_screen_rect.normalize(m_position);
|
||||
|
||||
if (m_dragging_window && !(packet.buttons & moon::MouseButton::Left))
|
||||
{
|
||||
os::println("Stopped drag: window at (%d,%d,%d,%d) with offset (%d,%d)", m_dragging_window->surface.pos.x,
|
||||
m_dragging_window->surface.pos.y, m_dragging_window->surface.width,
|
||||
m_dragging_window->surface.height, this->m_initial_drag_position.x,
|
||||
this->m_initial_drag_position.y);
|
||||
m_dragging_window = nullptr;
|
||||
}
|
||||
|
||||
if (m_dragging_window)
|
||||
{
|
||||
m_dragging_window->surface.pos =
|
||||
ui::Point { m_position.x - m_initial_drag_position.x, m_position.y - m_initial_drag_position.y };
|
||||
m_dragging_window->surface = m_dragging_window->surface.normalized();
|
||||
}
|
||||
|
||||
else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window)
|
||||
{
|
||||
// Iterate from the end of the list, since windows at the beginning are stacked at the bottom and windows at the
|
||||
// top are at the end.
|
||||
for (Window* window = g_windows.last().value_or(nullptr); window;
|
||||
window = g_windows.previous(window).value_or(nullptr))
|
||||
{
|
||||
if (window->surface.absolute(window->close_button).contains(m_position))
|
||||
{
|
||||
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))
|
||||
{
|
||||
m_dragging_window = window;
|
||||
m_initial_drag_position = window->surface.relative(m_position);
|
||||
os::println("Started drag: window at (%d,%d,%d,%d) with offset (%d,%d)", window->surface.pos.x,
|
||||
window->surface.pos.y, window->surface.width, window->surface.height,
|
||||
m_initial_drag_position.x, m_initial_drag_position.y);
|
||||
window->focus();
|
||||
break;
|
||||
}
|
||||
else if (window->surface.absolute(window->contents).contains(m_position))
|
||||
{
|
||||
window->focus();
|
||||
break; // We don't want to continue iterating, otherwise this would take into account windows whose
|
||||
// titlebar is underneath another window's contents!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
22
wind/Mouse.h
Normal file
22
wind/Mouse.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "Screen.h"
|
||||
#include "Window.h"
|
||||
#include <moon/Mouse.h>
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
class Mouse
|
||||
{
|
||||
public:
|
||||
Mouse(ui::Canvas& screen);
|
||||
|
||||
void update(const moon::MousePacket& packet);
|
||||
|
||||
void draw(ui::Canvas& screen);
|
||||
|
||||
private:
|
||||
ui::Point m_position;
|
||||
ui::Rect m_screen_rect;
|
||||
|
||||
Window* m_dragging_window = nullptr;
|
||||
ui::Point m_initial_drag_position;
|
||||
};
|
36
wind/Screen.cpp
Normal file
36
wind/Screen.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "Screen.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
Screen Screen::s_the;
|
||||
|
||||
Result<void> Screen::open()
|
||||
{
|
||||
int fd = ::open("/dev/fb0", O_RDWR);
|
||||
if (fd < 0) return err(errno);
|
||||
|
||||
int width = ioctl(fd, FB_GET_WIDTH);
|
||||
int height = ioctl(fd, FB_GET_HEIGHT);
|
||||
|
||||
void* p = mmap(nullptr, width * height * BYTES_PER_PIXEL, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (p == MAP_FAILED) { return err(errno); }
|
||||
|
||||
Screen screen;
|
||||
|
||||
screen.m_canvas = ui::Canvas::create((u8*)p, width, height);
|
||||
screen.m_size = width * height * BYTES_PER_PIXEL;
|
||||
|
||||
s_the = screen;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Screen::sync()
|
||||
{
|
||||
msync(m_canvas.ptr, size(), MS_SYNC);
|
||||
}
|
34
wind/Screen.h
Normal file
34
wind/Screen.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include <luna/OwnedPtr.h>
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
constexpr int BYTES_PER_PIXEL = 4;
|
||||
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
static Result<void> open();
|
||||
|
||||
ui::Canvas& canvas()
|
||||
{
|
||||
return m_canvas;
|
||||
}
|
||||
|
||||
int size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
static Screen& the()
|
||||
{
|
||||
return s_the;
|
||||
}
|
||||
|
||||
void sync();
|
||||
|
||||
private:
|
||||
ui::Canvas m_canvas;
|
||||
int m_size;
|
||||
|
||||
static Screen s_the;
|
||||
};
|
67
wind/Window.cpp
Normal file
67
wind/Window.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "Window.h"
|
||||
#include <luna/Utf8.h>
|
||||
#include <os/File.h>
|
||||
#include <sys/mman.h>
|
||||
#include <ui/Font.h>
|
||||
#include <ui/Image.h>
|
||||
|
||||
LinkedList<Window> g_windows;
|
||||
|
||||
void Window::draw(ui::Canvas& screen)
|
||||
{
|
||||
dirty = false;
|
||||
|
||||
auto window = screen.subcanvas(surface);
|
||||
window.subcanvas(contents).fill(pixels, contents.width);
|
||||
|
||||
wchar_t buffer[4096];
|
||||
Utf8StringDecoder decoder(name.chars());
|
||||
decoder.decode(buffer, sizeof(buffer)).release_value();
|
||||
|
||||
auto font = ui::Font::default_font();
|
||||
|
||||
auto titlebar_canvas = window.subcanvas(titlebar);
|
||||
titlebar_canvas.fill(ui::GRAY);
|
||||
|
||||
auto textarea = titlebar_canvas.subcanvas(ui::Rect { 10, 10, titlebar_canvas.width - 10, titlebar_canvas.height });
|
||||
font->render(buffer, ui::BLACK, textarea);
|
||||
|
||||
static SharedPtr<ui::Image> g_close_icon;
|
||||
|
||||
if (!g_close_icon) g_close_icon = ui::Image::load("/usr/share/icons/16x16/app-close.tga").release_value();
|
||||
|
||||
auto close_area = window.subcanvas(close_button);
|
||||
close_area.fill(g_close_icon->pixels(), g_close_icon->width());
|
||||
}
|
||||
|
||||
void Window::focus()
|
||||
{
|
||||
// Bring the window to the front of the list.
|
||||
g_windows.remove(this);
|
||||
g_windows.append(this);
|
||||
}
|
||||
|
||||
Window::Window(ui::Rect r, String&& n, bool d) : surface(r), name(move(n)), decorated(d)
|
||||
{
|
||||
auto font = ui::Font::default_font();
|
||||
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);
|
||||
}
|
||||
|
||||
int Window::titlebar_height()
|
||||
{
|
||||
auto font = ui::Font::default_font();
|
||||
return font->height() + 20;
|
||||
}
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
usize size = contents.width * contents.height * 4;
|
||||
|
||||
munmap(pixels, size);
|
||||
}
|
33
wind/Window.h
Normal file
33
wind/Window.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <luna/LinkedList.h>
|
||||
#include <luna/String.h>
|
||||
#include <ui/Canvas.h>
|
||||
#include <ui/Color.h>
|
||||
#include <ui/Rect.h>
|
||||
|
||||
struct Client;
|
||||
|
||||
struct Window : public LinkedListNode<Window>
|
||||
{
|
||||
ui::Rect surface;
|
||||
ui::Rect titlebar;
|
||||
ui::Rect close_button;
|
||||
ui::Rect contents;
|
||||
u32* pixels;
|
||||
String name;
|
||||
bool dirty { false };
|
||||
Client* client;
|
||||
int id;
|
||||
bool decorated;
|
||||
|
||||
static int titlebar_height();
|
||||
|
||||
Window(ui::Rect, String&&, bool);
|
||||
~Window();
|
||||
|
||||
void focus();
|
||||
|
||||
void draw(ui::Canvas& screen);
|
||||
};
|
||||
|
||||
extern LinkedList<Window> g_windows;
|
149
wind/main.cpp
Normal file
149
wind/main.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#include "Client.h"
|
||||
#include "IPC.h"
|
||||
#include "Mouse.h"
|
||||
#include "Screen.h"
|
||||
#include "Window.h"
|
||||
#include <errno.h>
|
||||
#include <moon/Keyboard.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/File.h>
|
||||
#include <os/LocalServer.h>
|
||||
#include <os/Process.h>
|
||||
#include <os/Security.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
srand((unsigned)time(NULL));
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath unix proc exec tty signal id", NULL));
|
||||
|
||||
StringView socket_path = "/tmp/wind.sock";
|
||||
StringView user;
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("The display server for Luna's graphical user interface."_sv);
|
||||
parser.add_system_program_info("wind"_sv);
|
||||
parser.add_value_argument(socket_path, 's', "socket"_sv, "the path for the local IPC socket"_sv);
|
||||
parser.add_value_argument(user, 'u', "user"_sv, "the user to run as"_sv);
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (geteuid() != 0)
|
||||
{
|
||||
os::eprintln("error: wind must be run as root to initialize resources, run with --user=<USERNAME> to drop "
|
||||
"privileges afterwards");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto mouse = TRY(os::File::open("/dev/mouse", os::File::ReadOnly));
|
||||
mouse->set_buffer(os::File::NotBuffered);
|
||||
mouse->set_close_on_exec();
|
||||
|
||||
auto keyboard = TRY(os::File::open("/dev/kbd", os::File::ReadOnly));
|
||||
keyboard->set_buffer(os::File::NotBuffered);
|
||||
keyboard->set_close_on_exec();
|
||||
|
||||
TRY(Screen::open());
|
||||
auto& screen = Screen::the();
|
||||
|
||||
Mouse mouse_pointer { screen.canvas() };
|
||||
|
||||
ioctl(STDIN_FILENO, TTYSETGFX, 1);
|
||||
|
||||
setpgid(0, 0);
|
||||
|
||||
int fd = open("/dev/null", O_RDONLY);
|
||||
if (fd >= 0)
|
||||
{
|
||||
dup2(fd, STDIN_FILENO);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
clearenv();
|
||||
|
||||
if (!user.is_empty())
|
||||
{
|
||||
auto* pwd = getpwnam(user.chars());
|
||||
if (pwd)
|
||||
{
|
||||
setgid(pwd->pw_gid);
|
||||
setuid(pwd->pw_uid);
|
||||
}
|
||||
}
|
||||
|
||||
auto server = TRY(os::LocalServer::create(socket_path, false));
|
||||
TRY(server->listen(20));
|
||||
|
||||
StringView args[] = { "/usr/bin/init"_sv, "--user"_sv };
|
||||
TRY(os::Process::spawn("/usr/bin/init"_sv, Slice<StringView> { args, 2 }, false));
|
||||
|
||||
ui::Color background = ui::BLACK;
|
||||
|
||||
Vector<Client> clients;
|
||||
Vector<struct pollfd> fds;
|
||||
TRY(fds.try_append({ .fd = mouse->fd(), .events = POLLIN, .revents = 0 }));
|
||||
TRY(fds.try_append({ .fd = keyboard->fd(), .events = POLLIN, .revents = 0 }));
|
||||
TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 }));
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath unix signal proc", NULL));
|
||||
|
||||
while (1)
|
||||
{
|
||||
screen.canvas().fill(background);
|
||||
for (auto* window : g_windows) window->draw(screen.canvas());
|
||||
mouse_pointer.draw(screen.canvas());
|
||||
screen.sync();
|
||||
|
||||
for (auto& pfd : fds) { pfd.revents = 0; }
|
||||
|
||||
int rc = poll(fds.data(), fds.size(), 1000);
|
||||
if (!rc) continue;
|
||||
if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); }
|
||||
|
||||
if (fds[0].revents & POLLIN)
|
||||
{
|
||||
moon::MousePacket packet;
|
||||
TRY(mouse->read_typed(packet));
|
||||
mouse_pointer.update(packet);
|
||||
}
|
||||
if (fds[1].revents & POLLIN)
|
||||
{
|
||||
moon::KeyboardPacket packet;
|
||||
TRY(keyboard->read_typed(packet));
|
||||
os::println("%s key %d", packet.released ? "Released" : "Pressed", packet.key);
|
||||
}
|
||||
for (usize i = 0; i < clients.size(); i++)
|
||||
{
|
||||
if (fds[i + 3].revents & POLLIN) wind::handle_ipc(clients[i]);
|
||||
if (fds[i + 3].revents & POLLHUP)
|
||||
{
|
||||
os::println("wind: Client %d disconnected", i);
|
||||
fds.remove_at(i + 3);
|
||||
auto client = clients.remove_at(i);
|
||||
client.conn.disconnect();
|
||||
for (auto& window : client.windows)
|
||||
{
|
||||
if (window)
|
||||
{
|
||||
g_windows.remove(window);
|
||||
delete window;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fds[2].revents & POLLIN)
|
||||
{
|
||||
auto client = TRY(server->accept());
|
||||
os::println("wind: New client connected!");
|
||||
TRY(fds.try_append({ .fd = client.fd(), .events = POLLIN, .revents = 0 }));
|
||||
Client c { move(client), {} };
|
||||
TRY(clients.try_append(move(c)));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user