From 9fab3062347d7d1fd47f838c6114e0ddc4cd6dd0 Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Thu, 27 Feb 2025 23:22:43 -0800 Subject: [PATCH] refactor: start over --- .github/workflows/build.yml | 14 +- .gitignore | 3 +- build.zig | 271 ++-------------- build.zig.zon | 21 +- flake.lock | 137 ++++---- flake.nix | 96 ++++-- {src => lib}/phantom.zig | 16 +- lib/phantom/display.zig | 19 ++ lib/phantom/display/Client.zig | 13 + lib/phantom/display/Input.zig | 13 + lib/phantom/display/Output.zig | 48 +++ lib/phantom/display/Provider.zig | 37 +++ lib/phantom/display/Server.zig | 13 + lib/phantom/display/Subsurface.zig | 69 ++++ lib/phantom/display/Surface.zig | 25 ++ lib/phantom/display/Toplevel.zig | 67 ++++ lib/phantom/fonts.zig | 9 + lib/phantom/fonts/Font.zig | 38 +++ lib/phantom/fonts/Loader.zig | 60 ++++ lib/phantom/fonts/Manager.zig | 51 +++ lib/phantom/gpu.zig | 11 + lib/phantom/gpu/Connector.zig | 13 + lib/phantom/gpu/Device.zig | 19 ++ lib/phantom/gpu/Provider.zig | 19 ++ lib/phantom/gpu/Texture.zig | 13 + lib/phantom/graphics.zig | 97 ++++++ lib/phantom/graphics/Color.zig | 12 + lib/phantom/graphics/Context.zig | 69 ++++ lib/phantom/graphics/Context/2d.zig | 131 ++++++++ lib/phantom/graphics/Context/3d.zig | 87 +++++ lib/phantom/graphics/Surface.zig | 41 +++ lib/phantom/graphics/backend.zig | 7 + lib/phantom/graphics/backend/soft3d.zig | 7 + .../graphics/backend/soft3d/Context.zig | 267 ++++++++++++++++ lib/phantom/graphics/backend/z2d.zig | 9 + lib/phantom/graphics/backend/z2d/Context.zig | 287 +++++++++++++++++ lib/phantom/graphics/backend/z2d/Surface.zig | 177 +++++++++++ lib/phantom/math.zig | 270 ++++++++++++++++ lib/phantom/math/mat4x4.zig | 94 ++++++ lib/phantom/math/vec4.zig | 26 ++ lib/phantom/scene.zig | 33 ++ lib/phantom/scene/Node/Container.zig | 5 + lib/phantom/scene/Node/Image.zig | 3 + lib/phantom/scene/Node/Text.zig | 5 + lib/phantom/scene/Properties.zig | 13 + lib/phantom/scene/Renderer.zig | 29 ++ lib/phantom/scene/Renderer/Canvas.zig | 123 ++++++++ lib/phantom/scene/Renderer/Html.zig | 166 ++++++++++ lib/phantom/widgets.zig | 13 + lib/phantom/widgets/BuildContext.zig | 208 ++++++++++++ lib/phantom/widgets/StateContext.zig | 80 +++++ lib/phantom/widgets/Text.zig | 105 +++++++ lib/phantom/widgets/Text/Style.zig | 64 ++++ lib/phantom/widgets/View.zig | 65 ++++ lib/phantom/widgets/Widget.zig | 39 +++ src/example.zig | 96 ------ src/phantom/display.zig | 12 - src/phantom/display/backends.zig | 5 - src/phantom/display/backends/headless.zig | 3 - .../display/backends/headless/display.zig | 53 ---- .../display/backends/headless/output.zig | 98 ------ .../display/backends/headless/surface.zig | 97 ------ src/phantom/display/base.zig | 40 --- src/phantom/display/output.zig | 93 ------ src/phantom/display/surface.zig | 86 ----- src/phantom/fonts.zig | 11 - src/phantom/fonts/backends.zig | 3 - src/phantom/fonts/font.zig | 33 -- src/phantom/fonts/format.zig | 42 --- src/phantom/gpu.zig | 5 - src/phantom/gpu/backends.zig | 3 - src/phantom/gpu/base.zig | 28 -- src/phantom/gpu/device.zig | 30 -- src/phantom/gpu/manager.zig | 67 ---- src/phantom/gpu/surface.zig | 44 --- src/phantom/math.zig | 16 - src/phantom/math/units.zig | 22 -- src/phantom/painting.zig | 48 --- src/phantom/painting/canvas.zig | 127 -------- src/phantom/painting/fb.zig | 4 - src/phantom/painting/fb/alloc.zig | 65 ---- src/phantom/painting/fb/base.zig | 139 -------- src/phantom/painting/fb/fd.zig | 72 ----- src/phantom/painting/fb/mem.zig | 63 ---- src/phantom/painting/image.zig | 3 - src/phantom/painting/image/base.zig | 32 -- src/phantom/painting/image/format.zig | 54 ---- src/phantom/painting/image/formats.zig | 3 - src/phantom/platform.zig | 11 - src/phantom/platform/backends.zig | 5 - src/phantom/platform/backends/std.zig | 2 - src/phantom/platform/backends/std/backend.zig | 28 -- src/phantom/platform/backends/std/sdk.zig | 42 --- .../backends/std/sdk/step/install-pkg.zig | 26 -- .../platform/backends/std/sdk/step/pkg.zig | 33 -- src/phantom/platform/base.zig | 13 - src/phantom/platform/sdk.zig | 31 -- src/phantom/platform/sdk/step.zig | 2 - src/phantom/platform/sdk/step/install-pkg.zig | 25 -- src/phantom/platform/sdk/step/pkg.zig | 40 --- src/phantom/scene.zig | 54 ---- src/phantom/scene/backends.zig | 6 - src/phantom/scene/backends/fb.zig | 96 ------ src/phantom/scene/backends/fb/scene.zig | 99 ------ src/phantom/scene/backends/headless.zig | 6 - src/phantom/scene/backends/headless/scene.zig | 41 --- src/phantom/scene/base.zig | 96 ------ src/phantom/scene/node-flex.zig | 140 --------- src/phantom/scene/node-stack.zig | 124 -------- src/phantom/scene/node-tree.zig | 245 --------------- src/phantom/scene/node.zig | 198 ------------ src/phantom/scene/nodes.zig | 4 - src/phantom/scene/nodes/arc.zig | 190 ----------- src/phantom/scene/nodes/fb.zig | 194 ------------ src/phantom/scene/nodes/rect.zig | 178 ----------- src/phantom/scene/nodes/text.zig | 176 ----------- src/phantom/sdk.zig | 296 ------------------ 117 files changed, 3257 insertions(+), 4268 deletions(-) rename {src => lib}/phantom.zig (51%) create mode 100644 lib/phantom/display.zig create mode 100644 lib/phantom/display/Client.zig create mode 100644 lib/phantom/display/Input.zig create mode 100644 lib/phantom/display/Output.zig create mode 100644 lib/phantom/display/Provider.zig create mode 100644 lib/phantom/display/Server.zig create mode 100644 lib/phantom/display/Subsurface.zig create mode 100644 lib/phantom/display/Surface.zig create mode 100644 lib/phantom/display/Toplevel.zig create mode 100644 lib/phantom/fonts.zig create mode 100644 lib/phantom/fonts/Font.zig create mode 100644 lib/phantom/fonts/Loader.zig create mode 100644 lib/phantom/fonts/Manager.zig create mode 100644 lib/phantom/gpu.zig create mode 100644 lib/phantom/gpu/Connector.zig create mode 100644 lib/phantom/gpu/Device.zig create mode 100644 lib/phantom/gpu/Provider.zig create mode 100644 lib/phantom/gpu/Texture.zig create mode 100644 lib/phantom/graphics.zig create mode 100644 lib/phantom/graphics/Color.zig create mode 100644 lib/phantom/graphics/Context.zig create mode 100644 lib/phantom/graphics/Context/2d.zig create mode 100644 lib/phantom/graphics/Context/3d.zig create mode 100644 lib/phantom/graphics/Surface.zig create mode 100644 lib/phantom/graphics/backend.zig create mode 100644 lib/phantom/graphics/backend/soft3d.zig create mode 100644 lib/phantom/graphics/backend/soft3d/Context.zig create mode 100644 lib/phantom/graphics/backend/z2d.zig create mode 100644 lib/phantom/graphics/backend/z2d/Context.zig create mode 100644 lib/phantom/graphics/backend/z2d/Surface.zig create mode 100644 lib/phantom/math.zig create mode 100644 lib/phantom/math/mat4x4.zig create mode 100644 lib/phantom/math/vec4.zig create mode 100644 lib/phantom/scene.zig create mode 100644 lib/phantom/scene/Node/Container.zig create mode 100644 lib/phantom/scene/Node/Image.zig create mode 100644 lib/phantom/scene/Node/Text.zig create mode 100644 lib/phantom/scene/Properties.zig create mode 100644 lib/phantom/scene/Renderer.zig create mode 100644 lib/phantom/scene/Renderer/Canvas.zig create mode 100644 lib/phantom/scene/Renderer/Html.zig create mode 100644 lib/phantom/widgets.zig create mode 100644 lib/phantom/widgets/BuildContext.zig create mode 100644 lib/phantom/widgets/StateContext.zig create mode 100644 lib/phantom/widgets/Text.zig create mode 100644 lib/phantom/widgets/Text/Style.zig create mode 100644 lib/phantom/widgets/View.zig create mode 100644 lib/phantom/widgets/Widget.zig delete mode 100644 src/example.zig delete mode 100644 src/phantom/display.zig delete mode 100644 src/phantom/display/backends.zig delete mode 100644 src/phantom/display/backends/headless.zig delete mode 100644 src/phantom/display/backends/headless/display.zig delete mode 100644 src/phantom/display/backends/headless/output.zig delete mode 100644 src/phantom/display/backends/headless/surface.zig delete mode 100644 src/phantom/display/base.zig delete mode 100644 src/phantom/display/output.zig delete mode 100644 src/phantom/display/surface.zig delete mode 100644 src/phantom/fonts.zig delete mode 100644 src/phantom/fonts/backends.zig delete mode 100644 src/phantom/fonts/font.zig delete mode 100644 src/phantom/fonts/format.zig delete mode 100644 src/phantom/gpu.zig delete mode 100644 src/phantom/gpu/backends.zig delete mode 100644 src/phantom/gpu/base.zig delete mode 100644 src/phantom/gpu/device.zig delete mode 100644 src/phantom/gpu/manager.zig delete mode 100644 src/phantom/gpu/surface.zig delete mode 100644 src/phantom/math.zig delete mode 100644 src/phantom/math/units.zig delete mode 100644 src/phantom/painting.zig delete mode 100644 src/phantom/painting/canvas.zig delete mode 100644 src/phantom/painting/fb.zig delete mode 100644 src/phantom/painting/fb/alloc.zig delete mode 100644 src/phantom/painting/fb/base.zig delete mode 100644 src/phantom/painting/fb/fd.zig delete mode 100644 src/phantom/painting/fb/mem.zig delete mode 100644 src/phantom/painting/image.zig delete mode 100644 src/phantom/painting/image/base.zig delete mode 100644 src/phantom/painting/image/format.zig delete mode 100644 src/phantom/painting/image/formats.zig delete mode 100644 src/phantom/platform.zig delete mode 100644 src/phantom/platform/backends.zig delete mode 100644 src/phantom/platform/backends/std.zig delete mode 100644 src/phantom/platform/backends/std/backend.zig delete mode 100644 src/phantom/platform/backends/std/sdk.zig delete mode 100644 src/phantom/platform/backends/std/sdk/step/install-pkg.zig delete mode 100644 src/phantom/platform/backends/std/sdk/step/pkg.zig delete mode 100644 src/phantom/platform/base.zig delete mode 100644 src/phantom/platform/sdk.zig delete mode 100644 src/phantom/platform/sdk/step.zig delete mode 100644 src/phantom/platform/sdk/step/install-pkg.zig delete mode 100644 src/phantom/platform/sdk/step/pkg.zig delete mode 100644 src/phantom/scene.zig delete mode 100644 src/phantom/scene/backends.zig delete mode 100644 src/phantom/scene/backends/fb.zig delete mode 100644 src/phantom/scene/backends/fb/scene.zig delete mode 100644 src/phantom/scene/backends/headless.zig delete mode 100644 src/phantom/scene/backends/headless/scene.zig delete mode 100644 src/phantom/scene/base.zig delete mode 100644 src/phantom/scene/node-flex.zig delete mode 100644 src/phantom/scene/node-stack.zig delete mode 100644 src/phantom/scene/node-tree.zig delete mode 100644 src/phantom/scene/node.zig delete mode 100644 src/phantom/scene/nodes.zig delete mode 100644 src/phantom/scene/nodes/arc.zig delete mode 100644 src/phantom/scene/nodes/fb.zig delete mode 100644 src/phantom/scene/nodes/rect.zig delete mode 100644 src/phantom/scene/nodes/text.zig delete mode 100644 src/phantom/sdk.zig 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; -}