Repository navigation
backend: viewports.Picture, fizzy.addDvui, the pointer over OS windows - #326
Merged
Merged
Conversation
A dvui app on fizzy's backend opens its OS windows through `@import("backend").viewports`,
the API fizzy's Popout used from `src/backend/backend_native.zig`, moved into
`backend/src/viewports.zig` as it was. Fizzy re-exports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Popout's float, menu, carry and overlay windows each drew their picture with a copy of the same steps; `viewports.Picture` and `sizedTarget` in the backend package are those steps once, for any dvui app on the backend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
`viewports_none` (backend/src/viewports_none.zig) has every name `viewports` has, each saying there are no OS windows: the web's stand-in moved into the package, for fizzy on the web, on dvui's testing backend and on dvui's own SDL3 backend, and for any dvui app built on either kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stand-in gains Picture and sizedTarget, so it keeps every name viewports has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #281: what example-app's `run-dvui` builds on. Stacked on #326 (on #324); review those first. The example-app side is fizzyedit/example-app#4. **`fizzy.addDvui(fizzy_dep, root_module, backend)`** gives an outside app's module dvui on one of two backends: - `.fizzy`: fizzy's own, where each floating window can be an OS window of its own; - `.sdl3`: dvui's own SDL3 backend, which has the main window only. The module gets `dvui` (fizzy's pin), its `backend`, and `viewports_none` (#324), so the same app code builds on either: ```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`, and on macOS the backend's Objective-C compiled into it. `build/exe.zig`'s backend switch is now `nativeDvui`, shared by fizzy's own executable and `addDvui`. ## Two fixes a plain dvui app needed 1. **The main window's drawable was acquired with a copy pass still open** (`GpuRenderer.acquireSwapchain`). The frame's texture uploads had left it open, and SDL asserted on every frame ("Cannot acquire a swapchain texture during a pass"). Fizzy never hit this because it draws its frame into a target first. Any dvui app draws straight into the window. The fix ends the copy pass before acquiring, as `presentInto` and `clearPass` already do. 2. **`platform`'s menu-bar exports weren't emitted** (`FizzyMenu*`). `macos/menu_target.m` calls them and is compiled into every macOS app on the backend, but Zig emits an `export fn` only when analysis reaches its file. Fizzy reaches `platform/menu.zig` through its menu bar; a dvui app with none failed to link. Now `platform/root.zig` references them, and they're `pub`. ## Verification - **Fizzy:** `zig build`, `test` (514/515, 1 skipped), `test-integration` (358/358), `check-web`, `test-sdk-version`, the backend's tests (46/46), and the Windows and Linux cross-builds pass on macOS. The soak tape ends in a passing verdict (1,429 viewport presents, no leaks, nothing logged as an error). - **example-app's `run-dvui`, built against this branch, on macOS:** - **On fizzy's backend:** it opens its main window and two floating windows. Each is an OS window of its own, opened over its place in the main window (window list: main at 540,332; floats at 652,476 and 684,500, each 280×212). No assertions or warnings, and it quits cleanly on SIGTERM. - **With `-Ddvui-backend=sdl3`:** the same app shows only its main window, with the floating windows in it. No warnings. - **Not seen:** I couldn't screenshot, since this session has no screen-recording permission. Window positions and the logs are what I checked, not how they look. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) Stacked on #326 (on #324); review those first. A demo or live tape's pointer was drawn only in the main window. Over a float's OS window, a menu or a dialog, it vanished, so a viewer couldn't see it reach a float's close button, a menu row or a dialog's buttons. Reported by @foxnne. ## Why - **The pointer's layer was the size of the main window.** `replay.overlay` made it a subwindow covering the whole window. Popout gives each OS window the subwindows whose middle is in its part of the frame, so the pointer layer always counted as the main window's. - **Even placed right, a float's window would have claimed it before a menu or dialog over the float could.** Floats are drawn first (`windowFrame` before `menuFrame`). Dialogs are made in `drawRetained`, after the pointer was raised, so they stack above it. ## Changes - **`sdk/replay/overlay.zig`:** the pointer's layer is a small rect round its tip, so its middle is where the pointer is. Drawing is still clipped to the screen the pointer is on (`dvui.screenFor`), not to that rect. Any dvui app that splits its frame across windows by subwindow middle gets the same behaviour. - **`Popout.overlaysFrame`, new, last in `endFrame`:** each subwindow that takes no input (`mouse_events` false: the pointer and its click ripple, a tooltip) is drawn into the topmost window over its middle. That's a menu's or dialog's window, else a float's. Over none of them, it's left to the main window. - `windowFrame` now leaves such subwindows for this pass, unless they are drawn everywhere (a drag's layer). - Carried layers stay the carry window's. - **`viewports.Picture.again`** (and its stand-in): draws more into a picture already handed over this frame, keeping what's there. `present` only names the target; the window copies it when the frame ends. `begin` is now `clear` plus `again`. ## Verification - `zig build`, `test` (514/515, 1 skipped), `test-integration` (358/358), `check-web`, `test-sdk-version` and the Windows cross-build pass on macOS. - **Sandbox, macOS:** - **Pointer in a float:** a tape floated the explorer out and moved the pointer onto a file in it. A temporary trace in `overlaysFrame` (removed before the commit) showed the pointer layer going into the float's window while over it (middle at `200354,388`, the float's band). The other input-less layer stayed with the main window. - **Soak:** the soak tape still ends in a passing verdict, with no leaks and nothing logged as an error. - **Not seen:** - The menu and dialog windows. The sandbox's menus close on the next frame because it isn't the active app, and this session has no screen-recording permission for screenshots. - The drawing itself. The routing is what's verified, not what it looks like. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#324 landed on main squashed; its files here are the same plus Picture, Picture.again and sizedTarget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 10, 2026
foxnne
added a commit
that referenced
this pull request
Oct 10, 2026
## What changes `sdk_version` goes from 0.2.20 to 0.2.21. Merging tags `sdk-v0.2.21`, publishes its tarball, and asks the store plugins to repin. **Why now.** The plugin boundary's fingerprint moved since `sdk-v0.2.20` (`0x7b455e7a…` → `0x1bf417f6…`, by #331). Store plugins built against 0.2.20 do not load in a fizzy built from `main`, and fizzyed.it/app is built from `main`. The chat plugin (`fizzyedit/chat`) also needs #331's `Host.documentTitleChanged`, and builds against a local fork until this is out. ## SDK impact - [ ] None - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched, PR labelled `sdk` - [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the last `sdk-v*` tag **The `sdk` PRs since `sdk-v0.2.20`:** - #331: a surface's title can change (`Host.setSurfaceTitle`, the Host keeping its own copy), and a document's tab follows its owner (`Host.documentTitleChanged`). `documentTitle`'s slice now need only last the call. **Also reaching plugins (`core/`, `sdk/replay`):** - #325: floats' screens are dvui's (`core/screens.zig`). - #326: `replay.overlay` draws through the backend's viewport picture. - #301: `sdk/replay_module.zig` follows `replay-app`'s move to fizzyedit/example-app. ## Verified - [x] macOS: - `zig build` and `test-sdk-version` on this head. - `scripts/pack-sdk.sh` packs `fizzy-sdk-v0.2.21.tar.gz`. - Every store plugin (fresh pulls of pixi, zig, drive, atlas and ghostty `main`) builds against this tree's `sdk/` with `zig build --fork=<sdk>` (31/31 and 28/28 steps) into a throwaway profile. The fork matched, and the built plugin reports `pinned fizzy SDK: 0.2.21`. The fingerprint moved, so each repin PR is a real rebuild, but none should open as a draft for a broken build. The agent and chat plugins build too (22/22, 19/19). - [ ] Windows: - [ ] Linux: CI. - [ ] Web: ## Follow-ups - Each repin PR merged and its plugin tagged (the plugin repos' tags stay the maintainer's). - `fizzyedit/agent` and `fizzyedit/chat` repin to the `sdk-v0.2.21` tarball. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #281.
This PR now carries three changes. #329 and #330 were stacked on it, and they merged into this branch, not into
main. Squash-merging it lands all three onmainas one commit:viewports.Picture, below: one way to draw an OS window's picture.fizzy.addDvui, a dvui app of its own on fizzy's backend or on dvui's. It also fixes a copy pass left open while the main window took its drawable, and the menu-bar exports a dvui app didn't link.main(#324, #325) is merged in. #324 landed squashed, and its two files here are the same plusPicture,Picture.againandsizedTarget. After the merge,zig build,test(514/515),test-integration(358/358),check-web,test-sdk-versionand the Windows cross-build pass on macOS.viewports.Pictureis the one way to draw an OS window's picture. Begin it on a target at the part of the frame the window shows, draw subwindows' drawing into it (taken out of the frame, or copied), end it, thenpresent. Popout drew its four kinds of window (float, menu or dialog, carry, overlay) with four near-copies of those steps. The windowing plan's §3, "One way to fill a window", asks for one. It is also what a plain dvui app on this backend needs to put advui.floatingWindowinto an OS window, which is what example-app'srun-dvuibuilds on next.Changes
backend/src/viewports.zigadds:Picture.begin(target, part)clears the target and makes it dvui's render target, offset topartof the frame.Picture.subwindow(sw, take)replays a subwindow's commands into it. Withtake, it also empties them, so dvui's replay into the main window draws none of it.Picture.moveTo(at)draws the same layer again from another part of the frame (the overlay draws carried things at each window's part).Picture.end()goes back to the frame's own target.sizedTarget(slot, w, h)keeps a target the size asked for, or makes a new one.src/editor/Popout.zig: the four sites use them, with the same order of drawing and the same take-or-copy choices. The float window keeps its ownkeepTarget, which holds a bigger texture through a full-screen transition.backend/src/viewports_none.zig(backend: the viewports API is the backend package's, with a stand-in #324's stand-in): no-opPictureandsizedTarget, so it keeps every nameviewportshas. The web, the integration tests and dvui's SDL3 backend analyse Popout against it.Verification
zig build,test,test-integration,check-web,test-sdk-version, the backend's tests, and the Windows and Linux cross-builds pass on macOS:test514/515 (1 skipped),test-integration358/358, backend 46/46.tests/tapes/soak.zon: floats out into OS windows three times, full screen, dialogs) ends in a passing verdict, with 1,426 viewport presents, OS windows back to one at the end, no leaks, and nothing logged as an error.🤖 Generated with Claude Code