Skip to content

Commit 376550e

Browse files
committed
Use SwiftUI persistent overlays for gameplay chrome
1 parent 8a6e115 commit 376550e

3 files changed

Lines changed: 10 additions & 115 deletions

File tree

app/src/main/swift/Models/AppState.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ final class AppState: @unchecked Sendable {
4040
}
4141
}
4242
}
43-
var hideHomeIndicator: Bool = false {
44-
didSet {
45-
if oldValue != hideHomeIndicator {
46-
NotificationCenter.default.post(name: Self.systemChromeNeedsUpdateNotification, object: nil)
47-
}
48-
}
49-
}
5043

5144
@ObservationIgnored private var pendingBootAction: (() -> Void)?
5245
@ObservationIgnored private var shutdownObserver: NSObjectProtocol?

app/src/main/swift/Models/SwiftUIHost.swift

Lines changed: 10 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ class ARMSX2HostingController<Content: View>: UIHostingController<Content> {
99
override var prefersStatusBarHidden: Bool {
1010
AppState.shared.hideStatusBar
1111
}
12-
override var prefersHomeIndicatorAutoHidden: Bool {
13-
AppState.shared.hideStatusBar || AppState.shared.hideHomeIndicator
14-
}
1512
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
1613
.fade
1714
}
@@ -57,7 +54,6 @@ class ARMSX2HostingController<Content: View>: UIHostingController<Content> {
5754

5855
@objc private func systemChromeNeedsUpdate() {
5956
setNeedsStatusBarAppearanceUpdate()
60-
setNeedsUpdateOfHomeIndicatorAutoHidden()
6157
}
6258

6359
private func applyNativeContentScale(to view: UIView) {
@@ -72,76 +68,18 @@ class ARMSX2HostingController<Content: View>: UIHostingController<Content> {
7268
}
7369

7470

75-
// ARMSX2_ALUNE_STYLE_HOME_BAR_V3_BEGIN
76-
// Alune does the Home/Gesture Bar part from the active emulation UIViewController itself:
77-
// its emulation controller directly overrides prefersHomeIndicatorAutoHidden.
78-
// ARMSX2 is SwiftUI-driven, so this root container presents GameScreenView inside a
79-
// dedicated UIKit controller when AppState.currentScreen == .playing.
8071
@MainActor
8172
private final class ARMSX2GameplayHostingController<Content: View>: UIHostingController<Content> {
82-
private var refreshTimer: Timer?
83-
8473
override var prefersStatusBarHidden: Bool {
8574
AppState.shared.hideStatusBar
8675
}
8776

88-
override var prefersHomeIndicatorAutoHidden: Bool {
89-
true
90-
}
91-
92-
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
93-
.all
94-
}
95-
96-
override var childForHomeIndicatorAutoHidden: UIViewController? {
97-
nil
98-
}
99-
100-
override var childForScreenEdgesDeferringSystemGestures: UIViewController? {
101-
nil
102-
}
103-
10477
override func viewDidLoad() {
10578
super.viewDidLoad()
10679
modalPresentationCapturesStatusBarAppearance = true
10780
view.backgroundColor = .black
10881
view.isOpaque = true
10982
}
110-
111-
override func viewWillAppear(_ animated: Bool) {
112-
super.viewWillAppear(animated)
113-
refreshSystemGestureChrome()
114-
}
115-
116-
override func viewDidAppear(_ animated: Bool) {
117-
super.viewDidAppear(animated)
118-
refreshSystemGestureChrome()
119-
// iOS may briefly re-evaluate the Home Indicator after SwiftUI/Metal view changes
120-
// or after the app becomes active. Keep nudging UIKit while the gameplay controller
121-
// is the active full-screen controller, like a game view controller would do.
122-
refreshTimer?.invalidate()
123-
// Swift 6 / Xcode 16 fix: avoid calling a @MainActor UIKit method from
124-
// Timer's @Sendable closure. Use target/selector on the main run loop instead.
125-
let timer = Timer(timeInterval: 0.75,
126-
target: self,
127-
selector: #selector(refreshSystemGestureChrome),
128-
userInfo: nil,
129-
repeats: true)
130-
refreshTimer = timer
131-
RunLoop.main.add(timer, forMode: .common)
132-
}
133-
134-
override func viewWillDisappear(_ animated: Bool) {
135-
super.viewWillDisappear(animated)
136-
refreshTimer?.invalidate()
137-
refreshTimer = nil
138-
}
139-
140-
@objc func refreshSystemGestureChrome() {
141-
setNeedsStatusBarAppearanceUpdate()
142-
setNeedsUpdateOfHomeIndicatorAutoHidden()
143-
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
144-
}
14583
}
14684

14785
@MainActor
@@ -153,22 +91,6 @@ private final class ARMSX2RootContainerController: UIViewController {
15391
AppState.shared.hideStatusBar
15492
}
15593

156-
override var prefersHomeIndicatorAutoHidden: Bool {
157-
AppState.shared.currentScreen == .playing || AppState.shared.hideHomeIndicator
158-
}
159-
160-
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
161-
(AppState.shared.currentScreen == .playing || AppState.shared.hideHomeIndicator) ? .all : []
162-
}
163-
164-
override var childForHomeIndicatorAutoHidden: UIViewController? {
165-
gameplayController ?? presentedViewController ?? menuController
166-
}
167-
168-
override var childForScreenEdgesDeferringSystemGestures: UIViewController? {
169-
gameplayController ?? presentedViewController ?? menuController
170-
}
171-
17294
override var childForStatusBarHidden: UIViewController? {
17395
gameplayController ?? presentedViewController ?? menuController
17496
}
@@ -205,7 +127,7 @@ private final class ARMSX2RootContainerController: UIViewController {
205127
override func viewDidAppear(_ animated: Bool) {
206128
super.viewDidAppear(animated)
207129
updateGameplayPresentation(animated: false)
208-
refreshSystemChrome()
130+
refreshStatusBarAppearance()
209131
}
210132

211133
private func embedMenuController() {
@@ -225,11 +147,11 @@ private final class ARMSX2RootContainerController: UIViewController {
225147

226148
@objc private func emulationPresentationNeedsUpdate() {
227149
updateGameplayPresentation(animated: false)
228-
refreshSystemChrome()
150+
refreshStatusBarAppearance()
229151
}
230152

231153
@objc private func systemChromeNeedsUpdate() {
232-
refreshSystemChrome()
154+
refreshStatusBarAppearance()
233155
}
234156

235157
private func updateGameplayPresentation(animated: Bool) {
@@ -243,41 +165,33 @@ private final class ARMSX2RootContainerController: UIViewController {
243165
controller.modalPresentationCapturesStatusBarAppearance = true
244166
gameplayController = controller
245167
present(controller, animated: animated) { [weak self, weak controller] in
246-
controller?.refreshSystemGestureChrome()
247-
self?.refreshSystemChrome()
168+
controller?.setNeedsStatusBarAppearanceUpdate()
169+
self?.refreshStatusBarAppearance()
248170
}
249171
} else {
250-
gameplayController?.refreshSystemGestureChrome()
172+
gameplayController?.setNeedsStatusBarAppearanceUpdate()
251173
}
252174
} else if let controller = gameplayController {
253175
gameplayController = nil
254176
controller.dismiss(animated: animated) { [weak self] in
255-
self?.refreshSystemChrome()
177+
self?.refreshStatusBarAppearance()
256178
}
257179
}
258180
}
259181

260-
private func refreshSystemChrome() {
182+
private func refreshStatusBarAppearance() {
261183
setNeedsStatusBarAppearanceUpdate()
262-
setNeedsUpdateOfHomeIndicatorAutoHidden()
263-
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
264-
gameplayController?.refreshSystemGestureChrome()
184+
gameplayController?.setNeedsStatusBarAppearanceUpdate()
265185
DispatchQueue.main.async { [weak self] in
266186
self?.setNeedsStatusBarAppearanceUpdate()
267-
self?.setNeedsUpdateOfHomeIndicatorAutoHidden()
268-
self?.setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
269-
self?.gameplayController?.refreshSystemGestureChrome()
187+
self?.gameplayController?.setNeedsStatusBarAppearanceUpdate()
270188
}
271189
}
272190
}
273-
// ARMSX2_ALUNE_STYLE_HOME_BAR_V3_END
274191

275192
@objc public class SwiftUIHost: NSObject {
276193
@MainActor
277194
@objc public static func createMenuController() -> UIViewController {
278-
// ARMSX2_ALUNE_STYLE_HOME_BAR_V3
279-
// Return a UIKit root container. The menu remains SwiftUI, but gameplay/BIOS is
280-
// presented by a dedicated full-screen UIViewController, matching Alune's model.
281195
return ARMSX2RootContainerController()
282196
}
283197
}

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ struct GameScreenView: View {
113113
@State private var retroAchievementsToastGeneration = 0
114114
@State private var retroAchievementsToastDismissTask: Task<Void, Never>?
115115
@State private var runtimeOverlayPauseActive = false
116-
@State private var previousHideHomeIndicator = false
117116

118117
@Environment(\.scenePhase) private var scenePhase
119118

@@ -242,7 +241,6 @@ struct GameScreenView: View {
242241
Text(settings.localized("Restart the current game? Unsaved progress will be lost."))
243242
}
244243
.onAppear {
245-
enterGameplaySystemChromeMode()
246244
AppState.requestEmulationPresentationUpdate()
247245
syncFullscreenStateFromWindow()
248246
applyInitialFullscreenPreference()
@@ -252,7 +250,6 @@ struct GameScreenView: View {
252250
.onDisappear {
253251
statusMessageDismissTask?.cancel()
254252
retroAchievementsToastDismissTask?.cancel()
255-
leaveGameplaySystemChromeMode()
256253
}
257254
.simultaneousGesture(
258255
TapGesture(count: 2).onEnded {
@@ -687,15 +684,6 @@ struct GameScreenView: View {
687684

688685
// MARK: - Lifecycle & Events
689686

690-
private func enterGameplaySystemChromeMode() {
691-
previousHideHomeIndicator = appState.hideHomeIndicator
692-
appState.hideHomeIndicator = true
693-
}
694-
695-
private func leaveGameplaySystemChromeMode() {
696-
appState.hideHomeIndicator = previousHideHomeIndicator
697-
}
698-
699687
private func applyInitialFullscreenPreference() {
700688
menuButtonHidden = settings.hideMenuButton
701689
if settings.autoFullscreen && !ARMSX2Bridge.isSDLFullscreen() {

0 commit comments

Comments
 (0)