diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1dbb224..54f4965 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,13 +19,13 @@ jobs:
optimize: [ReleaseSmall, Debug]
steps:
- uses: actions/checkout@v4
- - uses: goto-bus-stop/setup-zig@v2
+ - uses: mlugg/setup-zig@v1
- name: Build
run: |
zig build -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
- name: Test
run: |
- zig build test
+ zig build test -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
windows:
runs-on: windows-latest
strategy:
@@ -34,13 +34,14 @@ jobs:
optimize: [ReleaseSmall, Debug]
steps:
- uses: actions/checkout@v4
- - uses: goto-bus-stop/setup-zig@v2
+ - uses: mlugg/setup-zig@v1
- name: Build
run: |
zig build -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
- name: Test
+ if: ${{ startsWith(matrix.target, 'x86_64') }}
run: |
- zig build test
+ zig build test -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
linux:
runs-on: ubuntu-latest
strategy:
@@ -49,10 +50,11 @@ jobs:
optimize: [ReleaseSmall, Debug]
steps:
- uses: actions/checkout@v4
- - uses: goto-bus-stop/setup-zig@v2
+ - uses: mlugg/setup-zig@v1
+ - uses: docker/setup-qemu-action@v3
- name: Build
run: |
zig build -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
- name: Test
run: |
- zig build test
+ zig build test -Dtarget=${{ matrix.target }} -Doptimize=${{ matrix.optimize }}
diff --git a/.gitignore b/.gitignore
index 0e4541c..48c3c40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
zig-out
-zig-cache
+.zig-cache
result
result-*
+node_modules
diff --git a/build.zig b/build.zig
index ec97a52..6b6e5d4 100644
--- a/build.zig
+++ b/build.zig
@@ -1,271 +1,56 @@
const std = @import("std");
-const metap = @import("metaplus").@"meta+";
-pub const Sdk = @import("src/phantom/sdk.zig");
-
-pub const DisplayBackendType = metap.enums.fields.mix(metap.enums.fromDecls(@import("src/phantom/display/backends.zig")), Sdk.TypeFor(.displays));
-pub const FontBackendType = metap.enums.fields.mix(metap.enums.fromDecls(@import("src/phantom/fonts/backends.zig")), Sdk.TypeFor(.fonts));
-pub const PlatformBackendType = metap.enums.fields.mix(metap.enums.fromDecls(@import("src/phantom/platform/backends.zig")), Sdk.TypeFor(.platforms));
-pub const SceneBackendType = metap.enums.fields.mix(metap.enums.fromDecls(@import("src/phantom/scene/backends.zig")), Sdk.TypeFor(.scenes));
-pub const ImageFormatType = metap.enums.fields.mix(metap.enums.fromDecls(@import("src/phantom/painting/image/formats.zig")), Sdk.TypeFor(.imageFormats));
-
-fn addSourceFiles(
- b: *std.Build,
- fileOverrides: *std.StringHashMap([]const u8),
- rootSource: *[]const u8,
- phantomSource: *std.Build.Step.WriteFile,
- rootPath: []const u8,
-) !void {
- var depSourceRoot = try std.fs.openDirAbsolute(b.pathJoin(&.{ rootPath, "phantom" }), .{ .iterate = true });
- defer depSourceRoot.close();
-
- var walker = try depSourceRoot.walk(b.allocator);
- defer walker.deinit();
-
- while (try walker.next()) |entry| {
- if (entry.kind == .directory) continue;
-
- const entryPath = b.pathJoin(&.{ rootPath, "phantom", entry.path });
- var entrySource = try Sdk.readAll(b.allocator, entryPath);
- errdefer b.allocator.free(entrySource);
-
- const entrypointRel = try std.fs.path.relative(b.allocator, std.fs.path.dirname(entryPath).?, b.pathJoin(&.{ rootPath, "phantom.zig" }));
- defer b.allocator.free(entrypointRel);
-
- const entrySourceOrig = entrySource;
- entrySource = try std.mem.replaceOwned(u8, b.allocator, entrySourceOrig, "@import(\"phantom\")", b.fmt("@import(\"{s}\")", .{entrypointRel}));
- b.allocator.free(entrySourceOrig);
-
- if (fileOverrides.getPtr(entry.path)) |sourceptr| {
- const fullSource = try Sdk.updateSource(b.allocator, sourceptr.*, entrySource);
- errdefer b.allocator.free(fullSource);
-
- b.allocator.free(sourceptr.*);
- sourceptr.* = fullSource;
- } else {
- const origPath = b.pathFromRoot(b.pathJoin(&.{ "src/phantom", entry.path }));
- if (Sdk.readAll(b.allocator, origPath) catch null) |origSource| {
- defer b.allocator.free(origSource);
-
- const fullSource = try Sdk.updateSource(b.allocator, origSource, entrySource);
- errdefer b.allocator.free(fullSource);
-
- try fileOverrides.put(try b.allocator.dupe(u8, entry.path), fullSource);
- b.allocator.free(entrySource);
- } else {
- _ = phantomSource.add(b.pathJoin(&.{ "phantom", entry.path }), entrySource);
- }
- }
- }
-
- const src = try Sdk.readAll(b.allocator, b.pathJoin(&.{ rootPath, "phantom.zig" }));
- defer b.allocator.free(src);
-
- const fullSource = try Sdk.updateSource(b.allocator, rootSource.*, src);
- errdefer b.allocator.free(fullSource);
-
- b.allocator.free(rootSource.*);
- rootSource.* = fullSource;
-}
-
-pub fn build(b: *std.Build) !void {
+pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- const no_docs = b.option(bool, "no-docs", "skip installing documentation") orelse false;
- const no_importer = b.option(bool, "no-importer", "disables the import system (not recommended)") orelse false;
- const display_backend = b.option(DisplayBackendType, "display-backend", "The display backend to use for the example") orelse .headless;
- const platform_backend = b.option(PlatformBackendType, "platform-backend", "The display backend to use for the example") orelse .std;
- const scene_backend = b.option(SceneBackendType, "scene-backend", "The scene backend to use for the example") orelse .headless;
- const vizops = b.dependency("vizops", .{
+ const zigimg = b.dependency("zigimg", .{
.target = target,
.optimize = optimize,
});
- const metaplus = b.dependency("metaplus", .{
+ const z2d = b.dependency("z2d", .{
.target = target,
.optimize = optimize,
});
- const anyplus = b.dependency("any+", .{
+ const module = b.addModule("phantom", .{
+ .root_source_file = b.path("lib/phantom.zig"),
.target = target,
.optimize = optimize,
+ .imports = &.{
+ .{
+ .name = "zigimg",
+ .module = zigimg.module("zigimg"),
+ },
+ .{
+ .name = "z2d",
+ .module = z2d.module("z2d"),
+ },
+ },
});
- const phantomOptions = b.addOptions();
- phantomOptions.addOption(bool, "no_importer", no_importer);
-
- var phantomDeps = std.ArrayList(std.Build.Module.Import).init(b.allocator);
- errdefer phantomDeps.deinit();
-
- try phantomDeps.append(.{
- .name = "vizops",
- .module = vizops.module("vizops"),
- });
-
- try phantomDeps.append(.{
- .name = "meta+",
- .module = metaplus.module("meta+"),
- });
-
- try phantomDeps.append(.{
- .name = "any+",
- .module = anyplus.module("any+"),
+ const autodoc_test = b.addObject(.{
+ .name = "phantom",
+ .root_module = module,
});
- try phantomDeps.append(.{
- .name = "phantom.options",
- .module = phantomOptions.createModule(),
+ const install_docs = b.addInstallDirectory(.{
+ .source_dir = autodoc_test.getEmittedDocs(),
+ .install_dir = .prefix,
+ .install_subdir = "doc/phantom",
});
- const phantomSource = b.addWriteFiles();
-
- var fileOverrides = std.StringHashMap([]const u8).init(b.allocator);
- defer fileOverrides.deinit();
-
- var rootSource = try Sdk.readAll(b.allocator, b.pathFromRoot("src/phantom.zig"));
- defer b.allocator.free(rootSource);
-
- if (!no_importer) {
- inline for (Sdk.availableDepenencies) |dep| {
- const pkg = @field(@import("root").dependencies.packages, dep[1]);
- const pkgdep = b.dependencyInner(dep[0], pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg.deps, .{
- .target = target,
- .optimize = optimize,
- .@"no-importer" = true,
- });
+ b.getInstallStep().dependOn(&install_docs.step);
- const depSourceRootPath = b.pathJoin(&.{ pkg.build_root, "src" });
- try addSourceFiles(b, &fileOverrides, &rootSource, phantomSource, depSourceRootPath);
+ const step_test = b.step("test", "Run unit tests");
- var iter = pkgdep.module(dep[0]).import_table.iterator();
- while (iter.next()) |entry| {
- var alreadyExists = false;
- for (phantomDeps.items) |i| {
- if (std.mem.eql(u8, i.name, entry.key_ptr.*)) {
- alreadyExists = true;
- break;
- }
- }
-
- if (!alreadyExists or !std.mem.eql(u8, entry.key_ptr.*, "phantom")) {
- try phantomDeps.append(.{
- .name = entry.key_ptr.*,
- .module = entry.value_ptr.*,
- });
- }
- }
- }
- }
-
- if (b.option([]const u8, "import-module", "inject a module to be imported")) |importModuleString| {
- const modulePathLen = std.mem.indexOf(u8, importModuleString, ":") orelse importModuleString.len;
- const modulePath = importModuleString[0..modulePathLen];
-
- try addSourceFiles(b, &fileOverrides, &rootSource, phantomSource, modulePath);
-
- if (modulePathLen < importModuleString.len) {
- const imports = try Sdk.ModuleImport.decode(b.allocator, importModuleString[(modulePathLen + 1)..]);
- defer imports.deinit();
-
- for (imports.value) |dep| {
- var alreadyExists = false;
- for (phantomDeps.items) |i| {
- if (std.mem.eql(u8, i.name, dep.name)) {
- alreadyExists = true;
- break;
- }
-
- if (!alreadyExists or !std.mem.eql(u8, dep.name, "phantom")) {
- try phantomDeps.append(.{
- .name = dep.name,
- .module = try dep.createModule(b, target, optimize),
- });
- }
- }
- }
- }
- }
-
- var phantomSourceRoot = try std.fs.openDirAbsolute(b.pathFromRoot("src/phantom"), .{ .iterate = true });
- defer phantomSourceRoot.close();
-
- var walker = try phantomSourceRoot.walk(b.allocator);
- defer walker.deinit();
-
- while (try walker.next()) |entry| {
- if (entry.kind == .directory) continue;
-
- const entryPath = b.pathJoin(&.{ "phantom", entry.path });
-
- if (fileOverrides.get(entry.path)) |source| {
- _ = phantomSource.add(entryPath, source);
- } else {
- _ = phantomSource.addCopyFile(.{
- .path = b.pathFromRoot(b.pathJoin(&.{ "src/phantom", entry.path })),
- }, entryPath);
- }
- }
-
- const phantom = b.addModule("phantom", .{
- .root_source_file = phantomSource.add("phantom.zig", rootSource),
- .imports = phantomDeps.items,
- });
-
- const step_test = b.step("test", "Run all unit tests");
-
- const unit_tests = b.addTest(.{
- .root_source_file = phantom.root_source_file.?,
+ const test_exe = b.addTest(.{
.target = target,
.optimize = optimize,
+ .root_module = module,
});
- for (phantomDeps.items) |dep| {
- unit_tests.root_module.addImport(dep.name, dep.module);
- }
-
- const run_unit_tests = b.addRunArtifact(unit_tests);
- step_test.dependOn(&run_unit_tests.step);
- b.installArtifact(unit_tests);
-
- const exe_options = b.addOptions();
- exe_options.addOption(DisplayBackendType, "display_backend", display_backend);
- exe_options.addOption(PlatformBackendType, "platform_backend", platform_backend);
- exe_options.addOption(SceneBackendType, "scene_backend", scene_backend);
-
- const sdk = try Sdk.get(b, platform_backend, phantom);
- defer sdk.deinit();
-
- const pkg_example = sdk.addPackage(.{
- .id = "dev.phantomui.example",
- .name = "example",
- .root_module = .{
- .root_source_file = .{
- .path = b.pathFromRoot("src/example.zig"),
- },
- .target = target,
- .optimize = optimize,
- },
- .kind = .application,
- .version = .{
- .major = 0,
- .minor = 1,
- .patch = 0,
- },
- });
-
- pkg_example.root_module.addImport("vizops", vizops.module("vizops"));
- pkg_example.root_module.addImport("options", exe_options.createModule());
-
- sdk.installPackage(pkg_example);
-
- if (!no_docs) {
- const docs = b.addInstallDirectory(.{
- .source_dir = unit_tests.getEmittedDocs(),
- .install_dir = .prefix,
- .install_subdir = "docs",
- });
-
- b.getInstallStep().dependOn(&docs.step);
- }
+ const test_run = b.addRunArtifact(test_exe);
+ step_test.dependOn(&test_run.step);
}
diff --git a/build.zig.zon b/build.zig.zon
index 5c7ec7a..cab7046 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,19 +1,20 @@
.{
- .name = "phantom",
+ .name = .phantom,
.version = "0.1.0",
.paths = .{"."},
.dependencies = .{
- .vizops = .{
- .url = "http://gitlab.midstall.com/common/vizops/-/archive/cb7a23d02c3030056a59ff0cd856d95221e8da06/vizops-cb7a23d02c3030056a59ff0cd856d95221e8da06.tar.gz",
- .hash = "1220813b3d22d110b409ec8619fc1da2bc2ba2e71eb37b6a921ad228b485135793be",
+ .zigimg = .{
+ .url = "https://github.com/TUSF/zigimg/archive/256cf2655bb887a30c57d8cbc1b9590643f0a824.tar.gz",
+ .hash = "zigimg-0.1.0-lly-O3AVEQBq5UYuvW77W180h6v41vJ9WXtBhrbLwtNH",
},
- .metaplus = .{
- .url = "http://gitlab.midstall.com/common/meta-plus/-/archive/9f8c3828e3c4445b80185dd24eeb61d0386b23dc/meta-plus-9f8c3828e3c4445b80185dd24eeb61d0386b23dc.tar.gz",
- .hash = "122057d6a6bd7ba8b95fe67d91f584c4c0140ca26e8bea09b92f63b649dff470bf56",
+ .z2d = .{
+ .url = "https://github.com/vancluever/z2d/archive/67d2e2e1891cd467fb7cb7c072bcf33dae2589a4.tar.gz",
+ .hash = "z2d-0.6.2-j5P_HjzpCgBdWm_1K6nob_MIb5YQqW5zyUCX9B5QnUeZ",
},
- .@"any+" = .{
- .url = "http://gitlab.midstall.com/common/any-plus/-/archive/15e70210f0525fa619bfed533c1205ff57ac2100/any-plus-15e70210f0525fa619bfed533c1205ff57ac2100.tar.gz",
- .hash = "12209b041a839e89ece510c07dbe62ad53b045ff0e6c44645cd1a3ea38f3b42905b9",
+ .@"zig-wayland" = .{
+ .url = "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz",
+ .hash = "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy",
},
},
+ .fingerprint = 0x6896f6aeaa10b7f9,
}
diff --git a/flake.lock b/flake.lock
index 973c61d..e898c60 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,52 +1,13 @@
{
"nodes": {
- "disko": {
- "flake": false,
- "locked": {
- "lastModified": 1695864092,
- "narHash": "sha256-Hu1SkFPqO7ND95AOzBkZE2jGXSYhfZ965C03O72Kbu8=",
- "owner": "nix-community",
- "repo": "disko",
- "rev": "19b62324663b6b9859caf7f335d232cf4f1f6a32",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "disko",
- "type": "github"
- }
- },
- "expidus-sdk": {
- "inputs": {
- "disko": "disko",
- "flake-utils": "flake-utils",
- "mobile-nixos": "mobile-nixos",
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1696307625,
- "narHash": "sha256-LkhuVw0V7jx2h+PGizmAaLXXpp9ulYzuxnv0n4G6/Q0=",
- "owner": "ExpidusOS",
- "repo": "sdk",
- "rev": "909c53da1a4176aaa47c4bdf326344f2f9db2865",
- "type": "github"
- },
- "original": {
- "owner": "ExpidusOS",
- "repo": "sdk",
- "type": "github"
- }
- },
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1673956053,
- "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
@@ -55,31 +16,34 @@
"type": "github"
}
},
- "flake-utils": {
+ "flake-parts": {
"inputs": {
- "systems": "systems"
+ "nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
- "lastModified": 1694529238,
- "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+ "lastModified": 1743550720,
+ "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
- "owner": "numtide",
- "repo": "flake-utils",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
"type": "github"
}
},
- "flake-utils_2": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems_2"
+ },
"locked": {
- "lastModified": 1659877975,
- "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "lastModified": 1705309234,
+ "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@@ -88,57 +52,57 @@
"type": "github"
}
},
- "mobile-nixos": {
- "flake": false,
+ "nixpkgs": {
"locked": {
- "lastModified": 1696124168,
- "narHash": "sha256-EzGHYAR7rozQQLZEHbKEcb5VpUFGoxwEsM0OWfW4wqU=",
+ "lastModified": 1746064888,
+ "narHash": "sha256-PPGZvkyUE6Vjf+9+4r3wuzQj9nyYjK0mVWf6syLqeRY=",
"owner": "NixOS",
- "repo": "mobile-nixos",
- "rev": "7cee346c3f8e73b25b1cfbf7a086a7652c11e0f3",
+ "repo": "nixpkgs",
+ "rev": "aee0b9a18c04b6faf7444753cf460a91e0c1d212",
"type": "github"
},
"original": {
"owner": "NixOS",
- "repo": "mobile-nixos",
+ "repo": "nixpkgs",
"type": "github"
}
},
- "nixpkgs": {
+ "nixpkgs-lib": {
"locked": {
- "lastModified": 1697431825,
- "narHash": "sha256-qeTSjPhY1lcbow2bW5jfmSbPbOJVAPdKMPaKhmWGsTk=",
- "owner": "ExpidusOS",
- "repo": "nixpkgs",
- "rev": "7f6d94f704f2894539e114eaeedda6e7f7b9301d",
+ "lastModified": 1743296961,
+ "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
+ "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
},
"original": {
- "owner": "ExpidusOS",
- "repo": "nixpkgs",
+ "owner": "nix-community",
+ "repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1689088367,
- "narHash": "sha256-Y2tl2TlKCWEHrOeM9ivjCLlRAKH3qoPUE/emhZECU14=",
+ "lastModified": 1708161998,
+ "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "5c9ddb86679c400d6b7360797b8a22167c2053f8",
+ "rev": "84d981bae8b5e783b3b548de505b22880559515f",
"type": "github"
},
"original": {
"owner": "NixOS",
- "ref": "release-23.05",
+ "ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
- "expidus-sdk": "expidus-sdk",
+ "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
+ "systems": "systems",
"zig-overlay": "zig-overlay"
}
},
@@ -157,18 +121,33 @@
"type": "github"
}
},
+ "systems_2": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
"zig-overlay": {
"inputs": {
"flake-compat": "flake-compat",
- "flake-utils": "flake-utils_2",
+ "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
- "lastModified": 1698149298,
- "narHash": "sha256-c+o5oUprm8wnJWV8wpBVMlSptSIYy7hCk/vJHjVH4KU=",
+ "lastModified": 1745755888,
+ "narHash": "sha256-eANYKbFQm3iO2M93tgAXFFEBvUYcFtyJmh5bzhxzmtQ=",
"owner": "mitchellh",
"repo": "zig-overlay",
- "rev": "bbc407a319659eed86d97989ef50cc82e3677c45",
+ "rev": "ed16f7ceaaeea401867f7a1428909f42bad1e3a5",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 357b822..44ce13c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,38 +1,80 @@
{
description = "A truely cross platform GUI toolkit for Zig.";
- nixConfig = rec {
- trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ];
- substituters = [ "https://cache.nixos.org" "https://cache.garnix.io" ];
- trusted-substituters = substituters;
- fallback = true;
- http2 = false;
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs";
+ flake-parts.url = "github:hercules-ci/flake-parts";
+ systems.url = "github:nix-systems/default";
+ zig-overlay.url = "github:mitchellh/zig-overlay";
};
- inputs.expidus-sdk = {
- url = github:ExpidusOS/sdk;
- inputs.nixpkgs.follows = "nixpkgs";
- };
+ outputs =
+ {
+ self,
+ nixpkgs,
+ flake-parts,
+ systems,
+ zig-overlay,
+ ...
+ }@inputs:
+ flake-parts.lib.mkFlake { inherit inputs; } {
+ systems = import systems;
+
+ flake.overlays.default = final: prev: {
+ zig_0_15 = final.zigpkgs.master.overrideAttrs (
+ f: p: {
+ inherit (final.zig_0_13) meta;
- inputs.nixpkgs.url = github:ExpidusOS/nixpkgs;
+ passthru.hook = final.callPackage "${nixpkgs}/pkgs/development/compilers/zig/hook.nix" {
+ zig = f.finalPackage;
+ };
+ }
+ );
+ };
- inputs.zig-overlay.url = github:mitchellh/zig-overlay;
+ perSystem =
+ {
+ config,
+ pkgs,
+ system,
+ ...
+ }:
+ {
+ _module.args.pkgs = import inputs.nixpkgs {
+ inherit system;
+ overlays = [
+ self.overlays.default
+ zig-overlay.overlays.default
+ ];
+ config = { };
+ };
- outputs = { self, expidus-sdk, nixpkgs, zig-overlay }@inputs:
- with expidus-sdk.lib;
- flake-utils.eachSystem flake-utils.allSystems (system:
- let
- pkgs = expidus-sdk.legacyPackages.${system}.appendOverlays [
- zig-overlay.overlays.default
- (f: p: {
- zig = f.zigpkgs.master;
- })
- ];
- in {
- devShells.default = pkgs.mkShell {
- name = "expidus-phantom";
+ legacyPackages = pkgs;
- packages = with pkgs; [ zig ];
+ devShells =
+ let
+ optionalPackage' =
+ pkgs: pkg: pkgs.lib.optional (pkgs.lib.meta.availableOn pkgs.hostPlatform pkg) pkg;
+ mkShell =
+ pkgs:
+ pkgs.mkShell {
+ packages =
+ let
+ optionalPackage = optionalPackage' pkgs;
+ in
+ with pkgs;
+ [
+ buildPackages.zon2nix
+ buildPackages.zig_0_14
+ ];
+ };
+ in
+ {
+ default = mkShell pkgs;
+ cross-aarch64-linux = mkShell pkgs.pkgsCross.aarch64-multiplatform;
+ cross-x86_64-linux = mkShell pkgs.pkgsCross.gnu64;
+ cross-riscv64-linux = mkShell pkgs.pkgsCross.riscv64;
+ };
};
- });
+ };
}
diff --git a/src/phantom.zig b/lib/phantom.zig
similarity index 51%
rename from src/phantom.zig
rename to lib/phantom.zig
index 98aea54..7553239 100644
--- a/src/phantom.zig
+++ b/lib/phantom.zig
@@ -1,9 +1,17 @@
-const root = @import("root");
-
pub const display = @import("phantom/display.zig");
pub const fonts = @import("phantom/fonts.zig");
pub const gpu = @import("phantom/gpu.zig");
+pub const graphics = @import("phantom/graphics.zig");
pub const math = @import("phantom/math.zig");
-pub const painting = @import("phantom/painting.zig");
-pub const platform = @import("phantom/platform.zig");
pub const scene = @import("phantom/scene.zig");
+pub const widgets = @import("phantom/widgets.zig");
+
+test {
+ _ = display;
+ _ = fonts;
+ _ = gpu;
+ _ = graphics;
+ _ = math;
+ _ = scene;
+ _ = widgets;
+}
diff --git a/lib/phantom/display.zig b/lib/phantom/display.zig
new file mode 100644
index 0000000..03bd1d4
--- /dev/null
+++ b/lib/phantom/display.zig
@@ -0,0 +1,19 @@
+pub const Client = @import("display/Client.zig");
+pub const Input = @import("display/Input.zig");
+pub const Output = @import("display/Output.zig");
+pub const Provider = @import("display/Provider.zig");
+pub const Server = @import("display/Server.zig");
+pub const Surface = @import("display/Surface.zig");
+pub const Subsurface = @import("display/Subsurface.zig");
+pub const Toplevel = @import("display/Toplevel.zig");
+
+test {
+ _ = Client;
+ _ = Input;
+ _ = Output;
+ _ = Provider;
+ _ = Server;
+ _ = Surface;
+ _ = Subsurface;
+ _ = Toplevel;
+}
diff --git a/lib/phantom/display/Client.zig b/lib/phantom/display/Client.zig
new file mode 100644
index 0000000..89298e3
--- /dev/null
+++ b/lib/phantom/display/Client.zig
@@ -0,0 +1,13 @@
+const std = @import("std");
+const Self = @This();
+
+pub const VTable = struct {
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Input.zig b/lib/phantom/display/Input.zig
new file mode 100644
index 0000000..89298e3
--- /dev/null
+++ b/lib/phantom/display/Input.zig
@@ -0,0 +1,13 @@
+const std = @import("std");
+const Self = @This();
+
+pub const VTable = struct {
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Output.zig b/lib/phantom/display/Output.zig
new file mode 100644
index 0000000..5cb7fe4
--- /dev/null
+++ b/lib/phantom/display/Output.zig
@@ -0,0 +1,48 @@
+const std = @import("std");
+const graphics = @import("../graphics.zig");
+const math = @import("../math.zig");
+const Self = @This();
+
+pub const Mode = struct {
+ enabled: bool,
+ res: math.Vec2(usize),
+ scale: math.Vec2(f32),
+ format: graphics.Format,
+};
+
+pub const Info = struct {
+ phys_size: math.Vec2(f32),
+ name: []const u8,
+ manufacturer: []const u8,
+};
+
+pub const VTable = struct {
+ getModes: *const fn (*anyopaque) []const Mode,
+ testMode: *const fn (*anyopaque, Mode) bool,
+ setMode: *const fn (*anyopaque, Mode) anyerror!void,
+ getInfo: *const fn (*anyopaque) ?Info,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getModes(self: *Self) []const Mode {
+ return self.vtable.getModes(self.ptr);
+}
+
+pub inline fn testMode(self: *Self, mode: Mode) bool {
+ return self.vtable.testMode(self.ptr, mode);
+}
+
+pub inline fn setMode(self: *Self, mode: Mode) anyerror!void {
+ return self.vtable.setMode(self.ptr, mode);
+}
+
+pub inline fn getInfo(self: *Self) ?Info {
+ return self.vtable.getInfo(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Provider.zig b/lib/phantom/display/Provider.zig
new file mode 100644
index 0000000..1d82f3e
--- /dev/null
+++ b/lib/phantom/display/Provider.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const gpu = @import("../gpu.zig");
+const Input = @import("Input.zig");
+const Output = @import("Output.zig");
+const Toplevel = @import("Toplevel.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getGpuProvider: *const fn (*anyopaque) anyerror!?*gpu.Provider,
+ getInputs: *const fn (*anyopaque) []const Input,
+ getOutputs: *const fn (*anyopaque) []const Output,
+ getToplevels: *const fn (*anyopaque) []const Toplevel,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getGpuProvider(self: *Self) anyerror!?*gpu.Provider {
+ return self.vtable.getGpuProvider(self.ptr);
+}
+
+pub inline fn getInputs(self: *Self) []const Input {
+ return self.vtable.getInputs(self.ptr);
+}
+
+pub inline fn getOutputs(self: *Self) []const Output {
+ return self.vtable.getOutputs(self.ptr);
+}
+
+pub inline fn getToplevels(self: *Self) []const Toplevel {
+ return self.vtable.getToplevels(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Server.zig b/lib/phantom/display/Server.zig
new file mode 100644
index 0000000..89298e3
--- /dev/null
+++ b/lib/phantom/display/Server.zig
@@ -0,0 +1,13 @@
+const std = @import("std");
+const Self = @This();
+
+pub const VTable = struct {
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Subsurface.zig b/lib/phantom/display/Subsurface.zig
new file mode 100644
index 0000000..4a6795d
--- /dev/null
+++ b/lib/phantom/display/Subsurface.zig
@@ -0,0 +1,69 @@
+const std = @import("std");
+const graphics = @import("../graphics.zig");
+const math = @import("../math.zig");
+const Surface = @import("Surface.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getPosition: *const fn (*anyopaque) math.Vec2(usize),
+ getSize: *const fn (*anyopaque) math.Vec2(usize),
+ getChildren: *const fn (*anyopaque) []const Surface,
+ destroy: *const fn (*anyopaque) void,
+};
+
+pub const surface_vtable: Surface.VTable = .{
+ .getSize = vtable_getSize,
+ .getChildren = vtable_getChildren,
+ .destroy = vtable_destroy,
+};
+
+base: Surface,
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn init(self: *Self, ptr: *anyopaque, vtable: *const VTable) Self {
+ return .{
+ .base = .{
+ .ptr = self,
+ .vtable = &surface_vtable,
+ },
+ .ptr = ptr,
+ .vtable = vtable,
+ };
+}
+
+pub inline fn getPosition(self: *Self) math.Vec2(usize) {
+ return self.vtable.getPosition(self.ptr);
+}
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getChildren(self: *Self) []const Surface {
+ return self.vtable.getChildren(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+fn vtable_getSize(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getSize();
+}
+
+fn vtable_getPosition(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getPosition();
+}
+
+fn vtable_getChildren(ctx: *anyopaque) []const Surface {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getChildren();
+}
+
+fn vtable_destroy(ctx: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.destroy();
+}
diff --git a/lib/phantom/display/Surface.zig b/lib/phantom/display/Surface.zig
new file mode 100644
index 0000000..4a5991d
--- /dev/null
+++ b/lib/phantom/display/Surface.zig
@@ -0,0 +1,25 @@
+const std = @import("std");
+const graphics = @import("../graphics.zig");
+const math = @import("../math.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getSize: *const fn (*anyopaque) math.Vec2(usize),
+ getChildren: *const fn (*anyopaque) []const Self,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getChildren(self: *Self) []const Self {
+ return self.vtable.getChildren(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/display/Toplevel.zig b/lib/phantom/display/Toplevel.zig
new file mode 100644
index 0000000..c75153e
--- /dev/null
+++ b/lib/phantom/display/Toplevel.zig
@@ -0,0 +1,67 @@
+const std = @import("std");
+const graphics = @import("../graphics.zig");
+const math = @import("../math.zig");
+const Surface = @import("Surface.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getPosition: *const fn (*anyopaque) ?math.Vec2(usize),
+ getSize: *const fn (*anyopaque) math.Vec2(usize),
+ getChildren: *const fn (*anyopaque) []const Surface,
+ getTitle: *const fn (*anyopaque) ?[]const u8,
+ getClass: *const fn (*anyopaque) ?[]const u8,
+ destroy: *const fn (*anyopaque) void,
+};
+
+pub const surface_vtable: Surface.VTable = .{
+ .getSize = vtable_getSize,
+ .getChildren = vtable_getChildren,
+ .destroy = vtable_destroy,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getPosition(self: *Self) ?math.Vec2(usize) {
+ return self.vtable.getPosition(self.ptr);
+}
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getChildren(self: *Self) []const Surface {
+ return self.vtable.getChildren(self.ptr);
+}
+
+pub inline fn getTitle(self: *Self) ?[]const u8 {
+ return self.vtable.getTitle(self.ptr);
+}
+
+pub inline fn getClass(self: *Self) ?[]const u8 {
+ return self.vtable.getClass(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+fn vtable_getSize(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getSize();
+}
+
+fn vtable_getPosition(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getPosition();
+}
+
+fn vtable_getChildren(ctx: *anyopaque) []const Surface {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getChildren();
+}
+
+fn vtable_destroy(ctx: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.destroy();
+}
diff --git a/lib/phantom/fonts.zig b/lib/phantom/fonts.zig
new file mode 100644
index 0000000..df78cd0
--- /dev/null
+++ b/lib/phantom/fonts.zig
@@ -0,0 +1,9 @@
+pub const Font = @import("fonts/Font.zig");
+pub const Loader = @import("fonts/Loader.zig");
+pub const Manager = @import("fonts/Manager.zig");
+
+test {
+ _ = Font;
+ _ = Loader;
+ _ = Manager;
+}
diff --git a/lib/phantom/fonts/Font.zig b/lib/phantom/fonts/Font.zig
new file mode 100644
index 0000000..b5e7266
--- /dev/null
+++ b/lib/phantom/fonts/Font.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+const graphics = @import("../graphics.zig");
+const math = @import("../math.zig");
+const Self = @This();
+
+pub const Glyph = struct {
+ index: u32,
+ image: graphics.Source,
+ size: math.Vec2(u8),
+ bearing: math.Vec2(i8),
+ advance: math.Vec2(i8),
+};
+
+pub const VTable = struct {
+ lookupGlyph: *const fn (*anyopaque, u21) anyerror!Glyph,
+ getSize: *const fn (*anyopaque) math.Vec2(usize),
+ getLabel: *const fn (*anyopaque) ?[]const u8,
+ deinit: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn lookupGlyph(self: *Self, codepoint: u21) !Glyph {
+ return self.vtable.lookupGlyph(self.ptr, codepoint);
+}
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getLabel(self: *Self) ?[]const u8 {
+ return self.vtable.getLabel(self.ptr);
+}
+
+pub inline fn deinit(self: *Self) void {
+ return self.vtable.deinit(self.ptr);
+}
diff --git a/lib/phantom/fonts/Loader.zig b/lib/phantom/fonts/Loader.zig
new file mode 100644
index 0000000..73a740a
--- /dev/null
+++ b/lib/phantom/fonts/Loader.zig
@@ -0,0 +1,60 @@
+const std = @import("std");
+const Font = @import("Font.zig");
+const math = @import("../math.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ load: *const fn (*anyopaque, reader: std.io.AnyReader, data_size: ?usize) anyerror!*Font,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn load(self: *Self, reader: std.io.AnyReader, data_size: ?usize) anyerror!*Font {
+ return self.vtable.load(self.ptr, reader, data_size);
+}
+
+pub fn loadFile(self: *Self, file: std.fs.File) anyerror!*Font {
+ var metadata = try file.metadata();
+ return self.load(file.reader().any(), metadata.size());
+}
+
+pub fn lookupFont(self: *Self, sources: std.StringArrayHashMap(struct { std.io.AnyReader, ?usize }), name: []const u8, size: math.Vec2(usize)) anyerror!*Font {
+ var iter = sources.iterator();
+ while (iter.next()) |entry| {
+ const font = self.load(entry.value_ptr[0], entry.value_ptr[1]) catch continue;
+ const label = font.getLabel() orelse entry.key_ptr;
+
+ if (std.mem.eql(u8, label, name) and std.meta.eql(font.getSize().value, size.value)) {
+ return font;
+ }
+
+ font.deinit();
+ }
+ return error.NotFound;
+}
+
+pub fn lookupFontDir(self: *Self, dir: std.fs.Dir, name: []const u8, size: math.Vec2(usize)) anyerror!*Font {
+ var iter = dir.iterate();
+ while (try iter.next()) |entry| {
+ if (entry.kind != .file or entry.kind != .sym_link) continue;
+
+ var file = try dir.openFile(entry.name, .{});
+ defer file.close();
+
+ const font = self.loadFile(file) catch continue;
+ const label = font.getLabel() orelse std.fs.path.stem(entry.name);
+
+ if (std.mem.eql(u8, label, name) and std.meta.eql(font.getSize().value, size.value)) {
+ return font;
+ }
+
+ font.deinit();
+ }
+ return error.NotFound;
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/fonts/Manager.zig b/lib/phantom/fonts/Manager.zig
new file mode 100644
index 0000000..c625aa7
--- /dev/null
+++ b/lib/phantom/fonts/Manager.zig
@@ -0,0 +1,51 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Font = @import("Font.zig");
+const Loader = @import("Loader.zig");
+const math = @import("../math.zig");
+const Self = @This();
+
+allocator: Allocator,
+loaders: std.ArrayListUnmanaged(*Loader),
+dirs: std.ArrayListUnmanaged(std.fs.Dir),
+
+pub fn create(alloc: Allocator) Allocator.Error!*Self {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .loaders = .{},
+ .dirs = .{},
+ };
+ return self;
+}
+
+pub fn lookupFont(self: *Self, name: []const u8, size: math.Vec2(usize)) !*Font {
+ for (self.dirs.items) |dir| {
+ for (self.loaders.items) |loader| {
+ return loader.lookupFontDir(dir, name, size) catch |err| switch (err) {
+ error.NoEntry => continue,
+ else => |e| return e,
+ };
+ }
+ }
+ return error.NoEntry;
+}
+
+pub fn destroy(self: *Self) void {
+ for (self.loaders.items) |loader| loader.destroy();
+ self.loaders.deinit(self.allocator);
+
+ for (self.dirs.items) |*dir| dir.close();
+ self.dirs.deinit(self.allocator);
+
+ self.allocator.destroy(self);
+}
+
+test "Expect fail" {
+ const mngr = try create(std.testing.allocator);
+ defer mngr.destroy();
+
+ try std.testing.expectError(error.NoEntry, mngr.lookupFont("AAAA", math.Vec2(usize).zero));
+}
diff --git a/lib/phantom/gpu.zig b/lib/phantom/gpu.zig
new file mode 100644
index 0000000..0534aaf
--- /dev/null
+++ b/lib/phantom/gpu.zig
@@ -0,0 +1,11 @@
+pub const Connector = @import("gpu/Connector.zig");
+pub const Device = @import("gpu/Device.zig");
+pub const Provider = @import("gpu/Provider.zig");
+pub const Texture = @import("gpu/Texture.zig");
+
+test {
+ _ = Connector;
+ _ = Device;
+ _ = Provider;
+ _ = Texture;
+}
diff --git a/lib/phantom/gpu/Connector.zig b/lib/phantom/gpu/Connector.zig
new file mode 100644
index 0000000..89298e3
--- /dev/null
+++ b/lib/phantom/gpu/Connector.zig
@@ -0,0 +1,13 @@
+const std = @import("std");
+const Self = @This();
+
+pub const VTable = struct {
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/gpu/Device.zig b/lib/phantom/gpu/Device.zig
new file mode 100644
index 0000000..5e3cd01
--- /dev/null
+++ b/lib/phantom/gpu/Device.zig
@@ -0,0 +1,19 @@
+const std = @import("std");
+const Connector = @import("Connector.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getConnectors: *const fn (*anyopaque) anyerror![]*Connector,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getConnectors(self: *Self) anyerror![]*Connector {
+ return self.vtable.getConnectors(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/gpu/Provider.zig b/lib/phantom/gpu/Provider.zig
new file mode 100644
index 0000000..bb459f7
--- /dev/null
+++ b/lib/phantom/gpu/Provider.zig
@@ -0,0 +1,19 @@
+const std = @import("std");
+const Device = @import("Device.zig");
+const Self = @This();
+
+pub const VTable = struct {
+ getDevices: *const fn (*anyopaque) anyerror![]const *Device,
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getDevices(self: *Self) anyerror![]const *Device {
+ return self.vtable.getDevices(self.ptr);
+}
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/gpu/Texture.zig b/lib/phantom/gpu/Texture.zig
new file mode 100644
index 0000000..89298e3
--- /dev/null
+++ b/lib/phantom/gpu/Texture.zig
@@ -0,0 +1,13 @@
+const std = @import("std");
+const Self = @This();
+
+pub const VTable = struct {
+ destroy: *const fn (*anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn destroy(self: *Self) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/graphics.zig b/lib/phantom/graphics.zig
new file mode 100644
index 0000000..3db7d69
--- /dev/null
+++ b/lib/phantom/graphics.zig
@@ -0,0 +1,97 @@
+//! Core graphics API
+const std = @import("std");
+const zigimg = @import("zigimg");
+const z2d = @import("z2d");
+
+pub const backend = @import("graphics/backend.zig");
+
+pub const Color = @import("graphics/Color.zig");
+pub const Context = @import("graphics/Context.zig");
+pub const Surface = @import("graphics/Surface.zig");
+
+pub const Format = enum {
+ argb32,
+ rgba32,
+ rgbx32,
+ rgb24,
+
+ pub fn channels(self: Format) u8 {
+ return switch (self) {
+ .argb32, .rgba32 => 4,
+ .rgbx32, .rgb24 => 3,
+ };
+ }
+
+ pub fn asZigimg(self: Format) zigimg.PixelFormat {
+ return switch (self) {
+ .rgba32 => .rgba32,
+ .rgb24 => .rgb24,
+ else => .invalid,
+ };
+ }
+};
+
+pub const Subpixel = enum {
+ horiz_rgb,
+ horiz_bgr,
+ vert_rgb,
+ vert_bgr,
+
+ pub fn isHorizontal(self: Subpixel) bool {
+ return switch (self) {
+ .horiz_rgb, .horiz_bgr => true,
+ else => false,
+ };
+ }
+
+ pub fn isVertical(self: Subpixel) bool {
+ return switch (self) {
+ .vert_rgb, .vert_bgr => true,
+ else => false,
+ };
+ }
+
+ pub fn isRgb(self: Subpixel) bool {
+ return switch (self) {
+ .horiz_rgb, .vert_rgb => true,
+ else => false,
+ };
+ }
+
+ pub fn isBgr(self: Subpixel) bool {
+ return switch (self) {
+ .horiz_bgr, .vert_bgr => true,
+ else => false,
+ };
+ }
+};
+
+pub const Gradient = struct {
+ allocator: std.mem.Allocator,
+ value: z2d.Gradient,
+
+ pub inline fn deinit(self: *Gradient) void {
+ return self.value.deinit(self.allocator);
+ }
+};
+
+pub const Source = union(enum) {
+ img: zigimg.Image,
+ color: Color,
+ gradient: Gradient,
+
+ pub fn destroy(self: *Source) void {
+ return switch (self.*) {
+ .img => |*img| img.deinit(),
+ .gradient => |*gradient| gradient.deinit(),
+ else => {},
+ };
+ }
+};
+
+test {
+ _ = backend;
+ _ = Color;
+ _ = Context;
+ _ = Surface;
+}
diff --git a/lib/phantom/graphics/Color.zig b/lib/phantom/graphics/Color.zig
new file mode 100644
index 0000000..dc37a2f
--- /dev/null
+++ b/lib/phantom/graphics/Color.zig
@@ -0,0 +1,12 @@
+const std = @import("std");
+const Value = @Vector(4, f16);
+const Color = @This();
+
+value: Value,
+
+pub inline fn init(value: Value) Color {
+ return .{ .value = value };
+}
+
+pub const black = init(.{ 0, 0, 0, 1 });
+pub const white = init(.{ 1, 1, 1, 1 });
diff --git a/lib/phantom/graphics/Context.zig b/lib/phantom/graphics/Context.zig
new file mode 100644
index 0000000..648262e
--- /dev/null
+++ b/lib/phantom/graphics/Context.zig
@@ -0,0 +1,69 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const math = @import("../math.zig");
+const Context = @This();
+
+pub const @"2d" = @import("Context/2d.zig");
+pub const @"3d" = @import("Context/3d.zig");
+
+pub const Operation = union(Kind) {
+ @"2d": @"2d".Operation,
+ @"3d": @"3d".Operation,
+};
+
+pub const StateOperation = union(enum) {
+ save: *?*anyopaque,
+ load: *anyopaque,
+ clear: void,
+};
+
+pub const Kind = enum {
+ @"2d",
+ @"3d",
+
+ pub fn Type(comptime self: Kind) type {
+ return switch (self) {
+ .@"2d" => Context.@"2d",
+ .@"3d" => Context.@"3d",
+ };
+ }
+};
+
+pub const Error = std.mem.Allocator.Error || error{
+ InvalidKind,
+ Unknown,
+};
+
+pub const VTable = struct {
+ getSize: *const fn (self: *anyopaque) math.Vec2(usize),
+ getKind: *const fn (self: *anyopaque) Kind,
+ destroy: *const fn (self: *anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getSize(self: *const Context) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getKind(self: *const Context) Kind {
+ return self.vtable.getKind(self.ptr);
+}
+
+pub inline fn destroy(self: *const Context) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+pub fn as(self: *const Context, comptime kind: Kind) *kind.Type() {
+ assert(self.getKind() == kind);
+ return @ptrCast(@alignCast(self.ptr));
+}
+
+pub fn render(self: *Context, op: Operation) !void {
+ if (op != self.getKind()) return error.InvalidOperation;
+ return switch (op) {
+ .@"2d" => |a| self.as(.@"2d").render(a),
+ .@"3d" => |b| self.as(.@"3d").render(b),
+ };
+}
diff --git a/lib/phantom/graphics/Context/2d.zig b/lib/phantom/graphics/Context/2d.zig
new file mode 100644
index 0000000..3c660db
--- /dev/null
+++ b/lib/phantom/graphics/Context/2d.zig
@@ -0,0 +1,131 @@
+const std = @import("std");
+const math = @import("../../math.zig");
+const graphics = @import("../../graphics.zig");
+const Context = @import("../Context.zig");
+const Color = @import("../Color.zig");
+const zigimg = @import("zigimg");
+const Self = @This();
+
+pub const Operation = union(enum) {
+ rect: Rect,
+ path: Path,
+ composite: Composite,
+ setTransform: SetTransform,
+ clear: Clear,
+
+ pub const Mode = union(enum) {
+ fill: void,
+ stroke: Stroke,
+
+ pub const Stroke = struct {
+ width: f32 = 1.0,
+ };
+ };
+
+ pub const Rect = struct {
+ source: graphics.Source,
+ rect: math.Rect(f32),
+ border_radius: BorderRadius = .{},
+ mode: Mode,
+
+ pub const BorderRadius = struct {
+ top_left: ?f32 = null,
+ top_right: ?f32 = null,
+ bottom_left: ?f32 = null,
+ bottom_right: ?f32 = null,
+
+ pub fn all(value: ?f32) BorderRadius {
+ return .{
+ .top_left = value,
+ .top_right = value,
+ .bottom_left = value,
+ .bottom_right = value,
+ };
+ }
+ };
+ };
+
+ pub const Path = struct {
+ value: []const math.Vec2(f32),
+ source: graphics.Source,
+ mode: Path.Mode,
+
+ pub const Mode = union(enum) {
+ fill: void,
+ dots: void,
+ stroke: Operation.Mode.Stroke,
+ };
+ };
+
+ pub const Composite = struct {
+ source: graphics.Source,
+ position: math.Vec2(f32),
+ mode: Composite.Mode,
+
+ pub const Mode = enum {
+ in,
+ over,
+ };
+ };
+
+ pub const SetTransform = struct {
+ value: math.Mat3x3(f32),
+ };
+
+ pub const Clear = struct {
+ color: graphics.Color,
+ };
+};
+
+pub const VTable = struct {
+ getSize: *const fn (self: *anyopaque) math.Vec2(usize),
+ render: *const fn (self: *anyopaque, op: Operation) anyerror!void,
+ destroy: *const fn (self: *anyopaque) void,
+};
+
+pub const context_vtable: Context.VTable = .{
+ .getSize = vtable_getSize,
+ .getKind = vtable_getKind,
+ .destroy = vtable_destroy,
+};
+
+base: Context,
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn init(self: *Self, ptr: *anyopaque, vtable: *const VTable) Self {
+ return .{
+ .base = .{
+ .ptr = self,
+ .vtable = &context_vtable,
+ },
+ .ptr = ptr,
+ .vtable = vtable,
+ };
+}
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn render(self: *Self, op: Operation) anyerror!void {
+ return self.vtable.render(self.ptr, op);
+}
+
+pub inline fn destroy(self: *const Self) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+fn vtable_getSize(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getSize();
+}
+
+fn vtable_getKind(_: *anyopaque) Context.Kind {
+ return .@"2d";
+}
+
+fn vtable_destroy(ctx: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/graphics/Context/3d.zig b/lib/phantom/graphics/Context/3d.zig
new file mode 100644
index 0000000..1a44d78
--- /dev/null
+++ b/lib/phantom/graphics/Context/3d.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const graphics = @import("../../graphics.zig");
+const math = @import("../../math.zig");
+const Context = @import("../Context.zig");
+const Self = @This();
+
+pub const Operation = union(enum) {
+ polygon: Polygon,
+ setProjection: SetProjection,
+ clear: Clear,
+
+ pub const Polygon = struct {
+ vertices: []math.Vec4(f32),
+ indices: []usize,
+ view: math.Mat4x4(f32),
+ model: math.Mat4x4(f32),
+ source: graphics.Source,
+ };
+
+ pub const SetProjection = struct {
+ value: math.Mat4x4(f32),
+ };
+
+ pub const Clear = struct {
+ color: ?graphics.Color,
+ depth: ?Depth,
+
+ pub const Depth = union(enum) {
+ zbuffer: void,
+ culling: void,
+ none: void,
+ };
+ };
+};
+
+pub const VTable = struct {
+ getSize: *const fn (self: *anyopaque) math.Vec2(usize),
+ render: *const fn (self: *anyopaque, op: Operation) anyerror!void,
+ destroy: *const fn (self: *anyopaque) void,
+};
+
+pub const context_vtable: Context.VTable = .{
+ .getSize = vtable_getSize,
+ .getKind = vtable_getKind,
+ .destroy = vtable_destroy,
+};
+
+base: Context,
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn init(self: *Self, ptr: *anyopaque, vtable: *const VTable) Self {
+ return .{
+ .base = .{
+ .ptr = self,
+ .vtable = &context_vtable,
+ },
+ .ptr = ptr,
+ .vtable = vtable,
+ };
+}
+
+pub inline fn getSize(self: *Self) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn render(self: *Self, op: Operation) anyerror!void {
+ return self.vtable.render(self.ptr, op);
+}
+
+pub inline fn destroy(self: *const Self) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+fn vtable_getSize(ctx: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.getSize();
+}
+
+fn vtable_getKind(_: *anyopaque) Context.Kind {
+ return .@"3d";
+}
+
+fn vtable_destroy(ctx: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ctx));
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/graphics/Surface.zig b/lib/phantom/graphics/Surface.zig
new file mode 100644
index 0000000..0d044f1
--- /dev/null
+++ b/lib/phantom/graphics/Surface.zig
@@ -0,0 +1,41 @@
+const std = @import("std");
+const math = @import("../math.zig");
+const graphics = @import("../graphics.zig");
+const Context = @import("Context.zig");
+const Surface = @This();
+
+pub const VTable = struct {
+ getSize: *const fn (self: *anyopaque) math.Vec2(usize),
+ getFormat: *const fn (self: *anyopaque) ?graphics.Format,
+ getBuffer: *const fn (self: *anyopaque) ?[]const u8,
+ getContext: *const fn (self: *anyopaque, kind: Context.Kind) Context.Error!*Context,
+ snapshot: *const fn (self: *anyopaque) anyerror!graphics.Source,
+ destroy: *const fn (self: *anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn getSize(self: *const Surface) math.Vec2(usize) {
+ return self.vtable.getSize(self.ptr);
+}
+
+pub inline fn getFormat(self: *const Surface) ?graphics.Format {
+ return self.vtable.getFormat(self.ptr);
+}
+
+pub inline fn getBuffer(self: *const Surface) ?[]const u8 {
+ return self.vtable.getBuffer(self.ptr);
+}
+
+pub inline fn getContext(self: *Surface, kind: Context.Kind) Context.Error!*Context {
+ return self.vtable.getContext(self.ptr, kind);
+}
+
+pub inline fn snapshot(self: *Surface) anyerror!graphics.Source {
+ return self.vtable.snapshot(self.ptr);
+}
+
+pub inline fn destroy(self: *const Surface) void {
+ return self.vtable.destroy(self.ptr);
+}
diff --git a/lib/phantom/graphics/backend.zig b/lib/phantom/graphics/backend.zig
new file mode 100644
index 0000000..60056c2
--- /dev/null
+++ b/lib/phantom/graphics/backend.zig
@@ -0,0 +1,7 @@
+pub const soft3d = @import("backend/soft3d.zig");
+pub const z2d = @import("backend/z2d.zig");
+
+test {
+ _ = soft3d;
+ _ = z2d;
+}
diff --git a/lib/phantom/graphics/backend/soft3d.zig b/lib/phantom/graphics/backend/soft3d.zig
new file mode 100644
index 0000000..a91d681
--- /dev/null
+++ b/lib/phantom/graphics/backend/soft3d.zig
@@ -0,0 +1,7 @@
+//! Software 3-D graphics backend built in for Phantom UI
+
+pub const Context = @import("soft3d/Context.zig");
+
+test {
+ _ = Context;
+}
diff --git a/lib/phantom/graphics/backend/soft3d/Context.zig b/lib/phantom/graphics/backend/soft3d/Context.zig
new file mode 100644
index 0000000..c8b7e13
--- /dev/null
+++ b/lib/phantom/graphics/backend/soft3d/Context.zig
@@ -0,0 +1,267 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const graphics = @import("../../../graphics.zig");
+const math = @import("../../../math.zig");
+const Context = @import("../../Context.zig");
+const Self = @This();
+
+allocator: Allocator,
+base: Context.@"3d",
+context: *Context,
+proj: math.Mat4x4(f32),
+depth: Depth,
+
+const Depth = union(enum) {
+ zbuffer: struct {
+ buffer: []f32,
+ },
+ culling: void,
+ none: void,
+};
+
+const vtable: Context.@"3d".VTable = .{
+ .getSize = vtable_getSize,
+ .render = vtable_render,
+ .destroy = vtable_destroy,
+};
+
+pub fn create(alloc: Allocator, ctx: *Context) Allocator.Error!*Context {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .base = Context.@"3d".init(&self.base, self, &vtable),
+ .context = ctx,
+ .proj = math.mat4x4.identity(f32),
+ .depth = .none,
+ };
+ return &self.base.base;
+}
+
+pub fn getSize(self: *Self) math.Vec2(usize) {
+ return self.context.getSize();
+}
+
+pub fn render(self: *Self, op: Context.@"3d".Operation) anyerror!void {
+ switch (op) {
+ .polygon => |polygon| {
+ const mvp = math.mat4x4.mult(f32, self.proj, math.mat4x4.mult(f32, polygon.view, polygon.model));
+
+ var path = std.ArrayList(math.Vec2(f32)).init(self.allocator);
+ defer path.deinit();
+
+ const size = self.getSize();
+ const half_size = size.divValue(@splat(2));
+ const size_min1 = size.subValue(@splat(1));
+
+ for (polygon.indices) |indice| {
+ const v = math.mat4x4.multVec(f32, mvp, polygon.vertices[indice]);
+
+ const x = @as(f32, @floatFromInt(half_size.value[0])) + v.value[0] / v.value[3] * @as(f32, @floatFromInt(half_size.value[0]));
+ const y = @as(f32, @floatFromInt(half_size.value[1])) - v.value[1] / v.value[3] * @as(f32, @floatFromInt(half_size.value[1]));
+
+ try path.append(math.Vec2(f32).init(x, y));
+ }
+
+ if (self.depth == .zbuffer and path.items.len > 2) {
+ const ndc_zs = try self.allocator.alloc(f32, path.items.len);
+ defer self.allocator.free(ndc_zs);
+
+ for (polygon.indices, 0..) |indice, i| {
+ const v = math.mat4x4.multVec(f32, mvp, polygon.vertices[indice]);
+ ndc_zs[i] = v.value[2] / v.value[3];
+ }
+
+ for (1..(path.items.len - 1)) |i| {
+ const tri = [3]usize{ 0, i, i + 1 };
+
+ const v0 = path.items[tri[0]];
+ const v1 = path.items[tri[1]];
+ const v2 = path.items[tri[2]];
+
+ const z0 = ndc_zs[tri[0]];
+ const z1 = ndc_zs[tri[1]];
+ const z2 = ndc_zs[tri[2]];
+
+ const min: math.Vec2(f32) = .{ .value = @min(@min(v0.value, v1.value), v2.value) };
+ const max: math.Vec2(f32) = .{ .value = @max(@Vector(2, f32){
+ @floatFromInt(size_min1.value[0]),
+ @floatFromInt(size_min1.value[1]),
+ }, @max(@max(v0.value, v1.value), v2.value)) };
+
+ for (@intFromFloat(min.value[1])..@intFromFloat(max.value[1] + 1)) |py| {
+ for (@intFromFloat(min.value[0])..@intFromFloat(max.value[0] + 1)) |px| {
+ const bary = math.mat4x4.computeBarycentric(f32, v0, v1, v2, @floatFromInt(px), @floatFromInt(py));
+ if (bary.value[0] < 0 or bary.value[1] < 0 or bary.value[2] < 0) continue;
+
+ const z = bary.value[0] * z0 + bary.value[1] * z1 + bary.value[2] * z2;
+ const index = py * size.value[0] + px;
+
+ if (z < self.depth.zbuffer.buffer[index]) {
+ self.depth.zbuffer.buffer[index] = z;
+ try self.context.render(.{ .@"2d" = .{ .path = .{
+ .value = &[_]math.Vec2(f32){math.Vec2(f32).init(@floatFromInt(px), @floatFromInt(py))},
+ .source = polygon.source,
+ .mode = .dots,
+ } } });
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ if (self.depth == .culling and path.items.len > 2) {
+ const p0 = path.items[0];
+ const p1 = path.items[1];
+ const p2 = path.items[2];
+
+ const d1 = p1.sub(p0);
+ const d2 = p2.sub(p0);
+
+ const signed_area = d1.value[0] * d2.value[1] - d1.value[1] * d2.value[0];
+ if (signed_area < 0) return;
+ }
+
+ try self.context.render(.{ .@"2d" = .{ .path = .{
+ .value = path.items,
+ .source = polygon.source,
+ .mode = .fill,
+ } } });
+ },
+ .setProjection => |proj| {
+ self.proj = proj.value;
+ },
+ .clear => |clear| {
+ if (clear.color) |color| {
+ try self.context.render(.{ .@"2d" = .{ .clear = .{ .color = color } } });
+ }
+
+ if (clear.depth) |depth| {
+ if (self.depth == .zbuffer) {
+ self.allocator.free(self.depth.zbuffer.buffer);
+ }
+
+ self.depth = switch (depth) {
+ .zbuffer => .{ .zbuffer = blk: {
+ const buff = try self.allocator.alloc(f32, self.getSize().value[0] * self.getSize().value[1]);
+ @memset(buff, 1.0);
+ break :blk .{ .buffer = buff };
+ } },
+ inline else => |value, tag| @unionInit(Depth, @tagName(tag), value),
+ };
+ }
+ },
+ }
+}
+
+pub fn destroy(self: *Self) void {
+ if (self.depth == .zbuffer) {
+ self.allocator.free(self.depth.zbuffer.buffer);
+ }
+ self.allocator.destroy(self);
+}
+
+fn vtable_getSize(ptr: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getSize();
+}
+
+fn vtable_render(ptr: *anyopaque, op: Context.@"3d".Operation) anyerror!void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.render(op);
+}
+
+fn vtable_destroy(ptr: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.destroy();
+}
+
+test {
+ const surface = try @import("../z2d.zig").Surface.create(std.testing.allocator, .rgba32, 800, 600);
+ defer surface.destroy();
+
+ const ctx2d = try surface.getContext(.@"2d");
+ defer ctx2d.destroy();
+
+ const ctx = try create(std.testing.allocator, ctx2d);
+ defer ctx.destroy();
+
+ try ctx.render(.{
+ .@"3d" = .{
+ .clear = .{
+ .color = graphics.Color.white,
+ .depth = .zbuffer,
+ },
+ },
+ });
+
+ try ctx.render(.{
+ .@"3d" = .{
+ .setProjection = .{
+ .value = math.mat4x4.perspective(
+ f32,
+ std.math.pi / 2.5,
+ @as(f32, @floatFromInt(surface.getSize().value[0])) / @as(f32, @floatFromInt(surface.getSize().value[1])),
+ 0.1,
+ 100,
+ ),
+ },
+ },
+ });
+
+ var cube: [8]math.Vec4(f32) = .{
+ math.Vec4(f32).init(-1, -1, -1, 1), math.Vec4(f32).init(1, -1, -1, 1),
+ math.Vec4(f32).init(1, 1, -1, 1), math.Vec4(f32).init(-1, 1, -1, 1),
+ math.Vec4(f32).init(-1, -1, 1, 1), math.Vec4(f32).init(1, -1, 1, 1),
+ math.Vec4(f32).init(1, 1, 1, 1), math.Vec4(f32).init(-1, 1, 1, 1),
+ };
+
+ var faces: [6][4]usize = .{
+ .{ 0, 1, 2, 3 }, // bottom face (z = -1)
+ .{ 7, 6, 5, 4 }, // top face (z = 1)
+ .{ 4, 5, 1, 0 }, // front face (y = -1)
+ .{ 3, 2, 6, 7 }, // back face (y = 1)
+ .{ 0, 3, 7, 4 }, // left face (x = -1)
+ .{ 5, 6, 2, 1 }, // right face (x = 1)
+ };
+
+ const colors: [6]graphics.Color = .{
+ graphics.Color.init(.{ 0.2, 0.6, 1.0, 1.0 }),
+ graphics.Color.init(.{ 0.6, 0.2, 1.0, 1.0 }),
+ graphics.Color.init(.{ 0.2, 1.0, 0.6, 1.0 }),
+ graphics.Color.init(.{ 1.0, 0.6, 0.2, 1.0 }),
+ graphics.Color.init(.{ 1.0, 0.2, 0.4, 1.0 }),
+ graphics.Color.init(.{ 1.0, 1.0, 0.3, 1.0 }),
+ };
+
+ const view = math.mat4x4.translate(f32, 0, 0, -4);
+ const model = math.mat4x4.mult(f32, math.mat4x4.rotateY(f32, std.math.pi / 6.0), math.mat4x4.rotateX(f32, std.math.pi / 8.0));
+
+ for (&faces, 0..) |*face, i| {
+ try ctx.render(.{
+ .@"3d" = .{
+ .polygon = .{
+ .vertices = &cube,
+ .source = .{ .color = colors[i] },
+ .indices = face,
+ .view = view,
+ .model = model,
+ },
+ },
+ });
+ }
+
+ var snap = try surface.snapshot();
+ defer snap.destroy();
+
+ const buffer = try std.testing.allocator.alloc(u8, 1024 * snap.img.imageByteSize());
+ defer std.testing.allocator.free(buffer);
+
+ const buffer_hash = std.zig.hashSrc(try snap.img.writeToMemory(buffer, .{ .png = .{} }));
+
+ try std.testing.expectEqualSlices(u8, &[_]u8{
+ 0x75, 0xAB, 0xE9, 0xA6, 0xA7, 0xB5, 0xF8, 0xC0, 0x83, 0x5D, 0xE2, 0x9F, 0xEA, 0x12, 0x13, 0xB3,
+ }, &buffer_hash);
+}
diff --git a/lib/phantom/graphics/backend/z2d.zig b/lib/phantom/graphics/backend/z2d.zig
new file mode 100644
index 0000000..c750d1c
--- /dev/null
+++ b/lib/phantom/graphics/backend/z2d.zig
@@ -0,0 +1,9 @@
+//! A 2-D graphics backend backed by z2d.
+
+pub const Context = @import("z2d/Context.zig");
+pub const Surface = @import("z2d/Surface.zig");
+
+test {
+ _ = Context;
+ _ = Surface;
+}
diff --git a/lib/phantom/graphics/backend/z2d/Context.zig b/lib/phantom/graphics/backend/z2d/Context.zig
new file mode 100644
index 0000000..47941c6
--- /dev/null
+++ b/lib/phantom/graphics/backend/z2d/Context.zig
@@ -0,0 +1,287 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const math = @import("../../../math.zig");
+const Context = @import("../../Context.zig");
+const z2d = @import("z2d");
+const Self = @This();
+
+base: Context.@"2d",
+context: z2d.Context,
+
+const vtable: Context.@"2d".VTable = .{
+ .getSize = vtable_getSize,
+ .render = vtable_render,
+ .destroy = vtable_destroy,
+};
+
+pub fn create(alloc: Allocator, surface: *z2d.Surface) Allocator.Error!*Context {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .base = Context.@"2d".init(&self.base, self, &vtable),
+ .context = z2d.Context.init(alloc, surface),
+ };
+ return &self.base.base;
+}
+
+pub fn getSize(self: *Self) math.Vec2(usize) {
+ return math.Vec2(usize).init(
+ @intCast(self.context.surface.getWidth()),
+ @intCast(self.context.surface.getHeight()),
+ );
+}
+
+pub fn render(self: *Self, op: Context.@"2d".Operation) anyerror!void {
+ switch (op) {
+ .rect => |rect| {
+ self.context.resetPath();
+
+ self.context.setSource(switch (rect.source) {
+ .color => |color| .{ .opaque_pattern = .{ .pixel = .{ .rgba = z2d.pixel.RGBA.fromClamped(
+ color.value[0],
+ color.value[1],
+ color.value[2],
+ color.value[3],
+ ) } } },
+ .gradient => |gradient| .{ .gradient = @constCast(&gradient.value) },
+ else => return error.Unsupported,
+ });
+
+ const top_right_radius = rect.border_radius.top_right orelse 0.0;
+ const top_left_radius = rect.border_radius.top_left orelse 0.0;
+
+ const bottom_right_radius = rect.border_radius.bottom_right orelse 0.0;
+ const bottom_left_radius = rect.border_radius.bottom_left orelse 0.0;
+
+ const degrees = std.math.pi / 180.0;
+
+ const x = rect.rect.position.value[0];
+ const y = rect.rect.position.value[1];
+ const width = rect.rect.size.value[0];
+ const height = rect.rect.size.value[1];
+
+ if (top_right_radius > 0.0) {
+ try self.context.arc(
+ x + width - top_right_radius,
+ y + top_right_radius,
+ top_right_radius,
+ -90 * degrees,
+ 0 * degrees,
+ );
+ } else {
+ try self.context.lineTo(x + width, y);
+ }
+
+ // Bottom-right corner
+ if (bottom_right_radius > 0.0) {
+ try self.context.arc(
+ x + width - bottom_right_radius,
+ y + height - bottom_right_radius,
+ bottom_right_radius,
+ 0 * degrees,
+ 90 * degrees,
+ );
+ } else {
+ try self.context.lineTo(x + width, y + height);
+ }
+
+ // Bottom-left corner
+ if (bottom_left_radius > 0.0) {
+ try self.context.arc(
+ x + bottom_left_radius,
+ y + height - bottom_left_radius,
+ bottom_left_radius,
+ 90 * degrees,
+ 180 * degrees,
+ );
+ } else {
+ try self.context.lineTo(x, y + height);
+ }
+
+ // Top-left corner
+ if (top_left_radius > 0.0) {
+ try self.context.arc(
+ x + top_left_radius,
+ y + top_left_radius,
+ top_left_radius,
+ 180 * degrees,
+ 270 * degrees,
+ );
+ } else {
+ try self.context.lineTo(x, y);
+ }
+
+ try self.context.closePath();
+
+ switch (rect.mode) {
+ .fill => try self.context.fill(),
+ .stroke => |stroke| {
+ self.context.setLineWidth(stroke.width);
+ try self.context.stroke();
+ },
+ }
+ },
+ .path => |path| {
+ self.context.resetPath();
+
+ self.context.setSource(switch (path.source) {
+ .color => |color| .{ .opaque_pattern = .{ .pixel = .{ .rgba = z2d.pixel.RGBA.fromClamped(
+ color.value[0],
+ color.value[1],
+ color.value[2],
+ color.value[3],
+ ) } } },
+ .gradient => |gradient| .{ .gradient = @constCast(&gradient.value) },
+ else => return error.Unsupported,
+ });
+
+ if (path.mode == .dots) {
+ for (path.value) |e| {
+ const x: i32 = @intFromFloat(e.value[0]);
+ const y: i32 = @intFromFloat(e.value[1]);
+ self.context.surface.putPixel(x, y, self.context.pattern.getPixel(x, y));
+ }
+ } else {
+ for (path.value, 0..) |e, i| {
+ if (i == 0) {
+ try self.context.moveTo(e.value[0], e.value[1]);
+ } else {
+ try self.context.lineTo(e.value[0], e.value[1]);
+ }
+ }
+
+ try self.context.closePath();
+ }
+
+ switch (path.mode) {
+ .fill => try self.context.fill(),
+ .stroke => |stroke| {
+ self.context.setLineWidth(stroke.width);
+ try self.context.stroke();
+ },
+ else => {},
+ }
+ },
+ .composite => |comp| {
+ const size = math.Vec2(usize){ .value = switch (comp.source) {
+ .img => |img| .{ img.width, img.height },
+ .color => .{ 1, 1 },
+ .gradient => .{ 1, 1 },
+ } };
+
+ var src = try z2d.Surface.init(.image_surface_rgba, self.context.alloc, @intCast(size.value[0]), @intCast(size.value[1]));
+ defer src.deinit(self.context.alloc);
+
+ switch (comp.source) {
+ .img => |img| {
+ var it = img.iterator();
+
+ var x: usize = 0;
+ var y: usize = 0;
+
+ while (it.next()) |color| {
+ const rgba32 = color.toRgba32();
+ src.putPixel(@intCast(x), @intCast(y), .{ .rgba = .{
+ .r = rgba32.r,
+ .g = rgba32.g,
+ .b = rgba32.b,
+ .a = rgba32.a,
+ } });
+
+ x += 1;
+ if (x > img.width) {
+ x = 0;
+ y += 1;
+ if (y >= img.height) y = 0;
+ }
+ }
+ },
+ .color => |color| {
+ const width: usize = @intCast(src.getWidth());
+ const height: usize = @intCast(src.getHeight());
+
+ for (0..width) |x| {
+ for (0..height) |y| {
+ src.putPixel(@intCast(x), @intCast(y), .{ .rgba = .{
+ .r = @intFromFloat(color.value[0] * 255.0),
+ .g = @intFromFloat(color.value[1] * 255.0),
+ .b = @intFromFloat(color.value[2] * 255.0),
+ .a = @intFromFloat(color.value[3] * 255.0),
+ } });
+ }
+ }
+ },
+ .gradient => |gradient| {
+ const width: usize = @intCast(src.getWidth());
+ const height: usize = @intCast(src.getHeight());
+
+ for (0..width) |x| {
+ for (0..height) |y| {
+ src.putPixel(@intCast(x), @intCast(y), gradient.value.getPixel(@intCast(x), @intCast(y)));
+ }
+ }
+ },
+ }
+
+ self.context.surface.composite(
+ &src,
+ switch (comp.mode) {
+ .in => .src_in,
+ .over => .src_over,
+ },
+ @intFromFloat(comp.position.value[0] * @as(f32, @floatFromInt(self.context.surface.getWidth()))),
+ @intFromFloat(comp.position.value[1] * @as(f32, @floatFromInt(self.context.surface.getHeight()))),
+ .{},
+ );
+ },
+ .setTransform => |trans| {
+ self.context.setTransformation(.{
+ .ax = trans.value.value[0],
+ .by = trans.value.value[1],
+ .tx = trans.value.value[2],
+ .cx = trans.value.value[3],
+ .dy = trans.value.value[4],
+ .ty = trans.value.value[5],
+ });
+ },
+ .clear => |clear| {
+ self.context.setSourceToPixel(.{ .rgba = .{
+ .r = @intFromFloat(clear.color.value[0] * 255.0),
+ .g = @intFromFloat(clear.color.value[1] * 255.0),
+ .b = @intFromFloat(clear.color.value[2] * 255.0),
+ .a = @intFromFloat(clear.color.value[3] * 255.0),
+ } });
+
+ const size = self.getSize();
+
+ try self.context.moveTo(0, 0);
+ try self.context.lineTo(@floatFromInt(size.value[0]), 0);
+ try self.context.lineTo(@floatFromInt(size.value[0]), @floatFromInt(size.value[1]));
+ try self.context.lineTo(0, @floatFromInt(size.value[1]));
+ try self.context.closePath();
+
+ try self.context.fill();
+ },
+ }
+}
+
+pub fn destroy(self: *Self) void {
+ self.context.deinit();
+ self.context.alloc.destroy(self);
+}
+
+fn vtable_getSize(ptr: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getSize();
+}
+
+fn vtable_render(ptr: *anyopaque, op: Context.@"2d".Operation) anyerror!void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.render(op);
+}
+
+fn vtable_destroy(ptr: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.destroy();
+}
diff --git a/lib/phantom/graphics/backend/z2d/Surface.zig b/lib/phantom/graphics/backend/z2d/Surface.zig
new file mode 100644
index 0000000..e03f56a
--- /dev/null
+++ b/lib/phantom/graphics/backend/z2d/Surface.zig
@@ -0,0 +1,177 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const math = @import("../../../math.zig");
+const graphics = @import("../../../graphics.zig");
+const Context = @import("../../Context.zig");
+const Surface = @import("../../Surface.zig");
+const z2d = @import("z2d");
+const zigimg = @import("zigimg");
+const SelfContext = @import("Context.zig");
+const Self = @This();
+
+allocator: Allocator,
+base: Surface,
+surface: z2d.Surface,
+context: ?*Context,
+
+const vtable: Surface.VTable = .{
+ .getSize = vtable_getSize,
+ .getFormat = vtable_getFormat,
+ .getBuffer = vtable_getBuffer,
+ .getContext = vtable_getContext,
+ .snapshot = vtable_snapshot,
+ .destroy = vtable_destroy,
+};
+
+pub fn create(alloc: Allocator, format: graphics.Format, width: usize, height: usize) !*Surface {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .base = .{
+ .ptr = self,
+ .vtable = &vtable,
+ },
+ .surface = try z2d.Surface.init(switch (format) {
+ .rgba32 => .image_surface_rgba,
+ .rgbx32 => .image_surface_rgb,
+ else => return error.InvalidFormat,
+ }, alloc, @intCast(width), @intCast(height)),
+ .context = null,
+ };
+ return &self.base;
+}
+
+pub fn getSize(self: *const Self) math.Vec2(usize) {
+ return math.Vec2(usize).init(
+ @intCast(self.surface.getWidth()),
+ @intCast(self.surface.getHeight()),
+ );
+}
+
+pub fn getFormat(self: *const Self) ?graphics.Format {
+ return switch (self.surface.getFormat()) {
+ .rgba => .rgba32,
+ .rgb => .rgbx32,
+ else => null,
+ };
+}
+
+pub fn getBuffer(self: *const Self) ?[]const u8 {
+ return switch (self.surface) {
+ inline else => |t| @ptrCast(@alignCast(t.buf[0..@intCast(t.width * t.height)])),
+ };
+}
+
+pub fn getContext(self: *Self, kind: Context.Kind) Context.Error!*Context {
+ if (self.context) |ctx| return ctx;
+ if (kind != .@"2d") return error.InvalidKind;
+
+ const ctx = try SelfContext.create(self.allocator, &self.surface);
+ errdefer ctx.destroy();
+ self.context = ctx;
+ return ctx;
+}
+
+pub fn snapshot(self: *Self) !graphics.Source {
+ return .{
+ .img = try zigimg.Image.fromRawPixels(
+ self.allocator,
+ @intCast(self.surface.getWidth()),
+ @intCast(self.surface.getHeight()),
+ self.getBuffer() orelse unreachable,
+ (self.getFormat() orelse return error.InvalidFormat).asZigimg(),
+ ),
+ };
+}
+
+pub fn destroy(self: *Self) void {
+ self.surface.deinit(self.allocator);
+ self.allocator.destroy(self);
+}
+
+fn vtable_getSize(ptr: *anyopaque) math.Vec2(usize) {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getSize();
+}
+
+fn vtable_getFormat(ptr: *anyopaque) ?graphics.Format {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getFormat();
+}
+
+fn vtable_getBuffer(ptr: *anyopaque) ?[]const u8 {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getBuffer();
+}
+
+fn vtable_getContext(ptr: *anyopaque, kind: Context.Kind) Context.Error!*Context {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.getContext(kind);
+}
+
+fn vtable_snapshot(ptr: *anyopaque) anyerror!graphics.Source {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.snapshot();
+}
+
+fn vtable_destroy(ptr: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.destroy();
+}
+
+test {
+ const surface = try create(std.testing.allocator, .rgba32, 100, 100);
+ defer surface.destroy();
+
+ try std.testing.expect(surface.getBuffer() != null);
+ try std.testing.expectEqual(@Vector(2, usize){ 100, 100 }, surface.getSize().value);
+ try std.testing.expectEqual(.rgba32, surface.getFormat());
+
+ const ctx = try surface.getContext(.@"2d");
+ defer ctx.destroy();
+
+ try ctx.render(.{
+ .@"2d" = .{
+ .rect = .{
+ .source = .{ .color = graphics.Color.init(.{ 1, 0, 0, 1 }) },
+ .rect = math.Rect(f32).init(.{ 20, 20 }, .{ 50, 50 }),
+ .mode = .fill,
+ },
+ },
+ });
+
+ try ctx.render(.{
+ .@"2d" = .{
+ .rect = .{
+ .source = .{ .color = graphics.Color.init(.{ 0, 1, 0, 1 }) },
+ .rect = math.Rect(f32).init(.{ 15, 15 }, .{ 25, 25 }),
+ .mode = .{ .stroke = .{ .width = 6.0 } },
+ },
+ },
+ });
+
+ try ctx.render(.{
+ .@"2d" = .{
+ .rect = .{
+ .source = .{ .color = graphics.Color.init(.{ 0, 0, 1, 1 }) },
+ .rect = math.Rect(f32).init(.{ 50, 15 }, .{ 25, 25 }),
+ .mode = .{ .stroke = .{ .width = 6.0 } },
+ .border_radius = graphics.Context.@"2d".Operation.Rect.BorderRadius.all(0.6),
+ },
+ },
+ });
+
+ var snap = try surface.snapshot();
+ defer snap.destroy();
+
+ const buffer = try std.testing.allocator.alloc(u8, 1024 * snap.img.imageByteSize());
+ defer std.testing.allocator.free(buffer);
+
+ const buffer_hash = std.zig.hashSrc(try snap.img.writeToMemory(buffer, .{ .png = .{} }));
+
+ try std.testing.expectEqualSlices(u8, &[_]u8{
+ 0xF2, 0x05, 0xB9, 0xDA, 0x1E, 0xF3, 0x5B, 0x37, 0xF7, 0x69, 0x30, 0xFF, 0x13, 0x63, 0xF1, 0x1D,
+ }, &buffer_hash);
+}
diff --git a/lib/phantom/math.zig b/lib/phantom/math.zig
new file mode 100644
index 0000000..6272567
--- /dev/null
+++ b/lib/phantom/math.zig
@@ -0,0 +1,270 @@
+//! Math graphics types
+
+const std = @import("std");
+const expectEqual = std.testing.expectEqual;
+
+/// Base vector implementation
+pub fn Vec(comptime Size: comptime_int, comptime T: type, comptime FactoryFunc: fn (comptime Self: type) type) type {
+ return struct {
+ const Self = @This();
+
+ value: Value,
+
+ pub const Value = @Vector(Size, T);
+ pub const init = FactoryFunc(Self).init;
+
+ pub inline fn addValue(self: Self, other: Value) Self {
+ return .{ .value = self.value + other };
+ }
+
+ pub inline fn add(self: Self, other: Self) Self {
+ return self.addValue(other.value);
+ }
+
+ pub inline fn subValue(self: Self, other: Value) Self {
+ return .{ .value = self.value - other };
+ }
+
+ pub inline fn sub(self: Self, other: Self) Self {
+ return self.subValue(other.value);
+ }
+
+ pub inline fn mulValue(self: Self, other: Value) Self {
+ return .{ .value = self.value * other };
+ }
+
+ pub inline fn mul(self: Self, other: Self) Self {
+ return self.mulValue(other.value);
+ }
+
+ pub inline fn divValue(self: Self, other: Value) Self {
+ return .{ .value = self.value / other };
+ }
+
+ pub inline fn div(self: Self, other: Self) Self {
+ return self.divValue(other.value);
+ }
+
+ pub const zero: Self = .{
+ .value = [_]T{0} ** Size,
+ };
+
+ test {
+ _ = FactoryFunc(Self);
+ }
+ };
+}
+
+/// Vector with 2 elements
+pub fn Vec2(comptime T: type) type {
+ return Vec(2, T, (struct {
+ fn FactoryFunc(comptime Self: type) type {
+ return struct {
+ pub fn init(x: T, y: T) Self {
+ return .{ .value = .{ x, y } };
+ }
+
+ test {
+ const vec = init(1, 2);
+ try expectEqual(Self.Value{ 2, 4 }, vec.add(vec).value);
+ try expectEqual(Self.zero.value, vec.sub(vec).value);
+ }
+ };
+ }
+ }).FactoryFunc);
+}
+
+/// Vector with 3 elements
+pub fn Vec3(comptime T: type) type {
+ return Vec(3, T, (struct {
+ fn FactoryFunc(comptime Self: type) type {
+ return struct {
+ pub fn init(x: T, y: T, z: T) Self {
+ return .{ .value = .{ x, y, z } };
+ }
+
+ test {
+ const vec = init(1, 2, 3);
+ try expectEqual(Self.Value{ 2, 4, 6 }, vec.add(vec).value);
+ try expectEqual(Self.zero.value, vec.sub(vec).value);
+ }
+ };
+ }
+ }).FactoryFunc);
+}
+
+/// Vector with 4 elements
+pub fn Vec4(comptime T: type) type {
+ return Vec(4, T, (struct {
+ fn FactoryFunc(comptime Self: type) type {
+ return struct {
+ pub fn init(x: T, y: T, z: T, w: T) Self {
+ return .{ .value = .{ x, y, z, w } };
+ }
+
+ test {
+ const vec = init(1, 2, 3, 4);
+ try expectEqual(Self.Value{ 2, 4, 6, 8 }, vec.add(vec).value);
+ try expectEqual(Self.zero.value, vec.sub(vec).value);
+ }
+ };
+ }
+ }).FactoryFunc);
+}
+
+/// Base matrix implementation
+pub fn Mat(comptime MatSize: comptime_int, comptime VecSize: comptime_int, comptime T: type, comptime FactoryFunc: fn (comptime Self: type) type) type {
+ return Vec(MatSize * VecSize, T, FactoryFunc);
+}
+
+/// 3x3 matrix
+pub fn Mat3x3(comptime T: type) type {
+ return Mat(3, 3, T, (struct {
+ fn FactoryFunc(comptime Self: type) type {
+ return struct {
+ pub fn init(
+ x0: T,
+ y0: T,
+ z0: T,
+ x1: T,
+ y1: T,
+ z1: T,
+ x2: T,
+ y2: T,
+ z2: T,
+ ) Self {
+ return .{ .value = .{
+ x0,
+ y0,
+ z0,
+ x1,
+ y1,
+ z1,
+ x2,
+ y2,
+ z2,
+ } };
+ }
+ };
+ }
+ }).FactoryFunc);
+}
+
+/// 4x4 matrix
+pub fn Mat4x4(comptime T: type) type {
+ return Mat(4, 4, T, (struct {
+ fn FactoryFunc(comptime Self: type) type {
+ return struct {
+ pub fn init(
+ x0: T,
+ y0: T,
+ z0: T,
+ w0: T,
+ x1: T,
+ y1: T,
+ z1: T,
+ w1: T,
+ x2: T,
+ y2: T,
+ z2: T,
+ w2: T,
+ x3: T,
+ y3: T,
+ z3: T,
+ w3: T,
+ ) Self {
+ return .{ .value = .{
+ x0,
+ y0,
+ z0,
+ w0,
+ x1,
+ y1,
+ z1,
+ w1,
+ x2,
+ y2,
+ z2,
+ w2,
+ x3,
+ y3,
+ z3,
+ w3,
+ } };
+ }
+ };
+ }
+ }).FactoryFunc);
+}
+
+/// A rectangle
+pub fn Rect(comptime T: type) type {
+ return struct {
+ const Self = @This();
+ const Value = Vec2(T);
+
+ position: Value,
+ size: Value,
+
+ pub inline fn init(pos: Value.Value, size: Value.Value) Self {
+ return .{
+ .position = .{ .value = pos },
+ .size = .{ .value = size },
+ };
+ }
+
+ /// Gets the point at the absolute center.
+ pub inline fn center(self: Self) Value {
+ return self.position.add(self.size.divValue(.{ 2, 2 }));
+ }
+
+ /// Gets the point at the top left.
+ pub inline fn topLeft(self: Self) Value {
+ return self.position;
+ }
+
+ /// Gets the point at the top right.
+ pub inline fn topRight(self: Self) Value {
+ return self.position.addValue(.{ self.size.value[0], 0 });
+ }
+
+ /// Gets the point at the bottom left.
+ pub inline fn bottomLeft(self: Self) Value {
+ return self.position.addValue(.{ 0, self.size.value[1] });
+ }
+
+ /// Gets the point at the bottom right.
+ pub inline fn bottomRight(self: Self) Value {
+ return self.position.add(self.size);
+ }
+
+ test {
+ const rect = init(.{ 100, 100 }, .{ 100, 100 });
+
+ try expectEqual(Value.Value{ 150, 150 }, rect.center().value);
+
+ try expectEqual(Value.Value{ 100, 100 }, rect.topLeft().value);
+ try expectEqual(Value.Value{ 200, 100 }, rect.topRight().value);
+
+ try expectEqual(Value.Value{ 100, 200 }, rect.bottomLeft().value);
+ try expectEqual(Value.Value{ 200, 200 }, rect.bottomRight().value);
+ }
+ };
+}
+
+pub const mat4x4 = @import("math/mat4x4.zig");
+pub const vec4 = @import("math/vec4.zig");
+
+test {
+ inline for (&.{ usize, isize, f32, f64, f128 }) |T| {
+ _ = Rect(T);
+ _ = Vec2(T);
+ _ = Vec3(T);
+ _ = Vec4(T);
+ _ = Mat3x3(T);
+ _ = Mat4x4(T);
+ }
+
+ _ = mat4x4;
+ _ = vec4;
+}
diff --git a/lib/phantom/math/mat4x4.zig b/lib/phantom/math/mat4x4.zig
new file mode 100644
index 0000000..3c8c6d5
--- /dev/null
+++ b/lib/phantom/math/mat4x4.zig
@@ -0,0 +1,94 @@
+const math = @import("../math.zig");
+const Mat4x4 = math.Mat4x4;
+const Vec4 = math.Vec4;
+
+pub fn identity(comptime T: type) Mat4x4(T) {
+ var self = Mat4x4(T).zero;
+ self.value[0] = 1.0;
+ self.value[5] = 1.0;
+ self.value[10] = 1.0;
+ self.value[15] = 1.0;
+ return self;
+}
+
+pub fn viewport(comptime T: type, width: T, height: T) Mat4x4(T) {
+ var self = Mat4x4(T).zero;
+ self.value[0] = width / 2;
+ self.value[3] = width / 2;
+ self.value[5] = -height / 2;
+ self.value[7] = height / 2;
+ return self;
+}
+
+pub fn perspective(comptime T: type, fov: T, aspect: T, near: T, far: T) Mat4x4(T) {
+ const f: T = 1 / @tan(fov / 2);
+
+ var self = Mat4x4(T).zero;
+ self.value[0] = f / aspect;
+ self.value[5] = f;
+ self.value[10] = (far + near) / (near - far);
+ self.value[11] = (2 * far * near) / (near - far);
+ self.value[14] = -1;
+ return self;
+}
+
+pub fn translate(comptime T: type, x: T, y: T, z: T) Mat4x4(T) {
+ var self = identity(T);
+ self.value[3] = x;
+ self.value[7] = y;
+ self.value[11] = z;
+ return self;
+}
+
+pub fn rotateX(comptime T: type, angle: T) Mat4x4(T) {
+ var self = identity(T);
+ self.value[5] = @cos(angle);
+ self.value[6] = -@sin(angle);
+ self.value[9] = @sin(angle);
+ self.value[10] = @cos(angle);
+ return self;
+}
+
+pub fn rotateY(comptime T: type, angle: T) Mat4x4(T) {
+ var self = identity(T);
+ self.value[0] = @cos(angle);
+ self.value[2] = @sin(angle);
+ self.value[8] = -@sin(angle);
+ self.value[10] = @cos(angle);
+ return self;
+}
+
+pub fn multVec(comptime T: type, mat: Mat4x4(T), vec: Vec4(T)) Vec4(T) {
+ var self = Vec4(T).zero;
+ self.value[0] = (mat.value[0] * vec.value[0]) + (mat.value[1] * vec.value[1]) + (mat.value[2] * vec.value[2]) + (mat.value[3] * vec.value[3]);
+ self.value[1] = (mat.value[4] * vec.value[0]) + (mat.value[5] * vec.value[1]) + (mat.value[6] * vec.value[2]) + (mat.value[7] * vec.value[3]);
+ self.value[2] = (mat.value[8] * vec.value[0]) + (mat.value[9] * vec.value[1]) + (mat.value[10] * vec.value[2]) + (mat.value[11] * vec.value[3]);
+ self.value[3] = (mat.value[12] * vec.value[0]) + (mat.value[13] * vec.value[1]) + (mat.value[14] * vec.value[2]) + (mat.value[15] * vec.value[3]);
+ return self;
+}
+
+pub fn mult(comptime T: type, a: Mat4x4(T), b: Mat4x4(T)) Mat4x4(T) {
+ var self = Mat4x4(T).zero;
+ for (0..4) |row| {
+ for (0..4) |col| {
+ var sum: T = 0;
+ for (0..4) |k| {
+ const a_index = row * 4 + k;
+ const b_index = k * 4 + col;
+
+ sum += a.value[a_index] * b.value[b_index];
+ }
+ self.value[row * 4 + col] = sum;
+ }
+ }
+ return self;
+}
+
+pub fn computeBarycentric(comptime T: type, p0: math.Vec2(T), p1: math.Vec2(T), p2: math.Vec2(T), px: T, py: T) math.Vec3(T) {
+ const denom = (p1.value[1] - p2.value[1]) * (p0.value[0] - p2.value[0]) +
+ (p2.value[0] - p1.value[0]) * (p0.value[1] - p2.value[1]);
+ const w0 = ((p1.value[1] - p2.value[1]) * (px - p2.value[0]) + (p2.value[0] - p1.value[0]) * (py - p2.value[1])) / denom;
+ const w1 = ((p2.value[1] - p0.value[1]) * (px - p2.value[0]) + (p0.value[0] - p2.value[0]) * (py - p2.value[1])) / denom;
+ const w2 = 1.0 - w0 - w1;
+ return math.Vec3(T).init(w0, w1, w2);
+}
diff --git a/lib/phantom/math/vec4.zig b/lib/phantom/math/vec4.zig
new file mode 100644
index 0000000..866203e
--- /dev/null
+++ b/lib/phantom/math/vec4.zig
@@ -0,0 +1,26 @@
+const std = @import("std");
+const Vec4 = @import("../math.zig").Vec4;
+
+pub fn cross(comptime T: type, a: Vec4(T), b: Vec4(T)) Vec4(T) {
+ return Vec4(T).init(
+ a.value[1] * b.value[2] - a.value[2] * b.value[1],
+ a.value[2] * b.value[0] - a.value[0] * b.value[2],
+ a.value[0] * b.value[1] - a.value[1] * b.value[0],
+ 0,
+ );
+}
+
+pub fn length(comptime T: type, v: Vec4(T)) T {
+ return std.math.sqrt(v.value[0] * v.value[0] + v.value[1] * v.value[1] + v.value[2] * v.value[2]);
+}
+
+pub fn normalize(comptime T: type, v: Vec4(T)) Vec4(T) {
+ const len = length(T, v);
+ if (len == 0) return v;
+ return Vec4(T).init(
+ v.value[0] / len,
+ v.value[1] / len,
+ v.value[2] / len,
+ 0,
+ );
+}
diff --git a/lib/phantom/scene.zig b/lib/phantom/scene.zig
new file mode 100644
index 0000000..4eeae7a
--- /dev/null
+++ b/lib/phantom/scene.zig
@@ -0,0 +1,33 @@
+//! Scene based rendering API
+
+pub const Properties = @import("scene/Properties.zig");
+pub const Renderer = @import("scene/Renderer.zig");
+
+pub const Node = union(enum) {
+ container: Container,
+ image: Image,
+ text: Text,
+
+ pub const Container = @import("scene/Node/Container.zig");
+ pub const Image = @import("scene/Node/Image.zig");
+ pub const Text = @import("scene/Node/Text.zig");
+
+ pub fn getChildren(self: Node) []const Node {
+ return switch (self) {
+ .container => |container| container.children,
+ else => &.{},
+ };
+ }
+
+ test {
+ _ = Container;
+ _ = Image;
+ _ = Text;
+ }
+};
+
+test {
+ _ = Properties;
+ _ = Renderer;
+ _ = Node;
+}
diff --git a/lib/phantom/scene/Node/Container.zig b/lib/phantom/scene/Node/Container.zig
new file mode 100644
index 0000000..039d953
--- /dev/null
+++ b/lib/phantom/scene/Node/Container.zig
@@ -0,0 +1,5 @@
+const scene = @import("../../scene.zig");
+
+children: []const scene.Node,
+layout: scene.Properties.Layout,
+style: scene.Properties.Style,
diff --git a/lib/phantom/scene/Node/Image.zig b/lib/phantom/scene/Node/Image.zig
new file mode 100644
index 0000000..8a8d41f
--- /dev/null
+++ b/lib/phantom/scene/Node/Image.zig
@@ -0,0 +1,3 @@
+const graphics = @import("../../graphics.zig");
+
+source: graphics.Source,
diff --git a/lib/phantom/scene/Node/Text.zig b/lib/phantom/scene/Node/Text.zig
new file mode 100644
index 0000000..cd9c730
--- /dev/null
+++ b/lib/phantom/scene/Node/Text.zig
@@ -0,0 +1,5 @@
+const math = @import("../../math.zig");
+
+text: []const u8,
+font_size: math.Vec2(usize),
+font: []const u8,
diff --git a/lib/phantom/scene/Properties.zig b/lib/phantom/scene/Properties.zig
new file mode 100644
index 0000000..a82e696
--- /dev/null
+++ b/lib/phantom/scene/Properties.zig
@@ -0,0 +1,13 @@
+const math = @import("../math.zig");
+const graphics = @import("../graphics.zig");
+
+pub const Layout = struct {
+ position: ?math.Vec2(f32) = null,
+ size: ?math.Vec2(f32) = null,
+};
+
+pub const Style = struct {
+ background_color: ?graphics.Color = null,
+ foreground_color: ?graphics.Color = null,
+ border_radius: ?graphics.Context.@"2d".Operation.Rect.BorderRadius = null,
+};
diff --git a/lib/phantom/scene/Renderer.zig b/lib/phantom/scene/Renderer.zig
new file mode 100644
index 0000000..03139bd
--- /dev/null
+++ b/lib/phantom/scene/Renderer.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const math = @import("../math.zig");
+const scene = @import("../scene.zig");
+const Renderer = @This();
+
+pub const Canvas = @import("Renderer/Canvas.zig");
+pub const Html = @import("Renderer/Html.zig");
+
+pub const VTable = struct {
+ render: *const fn (self: *anyopaque, node: scene.Node) anyerror!void,
+ destroy: *const fn (self: *anyopaque) void,
+};
+
+ptr: *anyopaque,
+vtable: *const VTable,
+
+pub inline fn render(self: *const Renderer, node: scene.Node) anyerror!void {
+ return self.vtable.render(self.ptr, node);
+}
+
+pub inline fn destroy(self: *const Renderer) void {
+ return self.vtable.destroy(self.ptr);
+}
+
+test {
+ _ = Canvas;
+ _ = Html;
+}
diff --git a/lib/phantom/scene/Renderer/Canvas.zig b/lib/phantom/scene/Renderer/Canvas.zig
new file mode 100644
index 0000000..d3cd7e9
--- /dev/null
+++ b/lib/phantom/scene/Renderer/Canvas.zig
@@ -0,0 +1,123 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const graphics = @import("../../graphics.zig");
+const math = @import("../../math.zig");
+const SceneNode = @import("../../scene.zig").Node;
+const Renderer = @import("../Renderer.zig");
+const Self = @This();
+
+allocator: std.mem.Allocator,
+base: Renderer,
+context: *graphics.Context,
+
+const vtable: Renderer.VTable = .{
+ .render = vtable_render,
+ .destroy = vtable_destroy,
+};
+
+pub fn create(alloc: Allocator, context: *graphics.Context) Allocator.Error!*Renderer {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .base = .{
+ .ptr = self,
+ .vtable = &vtable,
+ },
+ .context = context,
+ };
+ return &self.base;
+}
+
+fn renderChild(self: *Self, node: SceneNode, offset: math.Vec2(f32)) !void {
+ const ctx = self.context.as(.@"2d");
+
+ switch (node) {
+ .container => |container| {
+ const pos = offset.add(container.layout.position orelse math.Vec2(f32).zero);
+ const size = container.layout.size orelse math.Vec2(f32).zero;
+
+ try ctx.render(.{
+ .rect = .{
+ .source = .{ .color = container.style.background_color orelse graphics.Color.black },
+ .border_radius = container.style.border_radius orelse .{},
+ .rect = math.Rect(f32).init(pos.value, size.value),
+ .mode = .fill,
+ },
+ });
+
+ for (container.children) |child| {
+ try self.renderChild(child, pos);
+ }
+ },
+ .image => |image| {
+ try ctx.render(.{
+ .composite = .{
+ .source = image.source,
+ .position = offset,
+ .mode = .over,
+ },
+ });
+ },
+ else => return error.Unsupported,
+ }
+}
+
+pub fn render(self: *Self, node: SceneNode) anyerror!void {
+ try self.renderChild(node, math.Vec2(f32).zero);
+}
+
+pub fn destroy(self: *Self) void {
+ self.allocator.destroy(self);
+}
+
+fn vtable_render(ptr: *anyopaque, node: SceneNode) anyerror!void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.render(node);
+}
+
+fn vtable_destroy(ptr: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.destroy();
+}
+
+test {
+ const surface = try graphics.backend.z2d.Surface.create(std.testing.allocator, .rgba32, 100, 100);
+ defer surface.destroy();
+
+ const context = try surface.getContext(.@"2d");
+ defer context.destroy();
+
+ const renderer = try create(std.testing.allocator, context);
+ defer renderer.destroy();
+
+ try renderer.render(.{
+ .container = .{
+ .style = .{
+ .background_color = graphics.Color.init(.{ 0.14, 0.15, 0.23, 1.0 }),
+ .foreground_color = graphics.Color.init(.{ 0.0, 0.0, 0.0, 1.0 }),
+ },
+ .layout = .{
+ .size = .{ .value = .{ 100, 100 } },
+ },
+ .children = &.{},
+ },
+ });
+
+ var snap = try surface.snapshot();
+ defer snap.destroy();
+
+ // TODO: figure out how to compare the output to what's expected.
+ // For now, we have to manually verify this.
+
+ var tmpdir = std.testing.tmpDir(.{});
+ defer tmpdir.cleanup();
+
+ var file = try tmpdir.dir.createFile("snapshot.png", .{});
+ defer file.close();
+
+ try snap.img.writeToFile(file, .{
+ .png = .{},
+ });
+}
diff --git a/lib/phantom/scene/Renderer/Html.zig b/lib/phantom/scene/Renderer/Html.zig
new file mode 100644
index 0000000..c5b8e2a
--- /dev/null
+++ b/lib/phantom/scene/Renderer/Html.zig
@@ -0,0 +1,166 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const graphics = @import("../../graphics.zig");
+const math = @import("../../math.zig");
+const SceneNode = @import("../../scene.zig").Node;
+const Renderer = @import("../Renderer.zig");
+
+fn fmtColor(color: graphics.Color, writer: anytype) !void {
+ try writer.writeAll("rgba(");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(color.value[0] * 255)), 10, .lower, .{}, writer);
+
+ try writer.writeAll(", ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(color.value[1] * 255)), 10, .lower, .{}, writer);
+
+ try writer.writeAll(", ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(color.value[2] * 255)), 10, .lower, .{}, writer);
+
+ try writer.writeAll(", ");
+ try std.fmt.formatType(color.value[3], "d", .{}, writer, 0);
+
+ try writer.writeAll(")");
+}
+
+pub fn Writer(comptime WriterType: type) type {
+ return struct {
+ const Self = @This();
+
+ allocator: std.mem.Allocator,
+ base: Renderer,
+ writer: WriterType,
+
+ const vtable: Renderer.VTable = .{
+ .render = vtable_render,
+ .destroy = vtable_destroy,
+ };
+
+ pub fn create(alloc: Allocator, writer: WriterType) Allocator.Error!*Renderer {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .base = .{
+ .ptr = self,
+ .vtable = &vtable,
+ },
+ .writer = writer,
+ };
+ return &self.base;
+ }
+
+ pub fn render(self: *Self, node: SceneNode) anyerror!void {
+ switch (node) {
+ .container => |container| {
+ try self.writer.writeAll("
0) try props.appendSlice("; ");
+
+ try props.appendSlice("background-color: ");
+ try fmtColor(bg, props.writer());
+ }
+
+ if (container.style.border_radius) |br| {
+ if (br.top_left) |value| {
+ if (props.items.len > 0) try props.appendSlice("; ");
+
+ try props.appendSlice("border-top-left-radius: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(value)), 10, .lower, .{}, props.writer());
+ }
+
+ if (br.top_right) |value| {
+ if (props.items.len > 0) try props.appendSlice("; ");
+
+ try props.appendSlice("border-top-right-radius: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(value)), 10, .lower, .{}, props.writer());
+ }
+
+ if (br.bottom_left) |value| {
+ if (props.items.len > 0) try props.appendSlice("; ");
+
+ try props.appendSlice("border-bottom-left-radius: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(value)), 10, .lower, .{}, props.writer());
+ }
+
+ if (br.bottom_right) |value| {
+ if (props.items.len > 0) try props.appendSlice("; ");
+
+ try props.appendSlice("border-bottom-right-radius: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(value)), 10, .lower, .{}, props.writer());
+ }
+ }
+
+ if (container.layout.size) |size| {
+ if (props.items.len > 0) try props.appendSlice("; ");
+
+ try props.appendSlice("width: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(size.value[0])), 10, .lower, .{}, props.writer());
+
+ try props.appendSlice("px; height: ");
+ try std.fmt.formatInt(@as(usize, @intFromFloat(size.value[1])), 10, .lower, .{}, props.writer());
+
+ try props.appendSlice("px");
+ }
+
+ if (props.items.len > 0) {
+ try self.writer.writeAll(" style=\"");
+ try self.writer.writeAll(props.items);
+ try self.writer.writeByte('"');
+ }
+
+ try self.writer.writeByte('>');
+
+ for (container.children) |child| {
+ try self.render(child);
+ }
+
+ try self.writer.writeAll("
");
+ },
+ else => {},
+ }
+ }
+
+ pub fn destroy(self: *Self) void {
+ self.allocator.destroy(self);
+ }
+
+ fn vtable_render(ptr: *anyopaque, node: SceneNode) anyerror!void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.render(node);
+ }
+
+ fn vtable_destroy(ptr: *anyopaque) void {
+ const self: *Self = @ptrCast(@alignCast(ptr));
+ return self.destroy();
+ }
+ };
+}
+
+test {
+ const ArrayList = std.ArrayList(u8);
+ const HtmlWriterRenderer = Writer(ArrayList.Writer);
+
+ var buffer = ArrayList.init(std.testing.allocator);
+ defer buffer.deinit();
+
+ const renderer = try HtmlWriterRenderer.create(std.testing.allocator, buffer.writer());
+ defer renderer.destroy();
+
+ try renderer.render(.{
+ .container = .{
+ .style = .{
+ .background_color = graphics.Color.init(.{ 0.14, 0.15, 0.23, 1.0 }),
+ },
+ .layout = .{
+ .size = .{ .value = .{ 100, 100 } },
+ },
+ .children = &.{},
+ },
+ });
+
+ try std.testing.expectEqualStrings("", buffer.items);
+}
diff --git a/lib/phantom/widgets.zig b/lib/phantom/widgets.zig
new file mode 100644
index 0000000..a1f51be
--- /dev/null
+++ b/lib/phantom/widgets.zig
@@ -0,0 +1,13 @@
+pub const BuildContext = @import("widgets/BuildContext.zig");
+pub const StateContext = @import("widgets/StateContext.zig");
+pub const Text = @import("widgets/Text.zig");
+pub const Widget = @import("widgets/Widget.zig");
+pub const View = @import("widgets/View.zig");
+
+test {
+ _ = BuildContext;
+ _ = StateContext;
+ _ = Text;
+ _ = Widget;
+ _ = View;
+}
diff --git a/lib/phantom/widgets/BuildContext.zig b/lib/phantom/widgets/BuildContext.zig
new file mode 100644
index 0000000..68d1128
--- /dev/null
+++ b/lib/phantom/widgets/BuildContext.zig
@@ -0,0 +1,208 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Widget = @import("Widget.zig");
+const StateContext = @import("StateContext.zig");
+const Self = @This();
+
+pub const Payload = union(enum) {
+ root: Root,
+ child: Child,
+
+ pub const Root = struct {
+ pub const ContextMap = std.AutoHashMapUnmanaged(usize, *Self);
+ pub const RebuildQueue = std.PriorityQueue(usize, *Root, compare);
+
+ state_context: StateContext,
+ context_map: ContextMap,
+ next_id: usize = 0,
+ rebuild_queue: RebuildQueue,
+
+ pub fn destroy(self: *Root, alloc: Allocator) void {
+ var iter = self.context_map.valueIterator();
+ while (iter.next()) |ctx| {
+ ctx.*.destroy();
+ }
+
+ self.context_map.deinit(alloc);
+ self.state_context.destroy();
+ self.rebuild_queue.deinit();
+ }
+
+ fn compare(self: *Root, a: usize, b: usize) std.math.Order {
+ _ = self;
+ // TODO: check how a or b depends on each other
+ return std.math.order(a, b);
+ }
+ };
+
+ pub const Child = struct {
+ id: usize,
+ widget: *const Widget,
+ };
+
+ pub fn destroy(self: *Payload, alloc: Allocator) void {
+ return switch (self.*) {
+ .root => |*root| root.destroy(alloc),
+ .child => {},
+ };
+ }
+};
+
+pub const AncestorIterator = struct {
+ value: ?*Self,
+
+ pub fn next(self: *AncestorIterator) ?*Self {
+ const v = self.value orelse return null;
+ self.value = v.parent;
+ return v;
+ }
+};
+
+pub const ChildIterator = struct {
+ root: *Payload.Root,
+ parent: *Self,
+ cmap_iter: Payload.Root.ContextMap.ValueIterator,
+
+ pub fn next(self: *ChildIterator) ?*Self {
+ while (self.cmap_iter.next()) |ctx| {
+ if (ctx.parent == self.parent) return ctx;
+ }
+ return null;
+ }
+};
+
+allocator: Allocator,
+parent: ?*Self,
+payload: Payload,
+
+pub fn create(alloc: Allocator) !*Self {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ self.* = .{
+ .allocator = alloc,
+ .parent = null,
+ .payload = .{
+ .root = .{
+ .state_context = .{
+ .allocator = alloc,
+ },
+ .rebuild_queue = Payload.Root.RebuildQueue.init(alloc, undefined),
+ .context_map = .{},
+ .next_id = 0,
+ }
+ },
+ };
+ self.payload.root.rebuild_queue.context = &self.payload.root;
+ return self;
+}
+
+pub fn inner(self: *Self, widget: *const Widget) !*Self {
+ const root = self.getRoot();
+
+ var iter = root.context_map.iterator();
+ while (iter.next()) |entry| {
+ if (entry.value_ptr.*.payload.child.widget == widget) return entry.value_ptr.*;
+ }
+
+ const id = root.next_id;
+ errdefer root.next_id = id;
+ root.next_id += 1;
+
+ const child = try self.allocator.create(Self);
+ errdefer self.allocator.destroy(child);
+
+ child.* = .{
+ .allocator = self.allocator,
+ .parent = self,
+ .payload = .{
+ .child = .{
+ .id = id,
+ .widget = widget,
+ },
+ },
+ };
+
+ try root.context_map.put(self.allocator, id, child);
+ return child;
+}
+
+pub fn destroy(self: *Self) void {
+ if (self.payload == .child) {
+ const root = self.getRoot();
+ _ = root.context_map.remove(self.payload.child.id);
+ }
+
+ self.payload.destroy(self.allocator);
+ self.allocator.destroy(self);
+}
+
+pub fn useState(self: *Self, comptime T: type, init: T, disposeFn: StateContext.DisposeFn) !*T {
+ const root = self.getRoot();
+ return try root.state_context.getState(T, self.payload.child.id, init, disposeFn);
+}
+
+pub fn markDirty(self: *Self) void {
+ const root = self.getRoot();
+ return try root.rebuild_queue.add(self.payload.child.id);
+}
+
+pub fn needsRebuild(self: *Self) bool {
+ const root = self.getRoot();
+
+ var iter = root.rebuild_queue.iterator();
+ while (iter.next()) |id| {
+ if (id == self.payload.child.id) return true;
+ }
+
+ return self.payload.child.widget.needsRebuild(self);
+}
+
+/// Wrapper around `findAncestorWidgetOfTag` which uses the type name.
+pub fn findAncestorWidgetOfType(self: *Self, comptime T: type) ?*T {
+ if (self.findAncestorWidgetOfTag(@typeName(T))) |widget| {
+ return @alignCast(@ptrCast(widget.ptr));
+ }
+ return null;
+}
+
+/// Finds an ancestor to the current build context with a tag that matches
+pub fn findAncestorWidgetOfTag(self: *Self, tag: []const u8) ?*const Widget {
+ var iter = self.ancestorIterator();
+ while (iter.next()) |ctx| {
+ if (std.mem.eql(u8, ctx.payload.child.widget.tag, tag)) {
+ return ctx.payload.child.widget;
+ }
+ }
+ return null;
+}
+
+pub fn ancestorIterator(self: *Self) AncestorIterator {
+ return .{
+ .value = self.parent,
+ };
+}
+
+pub fn childIterator(self: *Self) ChildIterator {
+ const root = self.getRoot();
+ return .{
+ .root = root,
+ .parent = self,
+ .cmap_iter = root.context_map.valueIterator(),
+ };
+}
+
+fn getRoot(self: *Self) *Payload.Root {
+ if (self.payload == .root) {
+ return &self.payload.root;
+ }
+
+ var iter = self.ancestorIterator();
+ while (iter.next()) |ctx| {
+ if (ctx.payload == .root) {
+ return &ctx.payload.root;
+ }
+ }
+
+ return undefined;
+}
diff --git a/lib/phantom/widgets/StateContext.zig b/lib/phantom/widgets/StateContext.zig
new file mode 100644
index 0000000..6a430f0
--- /dev/null
+++ b/lib/phantom/widgets/StateContext.zig
@@ -0,0 +1,80 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Self = @This();
+
+pub const DisposeFn = *const fn (*State, Allocator) void;
+
+pub const State = struct {
+ value: []u8,
+ disposeFn: ?DisposeFn,
+ unused: bool,
+ needsFree: bool,
+
+ pub fn dispose(self: *State, alloc: Allocator) void {
+ if (self.disposeFn) |f| return f(self, alloc);
+ }
+};
+
+pub const CleanupMode = enum {
+ unused,
+ not_unused,
+ everything,
+};
+
+allocator: Allocator,
+map: std.AutoHashMapUnmanaged(usize, State) = .{},
+
+pub fn getState(self: *Self, comptime T: type, id: usize, init: T, disposeFn: ?DisposeFn) !*T {
+ if (self.map.get(id)) |state| {
+ return @alignCast(@ptrCast(state.value.ptr));
+ }
+
+ const buff = try self.allocator.alloc(u8, @sizeOf(T));
+ errdefer self.allocator.free(buff);
+
+ const ptr: *T = @alignCast(@ptrCast(buff.ptr));
+ ptr.* = init;
+ try self.map.put(self.allocator, id, .{
+ .value = buff,
+ .disposeFn = disposeFn,
+ .unused = false,
+ .needsFree = false,
+ });
+ return ptr;
+}
+
+pub fn markUnused(self: *Self, id: usize) error{ AlreadyUnused, NotFound }!void {
+ const state = self.map.getPtr(id) orelse return error.NotFound;
+
+ if (state.unused) return error.AlreadyUnused;
+ state.unused = true;
+}
+
+pub fn cleanup(self: *Self, mode: CleanupMode) void {
+ var iter = self.map.valueIterator();
+ while (iter.next()) |entry| {
+ switch (mode) {
+ .unused => if (!entry.unused) continue,
+ .not_unused => if (entry.unused) continue,
+ .everything => {},
+ }
+
+ entry.dispose(self.allocator);
+ entry.needsFree = true;
+ }
+}
+
+pub fn collectGarbage(self: *Self) void {
+ var iter = self.map.iterator();
+ while (iter.next()) |entry| {
+ if (!entry.value_ptr.needsFree) continue;
+
+ self.allocator.free(entry.value_ptr.value);
+ self.map.removeByPtr(entry.key_ptr);
+ }
+}
+
+pub fn destroy(self: *Self) void {
+ self.cleanup(.everything);
+ self.collectGarbage();
+}
diff --git a/lib/phantom/widgets/Text.zig b/lib/phantom/widgets/Text.zig
new file mode 100644
index 0000000..f38d5c2
--- /dev/null
+++ b/lib/phantom/widgets/Text.zig
@@ -0,0 +1,105 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const SceneNode = @import("../scene.zig").Node;
+const BuildContext = @import("BuildContext.zig");
+const Widget = @import("Widget.zig");
+const Self = @This();
+
+pub const Style = @import("Text/Style.zig");
+
+value: []const u8,
+style: ?Style.Data,
+widget: Widget,
+
+pub fn create(alloc: Allocator, text: []const u8, style: ?Style.Data) Allocator.Error!*Widget {
+ const self = try alloc.create(Self);
+ self.* = .{
+ .value = text,
+ .style = style,
+ .widget = .{
+ .tag = @typeName(Self),
+ .ptr = self,
+ .toSceneNodeFn = toSceneNode,
+ .formatFn = formatFn,
+ .disposeFn = disposeFn,
+ },
+ };
+ return &self.widget;
+}
+
+fn toSceneNode(widget: *const Widget, ctx: *BuildContext) anyerror!SceneNode {
+ const self: *const Self = @alignCast(@fieldParentPtr("widget", widget));
+ const style = self.style orelse blk: {
+ const w = ctx.findAncestorWidgetOfType(Style) orelse return error.MissingStyle;
+ break :blk w.data;
+ };
+
+ return .{
+ .text = .{
+ .text = self.value,
+ .font_size = style.font_size,
+ .font = style.font,
+ }
+ };
+}
+
+fn formatFn(widget: *const Widget, options: std.fmt.FormatOptions, writer: std.io.AnyWriter) anyerror!void {
+ const self: *const Self = @alignCast(@fieldParentPtr("widget", widget));
+
+ try writer.writeAll(".style = ");
+ try std.fmt.formatType(self.style, "?", options, writer, 1);
+
+ try writer.writeAll(", .value = \"");
+ try writer.writeAll(self.value);
+ try writer.writeAll("\"");
+}
+
+fn disposeFn(widget: *Widget, alloc: Allocator) void {
+ const self: *Self = @alignCast(@fieldParentPtr("widget", widget));
+ alloc.destroy(self);
+}
+
+test {
+ _ = Style;
+}
+
+test "Style on parent compare to style inside" {
+ const ctx = try BuildContext.create(std.testing.allocator);
+ defer ctx.destroy();
+
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+
+ const alloc = arena.allocator();
+
+ const style_data: Style.Data = .{
+ .font = "ABC",
+ .font_size = .{ .value = @splat(80) },
+ };
+
+ const text: []const u8 = "Hello, world";
+
+ var parent_styled = try Style.create(alloc, style_data, try create(alloc, text, null));
+ defer parent_styled.dispose(alloc);
+
+ var inside_styled = try create(alloc, text, style_data);
+ defer inside_styled.dispose(alloc);
+
+ try std.testing.expectEqualDeep(try parent_styled.toSceneNode(ctx), inside_styled.toSceneNode(ctx));
+
+ try std.testing.expectEqualDeep(SceneNode{
+ .text = .{
+ .text = text,
+ .font_size = style_data.font_size,
+ .font = style_data.font,
+ }
+ }, try parent_styled.toSceneNode(ctx));
+
+ try std.testing.expectEqualDeep(SceneNode{
+ .text = .{
+ .text = text,
+ .font_size = style_data.font_size,
+ .font = style_data.font,
+ }
+ }, try inside_styled.toSceneNode(ctx));
+}
diff --git a/lib/phantom/widgets/Text/Style.zig b/lib/phantom/widgets/Text/Style.zig
new file mode 100644
index 0000000..9a6219f
--- /dev/null
+++ b/lib/phantom/widgets/Text/Style.zig
@@ -0,0 +1,64 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const math = @import("../../math.zig");
+const SceneNode = @import("../../scene.zig").Node;
+const BuildContext = @import("../BuildContext.zig");
+const Widget = @import("../Widget.zig");
+const Self = @This();
+
+pub const Data = struct {
+ font_size: math.Vec2(usize),
+ font: []const u8,
+
+ pub fn format(self: Data, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ try writer.writeAll(@typeName(Data));
+
+ try writer.writeAll("{ .font_size = ");
+ try std.fmt.formatType(self.font_size, "", options, writer, 2);
+
+ try writer.writeAll(", .font = \"");
+ try writer.writeAll(self.font);
+ try writer.writeAll("\" }");
+ }
+};
+
+data: Data,
+child: *Widget,
+widget: Widget,
+
+pub fn create(alloc: Allocator, data: Data, child: *Widget) Allocator.Error!*Widget {
+ const self = try alloc.create(Self);
+ self.* = .{
+ .data = data,
+ .child = child,
+ .widget = .{
+ .tag = @typeName(Self),
+ .ptr = self,
+ .toSceneNodeFn = toSceneNode,
+ .formatFn = formatFn,
+ .disposeFn = disposeFn,
+ },
+ };
+ return &self.widget;
+}
+
+fn toSceneNode(widget: *const Widget, ctx: *BuildContext) anyerror!SceneNode {
+ const self: *const Self = @alignCast(@fieldParentPtr("widget", widget));
+ return self.child.toSceneNode(ctx);
+}
+
+fn formatFn(widget: *const Widget, options: std.fmt.FormatOptions, writer: std.io.AnyWriter) anyerror!void {
+ const self: *const Self = @alignCast(@fieldParentPtr("widget", widget));
+
+ try writer.writeAll(".data = ");
+ try std.fmt.formatType(self.data, "", options, writer, 1);
+
+ try writer.writeAll(", .child = ");
+ try std.fmt.formatType(self.child, "", options, writer, 1);
+}
+
+fn disposeFn(widget: *Widget, alloc: Allocator) void {
+ const self: *Self = @alignCast(@fieldParentPtr("widget", widget));
+ self.child.dispose(alloc);
+ alloc.destroy(self);
+}
diff --git a/lib/phantom/widgets/View.zig b/lib/phantom/widgets/View.zig
new file mode 100644
index 0000000..3a177f5
--- /dev/null
+++ b/lib/phantom/widgets/View.zig
@@ -0,0 +1,65 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const math = @import("../math.zig");
+const FontManager = @import("../fonts/Manager.zig");
+const SceneNode = @import("../scene.zig").Node;
+const SceneRenderer = @import("../scene/Renderer.zig");
+const BuildContext = @import("BuildContext.zig");
+const Widget = @import("Widget.zig");
+const Self = @This();
+
+allocator: Allocator,
+build_context: *BuildContext,
+renderer: *SceneRenderer,
+font_manager: *FontManager,
+child: ?*const Widget,
+widget: Widget,
+
+pub fn create(alloc: Allocator, renderer: *SceneRenderer) !*Self {
+ const self = try alloc.create(Self);
+ errdefer alloc.destroy(self);
+
+ const build_context = try BuildContext.create(alloc);
+ errdefer build_context.destroy();
+
+ const font_manager = try FontManager.create(alloc);
+ errdefer font_manager.destroy();
+
+ self.* = .{
+ .allocator = alloc,
+ .build_context = build_context,
+ .renderer = renderer,
+ .font_manager = font_manager,
+ .child = null,
+ .widget = .{
+ .tag = @typeName(Self),
+ .ptr = self,
+ .toSceneNodeFn = impl_toSceneNodeFn,
+ .disposeFn = impl_disposeFn,
+ },
+ };
+ return self;
+}
+
+pub fn destroy(self: *Self) void {
+ self.font_manager.destroy();
+ self.build_context.destroy();
+ self.allocator.destroy(self);
+}
+
+pub fn render(self: *Self) anyerror!void {
+ // TODO: cache the root node and rebuild the tree as needed.
+ const root_node = try self.widget.toSceneNode(self.build_context);
+ try self.renderer.render(root_node);
+}
+
+fn impl_toSceneNodeFn(widget: *Widget, ctx: *BuildContext) anyerror!SceneNode {
+ const self: *Self = @fieldParentPtr("widget", widget);
+ const child = self.child orelse return error.NoChild;
+ return try child.toSceneNode(ctx);
+}
+
+fn impl_disposeFn(widget: *Widget) void {
+ const self: *Self = @fieldParentPtr("widget", widget);
+ self.destroy();
+}
diff --git a/lib/phantom/widgets/Widget.zig b/lib/phantom/widgets/Widget.zig
new file mode 100644
index 0000000..bcb95f5
--- /dev/null
+++ b/lib/phantom/widgets/Widget.zig
@@ -0,0 +1,39 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const SceneNode = @import("../scene.zig").Node;
+const BuildContext = @import("BuildContext.zig");
+const Self = @This();
+
+tag: []const u8,
+ptr: ?*anyopaque = null,
+needsRebuildFn: ?*const fn (*const Self, *const BuildContext) bool = null,
+disposeFn: ?*const fn (*Self, Allocator) void = null,
+toSceneNodeFn: *const fn (*const Self, *BuildContext) anyerror!SceneNode,
+formatFn: ?*const fn (*const Self, options: std.fmt.FormatOptions, writer: std.io.AnyWriter) anyerror!void = null,
+
+pub fn needsRebuild(self: *const Self, ctx: *const BuildContext) bool {
+ if (self.needsRebuildFn) |f| {
+ const self_ctx = try ctx.inner(self);
+ return f(self, self_ctx);
+ }
+ return false;
+}
+
+pub inline fn dispose(self: *Self, alloc: Allocator) void {
+ if (self.disposeFn) |f| return f(self, alloc);
+}
+
+pub inline fn toSceneNode(self: *const Self, ctx: *BuildContext) anyerror!SceneNode {
+ const self_ctx = try ctx.inner(self);
+ return try self.toSceneNodeFn(self, self_ctx);
+}
+
+pub inline fn format(self: *const Self, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ try writer.writeAll(self.tag);
+ try writer.writeAll("{ ");
+ if (self.formatFn) |f| {
+ try f(self, options, if (@hasDecl(@TypeOf(writer), "any")) writer.any() else writer);
+ try writer.writeByte(' ');
+ }
+ try writer.writeByte('}');
+}
diff --git a/src/example.zig b/src/example.zig
deleted file mode 100644
index 7978d07..0000000
--- a/src/example.zig
+++ /dev/null
@@ -1,96 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const options = @import("options");
-const phantom = @import("phantom");
-const vizops = @import("vizops");
-
-const displayBackendType: phantom.display.BackendType = @enumFromInt(@intFromEnum(options.display_backend));
-const displayBackend = phantom.display.Backend(displayBackendType);
-
-const platformBackendType: phantom.platform.BackendType = @enumFromInt(@intFromEnum(options.platform_backend));
-const platformBackend = phantom.platform.Backend(platformBackendType);
-
-const sceneBackendType: phantom.scene.BackendType = @enumFromInt(@intFromEnum(options.scene_backend));
-const sceneBackend = phantom.scene.Backend(sceneBackendType);
-
-pub fn main() !void {
- const alloc = if (builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
-
- var platform = try platformBackend.Backend.create(alloc);
- defer platform.deinit();
-
- // TODO: use platform to create an application
-
- var display = displayBackend.Display.init(alloc, .client);
- defer display.deinit();
-
- if (displayBackendType == .headless) {
- _ = try display.addOutput(.{
- .enable = true,
- .size = .{
- .phys = vizops.vector.Float32Vector2.init([_]f32{ 306, 229.5 }),
- .res = vizops.vector.UsizeVector2.init([_]usize{ 1024, 768 }),
- },
- .scale = vizops.vector.Float32Vector2.init(1.0),
- .name = "display-0",
- .manufacturer = "PhantomUI",
- .colorFormat = try vizops.color.fourcc.Value.decode(vizops.color.fourcc.formats.argb16161616),
- });
- }
-
- const outputs = try @constCast(&display.display()).outputs();
- defer outputs.deinit();
-
- if (outputs.items.len == 0) {
- std.debug.print("No display outputs exist\n", .{});
- return error.NoOutputs;
- }
-
- const output = outputs.items[0];
- const surface = try output.createSurface(.view, .{
- .title = "Phantom UI Example",
- .toplevel = true,
- .states = &.{.mapped},
- .size = vizops.vector.UsizeVector2.init(64),
- });
- defer {
- surface.destroy() catch @panic("Failed to destroy the surface");
- surface.deinit();
- }
-
- const scene = try surface.createScene(sceneBackendType);
-
- const flex = try scene.createNode(.NodeFlex, .{
- .direction = .horizontal,
- .children = &.{
- try scene.createNode(.NodeArc, .{
- .radius = 32.0,
- .angles = vizops.vector.Float32Vector2.init([_]f32{ 0, std.math.tau - 0.0001 }),
- .color = .{
- .float32 = .{
- .sRGB = .{
- .value = .{ 1.0, 0.0, 0.0, 1.0 },
- },
- },
- },
- }),
- try scene.createNode(.NodeRect, .{
- .color = .{
- .float32 = .{
- .sRGB = .{
- .value = .{ 0.0, 1.0, 0.0, 1.0 },
- },
- },
- },
- .size = vizops.vector.Float32Vector2.init([_]f32{ 10.0, 10.0 }),
- }),
- },
- });
- defer flex.deinit();
-
- while (true) {
- const seq = scene.seq;
- _ = try scene.frame(flex);
- if (seq != scene.seq) std.debug.print("Frame #{}\n", .{scene.seq});
- }
-}
diff --git a/src/phantom/display.zig b/src/phantom/display.zig
deleted file mode 100644
index 5bb03a5..0000000
--- a/src/phantom/display.zig
+++ /dev/null
@@ -1,12 +0,0 @@
-const std = @import("std");
-
-pub const Base = @import("display/base.zig");
-pub const Output = @import("display/output.zig");
-pub const Surface = @import("display/surface.zig");
-
-pub const backends = @import("display/backends.zig");
-pub const BackendType = std.meta.DeclEnum(backends);
-
-pub fn Backend(comptime T: BackendType) type {
- return @field(backends, @tagName(T));
-}
diff --git a/src/phantom/display/backends.zig b/src/phantom/display/backends.zig
deleted file mode 100644
index 17aa783..0000000
--- a/src/phantom/display/backends.zig
+++ /dev/null
@@ -1,5 +0,0 @@
-const root = @import("root");
-
-pub const headless = @import("backends/headless.zig");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "displayBackends")) root.phantomOptions.displayBackends else struct {} else struct {};
diff --git a/src/phantom/display/backends/headless.zig b/src/phantom/display/backends/headless.zig
deleted file mode 100644
index d777511..0000000
--- a/src/phantom/display/backends/headless.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-pub const Display = @import("headless/display.zig");
-pub const Output = @import("headless/output.zig");
-pub const Surface = @import("headless/surface.zig");
diff --git a/src/phantom/display/backends/headless/display.zig b/src/phantom/display/backends/headless/display.zig
deleted file mode 100644
index 978ba2c..0000000
--- a/src/phantom/display/backends/headless/display.zig
+++ /dev/null
@@ -1,53 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const Base = @import("../../base.zig");
-const Output = @import("../../output.zig");
-const HeadlessOutput = @import("output.zig");
-const HeadlessDisplay = @This();
-
-kind: Base.Kind,
-outputs: std.ArrayList(*HeadlessOutput),
-
-pub fn init(alloc: Allocator, kind: Base.Kind) HeadlessDisplay {
- return .{
- .kind = kind,
- .outputs = std.ArrayList(*HeadlessOutput).init(alloc),
- };
-}
-
-pub fn deinit(self: *HeadlessDisplay) void {
- for (self.outputs.items) |output| @constCast(&output.base).deinit();
- self.outputs.deinit();
-}
-
-pub fn display(self: *HeadlessDisplay) Base {
- return .{
- .vtable = &.{
- .outputs = impl_outputs,
- },
- .type = @typeName(HeadlessDisplay),
- .ptr = self,
- .kind = self.kind,
- };
-}
-
-pub fn addOutput(self: *HeadlessDisplay, info: Output.Info) !*HeadlessOutput {
- for (self.outputs.items) |output| {
- if (std.mem.eql(u8, output.info.name, info.name)) return error.AlreadyExists;
- }
-
- const output = try HeadlessOutput.new(self.outputs.allocator, self.kind, info);
- try self.outputs.append(output);
- return output;
-}
-
-fn impl_outputs(ctx: *anyopaque) anyerror!std.ArrayList(*Output) {
- const self: *HeadlessDisplay = @ptrCast(@alignCast(ctx));
- var outputs = try std.ArrayList(*Output).initCapacity(self.outputs.allocator, self.outputs.items.len);
-
- for (self.outputs.items) |output| {
- outputs.appendAssumeCapacity(@constCast(&output.base));
- }
-
- return outputs;
-}
diff --git a/src/phantom/display/backends/headless/output.zig b/src/phantom/display/backends/headless/output.zig
deleted file mode 100644
index 8cdf79e..0000000
--- a/src/phantom/display/backends/headless/output.zig
+++ /dev/null
@@ -1,98 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const Base = @import("../../base.zig");
-const Output = @import("../../output.zig");
-const Surface = @import("../../surface.zig");
-const HeadlessSurface = @import("surface.zig");
-const HeadlessOutput = @This();
-
-base: Output,
-info: Output.Info,
-surfaces: std.ArrayList(*HeadlessSurface),
-
-pub fn new(alloc: Allocator, displayKind: Base.Kind, info: Output.Info) Allocator.Error!*HeadlessOutput {
- const self = try alloc.create(HeadlessOutput);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .base = .{
- .ptr = self,
- .vtable = &.{
- .surfaces = impl_surfaces,
- .createSurface = impl_create_surface,
- .info = impl_info,
- .updateInfo = impl_update_info,
- .deinit = impl_deinit,
- },
- .displayKind = displayKind,
- .type = @typeName(HeadlessOutput),
- },
- .info = info,
- .surfaces = std.ArrayList(*HeadlessSurface).init(alloc),
- };
- return self;
-}
-
-pub fn dupe(self: *HeadlessOutput) Allocator.Error!*HeadlessOutput {
- const d = try self.surfaces.allocator.create(HeadlessOutput);
- errdefer self.surfaces.allocator.destroy(d);
-
- d.* = .{
- .base = .{
- .ptr = self,
- .vtable = self.base.vtable,
- .displayKind = self.base.displayKind,
- .type = @typeName(HeadlessOutput),
- },
- .info = self.info,
- .surfaces = try std.ArrayList(*HeadlessSurface).initCapacity(self.surfaces.allocator, self.surfaces.items.len),
- };
- errdefer d.surfaces.deinit();
-
- for (self.surfaces.items) |surface| {
- d.surfaces.appendAssumeCapacity(try surface.dupe());
- }
- return d;
-}
-
-fn impl_surfaces(ctx: *anyopaque) anyerror!std.ArrayList(*Surface) {
- const self: *HeadlessOutput = @ptrCast(@alignCast(ctx));
- var surfaces = try std.ArrayList(*Surface).initCapacity(self.surfaces.allocator, self.surfaces.items.len);
- errdefer surfaces.deinit();
-
- for (self.surfaces.items) |surface| {
- surfaces.appendAssumeCapacity(@constCast(&surface.base));
- }
- return surfaces;
-}
-
-fn impl_create_surface(ctx: *anyopaque, kind: Surface.Kind, info: Surface.Info) anyerror!*Surface {
- const self: *HeadlessOutput = @ptrCast(@alignCast(ctx));
-
- const surface = try HeadlessSurface.new(self.surfaces.allocator, self.base.displayKind, kind, info);
- surface.output = self;
- surface.id = self.surfaces.items.len;
-
- try self.surfaces.append(surface);
- return &surface.base;
-}
-
-fn impl_info(ctx: *anyopaque) anyerror!Output.Info {
- const self: *HeadlessOutput = @ptrCast(@alignCast(ctx));
- return self.info;
-}
-
-fn impl_update_info(_: *anyopaque, info: Output.Info, fields: []std.meta.FieldEnum(Output.Info)) anyerror!void {
- _ = info;
- _ = fields;
- return error.NotImplemented;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *HeadlessOutput = @ptrCast(@alignCast(ctx));
- const alloc = self.surfaces.allocator;
- for (self.surfaces.items) |surface| @constCast(&surface.base).deinit();
- self.surfaces.deinit();
- alloc.destroy(self);
-}
diff --git a/src/phantom/display/backends/headless/surface.zig b/src/phantom/display/backends/headless/surface.zig
deleted file mode 100644
index 6b853e9..0000000
--- a/src/phantom/display/backends/headless/surface.zig
+++ /dev/null
@@ -1,97 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const Base = @import("../../base.zig");
-const Surface = @import("../../surface.zig");
-const SceneModule = @import("../../../scene.zig");
-const Node = @import("../../../scene/node.zig");
-const HeadlessScene = @import("../../../scene/backends/headless/scene.zig");
-const HeadlessOutput = @import("output.zig");
-const HeadlessSurface = @This();
-
-base: Surface,
-allocator: Allocator,
-info: Surface.Info,
-scene: ?*SceneModule.Base,
-output: ?*HeadlessOutput,
-id: ?usize,
-
-pub fn new(alloc: Allocator, displayKind: Base.Kind, kind: Surface.Kind, info: Surface.Info) Allocator.Error!*HeadlessSurface {
- const self = try alloc.create(HeadlessSurface);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .base = .{
- .ptr = self,
- .vtable = &.{
- .deinit = impl_deinit,
- .destroy = impl_destroy,
- .info = impl_info,
- .updateInfo = impl_update_info,
- .createScene = impl_create_scene,
- },
- .kind = kind,
- .displayKind = displayKind,
- .type = @typeName(HeadlessSurface),
- },
- .allocator = alloc,
- .info = info,
- .scene = null,
- .output = null,
- .id = null,
- };
- return self;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *HeadlessSurface = @ptrCast(@alignCast(ctx));
-
- if (self.scene) |scene| scene.deinit();
-
- self.allocator.destroy(self);
-}
-
-fn impl_destroy(ctx: *anyopaque) anyerror!void {
- const self: *HeadlessSurface = @ptrCast(@alignCast(ctx));
- if (self.output) |output| {
- if (self.id) |id| {
- _ = output.surfaces.swapRemove(id);
- }
- }
-}
-
-fn impl_info(ctx: *anyopaque) anyerror!Surface.Info {
- const self: *HeadlessSurface = @ptrCast(@alignCast(ctx));
- return self.info;
-}
-
-fn impl_update_info(ctx: *anyopaque, info: Surface.Info, fields: []std.meta.FieldEnum(Surface.Info)) anyerror!void {
- _ = ctx;
- _ = info;
- _ = fields;
- return error.NotImplemented;
-}
-
-fn impl_create_scene(ctx: *anyopaque, backendType: SceneModule.BackendType) anyerror!*SceneModule.Base {
- const self: *HeadlessSurface = @ptrCast(@alignCast(ctx));
-
- if (self.scene) |scene| return scene;
-
- if (self.output) |output| {
- const outputInfo = try output.base.info();
-
- self.scene = try SceneModule.createBackend(backendType, .{
- .allocator = self.allocator,
- .frame_info = Node.FrameInfo.init(.{
- .res = self.info.size,
- .scale = outputInfo.scale,
- .physicalSize = outputInfo.size.phys.div(self.info.size.cast(f32)),
- .colorFormat = self.info.colorFormat orelse outputInfo.colorFormat,
- }),
- // TODO: determine if we should use the GPU or not
- .target = null,
- });
- return self.scene.?;
- }
- return error.MissingOutput;
-}
diff --git a/src/phantom/display/base.zig b/src/phantom/display/base.zig
deleted file mode 100644
index a1cf087..0000000
--- a/src/phantom/display/base.zig
+++ /dev/null
@@ -1,40 +0,0 @@
-const std = @import("std");
-const Output = @import("output.zig");
-const Base = @This();
-
-pub const Kind = enum {
- compositor,
- client,
-};
-
-pub const VTable = struct {
- outputs: *const fn (*anyopaque) anyerror!std.ArrayList(*Output),
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-kind: Kind,
-type: []const u8,
-
-pub inline fn outputs(self: *Base) !std.ArrayList(*Output) {
- return self.vtable.outputs(self.ptr);
-}
-
-pub fn format(self: *const Base, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = fmt;
- _ = options;
-
- try writer.print("{s}@{?s} {{", .{ self.type, std.enums.tagName(Kind, self.kind) });
-
- if (@constCast(self).outputs() catch null) |outputsList| {
- try writer.print(" .outputs = [{}] {{", .{outputsList.items.len});
-
- for (outputsList.items) |output| {
- try writer.print(" {},", .{output});
- }
-
- try writer.writeAll(" }");
- }
-
- try writer.writeAll(" }");
-}
diff --git a/src/phantom/display/output.zig b/src/phantom/display/output.zig
deleted file mode 100644
index 4119c60..0000000
--- a/src/phantom/display/output.zig
+++ /dev/null
@@ -1,93 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const vizops = @import("vizops");
-const Base = @import("base.zig");
-const Output = @This();
-const Surface = @import("surface.zig");
-
-pub const Info = struct {
- enable: bool = false,
- size: struct {
- phys: vizops.vector.Float32Vector2,
- res: vizops.vector.UsizeVector2,
- },
- scale: vizops.vector.Float32Vector2,
- name: []const u8 = "",
- manufacturer: []const u8 = "",
- colorFormat: vizops.color.fourcc.Value,
-
- pub fn format(self: Info, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = fmt;
- _ = options;
-
- try writer.print("{s} {{ .enable = {}, .physicalSize = {}x{}mm, .resolution = {}x{}px, .scale = {}x{}%, .name = \"{s}\", .manufacturer = \"{s}\", .colorFormat = {} }}", .{
- @typeName(Info),
- self.enable,
- self.size.phys.value[0],
- self.size.phys.value[1],
- self.size.res.value[0],
- self.size.res.value[1],
- self.scale.value[0],
- self.scale.value[1],
- self.name,
- self.manufacturer,
- self.colorFormat,
- });
- }
-};
-
-pub const VTable = struct {
- surfaces: *const fn (*anyopaque) anyerror!std.ArrayList(*Surface),
- createSurface: *const fn (*anyopaque, Surface.Kind, Surface.Info) anyerror!*Surface,
- info: *const fn (*anyopaque) anyerror!Info,
- updateInfo: *const fn (*anyopaque, Info, []std.meta.FieldEnum(Info)) anyerror!void,
- deinit: ?*const fn (*anyopaque) void,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-type: []const u8,
-displayKind: Base.Kind,
-
-pub inline fn surfaces(self: *Output) !std.ArrayList(*Surface) {
- return self.vtable.surfaces(self.ptr);
-}
-
-pub inline fn createSurface(self: *Output, kind: Surface.Kind, val: Surface.Info) !*Surface {
- return self.vtable.createSurface(self.ptr, kind, val);
-}
-
-pub inline fn info(self: *Output) !Info {
- return self.vtable.info(self.ptr);
-}
-
-pub inline fn updateInfo(self: *Output, val: Info, fields: []std.meta.FieldEnum(Info)) !void {
- return self.vtable.updateInfo(self.ptr, val, fields);
-}
-
-pub inline fn deinit(self: *Output) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub fn format(self: *const Output, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = fmt;
- _ = options;
-
- try writer.print("{s}@{?s} {{", .{ self.type, std.enums.tagName(Base.Kind, self.displayKind) });
-
- if (@constCast(self).info() catch null) |outputInfo| {
- try writer.print(" .info = {},", .{outputInfo});
- }
-
- if (@constCast(self).surfaces() catch null) |surfacesList| {
- try writer.print(" .surfaces = [{}] {{", .{surfacesList.items.len});
-
- for (surfacesList.items) |surface| {
- try writer.print(" {},", .{surface});
- }
-
- try writer.writeAll(" },");
- }
-
- try writer.writeAll(" }");
-}
diff --git a/src/phantom/display/surface.zig b/src/phantom/display/surface.zig
deleted file mode 100644
index cd663c9..0000000
--- a/src/phantom/display/surface.zig
+++ /dev/null
@@ -1,86 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const painting = @import("../painting.zig");
-const Scene = @import("../scene/base.zig");
-const SceneBackendType = @import("../scene.zig").BackendType;
-const Base = @import("base.zig");
-const Surface = @This();
-
-pub const Contents = union(enum) {
- fb: *painting.fb.Base,
- scene: *Scene,
-};
-
-pub const Kind = enum {
- output,
- popup,
- view,
-};
-
-pub const State = enum {
- minimize,
- maximize,
- fullscreen,
- resizing,
- activated,
- mapped,
-};
-
-pub const Info = struct {
- appId: ?[]const u8 = null,
- title: ?[]const u8 = null,
- class: ?[]const u8 = null,
- toplevel: bool = false,
- colorFormat: ?vizops.color.fourcc.Value = null,
- states: []const State = &.{},
- size: vizops.vector.UsizeVector2 = vizops.vector.UsizeVector2.zero(),
- maxSize: vizops.vector.UsizeVector2 = vizops.vector.UsizeVector2.zero(),
- minSize: vizops.vector.UsizeVector2 = vizops.vector.UsizeVector2.zero(),
-};
-
-pub const VTable = struct {
- deinit: ?*const fn (*anyopaque) void = null,
- destroy: *const fn (*anyopaque) anyerror!void,
- info: *const fn (*anyopaque) anyerror!Info,
- updateInfo: *const fn (*anyopaque, Info, []std.meta.FieldEnum(Info)) anyerror!void,
- createScene: *const fn (*anyopaque, SceneBackendType) anyerror!*Scene,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-displayKind: Base.Kind,
-kind: Kind,
-type: []const u8,
-
-pub inline fn deinit(self: *Surface) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub inline fn destroy(self: *Surface) !void {
- return self.vtable.destroy(self.ptr);
-}
-
-pub inline fn info(self: *Surface) !Info {
- return self.vtable.info(self.ptr);
-}
-
-pub inline fn updateInfo(self: *Surface, val: Info, fields: []std.meta.FieldEnum(Info)) !void {
- return self.vtable.updateInfo(self.ptr, val, fields);
-}
-
-pub inline fn createScene(self: *Surface, backendType: SceneBackendType) !*Scene {
- return self.vtable.createScene(self.ptr, backendType);
-}
-
-pub fn format(self: *const Surface, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
- _ = fmt;
- _ = options;
-
- try writer.print("{s}@{?s}@{?s} {{", .{ self.type, std.enums.tagName(Base.Kind, self.displayKind), std.enums.tagName(Kind, self.kind) });
-
- if (@constCast(self).info() catch null) |surfaceInfo| {
- try writer.print(" .info = {}", .{surfaceInfo});
- }
-
- try writer.writeAll(" }");
-}
diff --git a/src/phantom/fonts.zig b/src/phantom/fonts.zig
deleted file mode 100644
index 67af7bb..0000000
--- a/src/phantom/fonts.zig
+++ /dev/null
@@ -1,11 +0,0 @@
-const std = @import("std");
-
-pub const Font = @import("fonts/font.zig");
-pub const Format = @import("fonts/format.zig");
-
-pub const backends = @import("fonts/backends.zig");
-pub const BackendType = std.meta.DeclEnum(backends);
-
-pub fn Backend(comptime T: BackendType) type {
- return @field(backends, @tagName(T));
-}
diff --git a/src/phantom/fonts/backends.zig b/src/phantom/fonts/backends.zig
deleted file mode 100644
index 0f083c9..0000000
--- a/src/phantom/fonts/backends.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-const root = @import("root");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "fontBackends")) root.phantomOptions.fontBackends else struct {} else struct {};
diff --git a/src/phantom/fonts/font.zig b/src/phantom/fonts/font.zig
deleted file mode 100644
index 5b808b7..0000000
--- a/src/phantom/fonts/font.zig
+++ /dev/null
@@ -1,33 +0,0 @@
-const std = @import("std");
-const painting = @import("../painting.zig");
-const vizops = @import("vizops");
-const Self = @This();
-
-pub const Glyph = struct {
- index: u32,
- fb: *painting.fb.Base,
- size: vizops.vector.Uint8Vector2,
- bearing: vizops.vector.Int8Vector2,
- advance: vizops.vector.Int8Vector2,
-};
-
-pub const VTable = struct {
- lookupGlyph: *const fn (*anyopaque, u21) anyerror!Glyph,
- getSize: *const fn (*anyopaque) vizops.vector.UsizeVector2,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn lookupGlyph(self: *Self, codepoint: u21) !Glyph {
- return self.vtable.lookupGlyph(self.ptr, codepoint);
-}
-
-pub inline fn getSize(self: *Self) vizops.vector.UsizeVector2 {
- return self.vtable.getSize(self.ptr);
-}
-
-pub inline fn deinit(self: *Self) void {
- return if (self.vtable.deinit) |f| f(self.ptr) else {};
-}
diff --git a/src/phantom/fonts/format.zig b/src/phantom/fonts/format.zig
deleted file mode 100644
index a4e74a2..0000000
--- a/src/phantom/fonts/format.zig
+++ /dev/null
@@ -1,42 +0,0 @@
-const std = @import("std");
-const metap = @import("meta+");
-const Font = @import("font.zig");
-const vizops = @import("vizops");
-const Self = @This();
-
-pub const LoadOptions = struct {
- foregroundColor: vizops.color.Any,
- backgroundColor: vizops.color.Any,
- colorspace: std.meta.DeclEnum(vizops.color.types),
- colorFormat: vizops.color.fourcc.Value,
-};
-
-const BaseVTable = struct {
- loadBuffer: *const fn (*anyopaque, []const u8, LoadOptions) anyerror!*Font,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-const FsVTable = struct {
- loadFile: ?*const fn (*anyopaque, std.fs.File, LoadOptions) anyerror!*Font = null,
-};
-
-pub const VTable = metap.structs.fields.mix(BaseVTable, if (@hasDecl(std.os.system, "fd_t")) FsVTable else struct {});
-
-ptr: *anyopaque,
-vtable: *const VTable,
-
-pub inline fn loadBuffer(self: *Self, buff: []const u8, options: LoadOptions) !*Font {
- return self.vtable.loadBuffer(self.ptr, buff, options);
-}
-
-pub inline fn loadFile(self: *Self, file: std.fs.File, options: LoadOptions) !*Font {
- if (@hasDecl(std.os.system, "fd_t")) {
- if (self.vtable.loadFile) |f| return f(self.ptr, file, options);
- return error.NotImplemented;
- }
- return error.NotSupported;
-}
-
-pub inline fn deinit(self: *Self) void {
- return if (self.vtable.deinit) |f| f(self.ptr) else {};
-}
diff --git a/src/phantom/gpu.zig b/src/phantom/gpu.zig
deleted file mode 100644
index 7821c2c..0000000
--- a/src/phantom/gpu.zig
+++ /dev/null
@@ -1,5 +0,0 @@
-pub const backends = @import("gpu/backends.zig");
-pub const Base = @import("gpu/base.zig");
-pub const Device = @import("gpu/device.zig");
-pub const Surface = @import("gpu/surface.zig");
-pub const Manager = @import("gpu/manager.zig");
diff --git a/src/phantom/gpu/backends.zig b/src/phantom/gpu/backends.zig
deleted file mode 100644
index e33bfab..0000000
--- a/src/phantom/gpu/backends.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-const root = @import("root");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "gpuBackends")) root.phantomOptions.gpuBackends else struct {} else struct {};
diff --git a/src/phantom/gpu/base.zig b/src/phantom/gpu/base.zig
deleted file mode 100644
index 4ed876c..0000000
--- a/src/phantom/gpu/base.zig
+++ /dev/null
@@ -1,28 +0,0 @@
-const std = @import("std");
-const Device = @import("device.zig");
-const Base = @This();
-
-pub const Options = struct {
- allocator: std.mem.Allocator,
-};
-
-pub const VTable = struct {
- enumerate: *const fn (*anyopaque) anyerror!std.ArrayList(*Device),
- dupe: *const fn (*anyopaque) anyerror!*Base,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn enumerate(self: *Base) !std.ArrayList(*Device) {
- return self.vtable.enumerate(self.ptr);
-}
-
-pub inline fn dupe(self: *Base) !*Base {
- return self.vtable.dupe(self.ptr);
-}
-
-pub inline fn deinit(self: *Base) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/gpu/device.zig b/src/phantom/gpu/device.zig
deleted file mode 100644
index ca6b727..0000000
--- a/src/phantom/gpu/device.zig
+++ /dev/null
@@ -1,30 +0,0 @@
-const std = @import("std");
-const Surface = @import("surface.zig");
-const Fb = @import("../painting/fb/base.zig");
-const Device = @This();
-
-pub const VTable = struct {
- createSurface: *const fn (*anyopaque, Surface.Info) anyerror!*Surface,
- createFrameBuffer: *const fn (*anyopaque, Fb.Info) anyerror!*Fb,
- dupe: *const fn (*anyopaque) anyerror!*Device,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn createSurface(self: *Device, info: Surface.Info) anyerror!*Surface {
- return self.vtable.createSurface(self.ptr, info);
-}
-
-pub inline fn createFrameBuffer(self: *Device, info: Fb.Info) anyerror!*Fb {
- return self.vtable.createFrameBuffer(self.ptr, info);
-}
-
-pub inline fn dupe(self: *Device) !*Device {
- return self.vtable.dupe(self.ptr);
-}
-
-pub inline fn deinit(self: *Device) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/gpu/manager.zig b/src/phantom/gpu/manager.zig
deleted file mode 100644
index d7507d6..0000000
--- a/src/phantom/gpu/manager.zig
+++ /dev/null
@@ -1,67 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const Base = @import("base.zig");
-const Device = @import("device.zig");
-const Manager = @This();
-
-allocator: Allocator,
-backends: []*Base,
-
-pub fn create(alloc: Allocator) Allocator.Error!*Manager {
- const backends = @import("backends.zig");
-
- const self = try alloc.create(Manager);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .allocator = alloc,
- .backends = try alloc.alloc(*Base, std.meta.declarations(backends).len),
- };
- errdefer alloc.free(self.backends);
-
- inline for (std.meta.declarations(backends), 0..) |decl, i| {
- const backend = @field(backends, decl.name);
-
- self.backends[i] = try backend.create(.{
- .allocator = alloc,
- });
- errdefer self.backends[i].deinit();
- }
- return self;
-}
-
-pub fn deinit(self: *Manager) void {
- for (self.backends) |backend| backend.deinit();
- self.allocator.free(self.backends);
- self.allocator.destroy(self);
-}
-
-pub fn dupe(self: *Manager) !*Manager {
- const d = try self.allocator.create(Manager);
- errdefer self.allocator.destroy(d);
-
- d.* = .{
- .allocator = self.allocator,
- .backends = try self.allocator.alloc(*Base, self.backends.len),
- };
- errdefer self.allocator.free(d.backends);
-
- for (self.backends, &d.backends) |b, *bd| {
- bd.* = try b.dupe();
- errdefer bd.deinit();
- }
- return d;
-}
-
-pub fn devices(self: *Manager) !std.ArrayList(*Device) {
- var list = std.ArrayList(*Device).init(self.allocator);
- errdefer list.deinit();
-
- for (self.backends) |backend| {
- var blist = try backend.list();
- defer blist.deinit();
-
- for (blist.items) |dev| try list.append(try dev.dupe());
- }
- return list;
-}
diff --git a/src/phantom/gpu/surface.zig b/src/phantom/gpu/surface.zig
deleted file mode 100644
index 89836ef..0000000
--- a/src/phantom/gpu/surface.zig
+++ /dev/null
@@ -1,44 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const fb = @import("../painting/fb.zig");
-const Blt = @import("../painting.zig").Blt;
-const Device = @import("device.zig");
-const Surface = @This();
-
-pub const Info = struct {
- colorFormat: vizops.color.fourcc.Value,
- colorspace: std.meta.DeclEnum(vizops.color.types),
- size: vizops.vector.UsizeVector2,
-};
-
-pub const VTable = struct {
- info: *const fn (*anyopaque) anyerror!Info,
- updateInfo: *const fn (*anyopaque, Info) anyerror!void,
- blt: *const fn (*anyopaque, Blt, *fb.Base, vizops.vector.UsizeVector2) anyerror!void,
- dupe: *const fn (*anyopaque) anyerror!*Surface,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-device: *Device,
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn info(self: *Surface) !Info {
- return self.vtable.info(self.ptr);
-}
-
-pub inline fn updateInfo(self: *Surface, i: Info) !void {
- return self.vtable.updateInfo(self.ptr, i);
-}
-
-pub inline fn blt(self: *Surface, mode: Blt, op: *fb.Base, offset: vizops.vector.UsizeVector2) !void {
- return self.vtable.blt(self.ptr, mode, op, offset);
-}
-
-pub inline fn dupe(self: *Surface) !*Surface {
- return self.vtable.dupe(self.ptr);
-}
-
-pub inline fn deinit(self: *Surface) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/math.zig b/src/phantom/math.zig
deleted file mode 100644
index 6ce8eaf..0000000
--- a/src/phantom/math.zig
+++ /dev/null
@@ -1,16 +0,0 @@
-const std = @import("std");
-
-pub usingnamespace @import("math/units.zig");
-
-pub fn OneBiggerInt(comptime T: type) type {
- var info = @typeInfo(T);
- info.Int.bits += 1;
- return @Type(info);
-}
-
-pub fn add(a: anytype, b: anytype) @TypeOf(a) {
- if (b >= 0) {
- return a + @as(@TypeOf(a), @intCast(b));
- }
- return a - @as(@TypeOf(a), @intCast(-@as(OneBiggerInt(@TypeOf(b)), b)));
-}
diff --git a/src/phantom/math/units.zig b/src/phantom/math/units.zig
deleted file mode 100644
index b72b3b8..0000000
--- a/src/phantom/math/units.zig
+++ /dev/null
@@ -1,22 +0,0 @@
-const vizops = @import("vizops");
-const Node = @import("../scene/node.zig");
-
-pub fn rel(frameInfo: Node.FrameInfo, value: vizops.vector.Float32Vector2) vizops.vector.UsizeVector2 {
- return value.mul(frameInfo.scale).mul(frameInfo.size.res.cast(f32)).div(@as(f32, 100.0)).cast(usize);
-}
-
-pub fn inches(frameInfo: Node.FrameInfo, value: vizops.vector.Float32Vector2) vizops.vector.UsizeVector2 {
- const physicalInches = frameInfo.size.phys.div(@as(f32, 25.4));
- const dpi = frameInfo.size.res.cast(f32).div(physicalInches).mul(frameInfo.scale);
- return dpi.mul(value).cast(usize);
-}
-
-pub fn cm(frameInfo: Node.FrameInfo, value: vizops.vector.Float32Vector2) vizops.vector.UsizeVector2 {
- const physicalCm = frameInfo.size.phys.div(@as(f32, 10.0));
- const dpcm = frameInfo.size.res.cast(f32).div(physicalCm).mul(frameInfo.scale);
- return dpcm.mul(value).cast(usize);
-}
-
-pub fn mm(frameInfo: Node.FrameInfo, value: vizops.vector.Float32Vector2) vizops.vector.UsizeVector2 {
- return frameInfo.size.res.cast(f32).div(frameInfo.size.phys).mul(frameInfo.scale).mul(value).cast(usize);
-}
diff --git a/src/phantom/painting.zig b/src/phantom/painting.zig
deleted file mode 100644
index b1e69a9..0000000
--- a/src/phantom/painting.zig
+++ /dev/null
@@ -1,48 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-
-pub const Axis = enum {
- horizontal,
- vertical,
-};
-
-pub const Blt = enum { from, to };
-
-pub const BltOptions = struct {
- sourceOffset: vizops.vector.UsizeVector2 = .{},
- destOffset: vizops.vector.UsizeVector2 = .{},
- size: ?vizops.vector.UsizeVector2 = null,
- blend: vizops.color.BlendMode = .normal,
-};
-
-pub fn Radius(comptime T: type) type {
- return struct {
- const Self = @This();
-
- top: ?Side = null,
- bottom: ?Side = null,
-
- pub fn equal(self: Self, other: Self) bool {
- return std.simd.countTrues(@Vector(2, bool){
- if (self.top == null and other.top != null) false else if (self.top != null and other.top == null) false else self.top.?.equal(other.top.?),
- if (self.bottom == null and other.bottom != null) false else if (self.bottom != null and other.bottom == null) false else self.bottom.?.equal(other.bottom.?),
- }) == 2;
- }
-
- pub const Side = struct {
- left: ?T = null,
- right: ?T = null,
-
- pub fn equal(self: Side, other: Side) bool {
- return std.simd.countTrues(@Vector(2, bool){
- self.left == other.left,
- self.right == other.right,
- }) == 2;
- }
- };
- };
-}
-
-pub const fb = @import("painting/fb.zig");
-pub const image = @import("painting/image.zig");
-pub const Canvas = @import("painting/canvas.zig");
diff --git a/src/phantom/painting/canvas.zig b/src/phantom/painting/canvas.zig
deleted file mode 100644
index 9608136..0000000
--- a/src/phantom/painting/canvas.zig
+++ /dev/null
@@ -1,127 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const Fb = @import("fb/base.zig");
-const painting = @import("../painting.zig");
-const Self = @This();
-
-pub const Options = struct {
- pos: ?vizops.vector.UsizeVector2,
- size: ?vizops.vector.UsizeVector2,
-};
-
-fb: *Fb,
-bounds: vizops.vector.UsizeVector4,
-
-pub fn init(fb: *Fb, options: Options) Self {
- const pos = if (options.pos) |value| value else vizops.vector.UsizeVector2.zero();
- const size = if (options.size) |value| value else fb.info().res;
- return .{
- .fb = fb,
- .bounds = .{ .value = std.simd.join(pos.value, size.value) },
- };
-}
-
-pub fn setPixel(self: *const Self, pos: vizops.vector.UsizeVector2, buffer: []const u8) !void {
- const bufferInfo = self.fb.info();
- const stride = buffer.len * bufferInfo.res.value[0];
- const index = ((pos.value[1] + self.bounds.value[1]) * stride) + ((pos.value[0] + self.bounds.value[0]) * buffer.len);
- try self.fb.write(index, buffer);
-}
-
-pub fn line(self: *const Self, start: vizops.vector.UsizeVector2, end: vizops.vector.UsizeVector2, buffer: []const u8) !void {
- var x0 = start.value[0];
- var y0 = start.value[1];
- const x1 = end.value[0];
- const y1 = end.value[1];
-
- const dx = @max(x1, x0) - @min(x0, x1);
- const sx = if (x0 < x1) @as(isize, 1) else @as(isize, -1);
- const dy = @as(usize, @intCast(@as(isize, @intCast(@abs(y1 - y0))) * @as(isize, -1)));
- const sy = if (y0 < y1) @as(isize, 1) else @as(isize, -1);
- var err = dx + dy;
-
- while (true) {
- try self.setPixel(vizops.vector.UsizeVector2.init([_]usize{ x0, y0 }), buffer);
-
- if (x0 == x1 and y0 == y1) break;
-
- const e2 = 2 * err;
- if (e2 >= dy) {
- err += dy;
- if (sx < 0) {
- x0 -= 1;
- } else {
- x0 += 1;
- }
- }
- if (e2 <= dx) {
- err += dx;
- if (sy < 0) {
- y0 -= 1;
- } else {
- y0 += 1;
- }
- }
- }
-}
-
-pub fn arc(self: *const Self, pos: vizops.vector.UsizeVector2, angles: vizops.vector.Float32Vector2, radius: f32, buffer: []const u8) !void {
- const startAngle = std.math.degreesToRadians(angles.value[0]);
- const endAngle = std.math.degreesToRadians(angles.value[1]);
-
- const center = vizops.vector.UsizeVector2.init([_]usize{
- pos.value[0] - std.math.lossyCast(usize, @cos(startAngle) * radius),
- pos.value[1] - std.math.lossyCast(usize, @sin(startAngle) * radius),
- });
-
- var r: f32 = 0;
- while (r <= radius) : (r += 1.0) {
- var angle = startAngle;
- while (angle <= endAngle) : (angle += 1.0 / r) {
- const x = center.value[0] + std.math.lossyCast(usize, r * @cos(angle));
- const y = center.value[1] + std.math.lossyCast(usize, r * @sin(angle));
-
- try self.setPixel(vizops.vector.UsizeVector2.init([_]usize{ x, y }), buffer);
- }
- }
-}
-
-pub fn circle(self: *const Self, pos: vizops.vector.UsizeVector2, radius: usize, buffer: []const u8) !void {
- const circumferencePoint = vizops.vector.UsizeVector2.init([_]usize{ pos.value[0] + std.math.round(radius), pos.value[1] });
- const fullCircle = vizops.vector.UsizeVector2.init([_]usize{ 0, 360 });
- try self.arc(circumferencePoint, fullCircle, radius, buffer);
-}
-
-pub fn rect(self: *const Self, pos: vizops.vector.UsizeVector2, size: vizops.vector.UsizeVector2, radius: painting.Radius(f32), buffer: []const u8) !void {
- const x = pos.value[0];
- const y = pos.value[1];
- const width = size.value[0];
- const height = size.value[1];
-
- const topLeftRadius = (if (radius.top) |top| top.left else null) orelse @as(f32, 0);
- const topRightRadius = (if (radius.top) |top| top.right else null) orelse @as(f32, 0);
- const bottomLeftRadius = (if (radius.bottom) |bottom| bottom.left else null) orelse @as(f32, 0);
- const bottomRightRadius = (if (radius.bottom) |bottom| bottom.right else null) orelse @as(f32, 0);
-
- if (topLeftRadius > 0) {
- try self.arc(vizops.vector.UsizeVector2.init([_]usize{ x, y }), vizops.vector.Float32Vector2.init([_]f32{ 180, 270 }), topLeftRadius, buffer);
- }
- if (topRightRadius > 0) {
- try self.arc(vizops.vector.UsizeVector2.init([_]usize{ x + width, y }), vizops.vector.Float32Vector2.init([_]f32{ 270, 360 }), topRightRadius, buffer);
- }
- if (bottomLeftRadius > 0) {
- try self.arc(vizops.vector.UsizeVector2.init([_]usize{ x, y + height }), vizops.vector.Float32Vector2.init([_]f32{ 90, 180 }), bottomLeftRadius, buffer);
- }
- if (bottomRightRadius > 0) {
- try self.arc(vizops.vector.UsizeVector2.init([_]usize{ x + width, y + height }), vizops.vector.Float32Vector2.init([_]f32{ 0, 90 }), bottomRightRadius, buffer);
- }
-
- const leftEdge = x + std.math.lossyCast(usize, @max(topLeftRadius, bottomLeftRadius));
- const rightEdge = x + width - std.math.lossyCast(usize, @max(topRightRadius, bottomRightRadius));
- var i: usize = y;
- while (i < y + height) : (i += 1) {
- for (leftEdge..rightEdge) |xp| {
- try self.setPixel(vizops.vector.UsizeVector2.init([_]usize{ xp, i }), buffer);
- }
- }
-}
diff --git a/src/phantom/painting/fb.zig b/src/phantom/painting/fb.zig
deleted file mode 100644
index 548e828..0000000
--- a/src/phantom/painting/fb.zig
+++ /dev/null
@@ -1,4 +0,0 @@
-pub const Base = @import("fb/base.zig");
-pub const AllocatedFrameBuffer = @import("fb/alloc.zig");
-pub const MemoryFrameBuffer = @import("fb/mem.zig");
-pub const FileDescriptorFrameBuffer = @import("fb/fd.zig");
diff --git a/src/phantom/painting/fb/alloc.zig b/src/phantom/painting/fb/alloc.zig
deleted file mode 100644
index 982e5b1..0000000
--- a/src/phantom/painting/fb/alloc.zig
+++ /dev/null
@@ -1,65 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const Base = @import("base.zig");
-const AllocatedFrameBuffer = @This();
-
-base: Base,
-info: Base.Info,
-buffer: []u8,
-
-pub fn create(alloc: Allocator, info: Base.Info) !*Base {
- const self = try alloc.create(AllocatedFrameBuffer);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .info = info,
- .buffer = try alloc.alloc(u8, info.res.value[0] * info.res.value[1] * @divExact(info.colorFormat.width(), 8)),
- .base = .{
- .allocator = alloc,
- .vtable = &.{
- .addr = impl_addr,
- .info = impl_info,
- .dupe = impl_dupe,
- .deinit = impl_deinit,
- .blt = null,
- },
- .ptr = self,
- },
- };
- errdefer alloc.free(self.buffer);
- return &self.base;
-}
-
-fn impl_addr(ctx: *anyopaque) anyerror!*anyopaque {
- const self: *AllocatedFrameBuffer = @ptrCast(@alignCast(ctx));
- return @ptrCast(@alignCast(self.buffer));
-}
-
-fn impl_info(ctx: *anyopaque) Base.Info {
- const self: *AllocatedFrameBuffer = @ptrCast(@alignCast(ctx));
- return self.info;
-}
-
-fn impl_dupe(ctx: *anyopaque) anyerror!*Base {
- const self: *AllocatedFrameBuffer = @ptrCast(@alignCast(ctx));
- const d = try self.base.allocator.create(AllocatedFrameBuffer);
- errdefer self.base.allocator.destroy(d);
-
- d.* = .{
- .info = self.info,
- .buffer = try self.base.allocator.dupe(u8, self.buffer),
- .base = .{
- .ptr = d,
- .allocator = self.base.allocator,
- .vtable = self.base.vtable,
- },
- };
- return &d.base;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *AllocatedFrameBuffer = @ptrCast(@alignCast(ctx));
- self.base.allocator.free(self.buffer);
- self.base.allocator.destroy(self);
-}
diff --git a/src/phantom/painting/fb/base.zig b/src/phantom/painting/fb/base.zig
deleted file mode 100644
index ca51627..0000000
--- a/src/phantom/painting/fb/base.zig
+++ /dev/null
@@ -1,139 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const painting = @import("../../painting.zig");
-const Blt = painting.Blt;
-const BltOptions = painting.BltOptions;
-const Base = @This();
-
-pub const Info = struct {
- res: vizops.vector.UsizeVector2,
- colorspace: std.meta.DeclEnum(vizops.color.types),
- colorFormat: vizops.color.fourcc.Value,
-
- pub fn size(self: Info) usize {
- return self.colorFormat.width() * @reduce(.Mul, self.res.value);
- }
-};
-
-pub const VTable = struct {
- lock: ?*const fn (*anyopaque) anyerror!void = null,
- unlock: ?*const fn (*anyopaque) void = null,
- addr: *const fn (*anyopaque) anyerror!*anyopaque,
- info: *const fn (*anyopaque) Info,
- read: ?*const fn (*anyopaque, usize, []u8) anyerror!void = null,
- write: ?*const fn (*anyopaque, usize, []const u8) anyerror!void = null,
- dupe: *const fn (*anyopaque) anyerror!*Base,
- commit: ?*const fn (*anyopaque) anyerror!void = null,
- deinit: ?*const fn (*anyopaque) void = null,
- blt: ?*const fn (*anyopaque, Blt, *Base, BltOptions) anyerror!void,
-};
-
-allocator: std.mem.Allocator,
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn lock(self: *Base) anyerror!void {
- if (self.vtable.lock) |f| try f(self.ptr);
-}
-
-pub inline fn unlock(self: *Base) void {
- if (self.vtable.unlock) |f| f(self.ptr);
-}
-
-pub inline fn addr(self: *Base) !*anyopaque {
- return self.vtable.addr(self.ptr);
-}
-
-pub inline fn info(self: *Base) Info {
- return self.vtable.info(self.ptr);
-}
-
-pub inline fn read(self: *Base, i: usize, buf: []u8) anyerror!void {
- if (self.vtable.read) |f| return f(self.ptr, i, buf);
-
- const end = i + buf.len;
-
- const ptr: [*]const u8 = @ptrCast(@alignCast(try self.addr()));
- @memcpy(buf, ptr[i..end]);
-}
-
-pub inline fn write(self: *Base, i: usize, val: []const u8) !void {
- if (self.vtable.write) |f| return f(self.ptr, i, val);
-
- const ptr: [*]volatile u8 = @ptrCast(@alignCast(try self.addr()));
- @memcpy(ptr[i..(i + val.len)], val);
-}
-
-pub inline fn dupe(self: *Base) !*Base {
- return self.vtable.dupe(self.ptr);
-}
-
-pub inline fn commit(self: *Base) !void {
- if (self.vtable.commit) |f| return f(self.ptr);
-}
-
-pub inline fn deinit(self: *Base) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub fn blt(self: *Base, mode: Blt, op: *Base, options: BltOptions) !void {
- if (self.vtable.blt) |f| return f(self.ptr, mode, op, options);
-
- const src_info = switch (mode) {
- .from => op.info(),
- .to => self.info(),
- };
-
- const dest_info = switch (mode) {
- .from => self.info(),
- .to => op.info(),
- };
-
- const width = if (options.size) |s| s.value[0] else @min(src_info.res.value[0], dest_info.res.value[0]);
- const height = if (options.size) |s| s.value[1] else @min(src_info.res.value[1], dest_info.res.value[1]);
-
- const srcbuff = try self.allocator.alloc(u8, @divExact(src_info.colorFormat.width(), 8));
- defer self.allocator.free(srcbuff);
-
- const origbuff = try self.allocator.alloc(u8, @divExact(dest_info.colorFormat.width(), 8));
- defer self.allocator.free(origbuff);
-
- const destbuff = try self.allocator.alloc(u8, @divExact(dest_info.colorFormat.width(), 8));
- defer self.allocator.free(destbuff);
-
- const srcStride = srcbuff.len * src_info.res.value[0];
- const destStride = destbuff.len * dest_info.res.value[0];
-
- var y: usize = 0;
- while (y < height) : (y += 1) {
- var x: usize = 0;
- while (x < width) : (x += 1) {
- const srci = (y + options.sourceOffset.value[1]) * srcStride + (x + options.sourceOffset.value[0]) * srcbuff.len;
- const desti = (y + options.destOffset.value[1]) * destStride + (x + options.destOffset.value[0]) * destbuff.len;
-
- try switch (mode) {
- .from => op.read(srci, srcbuff),
- .to => self.read(srci, srcbuff),
- };
-
- var srcval = try vizops.color.readAnyBuffer(src_info.colorspace, src_info.colorFormat, srcbuff);
-
- if (options.blend != .normal) {
- try switch (mode) {
- .from => self.read(srci, origbuff),
- .to => op.read(srci, origbuff),
- };
-
- const origval = try vizops.color.readAnyBuffer(dest_info.colorspace, dest_info.colorFormat, origbuff);
- srcval = try vizops.color.blendAny(srcval, origval, options.blend);
- }
-
- try vizops.color.writeAnyBuffer(dest_info.colorFormat, destbuff, srcval);
-
- try switch (mode) {
- .from => self.write(desti, destbuff),
- .to => op.write(desti, destbuff),
- };
- }
- }
-}
diff --git a/src/phantom/painting/fb/fd.zig b/src/phantom/painting/fb/fd.zig
deleted file mode 100644
index 42fb4fc..0000000
--- a/src/phantom/painting/fb/fd.zig
+++ /dev/null
@@ -1,72 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const Base = @import("base.zig");
-const FileDescriptorFrameBuffer = @This();
-
-base: Base,
-info: Base.Info,
-fd: std.os.fd_t,
-buffer: @typeInfo(@typeInfo(@TypeOf(std.os.mmap)).Fn.return_type.?).ErrorUnion.payload,
-
-pub fn create(alloc: Allocator, info: Base.Info, fd: std.os.fd_t) !*Base {
- const self = try alloc.create(FileDescriptorFrameBuffer);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .info = info,
- .fd = fd,
- .buffer = try std.os.mmap(null, info.res.value[0] * info.res.value[1] * @divExact(info.colorFormat.width(), 8), std.os.PROT.READ | std.os.PROT.WRITE, .{
- .TYPE = .SHARED,
- }, fd, 0),
- .base = .{
- .allocator = alloc,
- .vtable = &.{
- .addr = impl_addr,
- .info = impl_info,
- .dupe = impl_dupe,
- .deinit = impl_deinit,
- .blt = null,
- },
- .ptr = self,
- },
- };
- errdefer std.os.munmap(self.buffer);
- return &self.base;
-}
-
-fn impl_addr(ctx: *anyopaque) anyerror!*anyopaque {
- const self: *FileDescriptorFrameBuffer = @ptrCast(@alignCast(ctx));
- return @ptrCast(@alignCast(self.buffer));
-}
-
-fn impl_info(ctx: *anyopaque) Base.Info {
- const self: *FileDescriptorFrameBuffer = @ptrCast(@alignCast(ctx));
- return self.info;
-}
-
-fn impl_dupe(ctx: *anyopaque) anyerror!*Base {
- const self: *FileDescriptorFrameBuffer = @ptrCast(@alignCast(ctx));
- const d = try self.base.allocator.create(FileDescriptorFrameBuffer);
- errdefer self.base.allocator.destroy(d);
-
- d.* = .{
- .info = self.info,
- .fd = self.fd,
- .buffer = try std.os.mmap(null, self.info.res.value[0] * self.info.res.value[1] * @divExact(self.info.colorFormat.width(), 8), std.os.PROT.READ | std.os.PROT.WRITE, .{
- .TYPE = .SHARED,
- }, self.fd, 0),
- .base = .{
- .ptr = d,
- .allocator = self.base.allocator,
- .vtable = self.base.vtable,
- },
- };
- return &d.base;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *FileDescriptorFrameBuffer = @ptrCast(@alignCast(ctx));
- std.os.munmap(self.buffer);
- self.base.allocator.destroy(self);
-}
diff --git a/src/phantom/painting/fb/mem.zig b/src/phantom/painting/fb/mem.zig
deleted file mode 100644
index 599dd12..0000000
--- a/src/phantom/painting/fb/mem.zig
+++ /dev/null
@@ -1,63 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const Base = @import("base.zig");
-const MemoryFrameBuffer = @This();
-
-base: Base,
-info: Base.Info,
-buffer: []u8,
-
-pub fn create(alloc: Allocator, info: Base.Info, ptr: [*]u8) !*Base {
- const self = try alloc.create(MemoryFrameBuffer);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .info = info,
- .buffer = ptr[0..(info.res.value[0] * info.res.value[1] * @divExact(info.colorFormat.width(), 8))],
- .base = .{
- .allocator = alloc,
- .vtable = &.{
- .addr = impl_addr,
- .info = impl_info,
- .dupe = impl_dupe,
- .deinit = impl_deinit,
- .blt = null,
- },
- .ptr = self,
- },
- };
- return &self.base;
-}
-
-fn impl_addr(ctx: *anyopaque) anyerror!*anyopaque {
- const self: *MemoryFrameBuffer = @ptrCast(@alignCast(ctx));
- return @ptrCast(@alignCast(self.buffer));
-}
-
-fn impl_info(ctx: *anyopaque) Base.Info {
- const self: *MemoryFrameBuffer = @ptrCast(@alignCast(ctx));
- return self.info;
-}
-
-fn impl_dupe(ctx: *anyopaque) anyerror!*Base {
- const self: *MemoryFrameBuffer = @ptrCast(@alignCast(ctx));
- const d = try self.base.allocator.create(MemoryFrameBuffer);
- errdefer self.base.allocator.destroy(d);
-
- d.* = .{
- .info = self.info,
- .buffer = self.buffer,
- .base = .{
- .ptr = d,
- .allocator = self.base.allocator,
- .vtable = self.base.vtable,
- },
- };
- return &d.base;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *MemoryFrameBuffer = @ptrCast(@alignCast(ctx));
- self.base.allocator.destroy(self);
-}
diff --git a/src/phantom/painting/image.zig b/src/phantom/painting/image.zig
deleted file mode 100644
index 7312e81..0000000
--- a/src/phantom/painting/image.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-pub const Base = @import("image/base.zig");
-pub const Format = @import("image/format.zig");
-pub const formats = @import("image/formats.zig");
diff --git a/src/phantom/painting/image/base.zig b/src/phantom/painting/image/base.zig
deleted file mode 100644
index de31e51..0000000
--- a/src/phantom/painting/image/base.zig
+++ /dev/null
@@ -1,32 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const Fb = @import("../fb/base.zig");
-const Self = @This();
-
-pub const Info = struct {
- res: vizops.vector.UsizeVector2,
- colorFormat: vizops.color.fourcc.Value,
- colorspace: std.meta.DeclEnum(vizops.color.types),
- seqCount: usize,
-};
-
-pub const VTable = struct {
- buffer: *const fn (*anyopaque, usize) anyerror!*Fb,
- info: *const fn (*anyopaque) Info,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-ptr: *anyopaque,
-vtable: *const VTable,
-
-pub inline fn buffer(self: Self, i: usize) !*Fb {
- return self.vtable.buffer(self.ptr, i);
-}
-
-pub inline fn info(self: Self) Info {
- return self.vtable.info(self.ptr);
-}
-
-pub inline fn deinit(self: Self) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/painting/image/format.zig b/src/phantom/painting/image/format.zig
deleted file mode 100644
index 1e808b2..0000000
--- a/src/phantom/painting/image/format.zig
+++ /dev/null
@@ -1,54 +0,0 @@
-const std = @import("std");
-const metap = @import("meta+");
-const Base = @import("base.zig");
-const Self = @This();
-
-const BaseVTable = struct {
- create: ?*const fn (*anyopaque, Base.Info) anyerror!*Base = null,
- readBuffer: *const fn (*anyopaque, []const u8) anyerror!*Base,
- writeBuffer: ?*const fn (*anyopaque, *Base, []u8) anyerror!usize = null,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-const FsVTable = struct {
- readFile: ?*const fn (*anyopaque, std.fs.File) anyerror!*Base = null,
- writeFile: ?*const fn (*anyopaque, *Base, std.fs.File) anyerror!usize = null,
-};
-
-pub const VTable = metap.structs.fields.mix(BaseVTable, if (@hasDecl(std.os.system, "fd_t")) FsVTable else struct {});
-
-ptr: *anyopaque,
-vtable: *const VTable,
-
-pub inline fn create(self: Self, info: Base.Info) !*Base {
- if (self.vtable.create) |f| return f(self.ptr, info);
-}
-
-pub inline fn readBuffer(self: Self, buff: []const u8) !*Base {
- return self.vtable.readBuffer(self.ptr, buff);
-}
-
-pub inline fn writeBuffer(self: Self, img: *Base, buff: []u8) !usize {
- if (self.vtable.writeBuffer) |f| return f(self.ptr, img, buff);
- return error.NotImplemented;
-}
-
-pub inline fn readFile(self: Self, file: std.fs.File) !*Base {
- if (@hasDecl(std.os.system, "fd_t")) {
- if (self.vtable.readFile) |f| return f(self.ptr, file);
- return error.NotImplemented;
- }
- return error.NotSupported;
-}
-
-pub inline fn writeFile(self: Self, img: *Base, file: std.fs.File) !usize {
- if (@hasDecl(std.os.system, "fd_t")) {
- if (self.vtable.writeFile) |f| return f(self.ptr, img, file);
- return error.NotImplemented;
- }
- return error.NotSupported;
-}
-
-pub inline fn deinit(self: Self) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/painting/image/formats.zig b/src/phantom/painting/image/formats.zig
deleted file mode 100644
index cf603fc..0000000
--- a/src/phantom/painting/image/formats.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-const root = @import("root");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "imageFormats")) root.phantomOptions.imageFormats else struct {} else struct {};
diff --git a/src/phantom/platform.zig b/src/phantom/platform.zig
deleted file mode 100644
index dd5582c..0000000
--- a/src/phantom/platform.zig
+++ /dev/null
@@ -1,11 +0,0 @@
-const std = @import("std");
-
-pub const Base = @import("platform/base.zig");
-pub const Sdk = @import("platform/sdk.zig");
-
-pub const backends = @import("platform/backends.zig");
-pub const BackendType = std.meta.DeclEnum(backends);
-
-pub fn Backend(comptime T: BackendType) type {
- return @field(backends, @tagName(T));
-}
diff --git a/src/phantom/platform/backends.zig b/src/phantom/platform/backends.zig
deleted file mode 100644
index 9027f6b..0000000
--- a/src/phantom/platform/backends.zig
+++ /dev/null
@@ -1,5 +0,0 @@
-const root = @import("root");
-
-pub const std = @import("backends/std.zig");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "platformBackends")) root.phantomOptions.platformBackends else struct {} else struct {};
diff --git a/src/phantom/platform/backends/std.zig b/src/phantom/platform/backends/std.zig
deleted file mode 100644
index 9ae42fb..0000000
--- a/src/phantom/platform/backends/std.zig
+++ /dev/null
@@ -1,2 +0,0 @@
-pub const Backend = @import("std/backend.zig");
-pub const Sdk = @import("std/sdk.zig");
diff --git a/src/phantom/platform/backends/std/backend.zig b/src/phantom/platform/backends/std/backend.zig
deleted file mode 100644
index f6543d5..0000000
--- a/src/phantom/platform/backends/std/backend.zig
+++ /dev/null
@@ -1,28 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const Backend = @import("../../base.zig");
-const Self = @This();
-
-allocator: Allocator,
-base: Backend,
-
-pub fn create(alloc: Allocator) !*Backend {
- const self = try alloc.create(Self);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .allocator = alloc,
- .base = .{
- .vtable = &.{
- .deinit = deinit,
- },
- .ptr = self,
- },
- };
- return &self.base;
-}
-
-fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
- self.allocator.destroy(self);
-}
diff --git a/src/phantom/platform/backends/std/sdk.zig b/src/phantom/platform/backends/std/sdk.zig
deleted file mode 100644
index 525db4c..0000000
--- a/src/phantom/platform/backends/std/sdk.zig
+++ /dev/null
@@ -1,42 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const Sdk = @import("../../sdk.zig");
-const Package = @import("sdk/step/pkg.zig");
-const InstallPackage = @import("sdk/step/install-pkg.zig");
-const Self = @This();
-
-base: Sdk,
-
-pub fn create(b: *std.Build, phantom: *std.Build.Module) !*Sdk {
- const self = try b.allocator.create(Self);
- errdefer b.allocator.destroy(self);
-
- self.* = .{
- .base = .{
- .vtable = &.{
- .addPackage = addPackage,
- .addInstallPackage = addInstallPackage,
- .deinit = deinit,
- },
- .ptr = self,
- .owner = b,
- .phantom = phantom,
- },
- };
- return &self.base;
-}
-
-fn addPackage(ctx: *anyopaque, _: *std.Build, options: Sdk.Step.Package.Options) *Sdk.Step.Package {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return Package.create(self, options) catch |e| @panic(@errorName(e));
-}
-
-fn addInstallPackage(ctx: *anyopaque, _: *std.Build, pkg: *Sdk.Step.Package) *Sdk.Step.InstallPackage {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return InstallPackage.create(self, pkg) catch |e| @panic(@errorName(e));
-}
-
-fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
- self.base.owner.allocator.destroy(self);
-}
diff --git a/src/phantom/platform/backends/std/sdk/step/install-pkg.zig b/src/phantom/platform/backends/std/sdk/step/install-pkg.zig
deleted file mode 100644
index 8988f10..0000000
--- a/src/phantom/platform/backends/std/sdk/step/install-pkg.zig
+++ /dev/null
@@ -1,26 +0,0 @@
-const std = @import("std");
-const Package = @import("../../../../sdk/step/pkg.zig");
-const InstallPackage = @import("../../../../sdk/step/install-pkg.zig");
-const Sdk = @import("../../sdk.zig");
-const Self = @This();
-
-base: InstallPackage,
-install: *std.Build.Step.InstallArtifact,
-
-pub fn create(sdk: *Sdk, pkg: *Package) !*InstallPackage {
- const b = sdk.base.owner;
- const self = try b.allocator.create(Self);
- errdefer b.allocator.free(self);
-
- self.base.init(&sdk.base, pkg, make);
-
- const compile = @as(*@import("pkg.zig"), @fieldParentPtr("base", pkg)).compile;
- self.install = b.addInstallArtifact(compile, .{});
- self.base.step.dependOn(&self.install.step);
-
- return &self.base;
-}
-
-fn make(step: *std.Build.Step, _: *std.Progress.Node) !void {
- _ = step;
-}
diff --git a/src/phantom/platform/backends/std/sdk/step/pkg.zig b/src/phantom/platform/backends/std/sdk/step/pkg.zig
deleted file mode 100644
index e9d51c3..0000000
--- a/src/phantom/platform/backends/std/sdk/step/pkg.zig
+++ /dev/null
@@ -1,33 +0,0 @@
-const std = @import("std");
-const Package = @import("../../../../sdk/step/pkg.zig");
-const Sdk = @import("../../sdk.zig");
-const Self = @This();
-
-base: Package,
-compile: *std.Build.Step.Compile,
-
-pub fn create(sdk: *Sdk, options: Package.Options) !*Package {
- const b = sdk.base.owner;
- const self = try b.allocator.create(Self);
- errdefer b.allocator.free(self);
-
- self.compile = std.Build.Step.Compile.create(b, .{
- .name = options.name,
- .root_module = options.root_module,
- .version = options.version,
- .kind = .exe,
- });
-
- self.compile.root_module.addImport("phantom", sdk.base.phantom);
-
- self.base.init(&sdk.base, options, make, &self.compile.root_module);
- self.base.step.dependOn(&self.compile.step);
- return &self.base;
-}
-
-fn make(step: *std.Build.Step, _: *std.Progress.Node) !void {
- const pkgStep: *Package = @fieldParentPtr("step", step);
- const self: *Self = @fieldParentPtr("base", pkgStep);
-
- pkgStep.output_file.path = step.owner.dupe(self.compile.generated_bin.?.path.?);
-}
diff --git a/src/phantom/platform/base.zig b/src/phantom/platform/base.zig
deleted file mode 100644
index 3415089..0000000
--- a/src/phantom/platform/base.zig
+++ /dev/null
@@ -1,13 +0,0 @@
-const std = @import("std");
-const Base = @This();
-
-pub const VTable = struct {
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn deinit(self: *const Base) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
diff --git a/src/phantom/platform/sdk.zig b/src/phantom/platform/sdk.zig
deleted file mode 100644
index 739b09f..0000000
--- a/src/phantom/platform/sdk.zig
+++ /dev/null
@@ -1,31 +0,0 @@
-const std = @import("std");
-const Base = @This();
-
-pub const Step = @import("sdk/step.zig");
-
-pub const VTable = struct {
- addPackage: *const fn (*anyopaque, *std.Build, Step.Package.Options) *Step.Package,
- addInstallPackage: *const fn (*anyopaque, *std.Build, *Step.Package) *Step.InstallPackage,
- deinit: ?*const fn (*anyopaque) void = null,
-};
-
-vtable: *const VTable,
-ptr: *anyopaque,
-owner: *std.Build,
-phantom: *std.Build.Module,
-
-pub inline fn addPackage(self: *const Base, options: Step.Package.Options) *Step.Package {
- return self.vtable.addPackage(self.ptr, self.owner, options);
-}
-
-pub inline fn addInstallPackage(self: *const Base, pkg: *Step.Package) *Step.InstallPackage {
- return self.vtable.addInstallPackage(self.ptr, self.owner, pkg);
-}
-
-pub inline fn deinit(self: *const Base) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub fn installPackage(self: *const Base, pkg: *Step.Package) void {
- self.owner.getInstallStep().dependOn(&self.addInstallPackage(pkg).step);
-}
diff --git a/src/phantom/platform/sdk/step.zig b/src/phantom/platform/sdk/step.zig
deleted file mode 100644
index 5cc5fc7..0000000
--- a/src/phantom/platform/sdk/step.zig
+++ /dev/null
@@ -1,2 +0,0 @@
-pub const Package = @import("step/pkg.zig");
-pub const InstallPackage = @import("step/install-pkg.zig");
diff --git a/src/phantom/platform/sdk/step/install-pkg.zig b/src/phantom/platform/sdk/step/install-pkg.zig
deleted file mode 100644
index 56655c1..0000000
--- a/src/phantom/platform/sdk/step/install-pkg.zig
+++ /dev/null
@@ -1,25 +0,0 @@
-const std = @import("std");
-const Sdk = @import("../../sdk.zig");
-const Package = @import("pkg.zig");
-const Self = @This();
-
-sdk: *Sdk,
-step: std.Build.Step,
-package: *Package,
-
-pub fn init(self: *Self, sdk: *Sdk, pkg: *Package, makeFn: std.Build.Step.MakeFn) void {
- const b = sdk.owner;
-
- self.* = .{
- .sdk = sdk,
- .step = std.Build.Step.init(.{
- .id = .custom,
- .name = b.fmt("Install package {s} (id: {s})", .{ pkg.name, pkg.id }),
- .owner = b,
- .makeFn = makeFn,
- }),
- .package = pkg,
- };
-
- self.step.dependOn(&pkg.step);
-}
diff --git a/src/phantom/platform/sdk/step/pkg.zig b/src/phantom/platform/sdk/step/pkg.zig
deleted file mode 100644
index 1b6e794..0000000
--- a/src/phantom/platform/sdk/step/pkg.zig
+++ /dev/null
@@ -1,40 +0,0 @@
-const std = @import("std");
-const Sdk = @import("../../sdk.zig");
-const Self = @This();
-
-pub const Kind = enum { application };
-
-pub const Options = struct {
- id: []const u8,
- name: []const u8,
- root_module: std.Build.Module.CreateOptions,
- kind: Kind,
- version: std.SemanticVersion,
-};
-
-sdk: *Sdk,
-step: std.Build.Step,
-id: []const u8,
-name: []const u8,
-output_file: std.Build.GeneratedFile,
-kind: Kind,
-root_module: *std.Build.Module,
-
-pub fn init(self: *Self, sdk: *Sdk, options: Options, makeFn: std.Build.Step.MakeFn, root_module: *std.Build.Module) void {
- const b = sdk.owner;
-
- self.* = .{
- .sdk = sdk,
- .step = std.Build.Step.init(.{
- .id = .custom,
- .name = b.fmt("Package {s} (id: {s})", .{ options.name, options.id }),
- .owner = b,
- .makeFn = makeFn,
- }),
- .id = options.id,
- .name = options.name,
- .output_file = .{ .step = &self.step },
- .kind = options.kind,
- .root_module = root_module,
- };
-}
diff --git a/src/phantom/scene.zig b/src/phantom/scene.zig
deleted file mode 100644
index 8eb2cb3..0000000
--- a/src/phantom/scene.zig
+++ /dev/null
@@ -1,54 +0,0 @@
-const std = @import("std");
-const metaplus = @import("meta+");
-
-const base = struct {
- pub const NodeTree = @import("scene/node-tree.zig");
- pub const NodeFlex = @import("scene/node-flex.zig");
- pub const NodeStack = @import("scene/node-stack.zig");
-};
-
-pub const Base = @import("scene/base.zig");
-pub const Node = @import("scene/node.zig");
-pub usingnamespace base;
-
-pub const nodes = @import("scene/nodes.zig");
-pub const backends = @import("scene/backends.zig");
-pub const BackendType = metaplus.enums.fromDecls(backends);
-pub const NodeType = metaplus.enums.fields.mix(std.meta.DeclEnum(base), std.meta.DeclEnum(nodes));
-
-pub fn NodeOptions(comptime T: NodeType) type {
- return @field(struct {
- pub usingnamespace base;
- pub usingnamespace nodes;
- }, @tagName(T)).Options;
-}
-
-pub fn Backend(comptime T: BackendType) type {
- return struct {
- pub usingnamespace @field(backends, @tagName(T));
- pub const Node = @import("scene/node.zig");
- pub usingnamespace base;
- };
-}
-
-pub fn createBackend(t: BackendType, options: Base.Options) !*Base {
- const tag = std.enums.tagName(BackendType, t) orelse return error.InvalidBackend;
- inline for (@typeInfo(backends).Struct.decls) |decl| {
- if (std.mem.eql(u8, decl.name, tag)) {
- const backend = Backend(@field(BackendType, decl.name));
- return &(try backend.Scene.new(options)).base;
- }
- }
- return error.InvalidBackend;
-}
-
-pub fn createNode(backendType: BackendType, alloc: std.mem.Allocator, id: ?usize, comptime nodeType: NodeType, options: NodeOptions(nodeType)) !*Node {
- const tag = std.enums.tagName(BackendType, backendType) orelse return error.InvalidBackend;
- inline for (@typeInfo(backends).Struct.decls) |decl| {
- if (std.mem.eql(u8, decl.name, tag)) {
- const backend = Backend(@field(BackendType, decl.name));
- return @field(backend, @tagName(nodeType)).new(alloc, id, options);
- }
- }
- return error.InvalidBackend;
-}
diff --git a/src/phantom/scene/backends.zig b/src/phantom/scene/backends.zig
deleted file mode 100644
index 8f7c5dd..0000000
--- a/src/phantom/scene/backends.zig
+++ /dev/null
@@ -1,6 +0,0 @@
-const root = @import("root");
-
-pub const headless = @import("backends/headless.zig");
-pub const fb = @import("backends/fb.zig");
-
-pub usingnamespace if (@hasDecl(root, "phantomOptions")) if (@hasDecl(root.phantomOptions, "sceneBackends")) root.phantomOptions.sceneBackends else struct {} else struct {};
diff --git a/src/phantom/scene/backends/fb.zig b/src/phantom/scene/backends/fb.zig
deleted file mode 100644
index 24b9f13..0000000
--- a/src/phantom/scene/backends/fb.zig
+++ /dev/null
@@ -1,96 +0,0 @@
-const std = @import("std");
-const vizops = @import("vizops");
-const math = @import("../../math.zig");
-const painting = @import("../../painting.zig");
-const BaseScene = @import("../base.zig");
-
-pub const Scene = @import("fb/scene.zig");
-
-pub const NodeArc = @import("../nodes/arc.zig").NodeArc(struct {
- const Self = @This();
- pub const Scene = @import("fb/scene.zig");
-
- pub fn frame(self: *NodeArc, scene: *Self.Scene, subscene: ?BaseScene.Sub) anyerror!void {
- const size = (try self.node.state(scene.base.frameInfo())).size;
- const pos: vizops.vector.UsizeVector2 = if (subscene) |sub| sub.pos else .{};
-
- const bufferInfo = scene.buffer.info();
- const buffer = try self.node.allocator.alloc(u8, @divExact(bufferInfo.colorFormat.width(), 8));
- defer self.node.allocator.free(buffer);
- try vizops.color.writeAnyBuffer(bufferInfo.colorFormat, buffer, self.options.color);
-
- try painting.Canvas.init(scene.buffer, .{
- .size = size,
- .pos = pos,
- }).arc(vizops.vector.UsizeVector2.init([_]usize{ std.math.lossyCast(usize, self.options.radius), 0 }), self.options.angles, self.options.radius, buffer);
- }
-});
-
-pub const NodeFrameBuffer = @import("../nodes/fb.zig").NodeFb(struct {
- const Self = @This();
- pub const Scene = @import("fb/scene.zig");
-
- pub fn frame(self: *NodeFrameBuffer, scene: *Self.Scene, subscene: ?BaseScene.Sub) anyerror!void {
- const size = (try self.node.state(scene.base.frameInfo())).size;
- const pos: vizops.vector.UsizeVector2 = if (subscene) |sub| sub.pos else .{};
-
- try self.options.source.blt(.to, scene.buffer, .{
- .sourceOffset = self.options.offset,
- .destOffset = pos,
- .size = size,
- .blend = self.options.blend,
- });
- }
-});
-
-pub const NodeRect = @import("../nodes/rect.zig").NodeRect(struct {
- const Self = @This();
- pub const Scene = @import("fb/scene.zig");
-
- pub fn frame(self: *NodeRect, scene: *Self.Scene, subscene: ?BaseScene.Sub) anyerror!void {
- const size = (try self.node.state(scene.base.frameInfo())).size;
- const pos: vizops.vector.UsizeVector2 = if (subscene) |sub| sub.pos else .{};
-
- const bufferInfo = scene.buffer.info();
- const buffer = try self.node.allocator.alloc(u8, @divExact(bufferInfo.colorFormat.width(), 8));
- defer self.node.allocator.free(buffer);
- try vizops.color.writeAnyBuffer(bufferInfo.colorFormat, buffer, self.options.color);
-
- const radius = self.options.radius orelse painting.Radius(f32){};
-
- try painting.Canvas.init(scene.buffer, .{
- .size = size,
- .pos = pos,
- }).rect(vizops.vector.UsizeVector2.zero(), size, radius, buffer);
- }
-});
-
-pub const NodeText = @import("../nodes/text.zig").NodeText(struct {
- const Self = @This();
- pub const Scene = @import("fb/scene.zig");
-
- pub fn frame(self: *NodeText, scene: *Self.Scene, subscene: ?BaseScene.Sub) anyerror!void {
- const size = (try self.node.state(scene.base.frameInfo())).size;
- const startPos: vizops.vector.UsizeVector2 = if (subscene) |sub| sub.pos else .{};
-
- var viewIter = self.options.view.iterator();
-
- var pos = vizops.vector.UsizeVector2.zero();
-
- while (viewIter.nextCodepoint()) |cp| {
- const glyph = try self.options.font.lookupGlyph(cp);
-
- var destOffset = pos.add(startPos);
-
- destOffset.value[0] = math.add(destOffset.value[0], glyph.bearing.value[0]);
- destOffset.value[1] = math.add(destOffset.value[1], size.value[1] - @as(u8, @intCast(glyph.bearing.value[1])));
-
- try glyph.fb.blt(.to, scene.buffer, .{
- .destOffset = destOffset,
- .size = glyph.size.cast(usize),
- });
-
- pos.value[0] += @intCast(glyph.advance.value[0]);
- }
- }
-});
diff --git a/src/phantom/scene/backends/fb/scene.zig b/src/phantom/scene/backends/fb/scene.zig
deleted file mode 100644
index 2a7ac6c..0000000
--- a/src/phantom/scene/backends/fb/scene.zig
+++ /dev/null
@@ -1,99 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const Scene = @import("../../base.zig");
-const Node = @import("../../node.zig");
-const Fb = @import("../../../painting/fb/base.zig");
-const FbScene = @This();
-
-frame_info: Node.FrameInfo,
-target: Scene.Target,
-buffer: *Fb,
-base: Scene,
-
-pub fn new(options: Scene.Options) !*FbScene {
- const alloc = options.allocator;
- const target = if (options.target) |target| target else return error.BadTarget;
-
- const buffer = switch (target) {
- .surface => |surf| blk: {
- const info = try surf.info();
- break :blk try surf.device.createFrameBuffer(.{
- .res = info.size,
- .colorspace = info.colorspace,
- .colorFormat = info.colorFormat,
- });
- },
- .fb => |fb| try fb.dupe(),
- else => return error.BadTarget,
- };
- errdefer buffer.deinit();
-
- const self = try alloc.create(FbScene);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .frame_info = options.frame_info,
- .base = .{
- .allocator = alloc,
- .ptr = self,
- .vtable = &.{
- .sub = null,
- .frameInfo = frameInfo,
- .deinit = deinit,
- .preFrame = preFrame,
- .postFrame = postFrame,
- },
- .subscene = null,
- .type = .fb,
- },
- .buffer = buffer,
- .target = target,
- };
- return self;
-}
-
-fn frameInfo(ctx: *anyopaque) Node.FrameInfo {
- const self: *FbScene = @ptrCast(@alignCast(ctx));
- return self.frame_info;
-}
-
-fn deinit(ctx: *anyopaque) void {
- const self: *FbScene = @ptrCast(@alignCast(ctx));
- self.buffer.deinit();
- self.target.deinit();
- self.base.allocator.destroy(self);
-}
-
-fn preFrame(ctx: *anyopaque, _: *Node) anyerror!void {
- const self: *FbScene = @ptrCast(@alignCast(ctx));
- return self.buffer.lock();
-}
-
-fn postFrame(ctx: *anyopaque, _: *Node, didWork: bool) anyerror!void {
- const self: *FbScene = @ptrCast(@alignCast(ctx));
- defer self.buffer.unlock();
-
- if (didWork) {
- try self.buffer.commit();
-
- if (self.target == .fb) {
- if (try self.target.fb.addr() == try self.buffer.addr()) {
- try self.target.fb.commit();
- return;
- }
- }
-
- try switch (self.target) {
- .surface => |s| s.blt(.from, self.buffer, .{}),
- .fb => |f| f.blt(.from, self.buffer, .{}),
- else => unreachable,
- };
- }
-
- try switch (self.target) {
- .surface => {},
- .fb => |f| f.commit(),
- else => unreachable,
- };
-}
diff --git a/src/phantom/scene/backends/headless.zig b/src/phantom/scene/backends/headless.zig
deleted file mode 100644
index 9bce30c..0000000
--- a/src/phantom/scene/backends/headless.zig
+++ /dev/null
@@ -1,6 +0,0 @@
-pub const Scene = @import("headless/scene.zig");
-
-pub const NodeArc = @import("../nodes/arc.zig").NodeArc(struct {});
-pub const NodeFrameBuffer = @import("../nodes/fb.zig").NodeFb(struct {});
-pub const NodeRect = @import("../nodes/rect.zig").NodeRect(struct {});
-pub const NodeText = @import("../nodes/text.zig").NodeText(struct {});
diff --git a/src/phantom/scene/backends/headless/scene.zig b/src/phantom/scene/backends/headless/scene.zig
deleted file mode 100644
index 63c3383..0000000
--- a/src/phantom/scene/backends/headless/scene.zig
+++ /dev/null
@@ -1,41 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const Scene = @import("../../base.zig");
-const Node = @import("../../node.zig");
-const HeadlessScene = @This();
-
-frame_info: Node.FrameInfo,
-base: Scene,
-
-pub fn new(options: Scene.Options) Allocator.Error!*HeadlessScene {
- const alloc = options.allocator;
- const self = try alloc.create(HeadlessScene);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .frame_info = options.frame_info,
- .base = .{
- .allocator = alloc,
- .ptr = self,
- .vtable = &.{
- .sub = null,
- .frameInfo = frameInfo,
- .deinit = deinit,
- },
- .subscene = null,
- .type = .headless,
- },
- };
- return self;
-}
-
-fn frameInfo(ctx: *anyopaque) Node.FrameInfo {
- const self: *HeadlessScene = @ptrCast(@alignCast(ctx));
- return self.frame_info;
-}
-
-fn deinit(ctx: *anyopaque) void {
- const self: *HeadlessScene = @ptrCast(@alignCast(ctx));
- self.base.allocator.destroy(self);
-}
diff --git a/src/phantom/scene/base.zig b/src/phantom/scene/base.zig
deleted file mode 100644
index 377ef03..0000000
--- a/src/phantom/scene/base.zig
+++ /dev/null
@@ -1,96 +0,0 @@
-const std = @import("std");
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const GpuSurface = @import("../gpu/surface.zig");
-const fb = @import("../painting/fb.zig");
-const sceneModule = @import("../scene.zig");
-const Node = @import("node.zig");
-const Scene = @This();
-
-pub const Target = union(enum) {
- surface: *GpuSurface,
- fb: *fb.Base,
- any: struct {
- value: anyplus.Anytype,
- deinitFunc: ?*const fn (*const anyplus.Anytype) void = null,
-
- pub fn deinit(self: *const @This()) void {
- if (self.deinitFunc) |f| f(&self.value);
- }
- },
-
- pub fn deinit(self: Target) void {
- switch (self) {
- .surface => |s| s.deinit(),
- .fb => |f| f.deinit(),
- .any => |a| a.deinit(),
- }
- }
-};
-
-pub const Sub = struct {
- pos: vizops.vector.UsizeVector2,
- size: vizops.vector.UsizeVector2,
-};
-
-pub const Options = struct {
- allocator: std.mem.Allocator,
- frame_info: Node.FrameInfo,
- target: ?Target,
-};
-
-pub const VTable = struct {
- sub: ?*const fn (*anyopaque, vizops.vector.UsizeVector2, vizops.vector.UsizeVector2) *anyopaque,
- frameInfo: *const fn (*anyopaque) Node.FrameInfo,
- deinit: ?*const fn (*anyopaque) void = null,
- preFrame: ?*const fn (*anyopaque, *Node) anyerror!void = null,
- postFrame: ?*const fn (*anyopaque, *Node, bool) anyerror!void = null,
-};
-
-allocator: std.mem.Allocator,
-vtable: *const VTable,
-type: sceneModule.BackendType,
-ptr: *anyopaque,
-subscene: ?Sub = null,
-seq: u64 = 0,
-
-pub fn sub(self: *Scene, pos: vizops.vector.UsizeVector2, size: vizops.vector.UsizeVector2) Scene {
- return .{
- .allocator = self.allocator,
- .vtable = self.vtable,
- .type = self.type,
- .ptr = if (self.vtable.sub) |f| f(self.ptr, pos, size) else self.ptr,
- .subscene = .{
- .pos = pos,
- .size = size,
- },
- };
-}
-
-pub inline fn frameInfo(self: *Scene) Node.FrameInfo {
- return self.vtable.frameInfo(self.ptr).withSequence(self.seq);
-}
-
-pub inline fn deinit(self: *Scene) void {
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub fn frame(self: *Scene, node: *Node) !bool {
- if (self.vtable.preFrame) |f| try f(self.ptr, node);
-
- if (try node.preFrame(self.frameInfo(), self)) {
- self.seq += 1;
- try node.frame(self);
- try node.postFrame(self);
-
- if (self.vtable.postFrame) |f| try f(self.ptr, node, true);
- return true;
- }
-
- if (self.vtable.postFrame) |f| try f(self.ptr, node, false);
- return false;
-}
-
-pub fn createNode(self: *Scene, comptime NodeType: sceneModule.NodeType, options: sceneModule.NodeOptions(NodeType)) !*Node {
- return sceneModule.createNode(self.type, self.allocator, @returnAddress(), NodeType, options);
-}
diff --git a/src/phantom/scene/node-flex.zig b/src/phantom/scene/node-flex.zig
deleted file mode 100644
index f101f6b..0000000
--- a/src/phantom/scene/node-flex.zig
+++ /dev/null
@@ -1,140 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const painting = @import("../painting.zig");
-const Scene = @import("base.zig");
-const Node = @import("node.zig");
-const NodeTree = @import("node-tree.zig");
-const NodeFlex = @This();
-
-pub const Options = struct {
- direction: painting.Axis,
- children: ?[]const *Node = null,
-};
-
-tree: NodeTree,
-direction: painting.Axis,
-children: std.ArrayList(*Node),
-
-pub inline fn init(comptime T: type, allocator: Allocator, id: ?usize, ptr: *anyopaque) NodeTree {
- return .{
- .vtable = &.{
- .children = impl_children,
- .overflow = impl_overflow,
- .dupe = impl_dupe,
- .deinit = impl_deinit,
- .format = impl_format,
- .setProperties = impl_set_properties,
- },
- .ptr = ptr,
- .node = NodeTree.init(T, allocator, id orelse @returnAddress(), @ptrFromInt(@intFromPtr(ptr) + @offsetOf(NodeFlex, "tree"))),
- };
-}
-
-pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(NodeFlex);
- self.* = .{
- .tree = init(NodeFlex, alloc, id orelse @returnAddress(), self),
- .direction = options.direction,
- .children = std.ArrayList(*Node).init(alloc),
- };
-
- if (options.children) |children| {
- try self.children.appendSlice(children);
- }
- return &self.tree.node;
-}
-
-fn impl_children(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!std.ArrayList(NodeTree.Child) {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
- var v = try std.ArrayList(NodeTree.Child).initCapacity(self.children.allocator, self.children.items.len);
- errdefer v.deinit();
-
- var pos = vizops.vector.UsizeVector2.zero();
-
- for (self.children.items) |child| {
- const cstate = try child.state(frameInfo);
- defer cstate.deinit(self.children.allocator);
-
- v.appendAssumeCapacity(.{
- .node = child,
- .pos = pos,
- });
-
- pos = pos.add(vizops.vector.UsizeVector2.init([_]usize{
- if (self.direction == .horizontal) cstate.size.value[0] else 0,
- if (self.direction == .vertical) cstate.size.value[1] else 0,
- }));
- }
-
- return v;
-}
-
-fn impl_overflow(ctx: *anyopaque, node: *Node) anyerror!void {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
- var name = std.ArrayList(u8).init(self.children.allocator);
- defer name.deinit();
-
- try node.formatName(self.children.allocator, name.writer());
-
- std.debug.panic("Node {s} is overflowing", .{name.items});
-}
-
-fn impl_dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
- const d = try self.children.allocator.create(NodeFlex);
- errdefer self.children.allocator.destroy(d);
-
- d.* = .{
- .tree = init(NodeFlex, self.children.allocator, @returnAddress(), d),
- .direction = self.direction,
- .children = try std.ArrayList(*Node).initCapacity(self.children.allocator, self.children.items.len),
- };
-
- errdefer d.children.deinit();
-
- for (self.children.items) |child| {
- d.children.appendAssumeCapacity(try child.dupe());
- }
- return &d.tree.node;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
- const alloc = self.children.allocator;
-
- for (self.children.items) |child| child.deinit();
- self.children.deinit();
- alloc.destroy(self);
-}
-
-fn impl_format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.children.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .direction = {?s}, .children = [{}] {any} }}", .{ std.enums.tagName(painting.Axis, self.direction), self.children.items.len, self.children.items });
- return output;
-}
-
-fn impl_set_properties(ctx: *anyopaque, args: std.StringHashMap(anyplus.Anytype)) anyerror!void {
- const self: *NodeFlex = @ptrCast(@alignCast(ctx));
-
- var iter = args.iterator();
- while (iter.next()) |entry| {
- const key = entry.key_ptr.*;
-
- if (std.mem.eql(u8, key, "children")) {
- const value = try entry.value_ptr.cast([]*Node);
- while (self.children.popOrNull()) |child| child.deinit();
-
- for (value) |child| {
- try self.children.append(try child.dupe());
- }
- } else if (std.mem.eql(u8, key, "direction")) {
- self.direction = try entry.value_ptr.cast(painting.Axis);
- } else return error.InvalidKey;
- }
-}
diff --git a/src/phantom/scene/node-stack.zig b/src/phantom/scene/node-stack.zig
deleted file mode 100644
index 3257c2f..0000000
--- a/src/phantom/scene/node-stack.zig
+++ /dev/null
@@ -1,124 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const painting = @import("../painting.zig");
-const Scene = @import("base.zig");
-const Node = @import("node.zig");
-const NodeTree = @import("node-tree.zig");
-const NodeStack = @This();
-
-pub const Options = struct {
- children: ?[]const *Node,
-};
-
-tree: NodeTree,
-children: std.ArrayList(*Node),
-
-pub inline fn init(comptime T: type, allocator: Allocator, id: ?usize, ptr: *anyopaque) NodeTree {
- return .{
- .vtable = &.{
- .children = impl_children,
- .overflow = impl_overflow,
- .dupe = impl_dupe,
- .deinit = impl_deinit,
- .format = impl_format,
- .setProperties = impl_set_properties,
- },
- .ptr = ptr,
- .node = NodeTree.init(T, allocator, id orelse @returnAddress(), @ptrFromInt(@intFromPtr(ptr) + @offsetOf(NodeStack, "tree"))),
- };
-}
-
-pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(NodeStack);
- self.* = .{
- .tree = init(NodeStack, alloc, id orelse @returnAddress(), self),
- .children = std.ArrayList(*Node).init(alloc),
- };
-
- if (options.children) |children| {
- try self.children.appendSlice(children);
- }
- return &self.tree.node;
-}
-
-fn impl_children(ctx: *anyopaque, _: Node.FrameInfo) anyerror!std.ArrayList(NodeTree.Child) {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
- var v = try std.ArrayList(NodeTree.Child).initCapacity(self.children.allocator, self.children.items.len);
- errdefer v.deinit();
-
- for (self.children.items) |child| {
- v.appendAssumeCapacity(.{
- .node = child,
- .pos = vizops.vector.UsizeVector2.zero(),
- });
- }
-
- return v;
-}
-
-fn impl_overflow(ctx: *anyopaque, node: *Node) anyerror!void {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
- var name = std.ArrayList(u8).init(self.children.allocator);
- defer name.deinit();
-
- try node.formatName(self.children.allocator, name.writer());
-
- std.debug.panic("Node {s} is overflowing", .{name.items});
-}
-
-fn impl_dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
- const d = try self.children.allocator.create(NodeStack);
- errdefer self.children.allocator.destroy(d);
-
- d.* = .{
- .tree = init(NodeStack, self.tree.node.allocator, @returnAddress(), d),
- .children = try std.ArrayList(*Node).initCapacity(self.children.allocator, self.children.items.len),
- };
-
- errdefer d.children.deinit();
-
- for (self.children.items) |child| {
- d.children.appendAssumeCapacity(try child.dupe());
- }
- return &d.tree.node;
-}
-
-fn impl_deinit(ctx: *anyopaque) void {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
- const alloc = self.children.allocator;
-
- for (self.children.items) |child| child.deinit();
- self.children.deinit();
- alloc.destroy(self);
-}
-
-fn impl_format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.children.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .children = [{}] {any} }}", .{ self.children.items.len, self.children.items });
- return output;
-}
-
-fn impl_set_properties(ctx: *anyopaque, args: std.StringHashMap(anyplus.Anytype)) anyerror!void {
- const self: *NodeStack = @ptrCast(@alignCast(ctx));
-
- var iter = args.iterator();
- while (iter.next()) |entry| {
- const key = entry.key_ptr.*;
-
- if (std.mem.eql(u8, key, "children")) {
- const value = try entry.value_ptr.cast([]*Node);
- while (self.children.popOrNull()) |child| child.deinit();
-
- for (value) |child| {
- try self.children.append(try child.dupe());
- }
- } else return error.InvalidKey;
- }
-}
diff --git a/src/phantom/scene/node-tree.zig b/src/phantom/scene/node-tree.zig
deleted file mode 100644
index c93d536..0000000
--- a/src/phantom/scene/node-tree.zig
+++ /dev/null
@@ -1,245 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const math = @import("../math.zig");
-const Scene = @import("base.zig");
-const Node = @import("node.zig");
-const NodeTree = @This();
-
-pub const Child = struct {
- node: *Node,
- pos: vizops.vector.UsizeVector2,
-};
-
-pub const VTable = struct {
- children: *const fn (*anyopaque, Node.FrameInfo) anyerror!std.ArrayList(Child),
- overflow: ?*const fn (*anyopaque, node: *Node) anyerror!void,
- dupe: *const fn (*anyopaque) anyerror!*Node,
- deinit: ?*const fn (*anyopaque) void = null,
- format: ?*const fn (*anyopaque, ?Allocator) anyerror!std.ArrayList(u8) = null,
- setProperties: ?*const fn (*anyopaque, std.StringHashMap(anyplus.Anytype)) anyerror!void = null,
-};
-
-const ChildState = struct {
- state: Node.State,
- pos: vizops.vector.UsizeVector2,
-
- pub fn equal(self: ChildState, other: ChildState) bool {
- return std.simd.countTrues(@Vector(2, bool){
- self.state.equal(other.state),
- std.simd.countTrues(self.pos.value == other.pos.value) == 2,
- }) == 2;
- }
-
- pub inline fn deinit(self: ChildState, alloc: ?Allocator) void {
- return self.state.deinit(alloc);
- }
-};
-
-const State = struct {
- children: std.ArrayList(ChildState),
-
- pub fn init(children: std.ArrayList(ChildState)) Allocator.Error!*State {
- const self = try children.allocator.create(State);
-
- self.* = .{
- .children = try children.clone(),
- };
- return self;
- }
-
- pub fn equal(self: *State, other: *State) bool {
- if (self.children.items.len == other.children.items.len) {
- for (self.children.items, other.children.items) |s, o| {
- if (!s.equal(o)) return false;
- }
- return true;
- }
- return false;
- }
-
- pub fn deinit(self: *State) void {
- for (self.children.items) |child| child.deinit(self.children.allocator);
- self.children.deinit();
- self.children.allocator.destroy(self);
- }
-};
-
-node: Node,
-vtable: *const VTable,
-ptr: *anyopaque,
-
-pub inline fn init(comptime T: type, allocator: Allocator, id: ?usize, ptr: *anyopaque) Node {
- return .{
- .allocator = allocator,
- .ptr = ptr,
- .type = @typeName(T),
- .id = id orelse @returnAddress(),
- .vtable = &.{
- .dupe = dupe,
- .state = state,
- .preFrame = preFrame,
- .frame = frame,
- .postFrame = postFrame,
- .deinit = deinit,
- .format = format,
- .setProperties = setProperties,
- },
- };
-}
-
-fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
- const self: *State = @ptrCast(@alignCast(ctx));
- const other: *State = @ptrCast(@alignCast(otherctx));
- return self.equal(other);
-}
-
-fn stateFree(ctx: *anyopaque, _: std.mem.Allocator) void {
- const self: *State = @ptrCast(@alignCast(ctx));
- self.deinit();
-}
-
-fn dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- return self.vtable.dupe(self.ptr);
-}
-
-fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- var size = vizops.vector.UsizeVector2.zero();
-
- const children = try self.vtable.children(self.ptr, frameInfo);
- defer children.deinit();
-
- var states = std.ArrayList(ChildState).init(children.allocator);
- defer states.deinit();
-
- for (children.items) |child| {
- const childSize = frameInfo.size.avail.sub(size);
- if (std.simd.countTrues(childSize.value == vizops.vector.UsizeVector2.zero().value) == 2 and self.vtable.overflow != null) {
- const overflow = self.vtable.overflow.?;
- try overflow(self.ptr, child.node);
- }
-
- const cstate = try child.node.state(frameInfo.child(childSize));
- errdefer cstate.deinit(children.allocator);
-
- size.value[0] = @max(size.value[0], child.pos.value[0] + cstate.size.value[0]);
- size.value[1] = @max(size.value[1], child.pos.value[1] + cstate.size.value[1]);
-
- try states.append(.{
- .state = cstate,
- .pos = child.pos,
- });
- }
-
- return .{
- .size = size,
- .frame_info = frameInfo,
- .allocator = children.allocator,
- .ptr = try State.init(states),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(NodeTree),
- };
-}
-
-fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, scene: *Scene) anyerror!Node.State {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- var size = vizops.vector.UsizeVector2.zero();
-
- const children = try self.vtable.children(self.ptr, frameInfo);
- defer children.deinit();
-
- var states = std.ArrayList(ChildState).init(children.allocator);
- defer states.deinit();
-
- for (children.items) |child| {
- const childSize = frameInfo.size.avail.sub(size);
- if (std.simd.countTrues(childSize.value == vizops.vector.UsizeVector2.zero().value) == 2 and self.vtable.overflow != null) {
- const overflow = self.vtable.overflow.?;
- try overflow(self.ptr, child.node);
- }
-
- const cframeInfo = frameInfo.child(childSize);
- const cstate = try child.node.state(cframeInfo);
- errdefer cstate.deinit(children.allocator);
-
- _ = try child.node.preFrame(cframeInfo, @constCast(&scene.sub(child.pos, cstate.size)));
-
- size.value[0] = @max(size.value[0], child.pos.value[0] + cstate.size.value[0]);
- size.value[1] = @max(size.value[1], child.pos.value[1] + cstate.size.value[1]);
-
- try states.append(.{
- .state = cstate,
- .pos = child.pos,
- });
- }
-
- return .{
- .size = size,
- .frame_info = frameInfo,
- .allocator = children.allocator,
- .ptr = try State.init(states),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(NodeTree),
- };
-}
-
-fn frame(ctx: *anyopaque, scene: *Scene) anyerror!void {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- const frameInfo = self.node.last_state.?.frame_info;
-
- const children = try self.vtable.children(self.ptr, frameInfo);
- defer children.deinit();
-
- for (children.items) |child| {
- try child.node.frame(@constCast(&scene.sub(child.pos, child.node.last_state.?.size)));
- }
-}
-
-fn postFrame(ctx: *anyopaque, scene: *Scene) anyerror!void {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- const frameInfo = self.node.last_state.?.frame_info;
-
- const children = try self.vtable.children(self.ptr, frameInfo);
- defer children.deinit();
-
- for (children.items) |child| {
- try child.node.postFrame(@constCast(&scene.sub(child.pos, child.node.last_state.?.size)));
- }
-}
-
-fn deinit(ctx: *anyopaque) void {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-fn format(ctx: *anyopaque, optAlloc: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- if (self.vtable.format) |f| return f(self.ptr, optAlloc);
-
- if (optAlloc) |alloc| {
- var output = std.ArrayList(u8).init(alloc);
- errdefer output.deinit();
-
- try output.writer().writeAll("{");
-
- if (self.node.last_state) |lastState| {
- if (self.vtable.children(self.ptr, lastState.frame_info) catch null) |children| {
- try output.writer().print(" .children = [{}] {any}", .{ children.items.len, children.items });
- }
- }
-
- try output.writer().writeAll(" }");
- return output;
- }
- return error.NoAlloc;
-}
-
-fn setProperties(ctx: *anyopaque, args: std.StringHashMap(anyplus.Anytype)) anyerror!void {
- const self: *NodeTree = @ptrCast(@alignCast(ctx));
- return if (self.vtable.setProperties) |f| f(self.ptr, args) else error.NoProperties;
-}
diff --git a/src/phantom/scene/node.zig b/src/phantom/scene/node.zig
deleted file mode 100644
index 43d63c4..0000000
--- a/src/phantom/scene/node.zig
+++ /dev/null
@@ -1,198 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const Scene = @import("base.zig");
-const Node = @This();
-
-pub const FrameInfo = struct {
- size: struct {
- phys: vizops.vector.Float32Vector2,
- res: vizops.vector.UsizeVector2,
- avail: vizops.vector.UsizeVector2,
- },
- scale: vizops.vector.Float32Vector2,
- colorFormat: vizops.color.fourcc.Value,
- seq: u64 = 0,
-
- pub const Options = struct {
- res: vizops.vector.UsizeVector2,
- scale: vizops.vector.Float32Vector2 = vizops.vector.Float32Vector2.init(1.0),
- physicalSize: vizops.vector.Float32Vector2 = vizops.vector.Float32Vector2.zero(),
- colorFormat: vizops.color.fourcc.Value,
- };
-
- pub fn init(options: Options) FrameInfo {
- return .{
- .size = .{
- .phys = options.physicalSize,
- .res = options.res,
- .avail = options.res,
- },
- .scale = options.scale,
- .colorFormat = options.colorFormat,
- };
- }
-
- pub fn equal(self: FrameInfo, other: FrameInfo) bool {
- return std.simd.countTrues(@Vector(5, bool){
- self.size.phys.eq(other.size.phys),
- self.size.res.eq(other.size.res),
- self.size.avail.eq(other.size.avail),
- self.scale.eq(other.scale),
- self.colorFormat.eq(other.colorFormat),
- }) == 5;
- }
-
- pub fn withSequence(self: FrameInfo, value: u64) FrameInfo {
- return .{
- .size = self.size,
- .scale = self.scale,
- .colorFormat = self.colorFormat,
- .seq = value,
- };
- }
-
- pub fn child(self: FrameInfo, availSize: vizops.vector.UsizeVector2) FrameInfo {
- return .{ .size = .{
- .phys = self.size.phys,
- .res = self.size.res,
- .avail = availSize,
- }, .scale = self.scale, .colorFormat = self.colorFormat, .seq = self.seq };
- }
-};
-
-pub const VTable = struct {
- dupe: *const fn (*anyopaque) anyerror!*Node,
- state: *const fn (*anyopaque, FrameInfo) anyerror!State,
- preFrame: *const fn (*anyopaque, FrameInfo, *Scene) anyerror!State,
- frame: *const fn (*anyopaque, *Scene) anyerror!void,
- postFrame: ?*const fn (*anyopaque, *Scene) anyerror!void = null,
- deinit: ?*const fn (*anyopaque) void = null,
- format: ?*const fn (*anyopaque, ?std.mem.Allocator) anyerror!std.ArrayList(u8) = null,
- cast: ?*const fn (*anyopaque, []const u8) error{BadCast}!*anyopaque = null,
- setProperties: ?*const fn (*anyopaque, std.StringHashMap(anyplus.Anytype)) anyerror!void = null,
-};
-
-pub const State = struct {
- size: vizops.vector.UsizeVector2,
- frame_info: FrameInfo,
- ptr: ?*anyopaque = null,
- allocator: ?std.mem.Allocator = null,
- ptrEqual: ?*const fn (*anyopaque, *anyopaque) bool = null,
- ptrFree: ?*const fn (*anyopaque, std.mem.Allocator) void = null,
- type: []const u8,
-
- pub inline fn deinit(self: State, alloc: ?std.mem.Allocator) void {
- return if (self.ptrFree) |f| f(self.ptr.?, (self.allocator orelse alloc).?);
- }
-
- pub fn equal(self: State, other: State) bool {
- const typeMatch = std.mem.eql(u8, self.type, other.type);
- return std.simd.countTrues(@Vector(4, bool){
- self.size.eq(other.size),
- self.frame_info.equal(other.frame_info),
- if (typeMatch) if (self.ptrEqual) |f| f(self.ptr.?, other.ptr.?) else self.ptr == other.ptr else false,
- typeMatch,
- }) == 4;
- }
-};
-
-allocator: std.mem.Allocator,
-vtable: *const VTable,
-ptr: *anyopaque,
-type: []const u8,
-id: usize,
-last_state: ?State = null,
-
-pub inline fn dupe(self: *Node) anyerror!*Node {
- return self.vtable.dupe(self.ptr);
-}
-
-pub inline fn state(self: *Node, frameInfo: FrameInfo) anyerror!State {
- return self.vtable.state(self.ptr, frameInfo);
-}
-
-pub fn preFrame(self: *Node, frameInfo: FrameInfo, scene: *Scene) anyerror!bool {
- const newState = try self.vtable.preFrame(self.ptr, frameInfo, scene);
- const shouldApply = !(if (self.last_state) |lastState| lastState.equal(newState) else false);
-
- if (shouldApply) {
- if (self.last_state) |l| l.deinit(self.allocator);
-
- self.last_state = newState;
- } else {
- newState.deinit(self.allocator);
- }
- return shouldApply;
-}
-
-pub inline fn frame(self: *Node, scene: *Scene) anyerror!void {
- return if (self.last_state != null) self.vtable.frame(self.ptr, scene);
-}
-
-pub inline fn postFrame(self: *Node, scene: *Scene) anyerror!void {
- return if (self.last_state != null) (if (self.vtable.postFrame) |f| f(self.ptr, scene));
-}
-
-pub inline fn deinit(self: *Node) void {
- if (self.last_state) |l| l.deinit(self.allocator);
- if (self.vtable.deinit) |f| f(self.ptr);
-}
-
-pub fn format(self: *const Node, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void {
- _ = options;
-
- if (self.vtable.format) |fmt| {
- const output = fmt(self.ptr, if (self.last_state) |s| s.allocator else null) catch return error.OutOfMemory;
- defer output.deinit();
-
- self.formatName(output.allocator, writer) catch return error.OutOfMemory;
- try writer.writeByte(' ');
- try writer.writeAll(output.items);
- } else {
- self.formatName(if (self.last_state) |s| s.allocator else null, writer) catch return error.OutOfMemory;
- try writer.print(" {{ .last_state = {?} }}", .{self.last_state});
- }
-}
-
-pub fn formatName(self: *const Node, optAlloc: ?std.mem.Allocator, writer: anytype) !void {
- if (builtin.mode == .Debug and !builtin.strip_debug_info and @hasDecl(std.posix, "errno") and @hasDecl(std.posix.system, "fd_t")) {
- const alloc = optAlloc orelse self.allocator;
- const debug = try std.debug.getSelfDebugInfo();
- const mod = try debug.getModuleForAddress(self.id);
- const sym = try mod.getSymbolAtAddress(alloc, self.id);
- defer sym.deinit(alloc);
-
- try std.fmt.format(writer, "{s}@", .{self.type});
-
- if (sym.line_info) |line| {
- try std.fmt.format(writer, "{s}:{}:{}", .{ line.file_name, line.line, line.column });
- } else {
- try std.fmt.format(writer, "{s}", .{sym.symbol_name});
- }
- } else if (builtin.mode == .ReleaseSmall) {
- return std.fmt.format(writer, "{s}@{x}", .{ self.type, @intFromPtr(self.ptr) });
- } else {
- return std.fmt.format(writer, "{s}@{x}", .{ self.type, self.id });
- }
-}
-
-pub fn cast(self: *Node, comptime T: type) error{BadCast}!*T {
- if (self.vtable.cast) |f| return f(self.ptr, @typeName(T));
- return error.BadCast;
-}
-
-pub fn setProperties(self: *Node, args: anytype) !void {
- if (self.vtable.setProperties) |f| {
- var argsMap = std.StringHashMap(anyplus.Anytype).init(self.allocator);
- defer argsMap.deinit();
-
- inline for (@typeInfo(@TypeOf(args)).Struct.fields) |fieldInfo| {
- const field = @field(args, fieldInfo.name);
- try argsMap.put(fieldInfo.name, anyplus.Anytype.init(field));
- }
- return f(self.ptr, argsMap);
- }
- return error.NoProperties;
-}
diff --git a/src/phantom/scene/nodes.zig b/src/phantom/scene/nodes.zig
deleted file mode 100644
index 5b9fa9e..0000000
--- a/src/phantom/scene/nodes.zig
+++ /dev/null
@@ -1,4 +0,0 @@
-pub const NodeArc = @import("nodes/arc.zig");
-pub const NodeFrameBuffer = @import("nodes/fb.zig");
-pub const NodeRect = @import("nodes/rect.zig");
-pub const NodeText = @import("nodes/text.zig");
diff --git a/src/phantom/scene/nodes/arc.zig b/src/phantom/scene/nodes/arc.zig
deleted file mode 100644
index f9d64bb..0000000
--- a/src/phantom/scene/nodes/arc.zig
+++ /dev/null
@@ -1,190 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const math = @import("../../math.zig");
-const Scene = @import("../base.zig");
-const Node = @import("../node.zig");
-
-pub const Options = struct {
- radius: f32,
- angles: vizops.vector.Float32Vector2,
- color: vizops.color.Any,
-};
-
-pub fn NodeArc(comptime Impl: type) type {
- return struct {
- const Self = @This();
- const ImplState = if (@hasDecl(Impl, "State")) Impl.State else void;
- const ImplScene = Impl.Scene;
-
- const State = struct {
- color: vizops.color.Any,
- implState: ImplState,
-
- pub fn init(alloc: Allocator, options: Options) Allocator.Error!*State {
- const self = try alloc.create(State);
- self.* = .{
- .color = options.color,
- .implState = if (ImplState != void) ImplState.init(alloc, options) else {},
- };
- return self;
- }
-
- pub fn equal(self: *State, other: *State) bool {
- return std.simd.countTrues(@Vector(2, bool){
- self.color.equal(other.color),
- if (ImplState != void) self.implState.equal(other.implState) else true,
- }) == 2;
- }
-
- pub fn deinit(self: *State, alloc: Allocator) void {
- if (ImplState != void) self.implState.deinit(alloc);
- alloc.destroy(self);
- }
- };
-
- options: Options,
- node: Node,
- impl: Impl,
-
- pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(Self);
- self.* = .{
- .options = options,
- .node = .{
- .allocator = alloc,
- .ptr = self,
- .type = @typeName(Self),
- .id = id orelse @returnAddress(),
- .vtable = &.{
- .dupe = dupe,
- .state = state,
- .preFrame = preFrame,
- .frame = frame,
- .deinit = deinit,
- .format = format,
- },
- },
- .impl = undefined,
- };
-
- if (@hasDecl(Impl, "new")) {
- try Impl.init(self);
- }
- return &self.node;
- }
-
- fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
- const self: *State = @ptrCast(@alignCast(ctx));
- const other: *State = @ptrCast(@alignCast(otherctx));
- return self.equal(other);
- }
-
- fn stateFree(ctx: *anyopaque, alloc: std.mem.Allocator) void {
- const self: *State = @ptrCast(@alignCast(ctx));
- self.deinit(alloc);
- }
-
- fn calcSize(self: *Self) vizops.vector.Float32Vector2 {
- const endpoint1 = vizops.vector.Float32Vector2.init([_]f32{
- self.options.radius * std.math.cos(self.options.angles.value[0]),
- self.options.radius * std.math.sin(self.options.angles.value[0]),
- });
-
- const endpoint2 = vizops.vector.Float32Vector2.init([_]f32{
- self.options.radius * std.math.cos(self.options.angles.value[1]),
- self.options.radius * std.math.sin(self.options.angles.value[1]),
- });
-
- var max = endpoint1.max(endpoint2);
- var min = endpoint1.min(endpoint2);
-
- if ((self.options.angles.value[0] <= 0 and self.options.angles.value[1] >= 0) or
- (self.options.angles.value[0] <= 2 * std.math.pi and self.options.angles.value[1] >= 2 * std.math.pi))
- {
- max.value[0] = @max(max.value[0], self.options.radius);
- }
-
- if (self.options.angles.value[0] <= std.math.pi and self.options.angles.value[1] >= std.math.pi) {
- min.value[0] = @min(min.value[0], -self.options.radius);
- }
-
- const halfPi = @as(f32, std.math.pi) / 2;
- if ((self.options.angles.value[0] <= halfPi and self.options.angles.value[1] >= halfPi) or
- (self.options.angles.value[0] <= 3 * halfPi and self.options.angles.value[1] >= 3 * halfPi))
- {
- max.value[1] = @max(max.value[1], self.options.radius);
- }
-
- if (self.options.angles.value[0] <= 3 * halfPi and self.options.angles.value[1] >= 3 * halfPi) {
- min.value[1] = @min(min.value[1], -self.options.radius);
- }
-
- return max.sub(min);
- }
-
- fn dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return try new(self.node.allocator, @returnAddress(), self.options);
- }
-
- fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return .{
- .size = math.rel(frameInfo, calcSize(self)),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, baseScene: *Scene) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "preFrame")) {
- try Impl.preFrame(self, frameInfo, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
-
- return .{
- .size = math.rel(frameInfo, calcSize(self)),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn frame(ctx: *anyopaque, baseScene: *Scene) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "frame")) {
- try Impl.frame(self, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
- }
-
- fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "deinit")) {
- Impl.deinit(self);
- }
-
- self.node.allocator.destroy(self);
- }
-
- fn format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.node.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .radius = {}, .color = {} }}", .{ self.options.radius, self.options.color });
- return output;
- }
- };
-}
diff --git a/src/phantom/scene/nodes/fb.zig b/src/phantom/scene/nodes/fb.zig
deleted file mode 100644
index dc63011..0000000
--- a/src/phantom/scene/nodes/fb.zig
+++ /dev/null
@@ -1,194 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const math = @import("../../math.zig");
-const Scene = @import("../base.zig");
-const Node = @import("../node.zig");
-const Fb = @import("../../painting/fb/base.zig");
-
-pub const Options = struct {
- source: *Fb,
- scale: vizops.vector.Float32Vector2 = .{ .value = @splat(1.0) },
- offset: vizops.vector.UsizeVector2 = .{},
- blend: vizops.color.BlendMode = .normal,
- size: ?vizops.vector.Float32Vector2 = null,
-};
-
-pub fn NodeFb(comptime Impl: type) type {
- return struct {
- const Self = @This();
- const ImplState = if (@hasDecl(Impl, "State")) Impl.State else void;
- const ImplScene = Impl.Scene;
-
- const State = struct {
- source: *Fb,
- scale: vizops.vector.Float32Vector2,
- offset: vizops.vector.UsizeVector2,
- blend: vizops.color.BlendMode,
- size: ?vizops.vector.Float32Vector2 = null,
- implState: ImplState,
-
- pub fn init(alloc: Allocator, options: Options) Allocator.Error!*State {
- const self = try alloc.create(State);
- self.* = .{
- .source = options.source,
- .scale = options.scale,
- .offset = options.offset,
- .blend = options.blend,
- .size = options.size,
- .implState = if (ImplState != void) ImplState.init(alloc, options) else {},
- };
- return self;
- }
-
- pub fn equal(self: *State, other: *State) bool {
- return std.simd.countTrues(@Vector(6, bool){
- self.scale.eq(other.scale),
- self.offset.eq(other.offset),
- self.blend == other.blend,
- if (self.size != null and other.size != null) self.size.?.eq(other.size.?) else false,
- @as(usize, @intFromPtr(self.source)) == @as(usize, @intFromPtr(other.source)) or (self.source.addr() catch null == other.source.addr() catch null),
- if (ImplState != void) self.implState.equal(other.implState) else true,
- }) == 6;
- }
-
- pub fn deinit(self: *State, alloc: Allocator) void {
- if (ImplState != void) self.implState.deinit(alloc);
- alloc.destroy(self);
- }
- };
-
- options: Options,
- node: Node,
- impl: Impl,
-
- pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(Self);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .options = options,
- .node = .{
- .allocator = alloc,
- .ptr = self,
- .type = @typeName(Self),
- .id = id orelse @returnAddress(),
- .vtable = &.{
- .dupe = dupe,
- .state = state,
- .preFrame = preFrame,
- .frame = frame,
- .deinit = deinit,
- .format = format,
- .setProperties = setProperties,
- },
- },
- .impl = undefined,
- };
-
- if (@hasDecl(Impl, "new")) {
- try Impl.init(self);
- }
- return &self.node;
- }
-
- fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
- const self: *State = @ptrCast(@alignCast(ctx));
- const other: *State = @ptrCast(@alignCast(otherctx));
- return self.equal(other);
- }
-
- fn stateFree(ctx: *anyopaque, alloc: std.mem.Allocator) void {
- const self: *State = @ptrCast(@alignCast(ctx));
- self.deinit(alloc);
- }
-
- fn dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return try new(self.node.allocator, @returnAddress(), self.options);
- }
-
- fn calcSize(self: Self, frameInfo: Node.FrameInfo) vizops.vector.UsizeVector2 {
- const base = if (self.options.size) |size| math.rel(frameInfo, size) else self.options.source.info().res;
- const scaled = base.cast(f32).mul(self.options.scale).cast(usize);
- return if (self.options.size == null) scaled.sub(self.options.offset) else scaled;
- }
-
- fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return .{
- .size = self.calcSize(frameInfo),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, baseScene: *Scene) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "preFrame")) {
- try Impl.preFrame(self, frameInfo, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
-
- return .{
- .size = self.calcSize(frameInfo),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn frame(ctx: *anyopaque, baseScene: *Scene) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "frame")) {
- try Impl.frame(self, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
- }
-
- fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "deinit")) {
- Impl.deinit(self);
- }
-
- self.options.source.deinit();
- self.node.allocator.destroy(self);
- }
-
- fn format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.node.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .scale = {}, .source = {} }}", .{ self.options.scale, self.options.source });
- return output;
- }
-
- fn setProperties(ctx: *anyopaque, args: std.StringHashMap(anyplus.Anytype)) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var iter = args.iterator();
- while (iter.next()) |entry| {
- const key = entry.key_ptr.*;
-
- if (std.mem.eql(u8, key, "scale")) {
- self.options.scale = try entry.value_ptr.cast(vizops.vector.Float32Vector2);
- } else if (std.mem.eql(u8, key, "source")) {
- self.options.source.deinit();
- self.options.source = try (try entry.value_ptr.cast(*Fb)).dupe();
- } else return error.InvalidKey;
- }
- }
- };
-}
diff --git a/src/phantom/scene/nodes/rect.zig b/src/phantom/scene/nodes/rect.zig
deleted file mode 100644
index 80d50c7..0000000
--- a/src/phantom/scene/nodes/rect.zig
+++ /dev/null
@@ -1,178 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const anyplus = @import("any+");
-const vizops = @import("vizops");
-const math = @import("../../math.zig");
-const painting = @import("../../painting.zig");
-const Node = @import("../node.zig");
-const Scene = @import("../base.zig");
-
-pub const Options = struct {
- radius: ?painting.Radius(f32) = null,
- size: vizops.vector.Float32Vector2,
- color: vizops.color.Any,
-};
-
-pub fn NodeRect(comptime Impl: type) type {
- return struct {
- const Self = @This();
- const ImplState = if (@hasDecl(Impl, "State")) Impl.State else void;
- const ImplScene = Impl.Scene;
-
- pub const State = struct {
- radius: ?painting.Radius(f32),
- color: vizops.color.Any,
- implState: ImplState,
-
- pub fn init(alloc: Allocator, options: Options) Allocator.Error!*State {
- const self = try alloc.create(State);
- self.* = .{
- .radius = options.radius,
- .color = options.color,
- .implState = if (ImplState != void) ImplState.init(alloc, options) else {},
- };
- return self;
- }
-
- pub fn equal(self: *State, other: *State) bool {
- return std.simd.countTrues(@Vector(3, bool){
- if (self.radius) |srd| if (other.radius) |ord| srd.equal(ord) else false else if (other.radius) |_| false else true,
- self.color.equal(other.color),
- if (ImplState != void) self.implState.equal(other.implState) else true,
- }) == 3;
- }
-
- pub fn deinit(self: *State, alloc: Allocator) void {
- if (ImplState != void) self.implState.deinit(alloc);
- alloc.destroy(self);
- }
- };
-
- options: Options,
- node: Node,
- impl: Impl,
-
- pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(Self);
- errdefer alloc.destroy(self);
-
- self.* = .{
- .options = options,
- .node = .{
- .allocator = alloc,
- .ptr = self,
- .type = @typeName(Self),
- .id = id orelse @returnAddress(),
- .vtable = &.{
- .dupe = dupe,
- .state = state,
- .preFrame = preFrame,
- .frame = frame,
- .deinit = deinit,
- .format = format,
- .setProperties = setProperties,
- },
- },
- .impl = undefined,
- };
-
- if (@hasDecl(Impl, "new")) {
- try Impl.init(self);
- }
- return &self.node;
- }
-
- fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
- const self: *State = @ptrCast(@alignCast(ctx));
- const other: *State = @ptrCast(@alignCast(otherctx));
- return self.equal(other);
- }
-
- fn stateFree(ctx: *anyopaque, alloc: std.mem.Allocator) void {
- const self: *State = @ptrCast(@alignCast(ctx));
- self.deinit(alloc);
- }
-
- fn dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return try new(self.node.allocator, @returnAddress(), self.options);
- }
-
- fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return .{
- .size = math.rel(frameInfo, self.options.size),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, baseScene: *Scene) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "preFrame")) {
- try Impl.preFrame(self, frameInfo, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
-
- return .{
- .size = math.rel(frameInfo, self.options.size),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn frame(ctx: *anyopaque, baseScene: *Scene) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
- if (@hasDecl(Impl, "frame")) {
- try Impl.frame(self, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
- }
-
- fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
- if (@hasDecl(Impl, "deinit")) {
- Impl.deinit(self);
- }
- self.node.allocator.destroy(self);
- }
-
- fn format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.node.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .size = {}, .color = {}", .{ self.options.size, self.options.color });
-
- if (self.options.radius) |r| {
- try output.writer().print(", .radius = {}", .{r});
- }
-
- try output.writer().writeAll(" }");
- return output;
- }
-
- fn setProperties(ctx: *anyopaque, args: std.StringHashMap(anyplus.Anytype)) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var iter = args.iterator();
- while (iter.next()) |entry| {
- const key = entry.key_ptr.*;
-
- if (std.mem.eql(u8, key, "size")) {
- self.options.size = try entry.value_ptr.cast(vizops.vector.Float32Vector2);
- } else if (std.mem.eql(u8, key, "color")) {
- self.options.color = try entry.value_ptr.cast(vizops.color.Any);
- } else return error.InvalidKey;
- }
- }
- };
-}
diff --git a/src/phantom/scene/nodes/text.zig b/src/phantom/scene/nodes/text.zig
deleted file mode 100644
index 3b64964..0000000
--- a/src/phantom/scene/nodes/text.zig
+++ /dev/null
@@ -1,176 +0,0 @@
-const std = @import("std");
-const Allocator = std.mem.Allocator;
-const vizops = @import("vizops");
-const fonts = @import("../../fonts.zig");
-const Scene = @import("../base.zig");
-const Node = @import("../node.zig");
-
-pub const Options = struct {
- font: *fonts.Font,
- view: std.unicode.Utf8View,
-};
-
-pub fn NodeText(comptime Impl: type) type {
- return struct {
- const Self = @This();
- const ImplState = if (@hasDecl(Impl, "State")) Impl.State else void;
- const ImplScene = Impl.Scene;
-
- const State = struct {
- font: *fonts.Font,
- view: std.unicode.Utf8View,
- implState: ImplState,
-
- pub fn init(alloc: Allocator, options: Options) Allocator.Error!*State {
- const self = try alloc.create(State);
- self.* = .{
- .font = options.font,
- .view = options.view,
- .implState = if (ImplState != void) ImplState.init(alloc, options) else {},
- };
- return self;
- }
-
- pub fn equal(self: *State, other: *State) bool {
- return std.simd.countTrues(@Vector(3, bool){
- self.font == other.font,
- std.mem.eql(u8, self.view.bytes, other.view.bytes),
- if (ImplState != void) self.implState.equal(other.implState) else true,
- }) == 2;
- }
-
- pub fn deinit(self: *State, alloc: Allocator) void {
- if (ImplState != void) self.implState.deinit(alloc);
- alloc.destroy(self);
- }
- };
-
- options: Options,
- node: Node,
- impl: Impl,
-
- pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
- const self = try alloc.create(Self);
- self.* = .{
- .options = options,
- .node = .{
- .allocator = alloc,
- .ptr = self,
- .type = @typeName(Self),
- .id = id orelse @returnAddress(),
- .vtable = &.{
- .dupe = dupe,
- .state = state,
- .preFrame = preFrame,
- .frame = frame,
- .deinit = deinit,
- .format = format,
- },
- },
- .impl = undefined,
- };
-
- if (@hasDecl(Impl, "new")) {
- try Impl.init(self);
- }
- return &self.node;
- }
-
- fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
- const self: *State = @ptrCast(@alignCast(ctx));
- const other: *State = @ptrCast(@alignCast(otherctx));
- return self.equal(other);
- }
-
- fn stateFree(ctx: *anyopaque, alloc: std.mem.Allocator) void {
- const self: *State = @ptrCast(@alignCast(ctx));
- self.deinit(alloc);
- }
-
- fn calcSize(self: *Self) !vizops.vector.UsizeVector2 {
- var viewIter = self.options.view.iterator();
-
- var width: usize = 0;
- var yMaxMin = vizops.vector.UsizeVector2.zero();
-
- while (viewIter.nextCodepoint()) |cp| {
- const glyph = try self.options.font.lookupGlyph(cp);
-
- width += @intCast(glyph.advance.value[0]);
- yMaxMin.value[0] = @max(yMaxMin.value[0], @as(usize, @intCast(glyph.bearing.value[1])));
-
- if (glyph.bearing.value[1] >= glyph.size.value[1]) {
- yMaxMin.value[1] = @min(yMaxMin.value[1], @as(usize, @intCast(glyph.bearing.value[1] - @as(i8, @intCast(glyph.size.value[1])))));
- } else {
- yMaxMin.value[1] = @min(yMaxMin.value[1], @as(usize, @intCast(glyph.size.value[1] - glyph.size.value[1])));
- }
- }
-
- return .{ .value = .{ width, yMaxMin.value[0] - yMaxMin.value[1] } };
- }
-
- fn dupe(ctx: *anyopaque) anyerror!*Node {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return try new(self.node.allocator, @returnAddress(), self.options);
- }
-
- fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
- return .{
- .size = try calcSize(self),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, baseScene: *Scene) anyerror!Node.State {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "preFrame")) {
- try Impl.preFrame(self, frameInfo, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
-
- return .{
- .size = try calcSize(self),
- .frame_info = frameInfo,
- .allocator = self.node.allocator,
- .ptr = try State.init(self.node.allocator, self.options),
- .ptrEqual = stateEqual,
- .ptrFree = stateFree,
- .type = @typeName(Self),
- };
- }
-
- fn frame(ctx: *anyopaque, baseScene: *Scene) anyerror!void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "frame")) {
- try Impl.frame(self, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
- }
- }
-
- fn deinit(ctx: *anyopaque) void {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- if (@hasDecl(Impl, "deinit")) {
- Impl.deinit(self);
- }
-
- self.node.allocator.destroy(self);
- }
-
- fn format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
- const self: *Self = @ptrCast(@alignCast(ctx));
-
- var output = std.ArrayList(u8).init(self.node.allocator);
- errdefer output.deinit();
-
- try output.writer().print("{{ .font = {}, .text = \"{s}\" }}", .{ self.options.font, std.unicode.fmtUtf8(self.options.view.bytes) });
- return output;
- }
- };
-}
diff --git a/src/phantom/sdk.zig b/src/phantom/sdk.zig
deleted file mode 100644
index 813f7e2..0000000
--- a/src/phantom/sdk.zig
+++ /dev/null
@@ -1,296 +0,0 @@
-const std = @import("std");
-
-pub const Platform = @import("platform/sdk.zig");
-
-const AvailableDep = struct { []const u8, []const u8 };
-const AvailableDeps = []const AvailableDep;
-
-fn runStep(step: *std.Build.Step, node: *std.Progress.Node) !void {
- for (step.dependencies.items) |child| {
- var childNode = node.start(child.name, child.dependencies.items.len);
- defer childNode.end();
-
- try runStep(child, &childNode);
- }
-
- try step.make(node);
-}
-
-pub const ModuleImport = struct {
- name: []const u8,
- source: []const u8,
- dependencies: []const ModuleImport = &.{},
-
- pub const Error = error{ MakeFailed, MakeSkipped } || std.mem.Allocator.Error || std.fs.File.OpenError || std.os.ReadError;
-
- const b64Codec = std.base64.standard_no_pad;
-
- pub fn init(value: []const std.Build.Module.Import, path: []const u8, alloc: std.mem.Allocator) ![]const u8 {
- const list = try initList(value, alloc);
- defer alloc.free(list);
-
- const imports = try encode(list, alloc);
- defer alloc.free(imports);
-
- return try std.fmt.allocPrint(alloc, "{s}:{s}", .{ path, imports });
- }
-
- pub fn create(name: []const u8, module: *std.Build.Module) Error!ModuleImport {
- if (module.root_source_file.? == .generated) {
- var prog = std.Progress{};
- var node = prog.start(name, 1);
- defer node.end();
-
- try runStep(module.root_source_file.?.generated.step, node);
- }
-
- return .{
- .name = name,
- .source = module.root_source_file.?.getPath(module.owner),
- .dependencies = try initTable(module.import_table, module.owner.allocator),
- };
- }
-
- pub fn initTable(tbl: std.StringArrayHashMapUnmanaged(*std.Build.Module), alloc: std.mem.Allocator) Error![]const ModuleImport {
- const value = try alloc.alloc(ModuleImport, tbl.count());
- errdefer alloc.free(value);
-
- var iter = tbl.iterator();
- var i: usize = 0;
- while (iter.next()) |entry| {
- value[i] = try create(entry.key_ptr.*, entry.value_ptr.*);
- i += 1;
- }
- return value;
- }
-
- pub fn initList(list: []const std.Build.Module.Import, alloc: std.mem.Allocator) Error![]const ModuleImport {
- const value = try alloc.alloc(ModuleImport, list.len);
- errdefer alloc.free(value);
-
- for (list, 0..) |entry, i| {
- value[i] = try create(entry.name, entry.module);
- }
- return value;
- }
-
- pub fn decode(alloc: std.mem.Allocator, str: []const u8) !std.json.Parsed([]const ModuleImport) {
- const buffer = try alloc.alloc(u8, try b64Codec.Decoder.calcSizeForSlice(str));
- defer alloc.free(buffer);
-
- try b64Codec.Decoder.decode(buffer, str);
- return try std.json.parseFromSlice([]const ModuleImport, alloc, buffer, .{
- .allocate = .alloc_always,
- });
- }
-
- pub fn encode(list: []const ModuleImport, alloc: std.mem.Allocator) ![]const u8 {
- const str = try std.json.stringifyAlloc(alloc, list, .{
- .emit_null_optional_fields = false,
- .whitespace = .minified,
- });
- defer alloc.free(str);
-
- const buffer = try alloc.alloc(u8, b64Codec.Encoder.calcSize(str.len));
- errdefer alloc.free(buffer);
-
- _ = b64Codec.Encoder.encode(buffer, str);
- return buffer;
- }
-
- pub fn createModule(self: *const ModuleImport, b: *std.Build, target: ?std.Build.ResolvedTarget, optimize: ?std.builtin.OptimizeMode) !*std.Build.Module {
- if (b.modules.get(self.name)) |value| return value;
-
- var imports = try std.ArrayList(std.Build.Module.Import).initCapacity(b.allocator, self.dependencies.len);
- errdefer imports.deinit();
-
- for (self.dependencies) |dep| {
- imports.appendAssumeCapacity(.{
- .name = dep.name,
- .module = try dep.createModule(b, target, optimize),
- });
- }
-
- const module = b.createModule(.{
- .root_source_file = .{
- .path = self.source,
- },
- .imports = imports.items,
- .target = target,
- .optimize = optimize,
- });
-
- try b.modules.put(b.dupe(self.name), module);
- return module;
- }
-};
-
-pub const PhantomModule = struct {
- // TODO: expected version of core
- provides: ?Provides = null,
-
- pub const Provides = struct {
- scenes: ?[]const [:0]const u8 = null,
- displays: ?[]const []const u8 = null,
- platforms: ?[]const []const u8 = null,
- imageFormats: ?[]const []const u8 = null,
- fonts: ?[]const []const u8 = null,
-
- pub fn value(self: Provides, kind: std.meta.FieldEnum(Provides)) []const []const u8 {
- return (switch (kind) {
- .scenes => self.scenes,
- .displays => self.displays,
- .platforms => self.platforms,
- .imageFormats => self.imageFormats,
- .fonts => self.fonts,
- }) orelse &[_][]const u8{};
- }
-
- pub fn has(self: Provides, kind: std.meta.FieldEnum(Provides), name: []const u8) bool {
- for (self.value(kind)) |item| {
- if (std.mem.eql(u8, item, name)) return true;
- }
- return false;
- }
-
- pub fn count(self: Provides, kind: std.meta.FieldEnum(Provides)) usize {
- return self.value(kind).len;
- }
- };
-
- pub fn getProvider(self: PhantomModule) Provides {
- return if (self.provides) |value| value else .{};
- }
-};
-
-pub const availableDepenencies = blk: {
- const buildDeps = @import("root").dependencies;
- var count: usize = 0;
- for (buildDeps.root_deps) |dep| {
- const pkg = @field(buildDeps.packages, dep[1]);
- if (@hasDecl(pkg, "build_zig")) {
- const buildZig = pkg.build_zig;
- if (@hasDecl(buildZig, "phantomModule") and @TypeOf(@field(buildZig, "phantomModule")) == PhantomModule) {
- count += 1;
- }
- }
- }
-
- var i: usize = 0;
- var deps: [count]AvailableDep = undefined;
- for (buildDeps.root_deps) |dep| {
- const pkg = @field(buildDeps.packages, dep[1]);
- if (@hasDecl(pkg, "build_zig")) {
- const buildZig = pkg.build_zig;
- if (@hasDecl(buildZig, "phantomModule") and @TypeOf(@field(buildZig, "phantomModule")) == PhantomModule) {
- deps[i] = dep;
- i += 1;
- }
- }
- }
- break :blk deps;
-};
-
-pub fn TypeFor(comptime kind: std.meta.FieldEnum(PhantomModule.Provides)) type {
- const buildDeps = @import("root").dependencies;
-
- var fieldCount: usize = 0;
- for (buildDeps.root_deps) |dep| {
- const pkg = @field(buildDeps.packages, dep[1]);
- if (@hasDecl(pkg, "build_zig")) {
- const buildZig = pkg.build_zig;
- if (@hasDecl(buildZig, "phantomModule") and @TypeOf(@field(buildZig, "phantomModule")) == PhantomModule) {
- const mod = buildZig.phantomModule;
- fieldCount += mod.getProvider().count(kind);
- }
- }
- }
-
- if (fieldCount == 0) {
- return @Type(.{
- .Enum = .{
- .tag_type = u0,
- .fields = &.{},
- .decls = &.{},
- .is_exhaustive = true,
- },
- });
- }
-
- var fields: [fieldCount]std.builtin.Type.EnumField = undefined;
- var i: usize = 0;
- for (buildDeps.root_deps) |dep| {
- const pkg = @field(buildDeps.packages, dep[1]);
- if (@hasDecl(pkg, "build_zig")) {
- const buildZig = pkg.build_zig;
- if (@hasDecl(buildZig, "phantomModule") and @TypeOf(@field(buildZig, "phantomModule")) == PhantomModule) {
- const mod = buildZig.phantomModule;
-
- for (mod.getProvider().value(kind)) |name| {
- fields[i] = .{
- .name = @ptrCast(name ++ &[_]u8{0}),
- .value = i,
- };
- i += 1;
- }
- }
- }
- }
-
- return @Type(.{
- .Enum = .{
- .tag_type = std.math.IntFittingRange(0, fields.len - 1),
- .fields = &fields,
- .decls = &.{},
- .is_exhaustive = true,
- },
- });
-}
-
-pub fn readAll(alloc: std.mem.Allocator, path: []const u8) ![]const u8 {
- var file = try std.fs.openFileAbsolute(path, .{});
- defer file.close();
- return try file.readToEndAlloc(alloc, (try file.metadata()).size());
-}
-
-pub fn updateSource(alloc: std.mem.Allocator, a: []const u8, b: []const u8) ![]const u8 {
- var lines = std.ArrayList(u8).init(alloc);
- errdefer lines.deinit();
- try lines.appendSlice(a);
-
- var bIter = std.mem.splitAny(u8, b, "\n");
- while (bIter.next()) |bline| {
- if (std.mem.indexOf(u8, a, bline) != null) continue;
- try lines.appendSlice(bline);
- }
- return lines.items;
-}
-
-pub fn get(b: *std.Build, platform: anytype, phantom: *std.Build.Module) !*@import("platform/sdk.zig") {
- const backends = @import("platform/backends.zig");
- inline for (comptime std.meta.declarations(backends)) |decl| {
- if (std.mem.eql(u8, decl.name, @tagName(platform))) {
- return try @field(backends, decl.name).Sdk.create(b, phantom);
- }
- }
-
- const buildDeps = @import("root").dependencies;
- inline for (buildDeps.root_deps) |dep| {
- const pkg = @field(buildDeps.packages, dep[1]);
- if (@hasDecl(pkg, "build_zig")) {
- const buildZig = pkg.build_zig;
- if (@hasDecl(buildZig, "phantomModule") and @TypeOf(@field(buildZig, "phantomModule")) == PhantomModule and @hasDecl(buildZig, "phantomPlatform")) {
- if (buildZig.phantomModule.getProvider().has(.platforms, @tagName(platform))) {
- inline for (comptime std.mem.declarations(buildZig.phantomPlatform)) |decl| {
- if (std.mem.eql(u8, decl.name, @tagName(platform))) {
- return try @field(buildZig.phantomPlatform, decl.name).create(b, phantom);
- }
- }
- unreachable;
- }
- }
- }
- }
-
- return error.PlatformNotFound;
-}