Add new SwiftUI Emulation View

This commit is contained in:
Stossy11 2025-02-09 15:49:10 +11:00
parent a2c3f6d624
commit f57d24706b
12 changed files with 167 additions and 104 deletions

View File

@ -618,7 +618,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 4TD3JXVDW7; DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO; ENABLE_TESTABILITY = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -628,6 +628,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
GCC_OPTIMIZATION_LEVEL = fast; GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -659,9 +663,17 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 0.0.8; MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
@ -679,7 +691,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 4TD3JXVDW7; DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -689,6 +701,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
GCC_OPTIMIZATION_LEVEL = fast; GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -720,9 +736,17 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 0.0.8; MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key> <key>Ryujinx.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>1</integer>
</dict> </dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key> <key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>1</integer> <integer>2</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -10,6 +10,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#import "utils.h" #import "utils.h"
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -1,70 +0,0 @@
//
// VirtualController.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
import SwiftUI
var hostingController: UIHostingController<ControllerView>? // Store reference to prevent deallocation
// Swts up a timer that adds subview to the Window and Repeats until the ControllerView is found in the Window to ensure that the controller shows.
func waitForController() {
guard let window = theWindow else { return }
// Function to search for an existing UIHostingController with ControllerView
func findGCControllerView(in view: UIView) -> UIHostingController<ControllerView>? {
if let hostingVC = view.next as? UIHostingController<ControllerView> {
return hostingVC
}
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
}
let controllerView = ControllerView()
let newHostingController = UIHostingController(rootView: controllerView)
hostingController = newHostingController
let containerView = newHostingController.view!
containerView.backgroundColor = .clear
containerView.frame = window.bounds
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Timer for controller
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if findGCControllerView(in: window) == nil {
// Adds Virtual Controller Subview
window.addSubview(containerView)
window.bringSubviewToFront(containerView)
if let sdlWindow = SDL_GetWindowFromID(1) {
SDL_SetWindowPosition(sdlWindow, 0, 0)
}
timer.invalidate()
}
}
}
class TransparentHostingContainerView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Check if the point is within the subviews of this container
let view = super.hitTest(point, with: event)
print(view)
// Return nil if the touch is outside visible content (passes through to views below)
return view === self ? nil : view
}
}

View File

@ -17,17 +17,11 @@ extension UIWindow {
// Also waits for the window to append the on-screen controller // Also waits for the window to append the on-screen controller
@objc func wdb_makeKeyAndVisible() { @objc func wdb_makeKeyAndVisible() {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene) // self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
} }
self.wdb_makeKeyAndVisible() self.wdb_makeKeyAndVisible()
theWindow = self theWindow = self
Ryujinx.shared.repeatuntilfindLayer()
if UserDefaults.standard.bool(forKey: "isVirtualController") {
if let window = theWindow {
waitForController()
}
}
} }
} }

View File

@ -55,7 +55,12 @@ class Ryujinx {
let virtualController = VirtualController() let virtualController = VirtualController()
@Published var controllerMap: [Controller] = [] @Published var controllerMap: [Controller] = []
@State var firmwareversion = "0" @Published var metalLayer: CAMetalLayer? = nil
@Published var firmwareversion = "0"
var shouldMetal: Bool {
metalLayer == nil
}
static let shared = Ryujinx() static let shared = Ryujinx()
@ -400,6 +405,63 @@ class Ryujinx {
print("Error removing folder: \(error)") print("Error removing folder: \(error)")
} }
} }
func repeatuntilfindLayer() {
DispatchQueue.global(qos: .background).async {
while self.metalLayer == nil {
let layer = self.getMetalLayer(nil)
if layer != nil {
DispatchQueue.main.async {
self.metalLayer = layer
}
break
}
Thread.sleep(forTimeInterval: 0.1)
}
}
}
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
var window = window
if window == nil {
window = SDL_GetWindowFromID(1)
}
var windowInfo = SDL_SysWMinfo()
SDL_GetWindowWMInfo(window, &windowInfo)
guard let uiWindow = windowInfo.info.uikit.window,
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
print("Unable to get root view")
return nil
}
func findMetalLayer(in view: UIView) -> CAMetalLayer? {
if let metalLayer = view.layer as? CAMetalLayer {
return metalLayer
}
for subview in view.subviews {
if let metalLayer = findMetalLayer(in: subview) {
return metalLayer
}
}
return nil
}
if let existingLayer = findMetalLayer(in: rootView) {
print("Found Metal Layer")
return existingLayer
}
print("found nothing")
return nil
}

