@@ -139,22 +139,27 @@ struct SimulatorStreamView: View {
139139 GeometryReader { proxy in
140140 let layout = DeviceViewportLayout (
141141 chromeProfile: model. chromeProfile,
142- chromeImageSize: model. chromeImage? . size,
143142 videoSize: model. videoSize,
144143 availableSize: proxy. size
145144 )
146145 let displayToken = model. streamDisplayToken
146+ let screenMaskImage = model. chromeProfile? . hasScreenMask == true ? model. chromeScreenMask : nil
147147
148148 ZStack ( alignment: . topLeading) {
149149 streamBackground
150150
151- RoundedRectangle ( cornerRadius : layout . screenCornerRadius + 2 , style : . continuous )
151+ Rectangle ( )
152152 . fill ( . black)
153153 . frame ( width: layout. screenBackingFrame. width, height: layout. screenBackingFrame. height)
154+ . clippedToSimulatorScreen ( cornerRadius: layout. screenCornerRadius + 2 , maskImage: screenMaskImage)
154155 . position ( x: layout. screenBackingFrame. midX, y: layout. screenBackingFrame. midY)
155156
156157 if showsCachedStreamFrame, let lastStreamFrame = model. lastStreamFrame {
157- CachedStreamFrameView ( image: lastStreamFrame, cornerRadius: layout. screenCornerRadius + 1 )
158+ CachedStreamFrameView (
159+ image: lastStreamFrame,
160+ cornerRadius: layout. screenCornerRadius + 1 ,
161+ maskImage: screenMaskImage
162+ )
158163 . frame ( width: layout. videoFrame. width, height: layout. videoFrame. height)
159164 . position ( x: layout. videoFrame. midX, y: layout. videoFrame. midY)
160165 . transition ( . opacity)
@@ -175,7 +180,7 @@ struct SimulatorStreamView: View {
175180 )
176181 . id ( displayToken)
177182 . frame ( width: layout. videoFrame. width, height: layout. videoFrame. height)
178- . clipShape ( RoundedRectangle ( cornerRadius: layout. screenCornerRadius + 1 , style : . continuous ) )
183+ . clippedToSimulatorScreen ( cornerRadius: layout. screenCornerRadius + 1 , maskImage : screenMaskImage )
179184 . position ( x: layout. videoFrame. midX, y: layout. videoFrame. midY)
180185 . opacity ( model. hasCurrentStreamFrame ? 1 : 0 )
181186 }
@@ -199,19 +204,22 @@ struct SimulatorStreamView: View {
199204 if let simulator = model. selectedSimulator, !simulator. isBooted {
200205 BootSimulatorOverlay ( model: model, simulator: simulator)
201206 . frame ( width: layout. screenFrame. width, height: layout. screenFrame. height)
207+ . clippedToSimulatorScreen ( cornerRadius: layout. screenCornerRadius, maskImage: screenMaskImage)
202208 . position ( x: layout. screenFrame. midX, y: layout. screenFrame. midY)
203209 }
204210
205211 if showsFirstFrameSpinner {
206212 StreamFirstFrameLoadingOverlay ( )
207213 . frame ( width: layout. screenFrame. width, height: layout. screenFrame. height)
214+ . clippedToSimulatorScreen ( cornerRadius: layout. screenCornerRadius, maskImage: screenMaskImage)
208215 . position ( x: layout. screenFrame. midX, y: layout. screenFrame. midY)
209216 . transition ( . opacity)
210217 }
211218
212219 if showsRetryOverlay {
213220 StreamRetryOverlay ( model: model)
214221 . frame ( width: layout. screenFrame. width, height: layout. screenFrame. height)
222+ . clippedToSimulatorScreen ( cornerRadius: layout. screenCornerRadius, maskImage: screenMaskImage)
215223 . position ( x: layout. screenFrame. midX, y: layout. screenFrame. midY)
216224 . transition ( . opacity)
217225 }
@@ -350,6 +358,7 @@ private struct StreamFirstFrameLoadingOverlay: View {
350358private struct CachedStreamFrameView : View {
351359 let image : UIImage
352360 let cornerRadius : CGFloat
361+ let maskImage : UIImage ?
353362
354363 var body : some View {
355364 Image ( uiImage: image)
@@ -358,7 +367,7 @@ private struct CachedStreamFrameView: View {
358367 . saturation ( 0.82 )
359368 . brightness ( - 0.08 )
360369 . overlay ( Color . black. opacity ( 0.28 ) )
361- . clipShape ( RoundedRectangle ( cornerRadius: cornerRadius, style : . continuous ) )
370+ . clippedToSimulatorScreen ( cornerRadius: cornerRadius, maskImage : maskImage )
362371 . shadow ( color: . black. opacity ( 0.34 ) , radius: 16 , y: 8 )
363372 . clipped ( )
364373 . allowsHitTesting ( false )
@@ -927,9 +936,8 @@ private struct DeviceViewportLayout {
927936 let screenCornerRadius : CGFloat
928937 let usesChrome : Bool
929938 private let chromeCoordinateScale : CGFloat
930- private let chromeMetricScale : Double
931939
932- init ( chromeProfile: ChromeProfile ? , chromeImageSize : CGSize ? , videoSize: CGSize , availableSize: CGSize ) {
940+ init ( chromeProfile: ChromeProfile ? , videoSize: CGSize , availableSize: CGSize ) {
933941 let viewport = CGRect ( origin: . zero, size: availableSize)
934942 . insetBy ( dx: min ( 20 , availableSize. width * 0.045 ) , dy: 16 )
935943
@@ -941,13 +949,11 @@ private struct DeviceViewportLayout {
941949 viewport. width > 0 ,
942950 viewport. height > 0 {
943951 let profileSize = CGSize ( width: CGFloat ( chromeProfile. totalWidth) , height: CGFloat ( chromeProfile. totalHeight) )
944- let metricScale = Self . chromeMetricScale ( profile: chromeProfile, imageSize: chromeImageSize)
945952 let shell = profileSize. aspectFit ( in: viewport)
946953 let scale = shell. width / profileSize. width
947- let screenRect = Self . chromeScreenRect ( profile: chromeProfile, videoSize: videoSize, metricScale : metricScale )
954+ let screenRect = Self . chromeScreenRect ( profile: chromeProfile, videoSize: videoSize)
948955 shellFrame = shell
949956 chromeCoordinateScale = scale
950- chromeMetricScale = metricScale
951957 screenFrame = CGRect (
952958 x: shell. minX + screenRect. minX * scale,
953959 y: shell. minY + screenRect. minY * scale,
@@ -959,8 +965,7 @@ private struct DeviceViewportLayout {
959965 screenCornerRadius = Self . screenCornerRadius (
960966 profile: chromeProfile,
961967 profileScreenRect: screenRect,
962- scale: scale,
963- metricScale: metricScale
968+ scale: scale
964969 )
965970 usesChrome = true
966971 return
@@ -977,39 +982,23 @@ private struct DeviceViewportLayout {
977982 screenCornerRadius = min ( 44 , screen. width * 0.14 )
978983 usesChrome = false
979984 chromeCoordinateScale = 1
980- chromeMetricScale = 1
981985 }
982986
983987 func chromeButtonFrame( _ button: ChromeButtonProfile ) -> CGRect {
984988 guard usesChrome else { return . zero }
985989 return CGRect (
986- x: shellFrame. minX + CGFloat( button. x / chromeMetricScale ) * chromeCoordinateScale,
987- y: shellFrame. minY + CGFloat( button. y / chromeMetricScale ) * chromeCoordinateScale,
988- width: CGFloat ( button. width / chromeMetricScale ) * chromeCoordinateScale,
989- height: CGFloat ( button. height / chromeMetricScale ) * chromeCoordinateScale
990+ x: shellFrame. minX + CGFloat( button. x) * chromeCoordinateScale,
991+ y: shellFrame. minY + CGFloat( button. y) * chromeCoordinateScale,
992+ width: CGFloat ( button. width) * chromeCoordinateScale,
993+ height: CGFloat ( button. height) * chromeCoordinateScale
990994 )
991995 }
992996
993- private static func chromeMetricScale( profile: ChromeProfile , imageSize: CGSize ? ) -> Double {
994- guard profile. totalWidth > 0 ,
995- profile. totalHeight > 0 ,
996- let imageSize,
997- imageSize. width > 0 ,
998- imageSize. height > 0 ,
999- profile. screenWidth > profile. totalWidth || profile. screenHeight > profile. totalHeight else {
1000- return 1
1001- }
1002- let widthScale = Double ( imageSize. width) / profile. totalWidth
1003- let heightScale = Double ( imageSize. height) / profile. totalHeight
1004- let scale = min ( widthScale, heightScale)
1005- return scale. isFinite && scale > 1 ? scale : 1
1006- }
1007-
1008- private static func chromeScreenRect( profile: ChromeProfile , videoSize: CGSize , metricScale: Double ) -> CGRect {
1009- let profileScreenWidth = profile. screenWidth / metricScale
1010- let profileScreenHeight = profile. screenHeight / metricScale
1011- let profileScreenX = profile. screenX / metricScale
1012- let profileScreenY = profile. screenY / metricScale
997+ private static func chromeScreenRect( profile: ChromeProfile , videoSize: CGSize ) -> CGRect {
998+ let profileScreenWidth = profile. screenWidth
999+ let profileScreenHeight = profile. screenHeight
1000+ let profileScreenX = profile. screenX
1001+ let profileScreenY = profile. screenY
10131002 let profileAspect = profileScreenWidth / profileScreenHeight
10141003 let videoAspect = videoSize. width > 0 && videoSize. height > 0
10151004 ? Double ( videoSize. width / videoSize. height)
@@ -1047,12 +1036,12 @@ private struct DeviceViewportLayout {
10471036 return CGRect ( x: CGFloat ( x) , y: CGFloat ( y) , width: CGFloat ( width) , height: CGFloat ( height) )
10481037 }
10491038
1050- private static func screenCornerRadius( profile: ChromeProfile , profileScreenRect: CGRect , scale: CGFloat , metricScale : Double ) -> CGFloat {
1039+ private static func screenCornerRadius( profile: ChromeProfile , profileScreenRect: CGRect , scale: CGFloat ) -> CGFloat {
10511040 let fullScreen = CGRect (
1052- x: CGFloat ( profile. screenX / metricScale ) ,
1053- y: CGFloat ( profile. screenY / metricScale ) ,
1054- width: CGFloat ( profile. screenWidth / metricScale ) ,
1055- height: CGFloat ( profile. screenHeight / metricScale )
1041+ x: CGFloat ( profile. screenX) ,
1042+ y: CGFloat ( profile. screenY) ,
1043+ width: CGFloat ( profile. screenWidth) ,
1044+ height: CGFloat ( profile. screenHeight)
10561045 )
10571046 guard abs ( profileScreenRect. minX - fullScreen. minX) <= 0.5 ,
10581047 abs ( profileScreenRect. minY - fullScreen. minY) <= 0.5 ,
@@ -1063,7 +1052,7 @@ private struct DeviceViewportLayout {
10631052 return min (
10641053 profileScreenRect. width * scale / 2 ,
10651054 profileScreenRect. height * scale / 2 ,
1066- CGFloat ( profile. cornerRadius / metricScale ) * scale
1055+ CGFloat ( profile. cornerRadius) * scale
10671056 )
10681057 }
10691058}
@@ -1112,6 +1101,19 @@ private extension CGSize {
11121101}
11131102
11141103private extension View {
1104+ @ViewBuilder
1105+ func clippedToSimulatorScreen( cornerRadius: CGFloat , maskImage: UIImage ? ) -> some View {
1106+ if let maskImage {
1107+ self . mask (
1108+ Image ( uiImage: maskImage)
1109+ . resizable ( )
1110+ . scaledToFill ( )
1111+ )
1112+ } else {
1113+ self . clipShape ( RoundedRectangle ( cornerRadius: cornerRadius, style: . continuous) )
1114+ }
1115+ }
1116+
11151117 @ViewBuilder
11161118 func streamTouchGesture< G: Gesture > ( _ enabled: Bool , gesture: G ) -> some View {
11171119 if enabled {
0 commit comments