Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ 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
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
173 changes: 173 additions & 0 deletions backend/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Fizzy's native backend, as a package an app depends on: SDL3 for the window and its events, an
//! SDL_GPU renderer of its own with custom fragment programs (`GpuRenderer`), and the platform
//! pieces (title bars, the OS's glass, OS windows for floats, menus and dialogs: `platform`).
//!
//! An app builds dvui in its `custom` mode and wires this under it (`backendModule`);
//! `platformModule` gives the platform pieces to an app on this backend or on dvui's own SDL3 one.
//! dvui is the caller's and never pinned here, so the backend compiles against the app's dvui.
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const test_step = b.step("test", "Run the backend's std-only tests");
for (unit_tests) |t| {
const compile = b.addTest(.{
.name = t.name,
.root_module = b.createModule(.{ .root_source_file = b.path(t.root), .target = target, .optimize = optimize }),
});
test_step.dependOn(&b.addRunArtifact(compile).step);
}
}

/// The backend's std-only files with tests of their own, relative to this package: run by
/// `zig build test` here, and by an app's own test step through `dependency.path(root)`.
pub const unit_tests = [_]struct { name: []const u8, root: []const u8 }{
.{ .name = "backend-window-layout-tests", .root = "src/platform/window_layout.zig" },
// The hit test Windows' WM_NCHITTEST and Linux's SDL hit test both answer from.
.{ .name = "backend-titlebar-tests", .root = "src/platform/titlebar.zig" },
// The rounded frame Linux's blur behind the window is stepped into (`wayland_blur`).
.{ .name = "backend-blur-region-tests", .root = "src/platform/blur_region.zig" },
// Where a viewport's OS window is on the desktop and where its part of the frame lies.
.{ .name = "backend-viewport-map-tests", .root = "src/viewport_map.zig" },
// The backend's health counters: frame-time percentiles, per-frame counts, SDL's log counted
// from any thread, a snapshot as ZON.
.{ .name = "backend-health-tests", .root = "src/Health.zig" },
};

/// Where the macOS SDK is, for a macOS target that is not the host's own (`-Dtarget=aarch64-macos`
/// on Apple Silicon): Zig finds it alone for a native build only.
pub const MacosSdk = struct {
include: std.Build.LazyPath,
framework: std.Build.LazyPath,
lib: std.Build.LazyPath,
};

pub const Options = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
/// Null on a native build.
macos_sdk: ?MacosSdk = null,
};

/// The `backend` module wired into each dvui: one per dvui module, however many executables use
/// it. dvui imports it by name, and a second module over the same file in one compilation is an
/// error.
var backend_modules: std.AutoHashMapUnmanaged(*std.Build.Module, *std.Build.Module) = .empty;
/// The `platform` module for each backend module, for the same reason.
var platform_modules: std.AutoHashMapUnmanaged(*std.Build.Module, *std.Build.Module) = .empty;

/// The backend under `dvui_mod`, a dvui built with `.backend = .custom`, and imported by it as
/// `backend`. Call `platformModule` with the result too: the backend imports the platform pieces.
pub fn backendModule(dep: *std.Build.Dependency, dvui_mod: *std.Build.Module, opts: Options) *std.Build.Module {
if (backend_modules.get(dvui_mod)) |m| return m;
const b = dep.builder;

// Optimized in a Debug app too: Zig builds C in Debug unoptimized and with its undefined-
// behaviour checks on every call, and SDL is on every frame's path — event pumping, the GPU
// device, every window's present. A Debug frame spent much of itself there (`sample`), and a
// window's way into full screen ran at half the display's rate in Debug alone. The app's own
// code stays Debug; stepping into SDL's C is what a Debug app gives up.
const sdl_optimize: std.builtin.OptimizeMode = if (opts.optimize == .Debug) .ReleaseFast else opts.optimize;
const sdl_dep = if (opts.macos_sdk) |p|
b.lazyDependency("sdl", .{
.target = opts.target,
.optimize = sdl_optimize,
.include_path = p.include,
.framework_path = p.framework,
.library_path = p.lib,
})
else
b.lazyDependency("sdl", .{ .target = opts.target, .optimize = sdl_optimize });

const sdl_translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/sdl3-c.h"),
.target = opts.target,
.optimize = opts.optimize,
});
if (sdl_dep) |sdl| sdl_translate_c.addIncludePath(sdl.artifact("SDL3").getEmittedIncludeTree());