View File

@ -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 isLoading { if Ryujinx.shared.metalLayer == nil {
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
@ -93,12 +93,10 @@ struct ContentView: View {
} }
} else { } else {
// This is when the game starts to stop the animation // This is when the game starts to stop the animation
VStack { EmulationView()
.onAppear() {
} isAnimating = false
.onAppear() { }
isAnimating = false
}
} }
} else { } else {
// This is the main menu view that includes the Settings and the Game Selector // This is the main menu view that includes the Settings and the Game Selector
@ -234,7 +232,7 @@ struct ContentView: View {
private func initializeSDL() { private func initializeSDL() {
setMoltenVKSettings() setMoltenVKSettings()
SDL_SetMainReady() // Sets SDL Ready SDL_SetMainReady() // Sets SDL Ready
SDL_iPhoneSetEventPump(SDL_TRUE) // Allow iOS Set Event Pump (Check out SDL2 Documentation here) SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true (Check out SDL2 Documentation here)
SDL_Init(SdlInitFlags) // Initialises SDL2 SDL_Init(SdlInitFlags) // Initialises SDL2
initialize() initialize()
} }

View File

@ -0,0 +1,26 @@
//
// EmulationView.swift
// MeloNX
//
// Created by Stossy11 on 09/02/2025.
//
import SwiftUI
// Emulation View
struct EmulationView: View {
@AppStorage("isVirtualController") var isVCA: Bool = true
var body: some View {
ZStack {
MetalView() // The Emulation View
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
// Above Emulation View
if isVCA {
ControllerView() // Virtual Controller
}
}
}
}

View File

@ -0,0 +1,27 @@
//
// MetalView.swift
// MeloNX
//
// Created by Stossy11 on 09/02/2025.
//
import SwiftUI
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)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
// nothin
}
}

View File

@ -9,15 +9,15 @@
<key>UTExportedTypeDeclarations</key> <key>UTExportedTypeDeclarations</key>
<array> <array>
<dict> <dict>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-package</string>
<key>UTTypeDescription</key>
<string>Nintendo Switch Package</string>
<key>UTTypeConformsTo</key> <key>UTTypeConformsTo</key>
<array> <array>
<string>public.data</string> <string>public.data</string>
<string>public.archive</string> <string>public.archive</string>
</array> </array>
<key>UTTypeDescription</key>
<string>Nintendo Switch Package</string>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-package</string>
<key>UTTypeTagSpecification</key> <key>UTTypeTagSpecification</key>
<dict> <dict>
<key>public.filename-extension</key> <key>public.filename-extension</key>
@ -29,15 +29,15 @@
</dict> </dict>
</dict> </dict>
<dict> <dict>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-cartridge</string>
<key>UTTypeDescription</key>
<string>Nintendo Switch Cartridge</string>
<key>UTTypeConformsTo</key> <key>UTTypeConformsTo</key>
<array> <array>
<string>public.data</string> <string>public.data</string>
<string>public.archive</string> <string>public.archive</string>
</array> </array>
<key>UTTypeDescription</key>
<string>Nintendo Switch Cartridge</string>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-cartridge</string>
<key>UTTypeTagSpecification</key> <key>UTTypeTagSpecification</key>
<dict> <dict>
<key>public.filename-extension</key> <key>public.filename-extension</key>

View File

@ -21,7 +21,7 @@ struct MeloNXApp: App {
if bool { if bool {
print("Ryujinx Files Initialized Successfully") print("Ryujinx Files Initialized Successfully")
} else { } else {
exit(0) // exit(0)
} }
} }
@ -29,7 +29,7 @@ struct MeloNXApp: App {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
InitializeRyujinx() { bool in InitializeRyujinx() { bool in
if !bool { if !bool {
exit(0) // exit(0)
} }
} }
@ -144,8 +144,9 @@ func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
exit(0) exit(0)
} }
let task = URLSession.shared.dataTask(with: URL(string: addFolders(path)!)!) { data, _, _ in let task = URLSession.shared.dataTask(with: URL(string: addFolders(path)!)!) { data, response, error in
let text = String(data: data ?? Data(), encoding: .utf8) ?? "" let text = String(data: data ?? Data(), encoding: .utf8) ?? ""
print(text)
completion(text.contains("true")) completion(text.contains("true"))
} }
task.resume() task.resume()