fix: scope control fades to preserve responsive window animation
This commit is contained in:
@@ -46,10 +46,12 @@ def check_window_layout(root, app, fixtures):
|
||||
)
|
||||
log = diagnostics.read_text(errors="replace") if diagnostics.exists() else ""
|
||||
report = json.loads(result_path.read_text()) if result_path.exists() else {}
|
||||
if result.returncode != 0 or not report.get("passed") or report.get("steps") != 32:
|
||||
expected_animations = 0 if report.get("reduceMotion") else 16
|
||||
if (result.returncode != 0 or not report.get("passed") or report.get("steps") != 32
|
||||
or report.get("animatedTransitions") != expected_animations):
|
||||
raise RuntimeError(f"native window layout failed: {report}; {result.stdout}{result.stderr}{log}")
|
||||
print("PASS native SwiftUI window layout: repeated compact/normal transitions with queue open/closed, "
|
||||
"rapid intent changes, original window and exact normal geometry restored")
|
||||
print(f"PASS native SwiftUI window layout: {report['animatedTransitions']} observed animations, "
|
||||
"responsive mid-animation requests, queue open/closed, minimum sizes and exact normal geometry restored")
|
||||
|
||||
|
||||
def check_cli_instances(root, app, fixtures):
|
||||
|
||||
@@ -23,6 +23,11 @@ private enum WindowLayoutCheck {
|
||||
static var step = 0
|
||||
static var windowIdentity: ObjectIdentifier?
|
||||
static var normalFrame = NSRect.zero
|
||||
static var animationStart: NSRect?
|
||||
static var animationTarget = NSRect.zero
|
||||
static var sawIntermediateFrame = false
|
||||
static var animatedTransitions = 0
|
||||
static var expectsAnimation: Bool { !NSWorkspace.shared.accessibilityDisplayShouldReduceMotion }
|
||||
// Infrastructure timeout only; assertions depend on native transition completion.
|
||||
static let deadline = ContinuousClock.now.advanced(by: .seconds(30))
|
||||
|
||||
@@ -47,17 +52,46 @@ private enum WindowLayoutCheck {
|
||||
|
||||
static func finish(_ error: String? = nil) {
|
||||
timer?.invalidate()
|
||||
let result: [String: Any] = ["passed": error == nil, "error": error ?? "", "steps": step]
|
||||
let result: [String: Any] = ["passed": error == nil, "error": error ?? "", "steps": step,
|
||||
"animatedTransitions": animatedTransitions, "reduceMotion": !expectsAnimation]
|
||||
try! JSONSerialization.data(withJSONObject: result).write(to: resultURL, options: .atomic)
|
||||
PlayerModel.shared.shutdown()
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
|
||||
static func sameFrame(_ lhs: NSRect, _ rhs: NSRect) -> Bool {
|
||||
abs(lhs.minX - rhs.minX) < 1 && abs(lhs.minY - rhs.minY) < 1 &&
|
||||
abs(lhs.width - rhs.width) < 1 && abs(lhs.height - rhs.height) < 1
|
||||
}
|
||||
|
||||
static func observeAnimation(from window: NSWindow, to target: NSRect) {
|
||||
animationStart = window.frame
|
||||
animationTarget = target
|
||||
sawIntermediateFrame = false
|
||||
}
|
||||
|
||||
static func advance() {
|
||||
let model = PlayerModel.shared
|
||||
guard ContinuousClock.now < deadline else { finish("Window layout did not settle at step \(step)"); return }
|
||||
guard model.canTogglePlayback, model.state.has_video, !model.compactWindow.isTransitioning,
|
||||
guard model.canTogglePlayback, model.state.has_video,
|
||||
let window = NSApp.windows.first(where: { $0.canBecomeMain && !($0 is NSPanel) }) else { return }
|
||||
if model.compactWindow.isTransitioning {
|
||||
if expectsAnimation, let start = animationStart, !sawIntermediateFrame,
|
||||
!sameFrame(window.frame, start), !sameFrame(window.frame, animationTarget) {
|
||||
sawIntermediateFrame = true
|
||||
// These run-loop-delivered requests must remain responsive mid-animation.
|
||||
model.toggleCompact()
|
||||
model.toggleCompact()
|
||||
}
|
||||
return
|
||||
}
|
||||
if animationStart != nil {
|
||||
if expectsAnimation {
|
||||
guard sawIntermediateFrame else { finish("No nonblocking animation frames at step \(step)"); return }
|
||||
animatedTransitions += 1
|
||||
}
|
||||
animationStart = nil
|
||||
}
|
||||
if windowIdentity == nil { windowIdentity = ObjectIdentifier(window) }
|
||||
guard ObjectIdentifier(window) == windowIdentity else { finish("Transition replaced the playback window"); return }
|
||||
window.layoutIfNeeded()
|
||||
@@ -74,8 +108,14 @@ private enum WindowLayoutCheck {
|
||||
case 1, 5:
|
||||
window.setFrame(NSRect(x: 100, y: 200, width: 763, height: 368), display: true)
|
||||
normalFrame = window.frame
|
||||
guard window.minSize.width >= (model.showsQueue ? 660 : 400), window.minSize.height >= 240 else {
|
||||
finish("Normal minimum size was lost: \(window.minSize)"); return
|
||||
}
|
||||
case 2, 6:
|
||||
// Repeated intent changes must keep the original restoration frame.
|
||||
observeAnimation(from: window, to: CompactWindowController.initialFrame(
|
||||
aspect: model.state.video_aspect_ratio,
|
||||
visibleFrame: (window.screen ?? NSScreen.main!).visibleFrame, width: 320))
|
||||
model.toggleCompact()
|
||||
model.toggleCompact()
|
||||
model.toggleCompact()
|
||||
@@ -84,6 +124,10 @@ private enum WindowLayoutCheck {
|
||||
abs(window.frame.width / window.frame.height - model.state.video_aspect_ratio) < 0.01 else {
|
||||
finish("Compact geometry did not settle at step \(step): \(window.frame)"); return
|
||||
}
|
||||
guard window.minSize.width >= 160, window.minSize.height >= 90 else {
|
||||
finish("Compact minimum size was lost: \(window.minSize)"); return
|
||||
}
|
||||
observeAnimation(from: window, to: normalFrame)
|
||||
model.toggleCompact()
|
||||
}
|
||||
step += 1
|
||||
|
||||
Reference in New Issue
Block a user