The board wears a picture — background becomes a mapping, and the window chrome follows it under a thin frost

background is {color:, image:} and only a mapping at every level; the board's image paints the full window under a transparent title bar, with a thin-material frost strip keeping the chrome legible and the standard accommodations intact.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-07 10:15:03 -04:00
parent 190a8e36f1
commit d5ad21c3da
37 changed files with 1182 additions and 76 deletions
+52
View File
@@ -109,6 +109,24 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
/// what the chrome draws from it.
private var titleVisibility: NSWindow.TitleVisibility?
/// Whether this window's content runs the full height of the frame, under a transparent title
/// bar **a board window carrying a custom background**, and nothing else (03-board-ui.md §
/// Styling Capabilities: the board's colour or image "paints the full window"; `BoardView
/// .boardBackground` draws the frosted strip that keeps the chrome legible over it).
///
/// `nil` leaves AppKit's own posture untouched, exactly as `titleVisibility` does the welcome,
/// bootstrap and card windows have no opinion, and neither does a board window while it loads
/// (the flag is driven off the snapshot, which does not exist yet). `nil` and `false` therefore
/// render identically; they differ only in whether this controller has *said* anything, which is
/// what keeps the loading half from having to state a default it does not own.
///
/// A slot rather than a one-shot write, and **repeat-safe rather than install-once** the
/// `hideTitle` pattern, for a stronger version of its reason: the value has to survive the
/// provisional-window swap (`detach()`), *and* it genuinely changes over a window's life. A
/// `background:` edited on disk reloads the snapshot, and the chrome follows it in both
/// directions.
private var extendsUnderTitlebar: Bool?
private static let logger = Logger(subsystem: "dev.rzen.indie.Kanban", category: "window")
// MARK: Attachment
@@ -129,6 +147,7 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
addTitlebarAccessoryIfPossible()
applyToolbarIfPossible()
applyTitleVisibilityIfPossible()
applyTitlebarExtensionIfPossible()
}
/// Puts the previous delegate back and takes the titlebar accessory and toolbar off the window
@@ -233,6 +252,39 @@ final class HostedWindowController: NSObject, NSWindowDelegate {
window.titleVisibility = titleVisibility
}
// MARK: Content under the title bar
/// Runs this window's content the full height of its frame, under a transparent title bar or
/// puts the standard chrome back (see `extendsUnderTitlebar`).
///
/// Safe whenever the caller learns the answer before the window exists (held, applied at
/// `attach`) or after (applied now) and safe to call repeatedly with the same value, which
/// matters more here than for `hideTitle`: the board window drives this off its snapshot, so it
/// is called on every reload that changes the reading and on plenty that do not.
///
/// **Not undone at `detach`**, `titleVisibility`'s posture: the slot survives the provisional-
/// window swap and reapplies itself to whichever window attaches next, and a window that is
/// genuinely going away takes its chrome with it.
func setExtendsContentUnderTitlebar(_ flag: Bool) {
extendsUnderTitlebar = flag
applyTitlebarExtensionIfPossible()
}
/// The two AppKit knobs the effect needs, and they are one decision: `fullSizeContentView` is
/// what lets the content view reach under the title bar, and `titlebarAppearsTransparent` is
/// what stops the title bar from painting its own material over it. Either alone is a visible
/// half-state an opaque bar over the board, or a board that stops at a bar that no longer
/// draws.
private func applyTitlebarExtensionIfPossible() {
guard let window, let extendsUnderTitlebar else { return }
window.titlebarAppearsTransparent = extendsUnderTitlebar
if extendsUnderTitlebar {
window.styleMask.insert(.fullSizeContentView)
} else {
window.styleMask.remove(.fullSizeContentView)
}
}
/// Closes the window for real, after the flush has run. `performClose` rather than `close` so the
/// standard path runs SwiftUI's own delegate gets its callbacks, tabbing behaves with the
/// flag telling our own `windowShouldClose` to stand aside.