From 1551e409c31992437cb2fa864f576d45c8433801 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 16 Aug 2026 15:49:12 -0300 Subject: big slow change: prebuilt shaders (SPIR-V/Metal), core gui reflow, docs, web + snapshot refresh --- src/macos/Sources/AppDelegate.swift | 57 ++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 17 deletions(-) (limited to 'src/macos/Sources/AppDelegate.swift') diff --git a/src/macos/Sources/AppDelegate.swift b/src/macos/Sources/AppDelegate.swift index 077367a4..0cecd5da 100644 --- a/src/macos/Sources/AppDelegate.swift +++ b/src/macos/Sources/AppDelegate.swift @@ -18,10 +18,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { /// The blur behind the grid, shown only while the theme declares no /// background of its own. See applyTheme. private var backdrop: NSVisualEffectView! - // One pump chain at a time. pump() re-arms itself while a theme transition - // is in flight and every input pumps as well, so without this a burst of - // keys during a fade would leave one 60 Hz chain per keystroke, all of them - // ticking until the fade ended. + // One display-clock chain at a time. Input and pty notifications may all + // call pump(), but none advances animation; they only observe this flag and + // leave the already-scheduled frame alone. private var pumpScheduled: Bool = false // The core is a singleton with no "is it alive" query, and Finder can hand // us documents before applicationDidFinishLaunching runs. Every entry point @@ -48,9 +47,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { installMainMenu() - // ponytail: 14pt, fixed. The SDL shell steps its font on Ctrl+/Ctrl- - // (gui.zig); doing that here means re-measuring the view's metrics and - // pushing a resize behind it, so it waits until the font has to move. + // Initial body size. PardesView owns later Ctrl+/Ctrl- changes and + // rebuilds both body and tagline metrics before publishing the resize. view = PardesView(fontSize: defaultFontSize) let want = NSSize(width: 1000, height: 700) @@ -199,6 +197,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { // core compares the effective pixel viewport, not the resize event, so // duplicate SIGWINCH-shaped notifications are already a no-op. pardesViewDidResize(view) + // That pump also consumes a Font from startup config, if present. Only + // now is the face on screen final enough to seed Config's effective + // name and point size; later adopts and zooms report from wear(). + view.reportEffectiveFont() // Positional paths, after the first frame's worth of state exists. // Anything starting with `-` is skipped rather than opened: the core @@ -534,6 +536,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { // read bytes. _ = pardes_tick() view.needsDisplay = true + // TaglineSize is host-owned raster state just like Font, but unlike a + // body face change it does not resize the grid. Polling the one-byte + // core setting keeps startup config and live commands on one path. + view.adoptTaglinePercent(pardes_gui_tagline_font_percent()) // After the tick, because a `Theme` command runs inside it and the // window has to follow the grid in the same frame rather than at the // next launch. @@ -571,19 +577,36 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate { NSApp.terminate(nil) return } - // A theme transition is the only thing that moves on its own, and it is - // ten 16 ms steps (animation.zig). Nothing else re-arms this, which is - // the point of an event-driven host: an idle pardes costs no CPU, where - // the SDL shell spins. - if pardes_animating() && !pumpScheduled { - pumpScheduled = true - DispatchQueue.main.asyncAfter(deadline: .now() + 0.016) { - self.pumpScheduled = false - self.pump() - } + // Input and pty pumps only drain work. A frame-count animation advances + // on this 16 ms clock, and only after the sample dirtied above was + // actually presented. A burst of input cannot make it run fast, while + // an occluded/coalesced AppKit view cannot consume invisible samples. + scheduleAnimationFrame() + } + + private func scheduleAnimationFrame() { + guard pardes_animating() && !pumpScheduled else { return } + let awaitingPresentation = view.presentationSerial + pumpScheduled = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.016) { + self.animationFrame(after: awaitingPresentation) } } + private func animationFrame(after awaitingPresentation: UInt64) { + pumpScheduled = false + guard view.presentationSerial != awaitingPresentation else { + // Keep the dirty sample pending. AppKit may have coalesced this + // draw or the window may be occluded; retry the clock without + // advancing until draw(_:) supplies the presentation permit. + view.needsDisplay = true + scheduleAnimationFrame() + return + } + _ = pardes_animation_tick() + pump() + } + func pardesViewDidResize(_ view: PardesView) { let grid = view.gridSize // cell_w/cell_h are physical pixels. The core hands them to the native -- cgit v1.3