| 1 | import AppKit |
| 2 | @_spi(Fixtures) import CallsKit |
| 3 | import SwiftUI |
| 4 | |
| 5 | /// The stage and every tile's placeholder (2026-10-05), drawn off screen |
| 6 | /// (no glass and no Metal video, as the other offline fixtures), with a busy frame as the picture on the stage's |
| 7 | /// tile so the filmstrip, the names and the bar show over a picture: |
| 8 | /// Speaker view with a camera on the stage, with a camera that is off on |
| 9 | /// it, and with a shared screen on it, controls shown and hidden, at the |
| 10 | /// default window and the smallest; Grid; and the mini window at 200, 320 |
| 11 | /// and 480 with someone whose camera is off, and its tab. |
| 12 | @MainActor enum StageSnapshots { |
| 13 | static func save(to folder: URL) async throws { |
| 14 | NSApplication.shared.setActivationPolicy(.prohibited) |
| 15 | try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) |
| 16 | func shot( |
| 17 | _ name: String, _ fixture: CallFixture = .grid, grid: Bool = false, shown: Bool, |
| 18 | size: CGSize = CGSize(width: 1000, height: 640), speaker: String? |
| 19 | ) async throws { |
| 20 | let call = Call(playAudio: false) |
| 21 | fixture.apply(to: call) |
| 22 | call.levels.speaker.showFixture(speaker) |
| 23 | try await draw( |
| 24 | MacCallView( |
| 25 | call: call, grid: grid, style: .fullWindow, controlsShown: shown, leave: {}, rejoin: {} |
| 26 | ) |
| 27 | .tint(CallsAppearance.accent) |
| 28 | .environment(\.fixtureStagePicture, .busy), |
| 29 | size: size, to: folder.appendingPathComponent("mac-\(name).png")) |
| 30 | } |
| 31 | for shown in [true, false] { |
| 32 | let state = shown ? "controls" : "hidden" |
| 33 | try await shot("stage-camera-\(state)", shown: shown, speaker: "ana") |
| 34 | try await shot("stage-camera-off-\(state)", shown: shown, speaker: "tom") |
| 35 | try await shot("stage-screen-\(state)", .screen, shown: shown, speaker: "ana") |
| 36 | try await shot( |
| 37 | "stage-camera-760x540-\(state)", shown: shown, size: CGSize(width: 760, height: 540), |
| 38 | speaker: "ana") |
| 39 | try await shot( |
| 40 | "stage-screen-900x800-\(state)", .screen, shown: shown, size: CGSize(width: 900, height: 800), |
| 41 | speaker: "ana") |
| 42 | try await shot("grid-\(state)", grid: true, shown: shown, speaker: "ana") |
| 43 | } |
| 44 | for width in [MiniWindowRules.minimumWidth, MiniWindowRules.size.width, 480] { |
| 45 | for (who, label) in [("tom", "camera-off"), (nil, "self")] as [(String?, String)] { |
| 46 | let call = Call(playAudio: false) |
| 47 | CallFixture.grid.apply(to: call) |
| 48 | call.levels.speaker.showFixture(who) |
| 49 | if who == nil { call.showFixtureSelf(muted: false, cameraOff: true, sharingScreen: false) } |
| 50 | try await draw( |
| 51 | MiniWindowSnapshot.window(call: call, hovering: false), |
| 52 | size: CGSize(width: width, height: width / MiniWindowRules.aspect), |
| 53 | to: folder.appendingPathComponent("mini-\(Int(width))-\(label).png")) |
| 54 | } |
| 55 | } |
| 56 | let call = Call(playAudio: false) |
| 57 | CallFixture.grid.apply(to: call) |
| 58 | try await draw( |
| 59 | MiniWindowSnapshot.tab(call: call), size: MiniWindowTab.size, |
| 60 | to: folder.appendingPathComponent("mini-tab.png")) |
| 61 | } |
| 62 | |
| 63 | private static func draw(_ content: some View, size: CGSize, to file: URL) async throws { |
| 64 | try await LobbySnapshots.render(content, scheme: .dark, size: size, to: file) |
| 65 | } |
| 66 | } |
| 67 | |