const m = b.createModule(.{
.root_source_file = b.path("src/SDLBackend.zig"),
.target = opts.target,
.optimize = opts.optimize,
.sanitize_c = .full,
.link_libc = true,
.imports = &.{
.{ .name = "sdl3-c", .module = sdl_translate_c.createModule() },
.{ .name = "dvui", .module = dvui_mod },
},
});
if (sdl_dep) |sdl| m.linkLibrary(sdl.artifact("SDL3"));
// The backend's AppKit helpers; the module calls them by name.
if (opts.target.result.os.tag == .macos) {
addMacosSdk(m, opts);
m.addCSourceFile(.{ .file = b.path("src/macos_monitor.m") });
}
dvui_mod.addImport("backend", m);
backend_modules.put(b.allocator, dvui_mod, m) catch @panic("OOM");
return m;
}

/// The window and platform pieces (`src/platform`) for an app on `backend_mod`: this package's
/// backend (`backendModule`), which imports them in turn, or dvui's own SDL3 backend. The app
/// reaches them as `platform`.
pub fn platformModule(dep: *std.Build.Dependency, dvui_mod: *std.Build.Module, backend_mod: *std.Build.Module, opts: Options) *std.Build.Module {
if (platform_modules.get(backend_mod)) |m| return m;
const b = dep.builder;
const m = b.createModule(.{
.root_source_file = b.path("src/platform/root.zig"),
.target = opts.target,
.optimize = opts.optimize,
.link_libc = true,
});
m.addImport("dvui", dvui_mod);
m.addImport("backend", backend_mod);
switch (opts.target.result.os.tag) {
.macos => if (objcModule(dep, opts)) |objc| m.addImport("objc", objc),
.windows => if (win32Module(dep)) |win32| m.addImport("win32", win32),
else => {},
}
// This package's backend imports the platform pieces by name; dvui's SDL3 one does not.
var it = backend_modules.valueIterator();
while (it.next()) |own| if (own.* == backend_mod) backend_mod.addImport("platform", m);
platform_modules.put(b.allocator, backend_mod, m) catch @panic("OOM");
return m;
}

/// The platform's Objective-C (`src/platform/macos/*.m`), added to an executable's root module: an
/// app on this backend calls it once for its executable. `platformModule` does not carry it, because
/// it calls back into the platform's Zig by name, which only a compile that reaches those files
/// emits: carried by the module, a test that imports the backend alone would fail to link.
pub fn addPlatformObjC(dep: *std.Build.Dependency, root_module: *std.Build.Module, opts: Options) void {
if (opts.target.result.os.tag != .macos) return;
const b = dep.builder;
addMacosSdk(root_module, opts);
root_module.addCSourceFile(.{ .file = b.path("src/platform/macos/visual_effect_view.m") });
root_module.addCSourceFile(.{ .file = b.path("src/platform/macos/menu_target.m") });
root_module.addCSourceFile(.{ .file = b.path("src/platform/macos/window_monitor.m") });
root_module.addCSourceFile(.{ .file = b.path("src/platform/macos/live_resize_trace.m") });
}

/// zig-objc, from this package's pin, for an app's own AppKit code. Null on the configure pass
/// that fetches it (Zig runs the build again).
pub fn objcModule(dep: *std.Build.Dependency, opts: Options) ?*std.Build.Module {
const objc = dep.builder.lazyDependency("zig_objc", .{ .target = opts.target, .optimize = opts.optimize }) orelse return null;
return objc.module("objc");
}

/// zigwin32, from this package's pin, for an app's own Windows code. Null on the configure pass
/// that fetches it.
pub fn win32Module(dep: *std.Build.Dependency) ?*std.Build.Module {
const win32 = dep.builder.lazyDependency("zigwin32", .{}) orelse return null;
return win32.module("win32");
}

