diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index c1646f74a..d302e4ee7 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift index 28833cb9b..2c78f9708 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift @@ -57,6 +57,7 @@ class Ryujinx { @Published var controllerMap: [Controller] = [] @Published var metalLayer: CAMetalLayer? = nil @Published var firmwareversion = "0" + @Published var emulationUIView = UIView() var shouldMetal: Bool { metalLayer == nil diff --git a/src/MeloNX/MeloNX/App/Views/ContentView.swift b/src/MeloNX/MeloNX/App/Views/ContentView.swift index ff98af785..31c8f3e6c 100644 --- a/src/MeloNX/MeloNX/App/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/App/Views/ContentView.swift @@ -75,7 +75,7 @@ struct ContentView: View { // MARK: - Body var body: some View { if game != nil, quits == false { - if Ryujinx.shared.metalLayer == nil { + if isLoading { emulationView .onAppear() { // This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative @@ -187,8 +187,10 @@ struct ContentView: View { Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in - if get_current_fps() != 0 { - isLoading = false + if Ryujinx.shared.metalLayer != nil { + withAnimation { + isLoading = false + } isAnimating = false timer.invalidate() } @@ -217,6 +219,11 @@ struct ContentView: View { refreshControllersList() } + Air.play(AnyView( + Text("Select Game") + .font(.system(size: 100)) + + )) let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED") diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift b/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift new file mode 100644 index 000000000..8842c50ea --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift @@ -0,0 +1,143 @@ +// Credit https://github.com/heestand-xyz/AirKit + +import UIKit +import SwiftUI + +public class Air { + + static let shared = Air() + + public var connected: Bool = false { + didSet { + connectionCallbacks.forEach({ $0(connected) }) + } + } + var connectionCallbacks: [(Bool) -> ()] = [] + + var airScreen: UIScreen? + var airWindow: UIWindow? + + var hostingController: UIHostingController? + + var appIsActive: Bool { UIApplication.shared.applicationState == .active } + + init() { + + NotificationCenter.default.addObserver(self, selector: #selector(didConnect), + name: UIScreen.didConnectNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(didDisconnect), + name: UIScreen.didDisconnectNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), + name: UIApplication.didBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), + name: UIApplication.willResignActiveNotification, object: nil) + } + + private func check() { + if let connectedScreen = UIScreen.screens.first(where: { $0 != .main }) { + add(screen: connectedScreen) { success in + guard success else { return } + self.connected = true + } + } + } + + public static func play(_ view: AnyView) { + Air.shared.hostingController = UIHostingController(rootView: view) + Air.shared.check() + } + + public static func stop() { + Air.shared.remove() + Air.shared.hostingController = nil + } + + public static func connection(_ callback: @escaping (Bool) -> ()) { + Air.shared.connectionCallbacks.append(callback) + } + + @objc func didConnect(sender: NSNotification) { + print("AirKit - Connect") + self.connected = true + guard let screen: UIScreen = sender.object as? UIScreen else { return } + add(screen: screen) { success in + guard success else { return } + self.connected = true + } + } + + func add(screen: UIScreen, completion: @escaping (Bool) -> ()) { + + print("AirKit - Add Screen") + + airScreen = screen + + airWindow = UIWindow(frame: airScreen!.bounds) + + guard let viewController: UIViewController = hostingController else { + print("AirKit - Add - Failed: Hosting Controller Not Found") + completion(false) + return + } + + findWindowScene(for: airScreen!) { windowScene in + guard let airWindowScene: UIWindowScene = windowScene else { + print("AirKit - Add - Failed: Window Scene Not Found") + completion(false) + return + } + self.airWindow?.rootViewController = viewController + self.airWindow?.windowScene = airWindowScene + self.airWindow?.isHidden = false + print("AirKit - Add Screen - Done") + completion(true) + } + + } + + func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) { + print("AirKit - Find Window Scene") + var matchingWindowScene: UIWindowScene? = nil + let scenes = UIApplication.shared.connectedScenes + for scene in scenes { + if let windowScene = scene as? UIWindowScene { + if windowScene.screen == screen { + matchingWindowScene = windowScene + break + } + } + } + guard let windowScene: UIWindowScene = matchingWindowScene else { + DispatchQueue.main.async { + self.findWindowScene(for: screen, shouldRecurse: false) { windowScene in + completion(windowScene) + } + } + return + } + completion(windowScene) + } + + @objc func didDisconnect() { + print("AirKit - Disconnect") + remove() + connected = false + } + + func remove() { + print("AirKit - Remove") + airWindow = nil + airScreen = nil + } + + @objc func didBecomeActive() { + print("AirKit - App Active") + } + + @objc func willResignActive() { + print("AirKit - App Inactive") + + } + +} diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift b/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift new file mode 100644 index 000000000..a3c90b241 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift @@ -0,0 +1,13 @@ +import UIKit +import SwiftUI + +public extension View { + + func airPlay() -> some View { + print("AirKit - airPlay") + Air.play(AnyView(self)) + return self + } + +} + diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift b/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift index cbbff7d98..108cf698e 100644 --- a/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift +++ b/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift @@ -10,11 +10,19 @@ import SwiftUI // Emulation View struct EmulationView: View { @AppStorage("isVirtualController") var isVCA: Bool = true + @State var isAirplaying = Air.shared.connected var body: some View { ZStack { - MetalView() // The Emulation View - .ignoresSafeArea() - .edgesIgnoringSafeArea(.all) + if isAirplaying { + Text("") + .onAppear { + Air.play(AnyView(MetalView().ignoresSafeArea())) + } + } else { + MetalView() // The Emulation View + .ignoresSafeArea() + .edgesIgnoringSafeArea(.all) + } // Above Emulation View @@ -22,5 +30,13 @@ struct EmulationView: View { ControllerView() // Virtual Controller } } + .onAppear { + Air.shared.connectionCallbacks.append { cool in + DispatchQueue.main.async { + isAirplaying = cool + print(cool) + } + } + } } } diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift b/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift index a822ef825..cc804aa69 100644 --- a/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift +++ b/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift @@ -11,14 +11,15 @@ import MetalKit struct MetalView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { - let view = UIView() let metalLayer = Ryujinx.shared.metalLayer! - metalLayer.frame = view.bounds - view.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3 - view.layer.addSublayer(metalLayer) + metalLayer.frame = Ryujinx.shared.emulationUIView.bounds + Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3 + if !Ryujinx.shared.emulationUIView.subviews.contains(where: { $0 == metalLayer }) { + Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer) + } - return view + return Ryujinx.shared.emulationUIView } func updateUIView(_ uiView: UIView, context: Context) {