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;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 4TD3JXVDW7;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO;
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",
);
GCC_OPTIMIZATION_LEVEL = fast;
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",
);
MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
@ -679,7 +691,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 4TD3JXVDW7;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = YES;
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",
);
GCC_OPTIMIZATION_LEVEL = fast;
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",
);
MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";

View File

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

View File

@ -10,6 +10,7 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#import "utils.h"
#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
@objc func wdb_makeKeyAndVisible() {
if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
// self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
}
self.wdb_makeKeyAndVisible()
theWindow = self
if UserDefaults.standard.bool(forKey: "isVirtualController") {
if let window = theWindow {
waitForController()
}
}
Ryujinx.shared.repeatuntilfindLayer()
}
}

View File

@ -55,7 +55,12 @@ class Ryujinx {
let virtualController = VirtualController()
@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()
@ -402,6 +407,63 @@ class Ryujinx {
}
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
}
static func log(_ message: String) {
print("[Ryujinx] \(message)")

View File

@ -75,7 +75,7 @@ struct ContentView: View {
// MARK: - Body
var body: some View {
if game != nil, quits == false {
if isLoading {
if Ryujinx.shared.metalLayer == nil {
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
@ -93,12 +93,10 @@ struct ContentView: View {
}
} else {
// This is when the game starts to stop the animation
VStack {
}
.onAppear() {
isAnimating = false
}
EmulationView()
.onAppear() {
isAnimating = false
}
}
} else {
// This is the main menu view that includes the Settings and the Game Selector
@ -234,7 +232,7 @@ struct ContentView: View {
private func initializeSDL() {
setMoltenVKSettings()
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
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>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-package</string>
<key>UTTypeDescription</key>
<string>Nintendo Switch Package</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.archive</string>
</array>
<key>UTTypeDescription</key>
<string>Nintendo Switch Package</string>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-package</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
@ -29,15 +29,15 @@
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-cartridge</string>
<key>UTTypeDescription</key>
<string>Nintendo Switch Cartridge</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.archive</string>
</array>
<key>UTTypeDescription</key>
<string>Nintendo Switch Cartridge</string>
<key>UTTypeIdentifier</key>
<string>com.nintendo.switch-cartridge</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>

View File

@ -21,7 +21,7 @@ struct MeloNXApp: App {
if bool {
print("Ryujinx Files Initialized Successfully")
} else {
exit(0)
// exit(0)
}
}
@ -29,7 +29,7 @@ struct MeloNXApp: App {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
InitializeRyujinx() { bool in
if !bool {
exit(0)
// exit(0)
}
}
@ -144,8 +144,9 @@ func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
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) ?? ""
print(text)
completion(text.contains("true"))
}
task.resume()