/// A non-native macOS target's SDK, for a module's Objective-C: zig-objc's paths do not always
/// reach `.m` compiles (Security.framework's `<libDER/DERItem.h>`).
fn addMacosSdk(m: *std.Build.Module, opts: Options) void {
const p = opts.macos_sdk orelse return;
m.addSystemIncludePath(p.include);
m.addSystemFrameworkPath(p.framework);
m.addLibraryPath(p.lib);
}
29 changes: 29 additions & 0 deletions backend/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.{
.name = .fizzy_backend,
.version = "0.1.0",
.fingerprint = 0x2bf08855a368b9a8,
.minimum_zig_version = "0.16.0",
.dependencies = .{
// SDL3 for the window, its events and the GPU: fizzyedit/sdl_zig's `fizzy` branch,
// building fizzyedit/SDL's `fizzy-3.4` with the patches this backend needs (fizzy's
// docs/DEPENDENCIES.md). Pinned here, by the package that uses it, and nowhere else.
.sdl = .{
.url = "https://github.com/fizzyedit/sdl_zig/archive/5e41c70e21f673d5b2ac6af2a3cba7e7aaa68489.tar.gz",
.hash = "sdl-1.0.3+3.4.16-i4QD0UwUqgA04UDxXFbmfpE1e2Oo6wwX0LJuIMG82mis",
.lazy = true,
},
// AppKit from Zig, for the macOS platform pieces (and an app's own, `objcModule`).
.zig_objc = .{
.url = "https://github.com/foxnne/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
.lazy = true,
},
// Win32 from Zig, for the Windows platform pieces (and an app's own, `win32Module`).
.zigwin32 = .{
.url = "https://github.com/marlersoft/zigwin32/archive/ec98bb4d9eea532320a8551720a9e3ec6de64994.tar.gz",
.hash = "win32-25.0.28-preview-mX5pFWMt5QPTVIGh3r2-OpPunpcCCjApyRbA6Zn6WALH",
.lazy = true,
},
},
.paths = .{ "build.zig", "build.zig.zon", "src" },
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub const viewport_map = @import("viewport_map.zig");
pub const Health = @import("Health.zig");

/// What an app gets from its window beyond drawing into it: native dialogs, files the OS hands
/// over, trackpad gestures, the window's chrome and state (`src/backend/native/platform`).
/// over, trackpad gestures, the window's chrome and state (`platform/`).
pub const platform = @import("platform");

/// SDL3 only. Kept as a constant so the event code below stays a verbatim copy of dvui's
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! What an app gets from its window beyond drawing into it — native file dialogs, files the OS
//! asks it to open, trackpad gestures, the window's chrome and state — for apps on dvui's SDL3
//! backends, fizzy's own (`src/backend/native`) and dvui's. Each piece takes the SDL window and
//! backends, fizzy's own (this package's `SDLBackend`) and dvui's. Each piece takes the SDL window and
//! nothing of any one app: where an app has a say (which folder a dialog opens in, what to do
//! with a file the OS hands over) it plugs in through a small hook.
//!
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
24 changes: 6 additions & 18 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build.zig.zon",
"update.zig",
"sdk",
"backend",
"assets",
"libs",
"web",
Expand All @@ -19,24 +20,6 @@
.version = "0.2.0",
.minimum_zig_version = "0.16.0",
.dependencies = .{
.zig_objc = .{
.url = "https://github.com/foxnne/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
.lazy = true,
},
.zigwin32 = .{
.url = "https://github.com/marlersoft/zigwin32/archive/ec98bb4d9eea532320a8551720a9e3ec6de64994.tar.gz",
.hash = "win32-25.0.28-preview-mX5pFWMt5QPTVIGh3r2-OpPunpcCCjApyRbA6Zn6WALH",
.lazy = true,
},
// App-only: SDL for fizzy's own native backend (`src/backend/native`). fizzyedit/sdl_zig's
// `fizzy` branch, building fizzyedit/SDL's `fizzy-3.4` — see docs/DEPENDENCIES.md. Plugins
// draw through dvui's proxy backend and never link SDL.
.sdl = .{
.url = "https://github.com/fizzyedit/sdl_zig/archive/5e41c70e21f673d5b2ac6af2a3cba7e7aaa68489.tar.gz",
.hash = "sdl-1.0.3+3.4.16-i4QD0UwUqgA04UDxXFbmfpE1e2Oo6wwX0LJuIMG82mis",
.lazy = true,
},
.icons = .{
.url = "https://github.com/fizzyedit/icons/archive/41e7d2162b121e3798438a7da25ff4648b204dc7.tar.gz",
.hash = "icons-0.1.0-iJxA-Z0TNABnFm_tPfx46ZnvojHweT6xON9ay91XZ4Xo",
Expand All @@ -45,6 +28,11 @@
.fizzy_sdk = .{
.path = "sdk/",
},
// Fizzy's native backend (SDL3, its GPU renderer, the platform pieces), with SDL,
// zig-objc and zigwin32 pinned there. App-only: plugins never link it.
.fizzy_backend = .{
.path = "backend",
},
.assetpack = .{
.url = "https://github.com/foxnne/assetpack/archive/ac7592f3f5988857840d0df4610e1e1fad690e2e.tar.gz",
.hash = "assetpack-0.2.0-5DA2d1ZkAADJanNVdWrUBOGMhOzUENhrUiqXcHADxY2x",
Expand Down
34 changes: 19 additions & 15 deletions build/app.zig
Original file line number Diff line number Diff line change
Expand Up @@ -554,17 +554,6 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.
.{ "fizzy-direction-tests", "core/math/direction.zig" },
.{ "fizzy-easing-tests", "core/math/easing.zig" },
.{ "fizzy-layout-anchor-tests", "core/math/layout_anchor.zig" },
.{ "fizzy-window-layout-tests", "src/backend/native/platform/window_layout.zig" },
// The hit test Windows' WM_NCHITTEST and Linux's SDL hit test both answer from.
.{ "fizzy-titlebar-tests", "src/backend/native/platform/titlebar.zig" },
// The rounded frame Linux's blur behind the window is stepped into (`wayland_blur`).
.{ "fizzy-blur-region-tests", "src/backend/native/platform/blur_region.zig" },
// Where a viewport's OS window is on the desktop and where its part of the frame lies.
// std-only (see viewport_map.zig); `SDLBackend` applies it.
.{ "fizzy-viewport-map-tests", "src/backend/native/viewport_map.zig" },
// The native backend's health counters: frame-time percentiles, per-frame counts, SDL's
// log counted from any thread, a snapshot as ZON. std-only (see Health.zig).
.{ "fizzy-backend-health-tests", "src/backend/native/Health.zig" },
.{ "fizzy-plugin-store-tests", "app/store/registry/store.zig" },
.{ "fizzy-paths-tests", "core/paths.zig" },
// The credential store behind `Host.secrets`: a 0600 file, keyed, round-tripped.
Expand Down Expand Up @@ -648,6 +637,21 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.
.filters = test_filters,
}));
}
// The backend package's std-only tests (window layout, the title-bar hit test, the blur
// region, the viewport map, the health counters), from its own list, so `zig build test`
// here still runs them.
const backend_dep = fizzy_exe.backendDependency(b);
for (@import("fizzy_backend").unit_tests) |t| {
try unit_test_artifacts.append(b.allocator, b.addTest(.{
.name = t.name,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.root_source_file = backend_dep.path(t.root),
}),
.filters = test_filters,
}));
}

// The single-instance listener (`libs/dvui-singleton-app`): its shutdown wakes the thread
// waiting in `accept`, which closing the socket does not on Linux. It talks to its socket
Expand Down Expand Up @@ -904,12 +908,12 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.
}));

if (target.result.os.tag == .macos) {
if (b.lazyDependency("zig_objc", .{ .target = target, .optimize = optimize })) |dep| {
fizzy_test_module.addImport("objc", dep.module("objc"));
if (@import("fizzy_backend").objcModule(fizzy_exe.backendDependency(b), fizzy_exe.backendOptions(target, optimize, macos_sdl_paths))) |objc| {
fizzy_test_module.addImport("objc", objc);
}
} else if (target.result.os.tag == .windows) {
if (b.lazyDependency("zigwin32", .{})) |dep| {
fizzy_test_module.addImport("win32", dep.module("win32"));
if (@import("fizzy_backend").win32Module(fizzy_exe.backendDependency(b))) |win32| {
fizzy_test_module.addImport("win32", win32);
}
}

Expand Down
Loading
Loading