import AppKit @_spi(Fixtures) import CallsKit import SwiftUI /// The full-window call's control bar at every window size (2026-10-03: /// Paul's 0.4.6 window showed it off the left edge, mute and camera cut /// off). A real, titled, resizable window as the app's — content to the top, /// the app's 760 × 540 minimum on its content — taken through a run of /// sizes by resizing the one window live, as a person dragging its edge /// does, and set below the minimum the way only the system can (tiling, /// a display change). At each size the bar must be whole inside the /// window, centred on it, and its buttons inside the bar. A snapshot of /// each goes to the folder. @MainActor enum BarLayoutHarness { struct Step { var name: String var size: CGSize /// Captured as soon as the resize has laid out, not after it settles. var quick = false /// Resized as the system's own animation does (zoom, tiling). var animate = false } static let steps: [Step] = [ Step(name: "1000x760", size: CGSize(width: 1000, height: 760)), // Paul's screenshot was 1718 px wide: 859 pt on a Retina display. Step(name: "859x700", size: CGSize(width: 859, height: 700)), Step(name: "760x540", size: CGSize(width: 760, height: 540)), Step(name: "1718x1000", size: CGSize(width: 1718, height: 1000)), Step(name: "1718x1000-to-800-quick", size: CGSize(width: 800, height: 600), quick: true), Step(name: "800x600", size: CGSize(width: 800, height: 600)), // Below the minimum: a window the system made narrower than the app // asked for. Step(name: "640x480-forced", size: CGSize(width: 640, height: 480)), Step(name: "480x400-forced", size: CGSize(width: 480, height: 400)), Step(name: "360x400-forced", size: CGSize(width: 360, height: 400)), Step(name: "1200x800-back", size: CGSize(width: 1200, height: 800)), // Half of a 1728-pt display, as window tiling makes it, animated. Step(name: "859x1080-tiled", size: CGSize(width: 859, height: 1080), animate: true), Step(name: "1718x1080-zoomed", size: CGSize(width: 1718, height: 1080), animate: true), Step(name: "859x1080-tiled-quick", size: CGSize(width: 859, height: 1080), quick: true, animate: true), ] static func run(to folder: URL) async -> Bool { NSApplication.shared.setActivationPolicy(.prohibited) try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) var failures: [String] = [] // The last two: no 760 × 540 minimum, as a window the system has made // narrower than the app asked for — the widths only ⋯ can serve. let variants: [(CallFixture, Bool, Bool, Bool)] = [ (.alone, false, false, true), (.alone, true, false, true), (.alone, false, true, true), (.grid, false, false, true), (.grid, true, false, true), (.grid, false, true, true), (.alone, false, false, false), (.grid, false, true, false), ] for (fixture, chat, devices, minimum) in variants { do { let call = Call(playAudio: false) fixture.apply(to: call) let content = MacCallView( call: call, grid: false, showingChat: chat, showingDevices: devices, style: .fullWindow, controlsShown: true, leave: {}, rejoin: {} ) .tint(CallsAppearance.accent) // As the app's window has it (CallsMac.swift). .frame(minWidth: minimum ? 760 : 0, minHeight: minimum ? 540 : 0) let view = NSHostingView(rootView: content.environment(\.colorScheme, .dark)) let window = NSWindow( contentRect: NSRect(origin: CGPoint(x: 40, y: 40), size: steps[0].size), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) window.isReleasedWhenClosed = false window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.desktopWindow)) - 1) window.appearance = NSAppearance(named: .darkAqua) window.contentView = view if !minimum { window.contentMinSize = CGSize(width: 200, height: 200) } window.orderBack(nil) try? await Task.sleep(for: .milliseconds(700)) let variant = "\(fixture.rawValue)\(chat ? "-chat" : "")\(devices ? "-devices" : "")\(minimum ? "" : "-nominimum")" for step in steps { window.setFrame( NSRect(origin: window.frame.origin, size: step.size), display: true, animate: step.animate) view.layoutSubtreeIfNeeded() try? await Task.sleep(for: .milliseconds(step.quick ? 30 : 500)) let width = view.bounds.width let pane = CallBarProbe.pane let row = CallBarProbe.row let name = "\(variant)-\(step.name)" var problems: [String] = [] if pane.minX < -0.5 || pane.maxX > width + 0.5 { problems.append("bar \(fmt(pane)) leaves the window (width \(fmt(width)))") } if row.minX < pane.minX - 0.5 || row.maxX > pane.maxX + 0.5 { problems.append("buttons \(fmt(row)) spill out of the bar \(fmt(pane))") } let off = abs(pane.midX - width / 2) if off > 1 { problems.append("bar off centre by \(fmt(off)) (bar \(fmt(pane)), window \(fmt(width)))") } if pane.width < 1 { problems.append("bar not laid out") } let line = "\(name): window \(fmt(width)) bar \(fmt(pane)) buttons \(fmt(row))" print(problems.isEmpty ? "ok \(line)" : "FAIL \(line): \(problems.joined(separator: "; "))") failures += problems.map { "\(name): \($0)" } let file = folder.appendingPathComponent("bar-\(name).png") capture(window, to: file) // What was drawn, not only what was laid out: the red of Leave // in the picture must be where the layout put Leave, and whole. if let red = leaveRed(in: file, windowWidth: window.frame.width) { let expected = CallBarProbe.last if abs(red.midX - expected.midX) > 3 || red.width < expected.width - 4 { let problem = "Leave drawn at \(fmt(red)), laid out at \(fmt(expected))" print("FAIL \(name): \(problem)") failures.append("\(name): \(problem)") } } else { print("FAIL \(name): no Leave in the picture") failures.append("\(name): no Leave in the picture") } } window.close() } } print(failures.isEmpty ? "bar layout: all sizes ok" : "bar layout: \(failures.count) failures") return failures.isEmpty } private static func fmt(_ value: CGFloat) -> String { String(format: "%.1f", value) } private static func fmt(_ rect: CGRect) -> String { "\(fmt(rect.minX))…\(fmt(rect.maxX)) (\(fmt(rect.width)))" } /// The horizontal extent, in points, of Leave's red in a capture of a /// window `windowWidth` points wide. private static func leaveRed(in file: URL, windowWidth: CGFloat) -> CGRect? { guard let image = NSImage(contentsOf: file), let cg = image.cgImage(forProposedRect: nil, context: nil, hints: nil), let data = cg.dataProvider?.data, let bytes = CFDataGetBytePtr(data) else { return nil } let width = cg.width let height = cg.height let row = cg.bytesPerRow let step = cg.bitsPerPixel / 8 let scale = CGFloat(width) / windowWidth let bgr = cg.bitmapInfo.contains(.byteOrder32Little) var minX = Int.max var maxX = Int.min // The bottom 140 points: the bar and its margin. let top = max(0, height - Int(140 * scale)) for y in top.. 170, g < 90, b < 90 { minX = min(minX, x) maxX = max(maxX, x) } } } guard minX <= maxX else { return nil } return CGRect(x: CGFloat(minX) / scale, y: 0, width: CGFloat(maxX - minX + 1) / scale, height: 1) } private static func capture(_ window: NSWindow, to file: URL) { let capture = Process() capture.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture") capture.arguments = ["-x", "-o", "-l", String(window.windowNumber), file.path] try? capture.run() capture.waitUntilExit() } }