Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ea83962
zig: the 0.17 switch, scaffolded with marked stand-ins (#437)
foxnne Oct 16, 2026
9a22df2
zig: the build's custom steps become helper programs (#438)
foxnne Oct 16, 2026
2491f4e
zig: release packaging on 0.17, with Velopack 1.2.161 (#448)
foxnne Oct 16, 2026
b18213c
backend: no crash quitting a plain dvui app on macOS (#480)
foxnne Oct 17, 2026
a59ff14
backend: a plain dvui app's floating windows open as OS windows (#481)
foxnne Oct 17, 2026
67cc6ea
macos: a plain dvui app's windows take fizzy's window shape (#482)
foxnne Oct 17, 2026
78a5019
macos: child windows stay above the main window (#483)
foxnne Oct 17, 2026
46ddcd8
zig/0.17: merge main (40 commits, to #469)
foxnne Oct 17, 2026
6d94889
zig/0.17: merge main (40 commits, to #469) (#486)
foxnne Oct 17, 2026
28766fa
zig: settings and command schemas read 0.17's @typeInfo (#488)
foxnne Oct 17, 2026
b45233a
zig: a lifted view keeps its drag through dvui's captureMouseGet (#489)
foxnne Oct 17, 2026
a1feb78
docs: the Windows traps of the Zig 0.17 move (#494)
foxnne Oct 18, 2026
300d797
core: a transparent blit reads its pixels as bytes on Zig 0.17 (#496)
foxnne Oct 18, 2026
1b6ad2c
sdk: a plugin can install a program beside itself (#498)
foxnne Oct 18, 2026
0be3e64
zig/0.17: record #486's merge of main as a parent
foxnne Oct 18, 2026
47a0c1e
zig/0.17: merge main (6 commits, to #490)
foxnne Oct 18, 2026
87ae2ae
zig: what 0.16 needed goes with the move (#505)
foxnne Oct 18, 2026
3513b9f
macos: a plain dvui app's OS windows are ordinary document windows (#…
foxnne Oct 18, 2026
eb452e8
backend: OS windows for the floating windows that ask, or all by choi…
foxnne Oct 18, 2026
7336d4a
macos: a float's full screen at the display's rate, clear and rounded…
foxnne Oct 18, 2026
2dc6fc4
macos: the pointer reaches the window it is over, not one behind it (…
foxnne Oct 18, 2026
4eca7f6
core: one window material, and the kinds each backend can show
foxnne Oct 18, 2026
d2128cb
app: every glass draws in the window material
foxnne Oct 18, 2026
9f5fc85
backend: a floating window dragged past the app's windows tears off (…
foxnne Oct 18, 2026
9e8e530
zig/0.17: merge main (22 commits, to #509) (#558)
foxnne Oct 18, 2026
e118432
zig/0.17: merge into backend/window-materials (to #558)
foxnne Oct 18, 2026
18c1631
Merge backend/window-materials into app/glass-material
foxnne Oct 18, 2026
54014c7
zig: settings own only strings, and builds read through b.root (#561)
foxnne Oct 18, 2026
39574be
core: one window material, and the kinds each backend can show (#546)
foxnne Oct 18, 2026
0b0f9e0
Merge zig/0.17 into app/glass-material
foxnne Oct 18, 2026
ae5e719
backend: plain dvui app windows made of the window material (#565)
foxnne Oct 18, 2026
9d9dbee
Merge main into app/glass-material
foxnne Oct 18, 2026
e17b4ce
macos: a window's glass bevel follows a live resize (#576)
foxnne Oct 18, 2026
d6732b6
backend: fizzy's in-app glass is the backend's, core re-exports it
foxnne Oct 18, 2026
2aa641c
main: merge into backend/glass-module
foxnne Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<name>.zig` hub — the build helper generates the dylib entry; files use named imports (`fizzy_sdk`/`dvui`/…). Builds standalone with `cd plugins/<name> && 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_<NAME>=1` env var forces static for a given built-in (useful when debugging dylib loading).
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions backend/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions backend/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
59 changes: 59 additions & 0 deletions backend/src/glass/prof.zig
Original file line number Diff line number Diff line change
@@ -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);
}
File renamed without changes.
25 changes: 25 additions & 0 deletions backend/src/glass/root.zig
Original file line number Diff line number Diff line change
@@ -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;
}
14 changes: 13 additions & 1 deletion build/app.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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(.{
Expand Down
6 changes: 4 additions & 2 deletions build/exe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build/web.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
2 changes: 1 addition & 1 deletion core/Spring.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions core/anim.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading
Loading