@@ -21,11 +21,42 @@ struct PadLayoutEditView: View {
2121 @State private var originalSnapshot : PadLayoutSnapshot ? = nil
2222
2323 var body : some View {
24- Group {
25- if editLandscape {
26- landscapeEditor
27- } else {
28- portraitEditor
24+ GeometryReader { geo in
25+ let isActuallyLandscape = geo. size. width > geo. size. height
26+ ZStack {
27+ if isActuallyLandscape {
28+ landscapeEditorCanvas ( width: geo. size. width, height: geo. size. height)
29+ } else {
30+ portraitEditorCanvas ( width: geo. size. width, height: geo. size. height)
31+ }
32+
33+ // Banner when editing data for a different orientation than current geometry
34+ if isActuallyLandscape != editLandscape {
35+ VStack {
36+ HStack ( spacing: 6 ) {
37+ Image ( systemName: " exclamationmark.triangle.fill " )
38+ Text ( editLandscape
39+ ? " Editing landscape layout in portrait. Rotate device to landscape for full canvas. "
40+ : " Editing portrait layout in landscape. Rotate device to portrait for full canvas. " )
41+ . font ( . caption. weight ( . semibold) )
42+ . multilineTextAlignment ( . center)
43+ . lineLimit ( 3 )
44+ }
45+ . foregroundStyle ( . yellow)
46+ . padding ( . horizontal, 12 )
47+ . padding ( . vertical, 8 )
48+ . background ( . black. opacity ( 0.6 ) , in: RoundedRectangle ( cornerRadius: 8 ) )
49+ Spacer ( )
50+ }
51+ . padding ( . top, 50 )
52+ . padding ( . horizontal, 20 )
53+ . allowsHitTesting ( false )
54+ }
55+
56+ VStack {
57+ compactToolbar
58+ Spacer ( )
59+ }
2960 }
3061 }
3162 . navigationBarHidden ( true )
@@ -35,108 +66,61 @@ struct PadLayoutEditView: View {
3566 if originalSnapshot == nil {
3667 originalSnapshot = captureSnapshot ( )
3768 }
69+ let bounds = UIScreen . main. bounds
70+ editLandscape = bounds. width > bounds. height
3871 }
3972 . onDisappear {
4073 NSLog ( " @@PAD_LAYOUT@@ disappear " )
4174 NotificationCenter . default. post ( name: Notification . Name ( " ARMSX2iOSPadLayoutEditorDismissed " ) , object: nil )
4275 }
43- . onChange ( of: editLandscape) { _, isLandscape in
44- if isLandscape {
45- requestOrientation ( . landscape)
46- } else {
47- requestOrientation ( . portrait)
48- }
49- }
5076 }
5177
52- // MARK: - Landscape editor (full-screen overlay, prompts rotation if needed)
53- private var landscapeEditor : some View {
54- GeometryReader { geo in
55- let isActuallyLandscape = geo. size. width > geo. size. height
56- if isActuallyLandscape {
57- ZStack {
58- Color . black. opacity ( 0.4 )
59- . allowsHitTesting ( false )
60-
61- // Subtle safe zone guide
62- let margin : CGFloat = 0.05
63- let guideRect = CGRect (
64- x: geo. size. width * margin,
65- y: geo. size. height * margin,
66- width: geo. size. width * ( 1 - margin * 2 ) ,
67- height: geo. size. height * ( 1 - margin * 2 )
68- )
69- Rectangle ( )
70- . stroke ( style: StrokeStyle ( lineWidth: 1 , dash: [ 6 , 4 ] ) )
71- . foregroundStyle ( . yellow. opacity ( 0.3 ) )
72- . frame ( width: guideRect. width, height: guideRect. height)
73- . position ( x: guideRect. midX, y: guideRect. midY)
74-
75- customSkinPreview ( isLandscape: true , width: geo. size. width, height: geo. size. height)
76-
77- editorContent ( areaW: geo. size. width, areaH: geo. size. height)
78-
79- VStack {
80- compactToolbar
81- Spacer ( )
82- }
83- }
84- . contentShape ( Rectangle ( ) )
85- . ignoresSafeArea ( )
86- } else {
87- // Device is still portrait — show rotation prompt
88- ZStack {
89- Color . black
90-
91- VStack ( spacing: 16 ) {
92- Image ( systemName: " rotate.right " )
93- . font ( . system( size: 48 ) )
94- . foregroundStyle ( . secondary)
95- Text ( " Rotate your device to landscape to edit the landscape layout. " )
96- . font ( . headline)
97- . foregroundStyle ( . secondary)
98- . multilineTextAlignment ( . center)
99- . padding ( . horizontal, 32 )
100- }
78+ // MARK: - Landscape editor canvas (always shown when device is landscape)
79+ private func landscapeEditorCanvas( width: CGFloat , height: CGFloat ) -> some View {
80+ ZStack {
81+ Color . black. opacity ( 0.4 )
82+ . allowsHitTesting ( false )
10183
102- VStack {
103- compactToolbar
104- Spacer ( )
105- }
106- }
107- . ignoresSafeArea ( )
108- }
84+ // Subtle safe zone guide
85+ let margin : CGFloat = 0.05
86+ let guideRect = CGRect (
87+ x: width * margin,
88+ y: height * margin,
89+ width: width * ( 1 - margin * 2 ) ,
90+ height: height * ( 1 - margin * 2 )
91+ )
92+ Rectangle ( )
93+ . stroke ( style: StrokeStyle ( lineWidth: 1 , dash: [ 6 , 4 ] ) )
94+ . foregroundStyle ( . yellow. opacity ( 0.3 ) )
95+ . frame ( width: guideRect. width, height: guideRect. height)
96+ . position ( x: guideRect. midX, y: guideRect. midY)
97+
98+ customSkinPreview ( isLandscape: true , width: width, height: height)
99+
100+ editorContent ( areaW: width, areaH: height)
109101 }
102+ . contentShape ( Rectangle ( ) )
103+ . ignoresSafeArea ( )
110104 }
111105
112- // MARK: - Portrait editor (matches Phase 1 gameplay: top game area, bottom controller deck)
113- private var portraitEditor : some View {
114- GeometryReader { geo in
115- let gameHeight = min ( geo. size. width * 3 / 4 , geo. size. height * 0.55 )
116- let padHeight = geo. size. height - gameHeight
117- ZStack {
118- VStack ( spacing: 0 ) {
119- // Top: game viewport area
120- Color . black
121- . allowsHitTesting ( false )
122- . frame ( height: gameHeight)
123-
124- // Bottom: controller deck editing area
125- ZStack {
126- Color . black
127- customSkinPreview ( isLandscape: false , width: geo. size. width, height: padHeight)
128- editorPortraitContent ( areaW: geo. size. width, areaH: padHeight)
129- }
130- . frame ( height: padHeight)
131- . clipped ( )
132- }
106+ // MARK: - Portrait editor canvas (always shown when device is portrait)
107+ private func portraitEditorCanvas( width: CGFloat , height: CGFloat ) -> some View {
108+ let gameHeight = min ( width * 3 / 4 , height * 0.55 )
109+ let padHeight = height - gameHeight
110+ VStack ( spacing: 0 ) {
111+ // Top: game viewport area
112+ Color . black
113+ . allowsHitTesting ( false )
114+ . frame ( height: gameHeight)
133115
134- VStack {
135- compactToolbar
136- Spacer ( )
137- }
116+ // Bottom: controller deck editing area
117+ ZStack {
118+ Color . black
119+ customSkinPreview ( isLandscape: false , width: width, height: padHeight)
120+ editorPortraitContent ( areaW: width, areaH: padHeight)
138121 }
139- . contentShape ( Rectangle ( ) )
122+ . frame ( height: padHeight)
123+ . clipped ( )
140124 }
141125 }
142126
@@ -307,11 +291,11 @@ struct PadLayoutEditView: View {
307291 ZStack {
308292 // Individual buttons for action and dpad
309293 ForEach ( PadLayoutStore . perButtonIDs, id: \. self) { ( id: String ) in
310- DraggableButton ( id: id, areaW: areaW, areaH: areaH, isLandscape: false , onBeginEdit: pushSnapshot)
294+ DraggableButton ( id: id, areaW: areaW, areaH: areaH, isLandscape: editLandscape , onBeginEdit: pushSnapshot)
311295 }
312296 // Group widgets for other controls
313297 ForEach ( PadLayoutStore . groupIDs. filter { $0 != " action " && $0 != " dpad " } , id: \. self) { ( id: String ) in
314- DraggableGroup ( id: id, areaW: areaW, areaH: areaH, isLandscape: false , onBeginEdit: pushSnapshot)
298+ DraggableGroup ( id: id, areaW: areaW, areaH: areaH, isLandscape: editLandscape , onBeginEdit: pushSnapshot)
315299 }
316300 }
317301 . environment ( \. padSkin, settings. virtualPadSkin)
@@ -344,21 +328,6 @@ struct PadLayoutEditView: View {
344328 }
345329}
346330
347- // MARK: - Orientation helper
348- @MainActor
349- private func requestOrientation( _ mask: UIInterfaceOrientationMask ) {
350- if #available( iOS 16 . 0 , * ) {
351- for case let scene as UIWindowScene in UIApplication . shared. connectedScenes {
352- guard scene. activationState == . foregroundActive || scene. activationState == . foregroundInactive else { continue }
353- let preferences = UIWindowScene . GeometryPreferences. iOS ( interfaceOrientations: mask)
354- scene. requestGeometryUpdate ( preferences) { error in
355- NSLog ( " @@PAD_LAYOUT@@ orientation request failed: %@ " , error. localizedDescription)
356- }
357- break
358- }
359- }
360- }
361-
362331// MARK: - Draggable group widget
363332private struct DraggableGroup : View {
364333 let id : String
0 commit comments