Compare commits
6 Commits
653b2074c0
...
f2c5306f10
Author | SHA1 | Date | |
---|---|---|---|
f2c5306f10 | |||
42646d3b49 | |||
d0b8068a7c | |||
d78cc52d22 | |||
d9557e5089 | |||
a5d33fdf44 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -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/
|
||||
|
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.
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
55
libui/include/ui/Font.h
Normal 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;
|
||||
};
|
||||
};
|
@ -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
117
libui/src/Font.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 };
|
||||
}
|
||||
|
@ -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!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user