The clear title bar is said in SwiftUI — a configuration pass stops painting the bar back over the board's picture
`titlebarAppearsTransparent` is not the app's to hold on a scene window: it is the AppKit face of the tree's resolved `toolbarBackgroundVisibility`, and SwiftUI writes it on every pass that re-applies a window's configuration. The board stated nothing, so `.automatic` resolved and each pass put `false` back over what `HostedWindowController` had set out of band — correct on open, because the walk lands after the pass that follows window creation, and opaque at the next one, with nothing to re-assert it since the board's own reading had not changed. The board now declares the posture where SwiftUI will keep asserting it; the AppKit write stays as the same value one turn earlier. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import AppKit
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Testing
|
||||
@testable import Kanban
|
||||
|
||||
@@ -441,3 +443,90 @@ struct BackgroundImagePathTests {
|
||||
#expect(BoardBackdrop.isCustom(try board("background: {color: mauve, image: /tmp/x.jpg}"), root: root) == false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - The window chrome that follows the background
|
||||
|
||||
/// **A board that paints a background runs its content under a clear title bar** (03-board-ui.md §
|
||||
/// Styling ▸ Capabilities), and that posture is said in two places on purpose.
|
||||
///
|
||||
/// `NSWindow.titlebarAppearsTransparent` belongs to SwiftUI on a scene window — it is the AppKit
|
||||
/// face of `toolbarBackgroundVisibility(for: .windowToolbar)`, and SwiftUI writes the value its tree
|
||||
/// resolves on every pass in which it re-applies a window's configuration. A tree that said nothing
|
||||
/// resolved `.automatic`, so those passes wrote `false` back over the flag `HostedWindowController`
|
||||
/// had set out of band: the board opened correct and its title bar turned opaque some time later,
|
||||
/// painting its own material and its separator hairline across a picture still being drawn full
|
||||
/// height underneath. So `BoardWindowHost` states it in SwiftUI as well, and the two tests here pin
|
||||
/// the two halves — the rule SwiftUI is told, and the window the controller writes.
|
||||
@MainActor
|
||||
@Suite("Board background ▸ the window chrome follows it")
|
||||
struct BoardChromeTests {
|
||||
|
||||
@Test("The toolbar background is gone over a board that paints one, and the system's otherwise")
|
||||
func toolbarBackgroundFollowsTheBoard() {
|
||||
#expect(BoardWindowHost.toolbarBackground(overCustomBackground: true) == .hidden)
|
||||
// `.automatic`, never `.visible` — a board with no background of its own keeps whatever the
|
||||
// system would have drawn, which is what "the standard chrome, untouched" means.
|
||||
#expect(BoardWindowHost.toolbarBackground(overCustomBackground: false) == .automatic)
|
||||
}
|
||||
|
||||
@Test("The controller says nothing about a window's title bar until it is told")
|
||||
func silentUntilTold() {
|
||||
let window = Self.window()
|
||||
let controller = HostedWindowController()
|
||||
controller.attach(to: window)
|
||||
|
||||
// `nil` and `false` render identically; they differ only in whether this controller has
|
||||
// spoken, which is what keeps the loading half from stating a default it does not own.
|
||||
#expect(window.titlebarAppearsTransparent == false)
|
||||
#expect(window.styleMask.contains(.fullSizeContentView) == false)
|
||||
}
|
||||
|
||||
@Test("Both knobs move together, in both directions, however often they are set")
|
||||
func bothKnobsMoveTogether() {
|
||||
let window = Self.window()
|
||||
let controller = HostedWindowController()
|
||||
controller.attach(to: window)
|
||||
|
||||
controller.setExtendsContentUnderTitlebar(true)
|
||||
#expect(window.titlebarAppearsTransparent)
|
||||
#expect(window.styleMask.contains(.fullSizeContentView))
|
||||
|
||||
// Repeat-safe: the board drives this off its snapshot, so it is called on every reload that
|
||||
// changes the reading and on plenty that do not.
|
||||
controller.setExtendsContentUnderTitlebar(true)
|
||||
#expect(window.titlebarAppearsTransparent)
|
||||
#expect(window.styleMask.contains(.fullSizeContentView))
|
||||
|
||||
// And back — a `background:` deleted on disk puts the standard chrome back.
|
||||
controller.setExtendsContentUnderTitlebar(false)
|
||||
#expect(window.titlebarAppearsTransparent == false)
|
||||
#expect(window.styleMask.contains(.fullSizeContentView) == false)
|
||||
}
|
||||
|
||||
@Test("The posture survives the provisional-window swap")
|
||||
func theSlotReappliesToTheNextWindow() {
|
||||
// SwiftUI dismantles and re-makes the background representable while it moves a scene's
|
||||
// content into its final window, so a posture stated against the first window has to reach
|
||||
// the second (`HostedWindowController.detach`).
|
||||
let provisional = Self.window()
|
||||
let controller = HostedWindowController()
|
||||
controller.attach(to: provisional)
|
||||
controller.setExtendsContentUnderTitlebar(true)
|
||||
|
||||
controller.detach()
|
||||
let real = Self.window()
|
||||
controller.attach(to: real)
|
||||
|
||||
#expect(real.titlebarAppearsTransparent)
|
||||
#expect(real.styleMask.contains(.fullSizeContentView))
|
||||
}
|
||||
|
||||
private static func window() -> NSWindow {
|
||||
NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
|
||||
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
||||
backing: .buffered,
|
||||
defer: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user