Skip to content

Commit cf9cf56

Browse files
jkuriclaude
andcommitted
fix(editor): hide screen shadow during transitions and disable cursor/zoom tabs without cursor data
Screen shadow now appears only when the screen is fully visible — hidden during entry/exit transitions in both preview and export. Also hides shadow when camera is fullscreen. Cursor and zoom tabs are disabled when no cursor metadata is available (e.g. device recordings). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 92ceab4 commit cf9cf56

File tree

4 files changed

+23
-8
lines changed

4 files changed

+23
-8
lines changed

Reframed/Compositor/CameraVideoCompositor+Screen.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ extension CameraVideoCompositor {
88
videoRect: CGRect,
99
instruction: CompositionInstruction,
1010
compositionTime: CMTime,
11-
outputHeight: Int
11+
outputHeight: Int,
12+
isTransitioning: Bool = false
1213
) {
13-
if instruction.videoShadow > 0 {
14+
if instruction.videoShadow > 0 && !isTransitioning {
1415
let blur = min(videoRect.width, videoRect.height) * instruction.videoShadow / 2000.0
1516
context.saveGState()
1617
context.setShadow(

Reframed/Compositor/CameraVideoCompositor.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ final class CameraVideoCompositor: NSObject, AVVideoCompositing, @unchecked Send
121121
height: CGFloat(height) - 2 * instruction.paddingV
122122
)
123123

124+
let isCamFullscreen: Bool = {
125+
let hidden = instruction.cameraHiddenRegions.contains { $0.timeRange.containsTime(compositionTime) }
126+
let fs = instruction.cameraFullscreenRegions.contains { $0.timeRange.containsTime(compositionTime) }
127+
return !hidden && fs
128+
}()
129+
130+
let camFsTransitioning: Bool = {
131+
guard isCamFullscreen else { return false }
132+
guard let r = instruction.cameraFullscreenRegions.first(where: { $0.timeRange.containsTime(compositionTime) }) else {
133+
return false
134+
}
135+
return resolveActiveTransitionType(compositionTime: compositionTime, region: r) != .none
136+
}()
137+
124138
let screenTransition: (type: RegionTransitionType, progress: CGFloat)? = {
125139
guard !instruction.videoRegions.isEmpty else { return nil }
126140
guard let region = instruction.videoRegions.first(where: { $0.timeRange.containsTime(compositionTime) }) else {
@@ -162,7 +176,8 @@ final class CameraVideoCompositor: NSObject, AVVideoCompositing, @unchecked Send
162176
videoRect: videoRect,
163177
instruction: instruction,
164178
compositionTime: compositionTime,
165-
outputHeight: height
179+
outputHeight: height,
180+
isTransitioning: screenTransition != nil || (isCamFullscreen && !camFsTransitioning)
166181
)
167182
}
168183

@@ -199,7 +214,6 @@ final class CameraVideoCompositor: NSObject, AVVideoCompositing, @unchecked Send
199214
let fsRegion = instruction.cameraFullscreenRegions.first {
200215
$0.timeRange.containsTime(compositionTime)
201216
}
202-
let isCamFullscreen = hiddenRegion == nil && fsRegion != nil
203217

204218
let regionTransition: (type: RegionTransitionType, progress: CGFloat)? = {
205219
if let ht = hiddenTransition, ht.type != .none { return ht }

Reframed/Editor/EditorView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ struct EditorView: View {
180180
let disabled =
181181
(tab == .camera && !editorState.hasWebcam)
182182
|| (tab == .audio && !editorState.hasSystemAudio && !editorState.hasMicAudio)
183+
|| (tab == .cursor && editorState.cursorMetadataProvider == nil)
184+
|| (tab == .zoom && editorState.cursorMetadataProvider == nil)
183185
|| (tab == .captions && !editorState.hasMicAudio && !editorState.hasSystemAudio)
184186
Button {
185187
selectedTab = tab

Reframed/Editor/VideoPreviewContainer+Layout.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,21 @@ extension VideoPreviewContainer {
6868
screenShadowLayer.opacity = 0
6969
} else if screenTransitionType != .none {
7070
let p = Float(screenTransitionProgress)
71+
screenShadowLayer.opacity = 0
7172
switch screenTransitionType {
7273
case .none:
7374
screenContainerLayer.opacity = 1
7475
screenContainerLayer.transform = CATransform3DIdentity
75-
screenShadowLayer.opacity = currentVideoShadow > 0 ? 0.6 : 0
7676
case .fade:
7777
screenContainerLayer.opacity = p
7878
screenContainerLayer.transform = CATransform3DIdentity
79-
screenShadowLayer.opacity = currentVideoShadow > 0 ? p * 0.6 : 0
8079
case .scale:
8180
screenContainerLayer.opacity = 1
8281
screenContainerLayer.transform = CATransform3DMakeScale(CGFloat(p), CGFloat(p), 1)
83-
screenShadowLayer.opacity = currentVideoShadow > 0 ? p * 0.6 : 0
8482
case .slide:
8583
screenContainerLayer.opacity = 1
8684
let offsetY = (1.0 - CGFloat(p)) * (screenRect.origin.y + screenRect.height)
8785
screenContainerLayer.transform = CATransform3DMakeTranslation(0, -offsetY, 0)
88-
screenShadowLayer.opacity = currentVideoShadow > 0 ? p * 0.6 : 0
8986
}
9087
} else {
9188
screenContainerLayer.opacity = 1
@@ -206,6 +203,7 @@ extension VideoPreviewContainer {
206203
webcamWrapper.layer?.shadowOpacity = 0
207204
let fsTransitioning = hasActiveTransition
208205
screenContainerLayer.isHidden = !fsTransitioning
206+
screenShadowLayer.isHidden = !fsTransitioning
209207
cursorOverlay.isHidden = !fsTransitioning
210208
webcamWrapper.frame = canvasRect
211209
webcamView.frame = webcamWrapper.bounds

0 commit comments

Comments
 (0)