forked from MeloNX/MeloNX
Add Airplay
This commit is contained in:
parent
f57d24706b
commit
2d5f1d8015
Binary file not shown.
@ -57,6 +57,7 @@ class Ryujinx {
|
|||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
@Published var metalLayer: CAMetalLayer? = nil
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
@Published var firmwareversion = "0"
|
@Published var firmwareversion = "0"
|
||||||
|
@Published var emulationUIView = UIView()
|
||||||
|
|
||||||
var shouldMetal: Bool {
|
var shouldMetal: Bool {
|
||||||
metalLayer == nil
|
metalLayer == nil
|
||||||
|
@ -75,7 +75,7 @@ struct ContentView: View {
|
|||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if game != nil, quits == false {
|
if game != nil, quits == false {
|
||||||
if Ryujinx.shared.metalLayer == nil {
|
if isLoading {
|
||||||
emulationView
|
emulationView
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
|
// 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
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||||
if get_current_fps() != 0 {
|
if Ryujinx.shared.metalLayer != nil {
|
||||||
isLoading = false
|
withAnimation {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
isAnimating = false
|
isAnimating = false
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
}
|
}
|
||||||
@ -217,6 +219,11 @@ struct ContentView: View {
|
|||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Air.play(AnyView(
|
||||||
|
Text("Select Game")
|
||||||
|
.font(.system(size: 100))
|
||||||
|
|
||||||
|
))
|
||||||
|
|
||||||
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
|
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
|
||||||
|
|
||||||
|
143
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift
Normal file
143
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift
Normal file
@ -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<AnyView>?
|
||||||
|
|
||||||
|
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<AnyView>(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")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift
Normal file
13
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
|
||||||
|
func airPlay() -> some View {
|
||||||
|
print("AirKit - airPlay")
|
||||||
|
Air.play(AnyView(self))
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,19 @@ import SwiftUI
|
|||||||
// Emulation View
|
// Emulation View
|
||||||
struct EmulationView: View {
|
struct EmulationView: View {
|
||||||
@AppStorage("isVirtualController") var isVCA: Bool = true
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
|
@State var isAirplaying = Air.shared.connected
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
MetalView() // The Emulation View
|
if isAirplaying {
|
||||||
.ignoresSafeArea()
|
Text("")
|
||||||
.edgesIgnoringSafeArea(.all)
|
.onAppear {
|
||||||
|
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MetalView() // The Emulation View
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
}
|
||||||
|
|
||||||
// Above Emulation View
|
// Above Emulation View
|
||||||
|
|
||||||
@ -22,5 +30,13 @@ struct EmulationView: View {
|
|||||||
ControllerView() // Virtual Controller
|
ControllerView() // Virtual Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
Air.shared.connectionCallbacks.append { cool in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isAirplaying = cool
|
||||||
|
print(cool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,15 @@ import MetalKit
|
|||||||
struct MetalView: UIViewRepresentable {
|
struct MetalView: UIViewRepresentable {
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
let view = UIView()
|
|
||||||
|
|
||||||
let metalLayer = Ryujinx.shared.metalLayer!
|
let metalLayer = Ryujinx.shared.metalLayer!
|
||||||
metalLayer.frame = view.bounds
|
metalLayer.frame = Ryujinx.shared.emulationUIView.bounds
|
||||||
view.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
|
Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
|
||||||
view.layer.addSublayer(metalLayer)
|
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) {
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user