diff --git a/AGENTS.md b/AGENTS.md index 73982c2a4..c17775474 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ Fizzy (Editor) ←── Host registries + EditorAPI ──→ Plugin (register( - **`sdk/src/`** — the entire contract. `Host` (registries + service locator), `Plugin` (identity + vtable of hooks Fizzy calls), `Surface` (the one drawable contribution: keywords say where it may go, the app's regions accept it), `RegionSpec` (a plugin declaring a place of its own inside the one it was given), `DocHandle` (opaque `{ptr, id, owner}` — Fizzy routes every doc op to `owner`, never inspects `ptr`), `EditorAPI` (Fizzy's own read/util surface plugins reach back through), `keywords.zig` (how a region accepts a surface), `dylib.zig`/`dvui_context.zig` (runtime-library C-ABI + dvui injection). - **`app/`** — the framework an application switches on, never in a dylib: `App` (the host runtime state — host, documents, settings, keymap, layout state, watchers; fizzy's `Editor` embeds one as `editor.app`), `layout` (Layout/Region/State, the picker, view drags, split trees), `settings`, `keymap`, `Recents`, `store`, `update`, `watch`, `window`, `single_instance`. The module root is `app/root.zig`. Everywhere it needed to name fizzy is a `{ctx, vtable}` seam the app fills in. - **`src/editor/`** — Fizzy itself: `Editor.zig` (frame loop, plugin registration/loading), `layout.zig` (fizzy's shape), `Menu.zig`, `Sidebar.zig`, `Settings.zig`, etc. -- **`core/`** — the shared floor used by Fizzy *and* plugins: `widgets` (Split, DockingWidget, Tabs, Tree, Canvas, BlurBackdrop), `anim`, `dialogs`, `draw`, `icon`, `image`, math, fs, paths, platform detection. Not plugin-owned; don't move it. (`Atlas`/`Sprite` are there only because pixi still loads its packed UI atlas through them — see the note in `core/core.zig`.) `core.fuzzy` is the one matcher behind every filter box in the app (settings tree, file tree, plugin store, LSP completions) — wrap zf through it rather than matching by hand, and remember **lower scores are better**. Draw icons with `core.icon.icon`, not `dvui.icon` — same arguments, cached as a texture. +- **`core/`** — the shared floor used by Fizzy *and* plugins: `widgets` (Split, DockingWidget, Tabs, Tree, Canvas; BlurBackdrop re-exported from the backend's glass), `anim`, `dialogs`, `draw`, `icon`, `image`, math, fs, paths, platform detection. Not plugin-owned; don't move it. (`Atlas`/`Sprite` are there only because pixi still loads its packed UI atlas through them — see the note in `core/core.zig`.) `core.fuzzy` is the one matcher behind every filter box in the app (settings tree, file tree, plugin store, LSP completions) — wrap zf through it rather than matching by hand, and remember **lower scores are better**. Draw icons with `core.icon.icon`, not `dvui.icon` — same arguments, cached as a texture. - **`plugins/`** — bundled built-in plugins. Each is file-for-file the **same shape a third-party plugin would use**: root `plugin.zig` + identity-only `plugin.zig.zon` + `build.zig` + `build.zig.zon` (optional `src/**`), plus fizzy-internal glue in `static/`. No author `root.zig` or `.zig` hub — the build helper generates the dylib entry; files use named imports (`fizzy_sdk`/`dvui`/…). Builds standalone with `cd plugins/ && zig build`. **Two link modes, one source:** built-in plugins compile **static** (linked directly, all targets incl. web) or **dynamic** (`.dylib`/`.so`/`.dll`, desktop-only, `dlopen`'d — this is how third-party plugins ship too). `FIZZY_STATIC_=1` env var forces static for a given built-in (useful when debugging dylib loading). @@ -119,8 +119,9 @@ That constrains how these are written, and the constraint is the point: core/ the shared floor both an app and a plugin dylib draw with — widgets (Split, DockingWidget, Tabs, Tree, Canvas), anim, dialogs, draw, icon, image, fs, paths, math, fuzzy, lsp -backend/ fizzy's native backend, a package of its own: SDL3, its GPU renderer and the platform - pieces (title bars, glass, OS windows); pins SDL, zig-objc and zigwin32 +backend/ fizzy's native backend, a package of its own: SDL3, its GPU renderer, the platform + pieces (title bars, glass, OS windows) and fizzy's in-app glass (`src/glass/`, which + `core` re-exports); pins SDL, zig-objc and zigwin32 sdk/ the plugin contract: `sdk/src/**` is the SDK itself, the files beside it are its build surface (this directory ships standalone as `fizzy-sdk-v*.tar.gz`); `sdk/tape/` is the std-only demo-tape library shipped with it, `sdk/replay/` the diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 4657436f0..9736c5eb3 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -4,6 +4,12 @@ SDL3, the SDL_GPU renderer (`GpuRenderer.zig`) and the platform pieces (`platfor the OS's glass, OS windows for floats, menus, dialogs). A package of its own (`build.zig` says how an app wires it). +- **Fizzy's in-app glass is here,** in `src/glass/` (its own module, `glassModule`): the frame + target, the frosted backdrops, the liquid glass and its program, motion. So a plain dvui app on + this backend can be made of the same glass as fizzy's surfaces. `core` re-exports each name + (`core.FrameTarget`, `core.widgets.BlurBackdrop`, …) and takes the backend's instance over the + same dvui (`core_module.addImports`); it depends on dvui and `glass_look` only. Its profiler + scopes reach `core.profile` through `glass/prof.zig`, which the profiler hands its scopes to. - **dvui is the caller's.** It is never pinned here; the backend compiles against the app's dvui. - **SDL is patched here,** not in a fork: `patches/sdl/*.patch`, applied by zig-patch. How to change, add or bump them is `docs/DEPENDENCIES.md` ("SDL", "Bumping"). Touching a patch or this diff --git a/backend/build.zig b/backend/build.zig index 875e94407..32eafa230 100644 --- a/backend/build.zig +++ b/backend/build.zig @@ -108,6 +108,23 @@ pub fn glassLookModule(dep: *std.Build.Dependency) *std.Build.Module { return m; } +/// The glass module for each dvui (`glassModule`), for the same reason. +var glass_modules: std.AutoHashMapUnmanaged(*std.Build.Module, *std.Build.Module) = .empty; + +/// Fizzy's in-app glass (`src/glass/root.zig`: the frame target, the frosted backdrops, the liquid +/// glass and its program, motion) over `dvui_mod`: one per dvui, as the backend is, so the backend +/// and the app's `core` that a build links together share it. Pass it to `core_module.addImports` +/// for a `core` linked beside this backend. +pub fn glassModule(dep: *std.Build.Dependency, dvui_mod: *std.Build.Module) *std.Build.Module { + if (glass_modules.get(dvui_mod)) |m| return m; + const b = dep.builder; + const m = b.createModule(.{ .root_source_file = b.path("src/glass/root.zig") }); + m.addImport("dvui", dvui_mod); + m.addImport("glass_look", glassLookModule(dep)); + glass_modules.put(b.allocator, dvui_mod, m) catch @panic("OOM"); + return m; +} + /// The `platform` module for each backend module, for the same reason. var platform_modules: std.AutoHashMapUnmanaged(*std.Build.Module, *std.Build.Module) = .empty; diff --git a/core/widgets/BlurBackdrop.zig b/backend/src/glass/BlurBackdrop.zig similarity index 99% rename from core/widgets/BlurBackdrop.zig rename to backend/src/glass/BlurBackdrop.zig index 371891066..1c8aed9cd 100644 --- a/core/widgets/BlurBackdrop.zig +++ b/backend/src/glass/BlurBackdrop.zig @@ -30,10 +30,10 @@ const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../motion.zig"); -const liquid_glass = @import("../gfx/liquid_glass.zig"); -const LiquidField = @import("../gfx/LiquidField.zig"); -const FrameTarget = @import("../gfx/FrameTarget.zig"); +const motion = @import("motion.zig"); +const liquid_glass = @import("liquid_glass.zig"); +const LiquidField = @import("LiquidField.zig"); +const FrameTarget = @import("FrameTarget.zig"); const Rect = dvui.Rect; const Size = dvui.Size; @@ -1226,7 +1226,7 @@ const FrostJob = struct { fn draw(ctx: ?*anyopaque) void { const self: *FrostJob = @ptrCast(@alignCast(ctx orelse return)); - const prof = @import("../profile.zig").begin("fizzy", "frost pane"); + const prof = @import("prof.zig").begin("fizzy", "frost pane"); defer prof.end(); // At full alpha, whatever alpha the pane was queued under. The replay runs under the // alpha of the moment it was queued, and a pane drawn inside a fade — a tooltip fading in diff --git a/core/gfx/FrameTarget.zig b/backend/src/glass/FrameTarget.zig similarity index 99% rename from core/gfx/FrameTarget.zig rename to backend/src/glass/FrameTarget.zig index 461209214..5880ce549 100644 --- a/core/gfx/FrameTarget.zig +++ b/backend/src/glass/FrameTarget.zig @@ -22,7 +22,7 @@ const std = @import("std"); const builtin = @import("builtin"); const dvui = @import("dvui"); -const profile = @import("../profile.zig"); +const profile = @import("prof.zig"); const FrameTarget = @This(); diff --git a/core/gfx/LiquidField.zig b/backend/src/glass/LiquidField.zig similarity index 100% rename from core/gfx/LiquidField.zig rename to backend/src/glass/LiquidField.zig diff --git a/core/gfx/liquid_blob.zig b/backend/src/glass/liquid_blob.zig similarity index 100% rename from core/gfx/liquid_blob.zig rename to backend/src/glass/liquid_blob.zig diff --git a/core/gfx/liquid_glass.zig b/backend/src/glass/liquid_glass.zig similarity index 100% rename from core/gfx/liquid_glass.zig rename to backend/src/glass/liquid_glass.zig diff --git a/core/motion.zig b/backend/src/glass/motion.zig similarity index 100% rename from core/motion.zig rename to backend/src/glass/motion.zig diff --git a/backend/src/glass/prof.zig b/backend/src/glass/prof.zig new file mode 100644 index 000000000..464b712ff --- /dev/null +++ b/backend/src/glass/prof.zig @@ -0,0 +1,59 @@ +//! Profiler scopes for the glass, recorded into the app's profiler (fizzy's `core.profile`) when +//! there is one, and nothing otherwise: a plain dvui app on fizzy's backend has none. +//! +//! The glass does not import the profiler: the profiler lives in fizzy's `core`, which the backend +//! does not depend on. The profiler hands its scopes over instead (`Hooks`): the host image sets +//! its own copy's (`host`) at the start of each frame, and publishes them through dvui's store, as +//! it publishes itself, for a plugin's copy of the glass to find (`hooks`) — so a plugin's frost +//! pane records into the app's profile as the plugin's own scopes do. Nothing is spent while no +//! profiler hands any over, or while it records nothing. +const std = @import("std"); +const dvui = @import("dvui"); + +/// An open scope; `end` it (deferred) where the timed code ends. The profiler's own, as it hands it +/// back (`Hooks.begin`). +pub const Scope = extern struct { + p: ?*anyopaque = null, + idx: u16 = 0, + + pub fn end(self: Scope) void { + if (self.p == null) return; + if (hooks()) |h| h.end(self); + } +}; + +/// The profiler's scopes, as the glass calls them: across images (a plugin's copy of the glass +/// calls the host's), so C-ABI and versioned. +pub const Hooks = extern struct { + abi: u32 = abi, + begin: *const fn (owner: [*]const u8, owner_len: usize, name: [*]const u8, name_len: usize) callconv(.c) Scope, + end: *const fn (scope: Scope) callconv(.c) void, +}; +pub const abi: u32 = 1; + +/// The host image's hooks, set by its profiler each frame. +pub var host: ?*const Hooks = null; +/// Where the host publishes them for a plugin's copy (`dvui.dataSet`). +pub const publish_id: dvui.Id = @fromBackingInt(@intCast(0x6669_7a7a_676c_7373)); // "fizzygls" +pub const publish_key = std.fmt.comptimePrint("_glass_profile{d}", .{abi}); + +/// A plugin's copy of the hooks, once found. +var found: ?*const Hooks = null; + +fn hooks() ?*const Hooks { + if (host) |h| return h; + if (found) |f| return f; + // Outside a window — between frames, in a test — there is no store to ask. + if (dvui.current_window == null) return null; + const addr = dvui.dataGet(null, publish_id, publish_key, usize) orelse return null; + const h: *const Hooks = @ptrFromInt(addr); + if (h.abi != abi) return null; + found = h; + return h; +} + +/// Time from here to `end` as `name` under `owner`, nested under the scope already open. +pub fn begin(owner: []const u8, name: []const u8) Scope { + const h = hooks() orelse return .{}; + return h.begin(owner.ptr, owner.len, name.ptr, name.len); +} diff --git a/core/gfx/programs.zig b/backend/src/glass/programs.zig similarity index 100% rename from core/gfx/programs.zig rename to backend/src/glass/programs.zig diff --git a/backend/src/glass/root.zig b/backend/src/glass/root.zig new file mode 100644 index 000000000..bdcc18a8b --- /dev/null +++ b/backend/src/glass/root.zig @@ -0,0 +1,25 @@ +//! Fizzy's in-app glass: the frame drawn into a target so what is under a surface can be read back +//! (`FrameTarget`), the frosted and blurred backdrops made from it (`BlurBackdrop`), the liquid +//! glass laid over them (`LiquidField`, `liquid_glass`, `liquid_blob`, its fragment program through +//! `programs`), and the motion setting they move by (`motion`). +//! +//! The backend's, so a plain dvui app on fizzy's backend can be made of the same glass as fizzy's +//! own surfaces (a floating window in the window's material); fizzy's `core` re-exports every name +//! here under the one it always had (`core.FrameTarget`, `core.widgets.BlurBackdrop`, …), and +//! plugins see no change. It depends on dvui and the glass's look (`glass_look`) only; its profiler +//! scopes go to the app's profiler through `prof`. +pub const FrameTarget = @import("FrameTarget.zig"); +pub const BlurBackdrop = @import("BlurBackdrop.zig"); +pub const LiquidField = @import("LiquidField.zig"); +pub const liquid_glass = @import("liquid_glass.zig"); +pub const liquid_blob = @import("liquid_blob.zig"); +pub const programs = @import("programs.zig"); +pub const motion = @import("motion.zig"); +pub const prof = @import("prof.zig"); + +// The glass's own test root (`build/app.zig`, on dvui's testing backend): the frost's capture +// sizes and the blobs' smooth union. +test { + _ = BlurBackdrop; + _ = liquid_blob; +} diff --git a/core/gfx/shaders/compiled/dxil/liquid_glass.fragment.dxil b/backend/src/glass/shaders/compiled/dxil/liquid_glass.fragment.dxil similarity index 100% rename from core/gfx/shaders/compiled/dxil/liquid_glass.fragment.dxil rename to backend/src/glass/shaders/compiled/dxil/liquid_glass.fragment.dxil diff --git a/core/gfx/shaders/compiled/glsl/liquid_glass.fragment.glsl b/backend/src/glass/shaders/compiled/glsl/liquid_glass.fragment.glsl similarity index 100% rename from core/gfx/shaders/compiled/glsl/liquid_glass.fragment.glsl rename to backend/src/glass/shaders/compiled/glsl/liquid_glass.fragment.glsl diff --git a/core/gfx/shaders/compiled/msl/liquid_glass.fragment.msl b/backend/src/glass/shaders/compiled/msl/liquid_glass.fragment.msl similarity index 100% rename from core/gfx/shaders/compiled/msl/liquid_glass.fragment.msl rename to backend/src/glass/shaders/compiled/msl/liquid_glass.fragment.msl diff --git a/core/gfx/shaders/compiled/spv/liquid_glass.fragment.spv b/backend/src/glass/shaders/compiled/spv/liquid_glass.fragment.spv similarity index 100% rename from core/gfx/shaders/compiled/spv/liquid_glass.fragment.spv rename to backend/src/glass/shaders/compiled/spv/liquid_glass.fragment.spv diff --git a/core/gfx/shaders/liquid_glass.fragment.hlsl b/backend/src/glass/shaders/liquid_glass.fragment.hlsl similarity index 100% rename from core/gfx/shaders/liquid_glass.fragment.hlsl rename to backend/src/glass/shaders/liquid_glass.fragment.hlsl diff --git a/build/app.zig b/build/app.zig index 46ff8c65b..ab8f923b4 100644 --- a/build/app.zig +++ b/build/app.zig @@ -1074,7 +1074,7 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - const icons_test = core_mod.addImports(b, core_module_test, dvui_testing_dep.module("dvui_testing"), b.dependency("fizzy_sdk", .{}).builder, target, optimize, null); + const icons_test = core_mod.addImports(b, core_module_test, dvui_testing_dep.module("dvui_testing"), b.dependency("fizzy_sdk", .{}).builder, target, optimize, null, null); fizzy_test_module.addImport("core", core_module_test); if (icons_test) |icons| fizzy_test_module.addImport("icons", icons); @@ -1531,10 +1531,22 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. // `core/widgets.zig` names widgets through `replay`'s anchors; the test `core`'s own. split_tests_module.addImport("replay", core_module_test.import_table.get("replay").?); split_tests_module.addImport("glass_look", core_module_test.import_table.get("glass_look").?); + split_tests_module.addImport("glass", core_module_test.import_table.get("glass").?); + + // The in-app glass (`backend/src/glass/root.zig`): the frost's capture sizes and the + // blobs' smooth union, over dvui's testing backend. + const glass_tests_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("backend/src/glass/root.zig"), + }); + glass_tests_module.addImport("dvui", dvui_testing_dep.module("dvui_testing")); + glass_tests_module.addImport("glass_look", core_module_test.import_table.get("glass_look").?); inline for (.{ .{ "fizzy-sdk-tests", sdk_tests_module }, .{ "fizzy-sizing-tests", split_tests_module }, + .{ "fizzy-glass-tests", glass_tests_module }, .{ "fizzy-plugin-loader-tests", plugin_loader_module }, }) |entry| { const t = b.addTest(.{ diff --git a/build/exe.zig b/build/exe.zig index b67a1c5b2..39ddba83d 100644 --- a/build/exe.zig +++ b/build/exe.zig @@ -268,6 +268,8 @@ pub fn addFizzyExecutableForTarget( // The glass look the backend dresses OS windows with; `core` takes the same module. const glass_look = fizzy_backend.glassLookModule(backendDependency(b)); + // Its in-app glass, the backend's (`glassModule`): `core` over the same dvui takes the same one. + const glass = fizzy_backend.glassModule(backendDependency(b), dvui_mod); // Shared `core` module (gfx/math/fs/generated atlas/platform/paths/dvui hub + // generic widgets). Import set is shared with the plugin SDK path — see sdk/core_module.zig. @@ -276,7 +278,7 @@ pub fn addFizzyExecutableForTarget( .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - const icons_module = core_mod.addImports(b, core_module, dvui_mod, b.dependency("fizzy_sdk", .{}).builder, resolved_target, optimize, glass_look); + const icons_module = core_mod.addImports(b, core_module, dvui_mod, b.dependency("fizzy_sdk", .{}).builder, resolved_target, optimize, glass_look, glass); exe.root_module.addImport("core", core_module); if (icons_module) |icons| exe.root_module.addImport("icons", icons); @@ -285,7 +287,7 @@ pub fn addFizzyExecutableForTarget( .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - _ = core_mod.addImports(b, core_proxy_module, dvui_proxy_mod, b.dependency("fizzy_sdk", .{}).builder, resolved_target, optimize, glass_look); + _ = core_mod.addImports(b, core_proxy_module, dvui_proxy_mod, b.dependency("fizzy_sdk", .{}).builder, resolved_target, optimize, glass_look, null); // `macos_fsevents` is load-bearing for `FolderWatcher`: it watches a whole project folder, // and the kqueue fallback needs a file descriptor per directory *and* per file — exactly the diff --git a/build/web.zig b/build/web.zig index 126eb1fee..dbbe0c054 100644 --- a/build/web.zig +++ b/build/web.zig @@ -164,7 +164,7 @@ pub fn addSteps( .link_libc = false, .single_threaded = true, }); - const icons_web = core_mod.addImports(b, core_module_web, dvui_web, b.dependency("fizzy_sdk", .{}).builder, web_target, optimize, null); + const icons_web = core_mod.addImports(b, core_module_web, dvui_web, b.dependency("fizzy_sdk", .{}).builder, web_target, optimize, null, null); web_exe.root_module.addImport("core", core_module_web); if (icons_web) |icons| web_exe.root_module.addImport("icons", icons); const sdk_module_web = sdk.wireSdkModule(b, web_target, optimize, dvui_web, dvui_web_proxy_bridge, core_module_web, web_exe.root_module); diff --git a/core/AGENTS.md b/core/AGENTS.md index d81662b95..ebd99fe80 100644 --- a/core/AGENTS.md +++ b/core/AGENTS.md @@ -9,6 +9,10 @@ paths, fuzzy matching, the LSP client, the profiler. the plugin SDK so the two can't drift. A new dependency of `core` goes there. - **A change here reaches store plugins only at the next SDK release,** even when the fingerprint doesn't move, because they build `core` from the released SDK tarball. +- **The in-app glass is the backend's** (`backend/src/glass/`: `FrameTarget`, `BlurBackdrop`, + `LiquidField`, `liquid_glass`, `liquid_blob`, `programs`, `motion`), re-exported here under the + names it always had. Import it as `@import("glass")`, never by path; it must not import `core` + (the profiler reaches it through `glass/prof.zig`). - **Use the shared pieces:** `core.fuzzy` for any filter box (lower scores are better); `core.icon.icon` rather than `dvui.icon` (cached as a texture). - **Tests:** std-only files (`math/*`, `reveal.zig`, `crossfade.zig`, `FrameClock.zig`, `fuzzy.zig`, diff --git a/core/Spring.zig b/core/Spring.zig index 956fd412f..6958ec97b 100644 --- a/core/Spring.zig +++ b/core/Spring.zig @@ -8,7 +8,7 @@ //! playful overshoots, minimal arrives without overshoot, off goes straight to the target. const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("motion.zig"); +const motion = @import("glass").motion; const Spring = @This(); diff --git a/core/anim.zig b/core/anim.zig index 21c4d824f..f092ab88a 100644 --- a/core/anim.zig +++ b/core/anim.zig @@ -10,14 +10,14 @@ //! the same code — see `core.widgets` for the divide. const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("motion.zig"); +const motion = @import("glass").motion; const builtin = @import("builtin"); const icons = @import("icons"); const platform = @import("platform.zig"); const reveal_phase = @import("reveal.zig"); -const BlurBackdrop = @import("widgets/BlurBackdrop.zig"); +const BlurBackdrop = @import("glass").BlurBackdrop; const dialogs = @import("dialogs.zig"); -const FrameTarget = @import("gfx/FrameTarget.zig"); +const FrameTarget = @import("glass").FrameTarget; pub const crossfade = @import("crossfade.zig"); pub const Kind = crossfade.Kind; diff --git a/core/core.zig b/core/core.zig index 8d9c2ac1a..f067ce0a7 100644 --- a/core/core.zig +++ b/core/core.zig @@ -23,7 +23,7 @@ fn defaultTrackpadPinchRatio() f32 { pub const image = @import("gfx/image.zig"); pub const icon = @import("gfx/icon.zig"); pub const perf = @import("gfx/perf.zig"); -pub const FrameTarget = @import("gfx/FrameTarget.zig"); +pub const FrameTarget = @import("glass").FrameTarget; /// TEMPORARY frame-hitch profiler (`FIZZY_HITCH_MS`). pub const hitch = @import("hitch.zig"); /// The frame profiler behind the profiler window: fizzy's phases, plugin hooks and surfaces, @@ -46,18 +46,18 @@ pub const widgets = @import("widgets.zig"); /// Reveals, cross-fades and transitions — the animation any region or pane swap runs through. pub const anim = @import("anim.zig"); /// How things move: the one motion setting every animation reads (off, minimal, playful). -pub const motion = @import("motion.zig"); +pub const motion = @import("glass").motion; /// A motion's clock stepped by its frames, so a long frame holds it rather than skipping it on. pub const FrameClock = @import("FrameClock.zig"); /// How round the app's corners are: the one Corner roundness setting every radius scales by. pub const corners = @import("corners.zig"); /// Frosted glass that bends what it shows — lens at the rim, ripples through it. -pub const liquid_glass = @import("gfx/liquid_glass.zig"); -pub const liquid_blob = @import("gfx/liquid_blob.zig"); +pub const liquid_glass = @import("glass").liquid_glass; +pub const liquid_blob = @import("glass").liquid_blob; /// One material on one value: every glass at the window's opacity, the OS's form and the app's. pub const glass_look = @import("glass_look"); -pub const LiquidField = @import("gfx/LiquidField.zig"); -pub const programs = @import("gfx/programs.zig"); +pub const LiquidField = @import("glass").LiquidField; +pub const programs = @import("glass").programs; pub const Spring = @import("Spring.zig"); /// The dialog framework, its window chrome, and the toasts and spinners that share it. pub const dialogs = @import("dialogs.zig"); diff --git a/core/dialogs.zig b/core/dialogs.zig index 52cdfca54..19a753f6d 100644 --- a/core/dialogs.zig +++ b/core/dialogs.zig @@ -10,9 +10,9 @@ const std = @import("std"); const dvui = @import("dvui"); const rounding = @import("corners.zig"); -const liquid_glass = @import("gfx/liquid_glass.zig"); -const liquid_blob = @import("gfx/liquid_blob.zig"); -const motion = @import("motion.zig"); +const liquid_glass = @import("glass").liquid_glass; +const liquid_blob = @import("glass").liquid_blob; +const motion = @import("glass").motion; const icon_tex = @import("gfx/icon.zig"); const builtin = @import("builtin"); const icons = @import("icons"); diff --git a/core/profile.zig b/core/profile.zig index 60f9d28a7..bea9d4ff9 100644 --- a/core/profile.zig +++ b/core/profile.zig @@ -551,6 +551,9 @@ pub fn hostFrameBegin(prev_submit_ns: ?u64, gpu_total: ?Gpu) void { is_host = true; host_profiler.frameBegin(prev_submit_ns, gpu_total); dvui.dataSet(null, publish_id, publish_key, @as(usize, @intFromPtr(&host_profiler))); + // The glass's scopes (`glass.prof`): the host's copy takes them here, a plugin's from the store. + glass_prof.host = &glass_hooks; + dvui.dataSet(null, glass_prof.publish_id, glass_prof.publish_key, @as(usize, @intFromPtr(&glass_hooks))); if (host_lookback == null and host_profiler.active) host_lookback = Lookback.create(std.heap.page_allocator, max_entries) catch null; if (host_lookback) |lb| dvui.dataSet(null, publish_id, lookback_key, @as(usize, @intFromPtr(lb))); dvui.dataSet(null, publish_id, phases_key, @as(usize, @intFromPtr(&host_phases))); @@ -604,6 +607,21 @@ pub fn writeTrace(w: *std.Io.Writer, ms: u32) (std.Io.Writer.Error || error{OutO try Spans.writeTrace(w, try sp.collect(arena.allocator(), since), sp.ui_thread.load(.monotonic)); } +/// The glass (`glass`, the backend's: the frame target, the frosted backdrops) cannot import this +/// file, which is fizzy's; its scopes come here through these (`glass.prof`). +const glass_prof = @import("glass").prof; +const glass_hooks: glass_prof.Hooks = .{ .begin = glassBegin, .end = glassEnd }; + +fn glassBegin(owner: [*]const u8, owner_len: usize, name: [*]const u8, name_len: usize) callconv(.c) glass_prof.Scope { + const scope = begin(owner[0..owner_len], name[0..name_len]); + return .{ .p = scope.p, .idx = scope.idx }; +} + +fn glassEnd(scope: glass_prof.Scope) callconv(.c) void { + const p: *Profiler = @ptrCast(@alignCast(scope.p orelse return)); + p.close(scope.idx); +} + /// An open scope; `end` it (deferred) where the timed code ends. pub const Scope = struct { p: ?*Profiler = null, diff --git a/core/sizing_tests.zig b/core/sizing_tests.zig index c892336bb..d096021a1 100644 --- a/core/sizing_tests.zig +++ b/core/sizing_tests.zig @@ -14,18 +14,14 @@ test { _ = @import("widgets/ContextWidget.zig"); // A tab's hand beside a split's arrow, and a tapped tab's hover look. _ = @import("widgets/Tabs.zig"); - // The local copies of upstream's docking + blur (see `widgets.zig`): referenced here so they + // The local copies of upstream's docking (see `widgets.zig`): referenced here so they // are analysed — and their own tests run — even before anything in the app draws them. _ = @import("widgets/DockingWidget.zig"); _ = @import("widgets/DockingWidget/Layout.zig"); _ = @import("widgets/DockingWidget/Row.zig"); - _ = @import("widgets/BlurBackdrop.zig"); // Not a sizing file, but it needs what this root has — dvui and the icon sets — and no other // test root carries both. Its tests are the key table and the modifier order per platform. _ = @import("keycaps.zig"); - // The smooth union the dialogs' and floats' bubbles run together with: its tests are - // arithmetic, but the file speaks in dvui's points. - _ = @import("gfx/liquid_blob.zig"); // `blitData` is pixi's, not fizzy's: nothing else here compiles it, and it speaks in dvui // colours. _ = @import("gfx/image.zig"); diff --git a/core/widgets.zig b/core/widgets.zig index efec29f9b..ac28bb45d 100644 --- a/core/widgets.zig +++ b/core/widgets.zig @@ -37,8 +37,8 @@ pub const TreeSelection = @import("widgets/TreeSelection.zig"); /// widgets/DockingWidget/Layout.zig,BlurBackdrop.zig}` readable. pub const DockingWidget = @import("widgets/DockingWidget.zig"); pub const DockLayout = DockingWidget.Layout; -pub const BlurBackdrop = @import("widgets/BlurBackdrop.zig"); -pub const LiquidField = @import("gfx/LiquidField.zig"); +pub const BlurBackdrop = @import("glass").BlurBackdrop; +pub const LiquidField = @import("glass").LiquidField; pub const Popover = @import("widgets/Popover.zig"); pub const ContextWidget = @import("widgets/ContextWidget.zig"); diff --git a/core/widgets/DockingWidget.zig b/core/widgets/DockingWidget.zig index e5023fbc3..67006b298 100644 --- a/core/widgets/DockingWidget.zig +++ b/core/widgets/DockingWidget.zig @@ -18,7 +18,7 @@ //! node indices (and thus `split_ratio` pointers) valid for the whole frame. const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../motion.zig"); +const motion = @import("glass").motion; const anim = @import("../anim.zig"); const icon_tex = @import("../gfx/icon.zig"); @@ -960,7 +960,7 @@ pub fn deinit(self: *Dockspace) void { const s = self.data().rectScale().s; const highlight = dvui.themeGet().color(.highlight, .fill); if (@import("../dialogs.zig").dialogFrost()) |frost| { - @import("BlurBackdrop.zig").frostPane(self.data().id.update("drop_zone_frost"), self.hover_rect, @import("../corners.zig").round(8), s, .{ + @import("glass").BlurBackdrop.frostPane(self.data().id.update("drop_zone_frost"), self.hover_rect, @import("../corners.zig").round(8), s, .{ .radius = frost.radius, .tint = highlight.opacity(0.6), .mix = 0.35, diff --git a/core/widgets/DropZones.zig b/core/widgets/DropZones.zig index 48fbbb630..0db908e0e 100644 --- a/core/widgets/DropZones.zig +++ b/core/widgets/DropZones.zig @@ -41,12 +41,12 @@ const dvui = @import("dvui"); const native_glass = @import("../native_glass.zig"); const dialogs = @import("../dialogs.zig"); const widgets = @import("../widgets.zig"); -const BlurBackdrop = @import("BlurBackdrop.zig"); +const BlurBackdrop = @import("glass").BlurBackdrop; const icons = @import("icons"); const icon_tex = @import("../gfx/icon.zig"); -const motion = @import("../motion.zig"); -const liquid_glass = @import("../gfx/liquid_glass.zig"); -const LiquidField = @import("../gfx/LiquidField.zig"); +const motion = @import("glass").motion; +const liquid_glass = @import("glass").liquid_glass; +const LiquidField = @import("glass").LiquidField; pub const Side = enum { left, right, top, bottom }; diff --git a/core/widgets/FloatingWindowWidget.zig b/core/widgets/FloatingWindowWidget.zig index 2f088fcc8..dcd5a52b6 100644 --- a/core/widgets/FloatingWindowWidget.zig +++ b/core/widgets/FloatingWindowWidget.zig @@ -1,9 +1,9 @@ const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../motion.zig"); +const motion = @import("glass").motion; const Event = dvui.Event; -const BlurBackdrop = @import("BlurBackdrop.zig"); +const BlurBackdrop = @import("glass").BlurBackdrop; const screens = @import("../screens.zig"); const Options = dvui.Options; const Point = dvui.Point; diff --git a/core/widgets/Split.zig b/core/widgets/Split.zig index 9d2a92fb6..522f79ac5 100644 --- a/core/widgets/Split.zig +++ b/core/widgets/Split.zig @@ -6,7 +6,7 @@ //! event-routing rules rather than layout logic. It depends on nothing but dvui. const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../motion.zig"); +const motion = @import("glass").motion; const icon_tex = @import("../gfx/icon.zig"); const icons = @import("icons"); const anim = @import("../anim.zig"); diff --git a/core/widgets/TreeWidget.zig b/core/widgets/TreeWidget.zig index 2c83af172..66ddc5531 100644 --- a/core/widgets/TreeWidget.zig +++ b/core/widgets/TreeWidget.zig @@ -1,6 +1,6 @@ const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../motion.zig"); +const motion = @import("glass").motion; const corners = @import("../corners.zig"); const dialogs = @import("../dialogs.zig"); const screens = @import("../screens.zig"); diff --git a/core/widgets/menu/FloatingMenu.zig b/core/widgets/menu/FloatingMenu.zig index eefbf653b..1ffc6f146 100644 --- a/core/widgets/menu/FloatingMenu.zig +++ b/core/widgets/menu/FloatingMenu.zig @@ -13,9 +13,9 @@ //! a floating surface. Everything else is dvui's, and updates by re-copying. const std = @import("std"); const dvui = @import("dvui"); -const motion = @import("../../motion.zig"); +const motion = @import("glass").motion; const dialogs = @import("../../dialogs.zig"); -const BlurBackdrop = @import("../BlurBackdrop.zig"); +const BlurBackdrop = @import("glass").BlurBackdrop; const screens = @import("../../screens.zig"); /// Natural units a menu stays in from the edges of the screen it opens on — the main window's, or diff --git a/core/widgets/menu/Popup.zig b/core/widgets/menu/Popup.zig index b8f077edd..980acf122 100644 --- a/core/widgets/menu/Popup.zig +++ b/core/widgets/menu/Popup.zig @@ -7,7 +7,7 @@ const dvui = @import("dvui"); const Menu = @import("Menu.zig"); const FloatingMenu = @import("FloatingMenu.zig"); -const BlurBackdrop = @import("../BlurBackdrop.zig"); +const BlurBackdrop = @import("glass").BlurBackdrop; const Popup = @This(); diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md index a7e0f324f..5db963bff 100644 --- a/docs/DEPENDENCIES.md +++ b/docs/DEPENDENCIES.md @@ -42,8 +42,8 @@ backend and never link SDL, so an SDL bump is never an SDK release and never mov 1. **render: a custom draw command (`deferRender`)** (`c703519`). A callback queued among a subwindow's render commands, run when they replay. The frost and liquid glass read back what - the windows under them drew, which only exists at that point (`core/widgets/BlurBackdrop.zig`, - `DropZones.zig`, `core/gfx/programs.zig`). Upstream: david-vanderson/dvui#988, open. + the windows under them drew, which only exists at that point (`backend/src/glass/BlurBackdrop.zig`, + `core/widgets/DropZones.zig`, `backend/src/glass/programs.zig`). Upstream: david-vanderson/dvui#988, open. 2. **Texture: precision option for render targets** (`f091da0`). `CreateOptions.precision = .high` and `Backend.support_precise_targets`: float targets for the blur pyramid, which 8 bits a channel steps into contour blobs. Upstream: david-vanderson/dvui#989, open. diff --git a/scripts/pack-sdk.sh b/scripts/pack-sdk.sh index cfda5bc06..8cb948478 100755 --- a/scripts/pack-sdk.sh +++ b/scripts/pack-sdk.sh @@ -4,8 +4,8 @@ # Zig has no "depend on a subdirectory of this archive" — plugins must fetch a # package whose root build.zig.zon has no Velopack. This script stages `sdk/` as # the package root. The SDK's own source already lives inside it (`sdk/src/`), so only -# `core/` and the glass look `core` imports (`backend/src/glass_look.zig`) are vendored in -# beside it — `core_module.repoPath` resolves them (see that function). +# `core/` and the backend's glass `core` imports (`backend/src/glass_look.zig`, +# `backend/src/glass/`) are vendored in beside it — `core_module.repoPath` resolves them (see that function). # # Usage: # ./scripts/pack-sdk.sh # writes zig-out/sdk/fizzy-sdk-vX.Y.Z.tar.gz @@ -44,6 +44,7 @@ cp -R sdk/. "$pkg/" cp -R core "$pkg/core" mkdir -p "$pkg/backend/src" cp backend/src/glass_look.zig "$pkg/backend/src/glass_look.zig" +cp -R backend/src/glass "$pkg/backend/src/glass" cp LICENSE "$pkg/LICENSE" # Drop build/editor-only noise if any leaked into the copy (none expected). diff --git a/sdk/core_module.zig b/sdk/core_module.zig index 5ddeb4293..04618dd22 100644 --- a/sdk/core_module.zig +++ b/sdk/core_module.zig @@ -23,9 +23,10 @@ const std = @import("std"); /// `tape/` and `replay/` in both layouts: `b` from the SDK's own build, the `fizzy_sdk` /// dependency's builder from the repo root. /// -/// `glass_look` is the backend's (`backend/build.zig`'s `glassLookModule`) where the build links -/// `core` beside fizzy's backend, which applies the same looks to OS windows; null makes one from -/// the file, beside this package in-repo and vendored into it in the release tarball. +/// `glass_look` and `glass` are the backend's (`backend/build.zig`'s `glassLookModule` and +/// `glassModule`) where the build links `core` beside fizzy's backend, which draws the same glass; +/// null makes them from the files, beside this package in-repo and vendored into it in the release +/// tarball. `glass` is built over `dvui_mod`, as `core` is. pub fn addImports( b: *std.Build, mod: *std.Build.Module, @@ -34,11 +35,19 @@ pub fn addImports( target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, glass_look: ?*std.Build.Module, + glass: ?*std.Build.Module, ) ?*std.Build.Module { mod.addImport("dvui", dvui_mod); - mod.addImport("glass_look", glass_look orelse sdk_pkg.createModule(.{ + const look = glass_look orelse sdk_pkg.createModule(.{ .root_source_file = repoPath(sdk_pkg, "backend/src/glass_look.zig"), - })); + }); + mod.addImport("glass_look", look); + mod.addImport("glass", glass orelse blk: { + const g = sdk_pkg.createModule(.{ .root_source_file = repoPath(sdk_pkg, "backend/src/glass/root.zig") }); + g.addImport("dvui", dvui_mod); + g.addImport("glass_look", look); + break :blk g; + }); mod.addImport("zf", zfModule(b, target, optimize)); // Demos and recordings: `tape` is the format and its engine (std-only), `replay` drives a diff --git a/sdk/plugin_sdk.zig b/sdk/plugin_sdk.zig index 135afe976..5c59ed415 100644 --- a/sdk/plugin_sdk.zig +++ b/sdk/plugin_sdk.zig @@ -591,7 +591,7 @@ pub fn exportModules( .root_source_file = repoPath(b, "core/core.zig"), .link_libc = !is_wasm, }); - _ = core_module.addImports(b, core_mod, dvui_proxy_mod, b, target, optimize, null); + _ = core_module.addImports(b, core_mod, dvui_proxy_mod, b, target, optimize, null, null); const sdk_mod = b.addModule("fizzy_sdk", .{ .target = target,