From 1bbc9e08cf1ec2c85bec59c6c300ce709451bcc4 Mon Sep 17 00:00:00 2001 From: foxnne Date: Sat, 10 Oct 2026 06:58:11 -0500 Subject: [PATCH] build: a dvui app of its own on fizzy's backend (fizzy.addDvui) `fizzy.addDvui(fizzy_dep, root_module, .fizzy | .sdl3)` gives a plain dvui app dvui, its backend and `viewports_none`. Two things only such an app hit are fixed with it: the main window's drawable was acquired with the uploads' copy pass still open, and platform's menu exports went unemitted. Co-Authored-By: Claude Opus 5.5 --- backend/src/GpuRenderer.zig | 4 +++ backend/src/platform/menu.zig | 10 +++---- backend/src/platform/root.zig | 13 +++++++++ build.zig | 32 +++++++++++++++++++++ build/exe.zig | 53 ++++++++++++++++++++++------------- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/backend/src/GpuRenderer.zig b/backend/src/GpuRenderer.zig index cb11db39c..21aff8296 100644 --- a/backend/src/GpuRenderer.zig +++ b/backend/src/GpuRenderer.zig @@ -559,6 +559,10 @@ fn acquireSwapchain(self: *GpuRenderer) !bool { return false; } const cmd = try self.ensureCmd(); + // No pass may be open while a drawable is acquired, the uploads' copy pass included. An app + // drawing its frame straight into the window (any dvui app; fizzy draws into a target first) + // had one open from the frame's uploads, and SDL asserted on every frame. + self.endCopy(); var tex: ?*c.SDL_GPUTexture = null; var w: u32 = 0; var h: u32 = 0; diff --git a/backend/src/platform/menu.zig b/backend/src/platform/menu.zig index b8cc34ce9..56485ae67 100644 --- a/backend/src/platform/menu.zig +++ b/backend/src/platform/menu.zig @@ -105,31 +105,31 @@ fn selector(name: [*:0]const u8) ?*anyopaque { return FizzyGetSelector(name); } -export fn FizzyMenuActivated(section: c_int, tag: c_int, from_key: bool) void { +pub export fn FizzyMenuActivated(section: c_int, tag: c_int, from_key: bool) void { if (section < 0 or section > 2 or tag < 0) return; if (section == 0) pending_from_key.store(from_key, .release); pending_tag[@intCast(section)].store(tag, .release); } -export fn FizzyMenuEnabled(section: c_int, tag: c_int) callconv(.c) bool { +pub export fn FizzyMenuEnabled(section: c_int, tag: c_int) callconv(.c) bool { const h = hooks orelse return true; if (h.input_blocked(h.ctx)) return false; if (section < 0 or section > 2 or tag < 0) return true; return h.enabled(h.ctx, .{ .section = @enumFromInt(section), .tag = @intCast(tag) }); } -export fn FizzyMenuTitle(section: c_int, tag: c_int) callconv(.c) ?[*:0]const u8 { +pub export fn FizzyMenuTitle(section: c_int, tag: c_int) callconv(.c) ?[*:0]const u8 { const h = hooks orelse return null; if (section < 0 or section > 2 or tag < 0) return null; return h.title(h.ctx, .{ .section = @enumFromInt(section), .tag = @intCast(tag) }); } -export fn FizzyMenuInputBlocked() callconv(.c) bool { +pub export fn FizzyMenuInputBlocked() callconv(.c) bool { const h = hooks orelse return false; return h.input_blocked(h.ctx); } -export fn FizzyMenuAbout() callconv(.c) void { +pub export fn FizzyMenuAbout() callconv(.c) void { pending_about.store(true, .release); } diff --git a/backend/src/platform/root.zig b/backend/src/platform/root.zig index b456a1709..8fadd88fd 100644 --- a/backend/src/platform/root.zig +++ b/backend/src/platform/root.zig @@ -16,3 +16,16 @@ pub const geometry = @import("geometry.zig"); pub const macos_monitor = @import("macos_monitor.zig"); pub const window_layout = @import("window_layout.zig"); pub const menu = @import("menu.zig"); + +comptime { + // The menu bar's Objective-C (`macos/menu_target.m`) calls these back, and it is compiled into + // every app on macOS (`addPlatformObjC`), so they are kept whether or not the app ever reaches + // `menu` — a plain dvui app with no menu bar of its own failed to link without them. + if (@import("builtin").os.tag == .macos) { + _ = &menu.FizzyMenuActivated; + _ = &menu.FizzyMenuEnabled; + _ = &menu.FizzyMenuTitle; + _ = &menu.FizzyMenuInputBlocked; + _ = &menu.FizzyMenuAbout; + } +} diff --git a/build.zig b/build.zig index b4c140e69..1f072f17d 100644 --- a/build.zig +++ b/build.zig @@ -89,3 +89,35 @@ pub fn buildApp(fizzy_dep: *std.Build.Dependency, plugins: []const BundledPlugin opts.app_plugins = plugins; try @import("build/app.zig").construct(fizzy_dep.builder, d.target, d.optimize, opts, d.cfg); } + +/// What a dvui app of its own builds on (`addDvui`): fizzy's backend, with OS windows besides the +/// main one, or dvui's own SDL3 backend, with the main window only. +pub const NativeBackend = @import("build/exe.zig").NativeBackend; + +/// A plain dvui app — dvui's widgets and `dvui.App`, nothing of fizzy the editor — on `backend`: +/// `root_module` gets `dvui` (fizzy's pin), its `backend`, and `viewports_none`, so the same code +/// builds on either backend: +/// +/// ```zig +/// const backend = @import("backend"); +/// const viewports = if (@hasDecl(backend, "viewports")) backend.viewports else @import("viewports_none"); +/// ``` +/// +/// On fizzy's backend it also gets `platform` (the window's chrome and state), and on macOS the +/// backend's Objective-C, compiled into it. Give `root_module` its target and optimize mode first. +pub fn addDvui(fizzy_dep: *std.Build.Dependency, root_module: *std.Build.Module, backend: NativeBackend) !void { + const b = fizzy_dep.builder; + const exe = @import("build/exe.zig"); + const target = root_module.resolved_target orelse @panic("fizzy.addDvui: give the root module its target first"); + const optimize = root_module.optimize orelse .Debug; + const macos_sdl_paths = try @import("build/common.zig").macosSdlPathsForExplicitTarget(b, target); + const native = exe.nativeDvui(b, backend, target, optimize, .off, macos_sdl_paths); + root_module.addImport("dvui", native.dvui); + root_module.addImport("backend", native.backend); + const backend_dep = exe.backendDependency(b); + root_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(backend_dep, native.dvui)); + if (backend == .fizzy) { + root_module.addImport("platform", exe.platformModule(b, native, target, optimize, macos_sdl_paths)); + @import("fizzy_backend").addPlatformObjC(backend_dep, root_module, exe.backendOptions(target, optimize, macos_sdl_paths)); + } +} diff --git a/build/exe.zig b/build/exe.zig index 28fed66e8..010402751 100644 --- a/build/exe.zig +++ b/build/exe.zig @@ -92,12 +92,42 @@ pub fn defaultNativeBackend(target: std.Build.ResolvedTarget) NativeBackend { } /// dvui as the executable links it, and the backend under it. -const NativeDvui = struct { +pub const NativeDvui = struct { dep: *std.Build.Dependency, dvui: *std.Build.Module, backend: *std.Build.Module, }; +/// dvui on the native backend asked for: fizzy's (`fizzyNativeDvui`), or dvui's own SDL3 backend. +/// The executable's, and a dvui app's of its own (`fizzy.addDvui`, in `build.zig`). +pub fn nativeDvui( + b: *std.Build, + native_backend: NativeBackend, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + accesskit: dvui.AccesskitOptions, + macos_sdl_paths: ?MacosSdlPaths, +) NativeDvui { + return switch (native_backend) { + .fizzy => fizzyNativeDvui(b, target, optimize, accesskit, macos_sdl_paths), + .sdl3 => blk: { + const dep = if (macos_sdl_paths) |p| + sdk.dvuiDependency(b, .{ + .target = target, + .optimize = optimize, + .backend = .sdl3, + .accesskit = accesskit, + .system_include_path = p.include, + .system_framework_path = p.framework, + .library_path = p.lib, + }) + else + sdk.dvuiDependency(b, .{ .target = target, .optimize = optimize, .backend = .sdl3, .accesskit = accesskit }); + break :blk .{ .dep = dep, .dvui = dep.module("dvui_sdl3"), .backend = dep.module("sdl3") }; + }, + }; +} + /// dvui in its `custom` mode with fizzy's backend linked under it — as `build/web.zig` does for /// the web. Every option is the one dvui's own `sdl3` mode sets, so dvui's shape, and with it /// the plugin ABI fingerprint, is the same either way. @@ -139,7 +169,7 @@ pub fn backendOptions(target: std.Build.ResolvedTarget, optimize: std.builtin.Op /// Window and platform pieces for apps on either SDL3 backend (the backend package's /// `platform`). Fizzy's own backend imports it as well. -fn platformModule( +pub fn platformModule( b: *std.Build, native: NativeDvui, target: std.Build.ResolvedTarget, @@ -175,24 +205,7 @@ pub fn addFizzyExecutableForTarget( /// `bundled_plugins` module under its plugin id. app_plugins: []const sdk.BundledPlugin, ) !FizzyExecutable { - const native: NativeDvui = switch (native_backend) { - .fizzy => fizzyNativeDvui(b, resolved_target, optimize, accesskit, macos_sdl_paths), - .sdl3 => blk: { - const dep = if (macos_sdl_paths) |p| - sdk.dvuiDependency(b, .{ - .target = resolved_target, - .optimize = optimize, - .backend = .sdl3, - .accesskit = accesskit, - .system_include_path = p.include, - .system_framework_path = p.framework, - .library_path = p.lib, - }) - else - sdk.dvuiDependency(b, .{ .target = resolved_target, .optimize = optimize, .backend = .sdl3, .accesskit = accesskit }); - break :blk .{ .dep = dep, .dvui = dep.module("dvui_sdl3"), .backend = dep.module("sdl3") }; - }, - }; + const native = nativeDvui(b, native_backend, resolved_target, optimize, accesskit, macos_sdl_paths); const dvui_dep = native.dep; const dvui_mod = native.dvui;