Checkpoint 4: harden native paths, authoring workflows and Player lifecycle

This commit is contained in:
Emil
2026-09-18 05:14:50 +03:00
parent 5ac4db438d
commit 45bc352d46
88 changed files with 12606 additions and 504 deletions
+8 -2
View File
@@ -1,3 +1,4 @@
#include <faset/core/io.hpp>
#include <faset/ui/ui.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
@@ -6,6 +7,7 @@
#include <fstream>
#include <hb-ft.h>
#include <hb.h>
#include <limits>
#include <map>
#include <stdexcept>
@@ -41,12 +43,16 @@ struct FontAtlas::Impl {
std::map<std::pair<unsigned, unsigned>, Glyph> glyphs;
unsigned x = 2, y = 2, row_height = 0, size = 0;
explicit Impl(const std::filesystem::path& path) {
std::ifstream in(path, std::ios::binary | std::ios::ate);
// FreeType's Windows path backend uses CreateFileA. Read a native path
// ourselves and retain its bytes until hb_font/FT_Face are destroyed.
std::ifstream in(native_io_path(path), std::ios::binary | std::ios::ate);
if (!in)
throw std::runtime_error("Cannot open UI font: " + path.string());
throw std::runtime_error("Cannot open UI font: " + path_to_utf8(path));
auto length = in.tellg();
if (length <= 0)
throw std::runtime_error("Empty UI font");
if (length > std::numeric_limits<FT_Long>::max())
throw std::runtime_error("UI font exceeds FreeType's supported buffer size");
font_bytes.resize(static_cast<std::size_t>(length));
in.seekg(0);
in.read(reinterpret_cast<char*>(font_bytes.data()), length);
+18 -1
View File
@@ -739,9 +739,26 @@ void Context::apply_layout(const Json& document) {
apply(root(), definition);
}
void Context::layout(float width, float height, float scale) {
if (!std::isfinite(width) || !std::isfinite(height) || !std::isfinite(scale) || scale <= 0)
throw std::invalid_argument("UI dimensions and display scale must be finite and valid");
scale = std::clamp(scale, .5f, 4.f);
if (impl_->scale != scale) {
// Pointer captures use the old drawable coordinate system. Keep edits,
// but cancel a drag rather than committing a jump after a monitor change.
impl_->cancel_capture();
const auto ratio = scale / impl_->scale;
std::function<void(Widget&)> rescale = [&](Widget& widget) {
widget.scroll_y *= ratio;
for (auto& child : widget.children)
rescale(*child);
};
rescale(root());
for (auto& [id, edit] : impl_->edits)
edit.scroll *= ratio;
}
impl_->width = std::max(0.f, width);
impl_->height = std::max(0.f, height);
impl_->scale = std::clamp(scale, .5f, 4.f);
impl_->scale = scale;
std::set<std::string> ids;
std::function<void(Widget&)> check = [&](Widget& w) {
if (w.id.empty() || !ids.insert(w.id).second)