Compare commits

...

6 Commits

Author SHA1 Message Date
f2c5306f10
wind: Add window titlebars using ui::Font
All checks were successful
continuous-integration/drone/pr Build is passing
2023-08-04 16:08:58 +02:00
42646d3b49
fix 2023-08-04 16:08:04 +02:00
d0b8068a7c
libui: Add PSF font loading and rendering 2023-08-04 16:07:44 +02:00
d78cc52d22
libui: Add Color::GRAY 2023-08-04 16:05:42 +02:00
d9557e5089
libui: Rename Rect::absolute to normalized and add a new absolute function 2023-08-04 16:03:25 +02:00
a5d33fdf44
libluna: Add assignment operators to Buffer 2023-08-04 15:10:33 +02:00
15 changed files with 267 additions and 17 deletions

6
.gitignore vendored
View File

@ -4,7 +4,11 @@ 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/*
.fakeroot
kernel/config.cmake
ports/out/

Binary file not shown.

Binary file not shown.

View File

@ -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);

View File

@ -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);

View File

@ -6,9 +6,11 @@ set(SOURCES
${HEADERS}
src/Canvas.cpp
src/Rect.cpp
src/Font.cpp
)
add_library(ui ${SOURCES})
target_compile_options(ui PRIVATE ${COMMON_FLAGS})
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)

View File

@ -94,6 +94,7 @@ namespace ui
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);

55
libui/include/ui/Font.h Normal file
View File

@ -0,0 +1,55 @@
#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
{
class Font : public Shareable
{
public:
enum FontWeight
{
Regular,
Bold,
};
static Result<SharedPtr<Font>> load(const os::Path& path);
static Result<SharedPtr<Font>> load_builtin(StringView name, FontWeight weight);
static SharedPtr<Font> default_font();
static SharedPtr<Font> default_bold_font();
void render(wchar_t codepoint, ui::Color color, ui::Canvas& canvas);
void render(const wchar_t* text, ui::Color color, ui::Canvas& canvas);
int width() const
{
return m_psf_header.width;
}
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;
};
};

View File

@ -37,11 +37,27 @@ namespace ui
*/
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 absolute();
Rect normalized();
};
}

117
libui/src/Font.cpp Normal file
View File

@ -0,0 +1,117 @@
#include <luna/String.h>
#include <os/File.h>
#include <ui/Font.h>
SharedPtr<ui::Font> s_default_font = {};
SharedPtr<ui::Font> s_default_bold_font = {};
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()
{
if (s_default_font) return 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()
{
if (s_default_bold_font) return 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)
{
check(canvas.width == m_psf_header.width && canvas.height == m_psf_header.height);
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 + 1) * 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 < m_psf_header.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;
}
}
}
}

View File

@ -25,7 +25,19 @@ namespace ui
return point;
}
Rect Rect::absolute()
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 };
}

View File

@ -33,22 +33,29 @@ void Mouse::update(const moon::MousePacket& packet)
{
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.absolute();
m_dragging_window->surface = m_dragging_window->surface.normalized();
}
else if ((packet.buttons & moon::MouseButton::Left) && !m_dragging_window)
{
g_windows.for_each_reversed([this](Window* window) {
if (!this->m_dragging_window && window->surface.contains(this->m_position))
// 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->titlebar).contains(m_position))
{
this->m_dragging_window = window;
this->m_initial_drag_position = ui::Point { this->m_position.x - window->surface.pos.x,
this->m_position.y - window->surface.pos.y };
m_dragging_window = window;
m_initial_drag_position =
ui::Point { m_position.x - window->surface.pos.x, m_position.y - window->surface.pos.y };
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,
this->m_initial_drag_position.x, this->m_initial_drag_position.y);
m_initial_drag_position.x, m_initial_drag_position.y);
window->focus();
}
});
else if (window->surface.absolute(window->contents).contains(m_position))
break; // We don't want to continue iterating, otherwise this would take into account windows whose
// titlebar is underneath another window's contents!
}
}
}

View File

@ -1,10 +1,26 @@
#include "Window.h"
#include <luna/Utf8.h>
#include <os/File.h>
#include <ui/Font.h>
LinkedList<Window> g_windows;
void Window::draw(ui::Canvas& screen)
{
screen.subcanvas(surface).fill(color);
auto window = screen.subcanvas(surface);
window.subcanvas(contents).fill(color);
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);
}
void Window::focus()
@ -14,7 +30,10 @@ void Window::focus()
g_windows.append(this);
}
Window::Window(ui::Rect r, ui::Color c) : surface(r), color(c)
Window::Window(ui::Rect r, ui::Color c, StringView n) : surface(r), color(c), name(n)
{
auto font = ui::Font::default_font();
titlebar = ui::Rect { 0, 0, surface.width, font->height() + 20 };
contents = ui::Rect { 0, font->height() + 20, surface.width, surface.height - (font->height() + 20) };
g_windows.append(this);
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <luna/LinkedList.h>
#include <luna/StringView.h>
#include <ui/Canvas.h>
#include <ui/Color.h>
#include <ui/Rect.h>
@ -7,9 +8,12 @@
struct Window : public LinkedListNode<Window>
{
ui::Rect surface;
ui::Rect titlebar;
ui::Rect contents;
ui::Color color;
StringView name;
Window(ui::Rect, ui::Color);
Window(ui::Rect, ui::Color, StringView);
void focus();

View File

@ -72,9 +72,9 @@ Result<int> luna_main(int argc, char** argv)
ui::Color background = ui::BLACK;
TRY(make<Window>(ui::Rect { 200, 200, 600, 400 }, ui::GREEN));
TRY(make<Window>(ui::Rect { 100, 100, 300, 200 }, ui::RED));
TRY(make<Window>(ui::Rect { 600, 130, 350, 250 }, ui::CYAN));
TRY(make<Window>(ui::Rect { 200, 200, 600, 400 }, ui::GREEN, "Calculator"_sv));
TRY(make<Window>(ui::Rect { 100, 100, 300, 200 }, ui::RED, "Settings"_sv));
TRY(make<Window>(ui::Rect { 600, 130, 350, 250 }, ui::CYAN, "File Manager"_sv));
Vector<SharedPtr<os::LocalServer::Client>> clients;