Mac call: grid's last-row names clear the bar; a top-left self view clears the title
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+37
−7
native/apple/Sources/CallsInterop/CallSnapshots.swift+7 −0native/apple/Sources/CallsKit/MacCallView.swift+3 −0native/apple/Sources/CallsKit/MacFullWindowCall.swift+27 −7
CI passed — run details
CI log
=== vet === go vet ./... === fmt === === test === go test ./... ok amadan.net/oficina/calls/callscore 6.680s ? amadan.net/oficina/calls/cmd/calls [no test files] ok amadan.net/oficina/calls/internal/calls 1.451s ok amadan.net/oficina/calls/internal/calls/apns 0.007s ok amadan.net/oficina/calls/internal/calls/hub 0.303s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 0.003s ok amadan.net/oficina/calls/internal/calls/seal 0.002s ok amadan.net/oficina/calls/internal/callstest 8.515s ? amadan.net/oficina/calls/protocol [no test files] === migration-check === go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check rastrillo migration check: models and migrations agree === race === CGO_ENABLED=1 go test -race ./internal/... ok amadan.net/oficina/calls/internal/calls 8.816s ok amadan.net/oficina/calls/internal/calls/apns 1.021s ok amadan.net/oficina/calls/internal/calls/hub 3.264s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 1.018s ok amadan.net/oficina/calls/internal/calls/seal 1.013s ok amadan.net/oficina/calls/internal/callstest 24.660s
diff --git a/native/apple/Sources/CallsInterop/CallSnapshots.swift b/native/apple/Sources/CallsInterop/CallSnapshots.swift| index add0cb4..2d1336a 100644 |
| --- a/native/apple/Sources/CallsInterop/CallSnapshots.swift |
| +++ b/native/apple/Sources/CallsInterop/CallSnapshots.swift |
| @@ -41,6 +41,13 @@ import SwiftUI |
| try await shot("notices-hidden", .notices, shown: false) |
| try await shot("classic-focus", .focus, style: .classic) |
| try await shot("classic-grid", .grid, grid: true, style: .classic) |
| + // The self view in the corner the title pill shares: it sits below |
| + // the pill while the controls show. |
| + let corner = UserDefaults.standard.string(forKey: PiPCorner.key) |
| + UserDefaults.standard.set(PiPCorner.topLeading.rawValue, forKey: PiPCorner.key) |
| + try await shot("pip-top-leading-controls", .focus) |
| + try await shot("pip-top-leading-hidden", .focus, shown: false) |
| + UserDefaults.standard.set(corner, forKey: PiPCorner.key) |
| // The overlay over the worst pictures there are for it, in each of |
| // your own states: it should look the same in light and dark. |
| for backdrop in FixtureBackdrop.allCases { |
diff --git a/native/apple/Sources/CallsKit/MacCallView.swift b/native/apple/Sources/CallsKit/MacCallView.swift| index c153497..2a1c893 100644 |
| --- a/native/apple/Sources/CallsKit/MacCallView.swift |
| +++ b/native/apple/Sources/CallsKit/MacCallView.swift |
| @@ -18,6 +18,9 @@ |
| @AppStorage(CallLayoutStyle.key) private var layoutChoice = CallLayoutStyle.defaultStyle.rawValue |
| @AppStorage(PiPCorner.key) var pipCorner = PiPCorner.bottomTrailing.rawValue |
| @State var pipDrag: CGSize = .zero |
| + /// The title pill's height, so a self view in the top-left corner |
| + /// sits below it rather than under it. |
| + @State var titleHeight: CGFloat = 0 |
| @State var menuTracking = false |
| @State var sheetShowing = false |
| @State var dismissedNotices: Set<String> = [] |
diff --git a/native/apple/Sources/CallsKit/MacFullWindowCall.swift b/native/apple/Sources/CallsKit/MacFullWindowCall.swift| index e6b3895..fdd5a54 100644 |
| --- a/native/apple/Sources/CallsKit/MacFullWindowCall.swift |
| +++ b/native/apple/Sources/CallsKit/MacFullWindowCall.swift |
| @@ -181,30 +181,40 @@ |
| let across = |
| fitted.width >= 200 |
| ? columns : max(1, Int((area.width + 4) / (tile.width + 4))) |
| + let rows = (count + across - 1) / across |
| + let gridHeight = CGFloat(rows) * tile.height + CGFloat(max(rows - 1, 0)) * 4 |
| + // The last row's names clear the bar while it shows: lifted by as |
| + // much of the bar as reaches over them (the grid is centred). |
| + let below = max(0, (size.height - 8 - gridHeight) / 2) + 4 |
| + let lastLift = max(0, lift - below) |
| + let firstOfLastRow = (rows - 1) * across |
| ScrollView { |
| LazyVGrid( |
| columns: Array(repeating: GridItem(.fixed(tile.width), spacing: 4), count: across), |
| spacing: 4 |
| ) { |
| - ForEach(call.screens, id: \.self) { peer in |
| + ForEach(Array(call.screens.enumerated()), id: \.element) { index, peer in |
| MacVideoTile( |
| track: call.remoteScreens[peer], label: screenCaption(call.name(of: peer)), |
| - screen: true, cornerRadius: 6, compact: true, backdrop: Self.tileBackdrop |
| + screen: true, cornerRadius: 6, compact: true, |
| + labelLift: index >= firstOfLastRow ? lastLift : 0, backdrop: Self.tileBackdrop |
| ) |
| .frame(width: tile.width, height: tile.height) |
| } |
| MacVideoTile( |
| track: call.localTrack, label: "You", muted: call.muted, mirrored: true, |
| cameraOff: call.cameraOff, placeholder: call.selfPlaceholder, cornerRadius: 6, |
| - compact: true, backdrop: Self.tileBackdrop, audio: call.selfAudio |
| + compact: true, labelLift: call.screens.count >= firstOfLastRow ? lastLift : 0, |
| + backdrop: Self.tileBackdrop, audio: call.selfAudio |
| ) |
| .frame(width: tile.width, height: tile.height) |
| - ForEach(peers) { peer in |
| + ForEach(Array(peers.enumerated()), id: \.element.id) { index, peer in |
| MacVideoTile( |
| track: call.remoteTracks[peer.peer], label: peer.name, |
| muted: call.isMuted(peer.peer), cameraOff: call.isCameraOff(peer.peer), |
| - cornerRadius: 6, compact: true, backdrop: Self.tileBackdrop, |
| - audio: call.tileAudio(peer.peer) |
| + cornerRadius: 6, compact: true, |
| + labelLift: call.screens.count + 1 + index >= firstOfLastRow ? lastLift : 0, |
| + backdrop: Self.tileBackdrop, audio: call.tileAudio(peer.peer) |
| ) |
| .frame(width: tile.width, height: tile.height) |
| } |
| @@ -259,8 +269,14 @@ |
| var trailing: CGFloat = 0 |
| if showingChat { trailing += Self.panelWidth + 12 } |
| if stageShowsScreen, !peers.isEmpty { trailing += strip(in: size).width + 12 } |
| + // The title pill sits beside the traffic lights while the controls |
| + // show; a self view in that corner goes below it. |
| + let corner = PiPCorner(rawValue: pipCorner) ?? .bottomTrailing |
| + let top = |
| + corner == .topLeading && chrome.visible |
| + ? max(Self.titleBarHeight, 6 + titleHeight + 4) : Self.titleBarHeight |
| return EdgeInsets( |
| - top: Self.titleBarHeight, leading: 0, |
| + top: top, leading: 0, |
| bottom: chrome.visible ? Self.barReserve - 12 : 0, trailing: trailing) |
| } |
| @@ -292,6 +308,10 @@ |
| } |
| .padding(.horizontal, 12).padding(.vertical, 7) |
| .callOverlaySurface(RoundedRectangle(cornerRadius: 12, style: .continuous)) |
| + .background(GeometryReader { proxy in |
| + Color.clear.onAppear { titleHeight = proxy.size.height } |
| + .onChange(of: proxy.size.height) { _, height in titleHeight = height } |
| + }) |
| .accessibilityElement(children: .contain) |
| .accessibilityIdentifier("callTitle") |
| .frame(maxWidth: 420, alignment: .leading) |