Add Virtual Controller from Pomelo
@ -7,8 +7,8 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -146,8 +146,8 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
|
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -218,7 +218,7 @@
|
|||||||
);
|
);
|
||||||
name = MeloNX;
|
name = MeloNX;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
4E80AA202CD705DD00029585 /* SDL */,
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
||||||
@ -303,9 +303,9 @@
|
|||||||
mainGroup = 4E80A9842CD6F54500029585;
|
mainGroup = 4E80A9842CD6F54500029585;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 60;
|
preferredProjectObjectVersion = 56;
|
||||||
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -612,6 +612,18 @@
|
|||||||
"$(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",
|
||||||
|
"$(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 = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -736,6 +748,18 @@
|
|||||||
"$(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",
|
||||||
|
"$(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 = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -861,21 +885,21 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
|
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.4.1;
|
minimumVersion = 1.5.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
4E80AA202CD705DD00029585 /* SDL */ = {
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
|
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
|
||||||
productName = SDL;
|
productName = SwiftUIJoystick;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
|
"originHash" : "0c2b07bd02731650383ed39e41433281bed19b2ed6cc7103e7daec7fb1d05e44",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "swiftsdl2",
|
"identity" : "swiftuijoystick",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/ctreffs/SwiftSDL2",
|
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
|
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
||||||
"version" : "1.4.1"
|
"version" : "1.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#ifndef RyujinxHeader
|
#ifndef RyujinxHeader
|
||||||
#define RyujinxHeader
|
#define RyujinxHeader
|
||||||
|
|
||||||
|
|
||||||
|
#import "SDL.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
@ -2,92 +2,109 @@
|
|||||||
// VirtualController.swift
|
// VirtualController.swift
|
||||||
// MeloNX
|
// MeloNX
|
||||||
//
|
//
|
||||||
// Created by Stossy11 on 28/11/2024.
|
// Created by Stossy11 on 8/12/2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public var controllerCallback: (() -> Void)?
|
class VirtualController {
|
||||||
|
private var instanceID: SDL_JoystickID = -1
|
||||||
var VirtualController: GCVirtualController!
|
private var controller: OpaquePointer?
|
||||||
func showVirtualController() {
|
|
||||||
let config = GCVirtualController.Configuration()
|
|
||||||
|
|
||||||
var controllere = [
|
init() {
|
||||||
GCInputLeftThumbstick,
|
setupVirtualController()
|
||||||
GCInputButtonA,
|
}
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
// GCInputRightThumbstick,
|
|
||||||
GCInputRightTrigger,
|
|
||||||
GCInputLeftTrigger,
|
|
||||||
GCInputLeftShoulder,
|
|
||||||
GCInputRightShoulder
|
|
||||||
]
|
|
||||||
|
|
||||||
if !UserDefaults.standard.bool(forKey: "RyuDemoControls") {
|
private func setupVirtualController() {
|
||||||
|
// Initialize SDL if not already initialized
|
||||||
|
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
|
||||||
|
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
|
||||||
|
}
|
||||||
|
|
||||||
controllere.append(GCInputRightThumbstick)
|
// Create virtual controller
|
||||||
}
|
instanceID = SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||||
|
if instanceID < 0 {
|
||||||
config.elements = Set(controllere)
|
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||||
|
return
|
||||||
VirtualController = GCVirtualController(configuration: config)
|
|
||||||
VirtualController.connect { err in
|
|
||||||
print("controller connect: \(String(describing: err))")
|
|
||||||
patchMakeKeyAndVisible()
|
|
||||||
if let controllerCallback {
|
|
||||||
controllerCallback()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitforcontroller() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
|
||||||
|
|
||||||
if let window = UIApplication.shared.windows.first {
|
// Open a game controller for the virtual joystick
|
||||||
// Function to recursively search for GCControllerView
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
func findGCControllerView(in view: UIView) -> UIView? {
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
// Check if current view is GCControllerView
|
|
||||||
if String(describing: type(of: view)) == "GCControllerView" {
|
if controller == nil {
|
||||||
return view
|
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||||
}
|
return
|
||||||
|
|
||||||
// Search through subviews
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let found = findGCControllerView(in: subview) {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let gcControllerView = findGCControllerView(in: window) {
|
|
||||||
// Found the GCControllerView
|
|
||||||
print("Found GCControllerView:", gcControllerView)
|
|
||||||
|
|
||||||
if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) {
|
|
||||||
theWindow.addSubview(gcControllerView)
|
|
||||||
|
|
||||||
theWindow.bringSubviewToFront(gcControllerView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
|
||||||
|
guard controller != nil else { return }
|
||||||
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
|
SDL_JoystickSetVirtualAxis(joystick, axis.rawValue, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func thumbstickMoved(_ stick: ThumbstickType, x: Float, y: Float) {
|
||||||
|
// Convert float values (-1.0 to 1.0) to SDL axis values (-32768 to 32767)
|
||||||
|
let scaledX = Sint16(x * 32767.0)
|
||||||
|
let scaledY = Sint16(y * 32767.0)
|
||||||
|
|
||||||
|
if stick == .right {
|
||||||
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
||||||
|
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTY.rawValue))
|
||||||
|
} else { // ThumbstickType.left
|
||||||
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTX.rawValue))
|
||||||
|
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTY.rawValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||||
|
guard controller != nil else { return }
|
||||||
|
|
||||||
|
print("Button: \(button.rawValue) {state: \(state)}")
|
||||||
|
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||||
|
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
|
let value: Int = (state == 1) ? 32767 : 0
|
||||||
|
updateAxisValue(value: Sint16(value), forAxis: axis)
|
||||||
|
} else {
|
||||||
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
|
SDL_JoystickSetVirtualButton(joystick, Int32(button.rawValue), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
if let controller = controller {
|
||||||
|
SDL_GameControllerClose(controller)
|
||||||
|
self.controller = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
enum VirtualControllerButton: Int {
|
||||||
func reconnectVirtualController() {
|
case B
|
||||||
VirtualController.disconnect()
|
case A
|
||||||
DispatchQueue.main.async {
|
case Y
|
||||||
VirtualController.connect { err in
|
case X
|
||||||
print("reconnected: err \(String(describing: err))")
|
case back
|
||||||
}
|
case guide
|
||||||
}
|
case start
|
||||||
|
case leftStick
|
||||||
|
case rightStick
|
||||||
|
case leftShoulder
|
||||||
|
case rightShoulder
|
||||||
|
case dPadUp
|
||||||
|
case dPadDown
|
||||||
|
case dPadLeft
|
||||||
|
case dPadRight
|
||||||
|
case leftTrigger
|
||||||
|
case rightTrigger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ThumbstickType: Int {
|
||||||
|
case left
|
||||||
|
case right
|
||||||
|
}
|
||||||
|
52
src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// VirtualController.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 28/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GameController
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
func waitforcontroller() {
|
||||||
|
if let window = theWindow {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Function to recursively search for GCControllerView
|
||||||
|
func findGCControllerView(in view: UIView) -> UIView? {
|
||||||
|
// Check if current view is GCControllerView
|
||||||
|
if String(describing: type(of: view)) == "ControllerView" {
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search through subviews
|
||||||
|
for subview in view.subviews {
|
||||||
|
if let found = findGCControllerView(in: subview) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerView = ControllerView()
|
||||||
|
|
||||||
|
let hostingController = UIHostingController(rootView: controllerView)
|
||||||
|
|
||||||
|
hostingController.view.frame = window.bounds // Set the frame of the SwiftUI view
|
||||||
|
hostingController.view.backgroundColor = .clear
|
||||||
|
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||||
|
if findGCControllerView(in: window) == nil {
|
||||||
|
window.addSubview(hostingController.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.bringSubviewToFront(hostingController.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -19,13 +19,12 @@ extension UIWindow {
|
|||||||
}
|
}
|
||||||
self.wdb_makeKeyAndVisible()
|
self.wdb_makeKeyAndVisible()
|
||||||
theWindow = self
|
theWindow = self
|
||||||
if #available(iOS 15.0, *) {
|
|
||||||
// reconnectVirtualController()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if let window = theWindow {
|
if UserDefaults.standard.bool(forKey: "isVirtualController") {
|
||||||
waitforcontroller()
|
if let window = theWindow {
|
||||||
|
waitforcontroller()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ struct iOSNav<Content: View>: View {
|
|||||||
class Ryujinx {
|
class Ryujinx {
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
|
|
||||||
|
let virtualController = VirtualController()
|
||||||
|
|
||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
|
|
||||||
static let shared = Ryujinx()
|
static let shared = Ryujinx()
|
||||||
|
@ -12,7 +12,7 @@ struct MeloNXApp: App {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
drmcheck()
|
// drmcheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ struct MeloNXApp: App {
|
|||||||
func drmcheck() {
|
func drmcheck() {
|
||||||
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
|
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
|
||||||
if let value = Bundle.main.infoDictionary?["MeloID"] as? String {
|
if let value = Bundle.main.infoDictionary?["MeloID"] as? String {
|
||||||
if let url = URL(string: "https://a0a5-175-32-163-60.ngrok-free.app/auth/\(value)/\(base64device)") {
|
if let url = URL(string: "https://950e-175-32-92-74.ngrok-free.app/auth/\(value)/\(base64device)") {
|
||||||
// Create a URLSession
|
// Create a URLSession
|
||||||
let session = URLSession.shared
|
let session = URLSession.shared
|
||||||
|
|
||||||
|
@ -6,10 +6,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SDL2
|
// import SDL2
|
||||||
import GameController
|
import GameController
|
||||||
import Darwin
|
import Darwin
|
||||||
import UIKit
|
import UIKit
|
||||||
|
// import SDL
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
struct MoltenVKSettings: Codable, Hashable {
|
||||||
let string: String
|
let string: String
|
||||||
@ -27,9 +28,9 @@ struct ContentView: View {
|
|||||||
@State private var config: Ryujinx.Configuration
|
@State private var config: Ryujinx.Configuration
|
||||||
@State private var settings: [MoltenVKSettings]
|
@State private var settings: [MoltenVKSettings]
|
||||||
@State private var isVirtualControllerActive: Bool = false
|
@State private var isVirtualControllerActive: Bool = false
|
||||||
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
@State var onscreencontroller: Controller = Controller(id: "", name: "")
|
@State var onscreencontroller: Controller = Controller(id: "", name: "")
|
||||||
@AppStorage("JIT") var isJITEnabled: Bool = false
|
@AppStorage("JIT") var isJITEnabled: Bool = false
|
||||||
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
init() {
|
init() {
|
||||||
@ -58,13 +59,6 @@ struct ContentView: View {
|
|||||||
mainMenuView
|
mainMenuView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: isVirtualControllerActive) { newValue in
|
|
||||||
if newValue {
|
|
||||||
createVirtualController()
|
|
||||||
} else {
|
|
||||||
destroyVirtualController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - View Components
|
// MARK: - View Components
|
||||||
@ -79,7 +73,6 @@ struct ContentView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
GameListView(startemu: $game)
|
GameListView(startemu: $game)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
createVirtualController()
|
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,59 +119,42 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Controller Management
|
|
||||||
private func createVirtualController() {
|
|
||||||
let configuration = GCVirtualController.Configuration()
|
|
||||||
configuration.elements = [
|
|
||||||
/*
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputRightThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
*/
|
|
||||||
]
|
|
||||||
|
|
||||||
virtualController = GCVirtualController(configuration: configuration)
|
|
||||||
virtualController?.connect()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func destroyVirtualController() {
|
|
||||||
virtualController?.disconnect()
|
|
||||||
virtualController = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
// MARK: - Helper Methods
|
||||||
|
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
|
||||||
private func initializeSDL() {
|
private func initializeSDL() {
|
||||||
DispatchQueue.main.async {
|
setMoltenVKSettings()
|
||||||
setMoltenVKSettings()
|
SDL_SetMainReady()
|
||||||
SDL_SetMainReady()
|
SDL_iPhoneSetEventPump(SDL_TRUE)
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE)
|
SDL_Init(SdlInitFlags)
|
||||||
SDL_Init(SDL_INIT_VIDEO)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupEmulation() {
|
private func setupEmulation() {
|
||||||
virtualController?.disconnect()
|
virtualController?.disconnect()
|
||||||
|
patchMakeKeyAndVisible()
|
||||||
|
|
||||||
controllerCallback = {
|
if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) {
|
||||||
DispatchQueue.main.async {
|
|
||||||
|
isVCA = true
|
||||||
start(displayid: 1)
|
|
||||||
}
|
start(displayid: 1)
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
isVCA = false
|
||||||
|
|
||||||
|
start(displayid: 1)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// controllerCallback!()
|
||||||
|
|
||||||
showVirtualController()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshControllersList() {
|
private func refreshControllersList() {
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
||||||
controllersList = Ryujinx.shared.getConnectedControllers()
|
controllersList = Ryujinx.shared.getConnectedControllers()
|
||||||
|
|
||||||
if let onscreen = controllersList.first(where: { $0.name.hasPrefix("Apple")}) {
|
if let onscreen = controllersList.first(where: { $0.name == "Virtual Controller" }) {
|
||||||
self.onscreencontroller = onscreen
|
self.onscreencontroller = onscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
267
src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
//
|
||||||
|
// ControllerView.swift
|
||||||
|
// Pomelo-V2
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 16/7/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import GameController
|
||||||
|
import SwiftUIJoystick
|
||||||
|
import CoreMotion
|
||||||
|
|
||||||
|
struct ControllerView: View {
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this works
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .start) // Adding the + button
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
ButtonView(button: .back) // Adding the - button
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, geometry.size.height / 3.2) // very broken
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// could be landscape
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
// gotta fuckin add + and - now
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
// Spacer()
|
||||||
|
VStack {
|
||||||
|
// Spacer()
|
||||||
|
ButtonView(button: .start) // Adding the + button
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
// Spacer()
|
||||||
|
ButtonView(button: .back) // Adding the - button
|
||||||
|
}
|
||||||
|
// Spacer()
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this work s
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// .padding(.bottom, geometry.size.height / 11) // also extremally broken (
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewLeft: View {
|
||||||
|
@State var width: CGFloat = 160
|
||||||
|
@State var height: CGFloat = 20
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .leftTrigger)
|
||||||
|
.padding(.horizontal)
|
||||||
|
ButtonView(button: .leftShoulder)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewRight: View {
|
||||||
|
@State var width: CGFloat = 160
|
||||||
|
@State var height: CGFloat = 20
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .rightShoulder)
|
||||||
|
.padding(.horizontal)
|
||||||
|
ButtonView(button: .rightTrigger)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DPadView: View {
|
||||||
|
@State var size: CGFloat = 145
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .dPadUp)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .dPadLeft)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .dPadRight)
|
||||||
|
}
|
||||||
|
ButtonView(button: .dPadDown)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
size *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ABXYView: View {
|
||||||
|
@State var size: CGFloat = 145
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .X)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .Y)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .A)
|
||||||
|
}
|
||||||
|
ButtonView(button: .B)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
size *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ButtonView: View {
|
||||||
|
var button: VirtualControllerButton
|
||||||
|
@State var width: CGFloat = 45
|
||||||
|
@State var height: CGFloat = 45
|
||||||
|
@State var isPressed = false
|
||||||
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Image(systemName: buttonText)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
|
||||||
|
.opacity(isPressed ? 0.4 : 0.7)
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { _ in
|
||||||
|
if !self.isPressed {
|
||||||
|
self.isPressed = true
|
||||||
|
Ryujinx.shared.virtualController.setButtonState(1, for: button)
|
||||||
|
Haptics.shared.play(.heavy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
self.isPressed = false
|
||||||
|
Ryujinx.shared.virtualController.setButtonState(0, for: button)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onAppear() {
|
||||||
|
if button == .leftTrigger || button == .rightTrigger || button == .leftShoulder || button == .rightShoulder {
|
||||||
|
width = 65
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if button == .back || button == .start || button == .guide {
|
||||||
|
width = 35
|
||||||
|
height = 35
|
||||||
|
}
|
||||||
|
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private var buttonText: String {
|
||||||
|
switch button {
|
||||||
|
case .A:
|
||||||
|
return "a.circle.fill"
|
||||||
|
case .B:
|
||||||
|
return "b.circle.fill"
|
||||||
|
case .X:
|
||||||
|
return "x.circle.fill"
|
||||||
|
case .Y:
|
||||||
|
return "y.circle.fill"
|
||||||
|
case .dPadUp:
|
||||||
|
return "arrowtriangle.up.circle.fill"
|
||||||
|
case .dPadDown:
|
||||||
|
return "arrowtriangle.down.circle.fill"
|
||||||
|
case .dPadLeft:
|
||||||
|
return "arrowtriangle.left.circle.fill"
|
||||||
|
case .dPadRight:
|
||||||
|
return "arrowtriangle.right.circle.fill"
|
||||||
|
case .leftTrigger:
|
||||||
|
return"zl.rectangle.roundedtop.fill"
|
||||||
|
case .rightTrigger:
|
||||||
|
return "zr.rectangle.roundedtop.fill"
|
||||||
|
case .leftShoulder:
|
||||||
|
return "l.rectangle.roundedbottom.fill"
|
||||||
|
case .rightShoulder:
|
||||||
|
return "r.rectangle.roundedbottom.fill"
|
||||||
|
case .start:
|
||||||
|
return "plus.circle.fill" // System symbol for +
|
||||||
|
case .back:
|
||||||
|
return "minus.circle.fill" // System symbol for -
|
||||||
|
case .guide:
|
||||||
|
return "house.circle.fill"
|
||||||
|
// This should be all the cases
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
27
src/MeloNX/MeloNX/Views/ControllerView/Haptics/Haptics.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Haptics.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 11/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class Haptics {
|
||||||
|
static let shared = Haptics()
|
||||||
|
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
|
print("haptics")
|
||||||
|
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBase@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBase@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBase@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 8.6 KiB |
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandle@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandle@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandle@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBaseCustom@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBaseCustom@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickBaseCustom@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 6.4 KiB |
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandleCustom@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandleCustom@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "JoyStickHandleCustom@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 54 KiB |
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// JoystickView.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 30/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftUIJoystick
|
||||||
|
|
||||||
|
public struct Joystick: View {
|
||||||
|
@State var iscool: Bool? = nil
|
||||||
|
|
||||||
|
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||||
|
var dragDiameter: CGFloat {
|
||||||
|
var selfs = CGFloat(160)
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
return selfs * 1.2
|
||||||
|
}
|
||||||
|
return selfs
|
||||||
|
}
|
||||||
|
private let shape: JoystickShape = .circle
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack{
|
||||||
|
JoystickBuilder(
|
||||||
|
monitor: self.joystickMonitor,
|
||||||
|
width: self.dragDiameter,
|
||||||
|
shape: .circle,
|
||||||
|
background: {
|
||||||
|
Text("")
|
||||||
|
.hidden()
|
||||||
|
},
|
||||||
|
foreground: {
|
||||||
|
Circle().fill(Color.gray)
|
||||||
|
.opacity(0.7)
|
||||||
|
},
|
||||||
|
locksInPlace: false)
|
||||||
|
.onChange(of: self.joystickMonitor.xyPoint) { newValue in
|
||||||
|
let scaledX = Float(newValue.x)
|
||||||
|
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||||
|
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||||
|
|
||||||
|
if iscool != nil {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: scaledX, y: scaledY)
|
||||||
|
} else {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: scaledX, y: scaledY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|