Skip to content

Commit a2b796b

Browse files
committed
Fix portrait controller layout and touch targets
- Portrait gameplay now uses a VStack with a top game viewport and a bottom controller deck, instead of full-screen game with overlay. - Game viewport height is min(width * 3/4, height * 0.55) for aspect-correct sizing while keeping the controller deck usable. - Portrait no longer blanket-ignores safe area; top safe area is respected so OSD text stays below the Dynamic Island. Bottom safe area is ignored so controller buttons remain usable near the home indicator. - Menu button is placed via .overlay(alignment: .topTrailing) in portrait. - PSBtn and PadBtn now have a minimum 55x55 pt touch target regardless of visual skin size. The UIKitPadPressSurface UIButton and the DragGesture both cover the expanded target while the visual button stays unchanged. - PadLayoutStore gains resetAll() which resets both portrait and landscape group positions and all per-button overrides. Visibility is not reset. - PadLayoutEditView Reset All button calls resetAll() so it correctly resets both orientations and per-button positions. Undo and Cancel still work.
1 parent 55fddb3 commit a2b796b

4 files changed

Lines changed: 73 additions & 28 deletions

File tree

app/src/main/swift/Models/PadLayoutStore.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,13 @@ final class PadLayoutStore: @unchecked Sendable {
338338
}
339339
}
340340
}
341+
342+
func resetAll() {
343+
portrait = Self.defaultPortrait
344+
landscape = Self.defaultLandscape
345+
perButtonPortrait.removeAll()
346+
perButtonLandscape.removeAll()
347+
// Note: controlVisibility is intentionally NOT reset here.
348+
// Use resetControlVisibility() for that.
349+
}
341350
}

app/src/main/swift/Views/GameScreenView.swift

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,32 @@ struct GameScreenView: View {
8686
}
8787
.ignoresSafeArea()
8888
} else {
89-
// Portrait: full-screen game with controller overlay at bottom.
90-
ZStack {
89+
// Portrait: top game viewport, bottom controller deck.
90+
// Game respects the top safe area so OSD stays below the Dynamic Island.
91+
// Controller ignores the bottom safe area so buttons remain usable near the home indicator.
92+
VStack(spacing: 0) {
93+
let gameHeight = min(geo.size.width * 3 / 4, geo.size.height * 0.55)
9194
MetalGameView()
95+
.frame(height: gameHeight)
96+
.clipped()
97+
9298
if effectiveVirtualPadVisible {
93-
VirtualControllerView()
94-
.frame(height: geo.size.height / 2)
95-
.frame(maxHeight: .infinity, alignment: .bottom)
99+
ZStack {
100+
Color.black
101+
VirtualControllerView()
102+
.frame(maxWidth: .infinity, maxHeight: .infinity)
103+
}
104+
.frame(maxWidth: .infinity, maxHeight: .infinity)
96105
}
97-
menuButtonOverlay(isLandscape: false)
98106
}
99-
.ignoresSafeArea()
107+
.overlay(alignment: .topTrailing) {
108+
if !menuButtonHidden {
109+
menuButton()
110+
.padding(.top, 4)
111+
.padding(.trailing, 4)
112+
}
113+
}
114+
.ignoresSafeArea(.container, edges: .bottom)
100115
}
101116
}
102117
.preference(key: GameScreenSizePreferenceKey.self, value: geo.size)

app/src/main/swift/Views/Settings/PadLayoutEditView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ struct PadLayoutEditView: View {
252252

253253
Button("Reset All") {
254254
pushSnapshot()
255-
layout.reset(isLandscape: isLandscape)
255+
layout.resetAll()
256256
}
257257
.font(.caption)
258258
.foregroundStyle(.red.opacity(0.7))

app/src/main/swift/Views/VirtualControllerView.swift

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -917,23 +917,33 @@ struct PSBtn: View {
917917
@Environment(\.padSkin) private var padSkin
918918
@Environment(\.padUsesFullSkin) private var padUsesFullSkin
919919

920+
private var touchTarget: CGFloat { max(sz, 55) }
921+
920922
var body: some View {
921-
Group {
922-
if ARMSX2UsesUIKitPadPressSurface() {
923+
if ARMSX2UsesUIKitPadPressSurface() {
924+
ZStack {
923925
UIKitPadPressSurface(onPress: updatePressed) {
924926
buttonFace
927+
.frame(width: sz, height: sz)
925928
}
926-
} else {
929+
.frame(width: touchTarget, height: touchTarget)
930+
}
931+
.frame(width: touchTarget, height: touchTarget)
932+
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
933+
.animation(.easeOut(duration: 0.06), value: on)
934+
} else {
935+
ZStack {
927936
buttonFace
928-
.contentShape(Circle())
929-
.simultaneousGesture(DragGesture(minimumDistance: 0)
930-
.onChanged { _ in updatePressed(true) }
931-
.onEnded { _ in updatePressed(false) })
937+
.frame(width: sz, height: sz)
932938
}
939+
.frame(width: touchTarget, height: touchTarget)
940+
.contentShape(Rectangle())
941+
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
942+
.animation(.easeOut(duration: 0.06), value: on)
943+
.simultaneousGesture(DragGesture(minimumDistance: 0)
944+
.onChanged { _ in updatePressed(true) }
945+
.onEnded { _ in updatePressed(false) })
933946
}
934-
.frame(width: sz, height: sz)
935-
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
936-
.animation(.easeOut(duration: 0.06), value: on)
937947
}
938948

939949
private var buttonFace: some View {
@@ -981,23 +991,34 @@ struct PadBtn: View {
981991
@Environment(\.padSkin) private var padSkin
982992
@Environment(\.padUsesFullSkin) private var padUsesFullSkin
983993

994+
private var touchW: CGFloat { max(w, 55) }
995+
private var touchH: CGFloat { max(h, 55) }
996+
984997
var body: some View {
985-
Group {
986-
if ARMSX2UsesUIKitPadPressSurface() {
998+
if ARMSX2UsesUIKitPadPressSurface() {
999+
ZStack {
9871000
UIKitPadPressSurface(onPress: updatePressed) {
9881001
buttonFace
1002+
.frame(width: w, height: h)
9891003
}
990-
} else {
1004+
.frame(width: touchW, height: touchH)
1005+
}
1006+
.frame(width: touchW, height: touchH)
1007+
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
1008+
.animation(.easeOut(duration: 0.06), value: on)
1009+
} else {
1010+
ZStack {
9911011
buttonFace
992-
.contentShape(Rectangle())
993-
.simultaneousGesture(DragGesture(minimumDistance: 0)
994-
.onChanged { _ in updatePressed(true) }
995-
.onEnded { _ in updatePressed(false) })
1012+
.frame(width: w, height: h)
9961013
}
1014+
.frame(width: touchW, height: touchH)
1015+
.contentShape(Rectangle())
1016+
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
1017+
.animation(.easeOut(duration: 0.06), value: on)
1018+
.simultaneousGesture(DragGesture(minimumDistance: 0)
1019+
.onChanged { _ in updatePressed(true) }
1020+
.onEnded { _ in updatePressed(false) })
9971021
}
998-
.frame(width: w, height: h)
999-
.opacity(padUsesFullSkin ? 1.0 : padOpacity)
1000-
.animation(.easeOut(duration: 0.06), value: on)
10011022
}
10021023

10031024
private var buttonFace: some View {

0 commit comments

Comments
 (0)