diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index f3984372d..9888d6112 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; }; 4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; }; - 4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -146,8 +146,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */, 4E551F202CF128540096A2DF /* GameController.framework in Frameworks */, - 4E80AA212CD705DD00029585 /* SDL in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -218,7 +218,7 @@ ); name = MeloNX; packageProductDependencies = ( - 4E80AA202CD705DD00029585 /* SDL */, + 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */, ); productName = MeloNX; productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */; @@ -303,9 +303,9 @@ mainGroup = 4E80A9842CD6F54500029585; minimizedProjectReferenceProxies = 1; packageReferences = ( - 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */, + 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */, ); - preferredProjectObjectVersion = 60; + preferredProjectObjectVersion = 56; productRefGroup = 4E80A98E2CD6F54500029585 /* Products */; projectDirPath = ""; 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", ); MARKETING_VERSION = 0.0.8; 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", ); MARKETING_VERSION = 0.0.8; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -861,21 +885,21 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = { + 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ctreffs/SwiftSDL2"; + repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.4.1; + minimumVersion = 1.5.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 4E80AA202CD705DD00029585 /* SDL */ = { + 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = { isa = XCSwiftPackageProductDependency; - package = 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */; - productName = SDL; + package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */; + productName = SwiftUIJoystick; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3e2cd50f1..6af5f2dd7 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5", + "originHash" : "0c2b07bd02731650383ed39e41433281bed19b2ed6cc7103e7daec7fb1d05e44", "pins" : [ { - "identity" : "swiftsdl2", + "identity" : "swiftuijoystick", "kind" : "remoteSourceControl", - "location" : "https://github.com/ctreffs/SwiftSDL2", + "location" : "https://github.com/michael94ellis/SwiftUIJoystick", "state" : { - "revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a", - "version" : "1.4.1" + "revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65", + "version" : "1.5.0" } } ], 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 0c55c3f3f..15ed88ee1 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/Core/Headers/Ryujinx-Header.h b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h index 89a1b6329..185a2b83c 100644 --- a/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h +++ b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h @@ -8,6 +8,9 @@ #ifndef RyujinxHeader #define RyujinxHeader + +#import "SDL.h" + #ifdef __cplusplus extern "C" { #endif diff --git a/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift b/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift index 66b580191..6c451f2cd 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift @@ -2,92 +2,109 @@ // VirtualController.swift // MeloNX // -// Created by Stossy11 on 28/11/2024. +// Created by Stossy11 on 8/12/2024. // import Foundation -import GameController -import UIKit -public var controllerCallback: (() -> Void)? - -var VirtualController: GCVirtualController! -func showVirtualController() { - let config = GCVirtualController.Configuration() +class VirtualController { + private var instanceID: SDL_JoystickID = -1 + private var controller: OpaquePointer? - var controllere = [ - GCInputLeftThumbstick, - GCInputButtonA, - GCInputButtonB, - GCInputButtonX, - GCInputButtonY, - // GCInputRightThumbstick, - GCInputRightTrigger, - GCInputLeftTrigger, - GCInputLeftShoulder, - GCInputRightShoulder - ] + init() { + setupVirtualController() + } - 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) - } - - config.elements = Set(controllere) - - VirtualController = GCVirtualController(configuration: config) - VirtualController.connect { err in - print("controller connect: \(String(describing: err))") - patchMakeKeyAndVisible() - if let controllerCallback { - controllerCallback() + // Create virtual controller + instanceID = SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1) + if instanceID < 0 { + print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))") + return } - } -} - -func waitforcontroller() { - Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in - if let window = UIApplication.shared.windows.first { - // Function to recursively search for GCControllerView - func findGCControllerView(in view: UIView) -> UIView? { - // Check if current view is GCControllerView - if String(describing: type(of: view)) == "GCControllerView" { - return view - } - - // 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) - } - } + // Open a game controller for the virtual joystick + let joystick = SDL_JoystickFromInstanceID(instanceID) + controller = SDL_GameControllerOpen(Int32(instanceID)) + + if controller == nil { + print("Failed to create virtual controller: \(String(cString: SDL_GetError()))") + return } } + + 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, *) -func reconnectVirtualController() { - VirtualController.disconnect() - DispatchQueue.main.async { - VirtualController.connect { err in - print("reconnected: err \(String(describing: err))") - } - } +enum VirtualControllerButton: Int { + case B + case A + case Y + case X + 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 +} diff --git a/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift b/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift new file mode 100644 index 000000000..f5f0b91f7 --- /dev/null +++ b/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift @@ -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) + } + + } +} diff --git a/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift b/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift index b7e870165..271a05e37 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift @@ -19,13 +19,12 @@ extension UIWindow { } self.wdb_makeKeyAndVisible() theWindow = self - if #available(iOS 15.0, *) { - // reconnectVirtualController() - } - if let window = theWindow { - waitforcontroller() + if UserDefaults.standard.bool(forKey: "isVirtualController") { + if let window = theWindow { + waitforcontroller() + } } } } diff --git a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift index 3bfa8d386..2acaf9c20 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift @@ -32,6 +32,8 @@ struct iOSNav: View { class Ryujinx { private var isRunning = false + let virtualController = VirtualController() + @Published var controllerMap: [Controller] = [] static let shared = Ryujinx() diff --git a/src/MeloNX/MeloNX/MeloNXApp.swift b/src/MeloNX/MeloNX/MeloNXApp.swift index f71c7a89c..612e30e30 100644 --- a/src/MeloNX/MeloNX/MeloNXApp.swift +++ b/src/MeloNX/MeloNX/MeloNXApp.swift @@ -12,7 +12,7 @@ struct MeloNXApp: App { init() { DispatchQueue.main.async { - drmcheck() + // drmcheck() } } @@ -27,7 +27,7 @@ struct MeloNXApp: App { func drmcheck() { 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 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 let session = URLSession.shared diff --git a/src/MeloNX/MeloNX/Views/ContentView.swift b/src/MeloNX/MeloNX/Views/ContentView.swift index e1d7b1f48..ae3e752b8 100644 --- a/src/MeloNX/MeloNX/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/Views/ContentView.swift @@ -6,10 +6,11 @@ // import SwiftUI -import SDL2 +// import SDL2 import GameController import Darwin import UIKit +// import SDL struct MoltenVKSettings: Codable, Hashable { let string: String @@ -27,9 +28,9 @@ struct ContentView: View { @State private var config: Ryujinx.Configuration @State private var settings: [MoltenVKSettings] @State private var isVirtualControllerActive: Bool = false + @AppStorage("isVirtualController") var isVCA: Bool = true @State var onscreencontroller: Controller = Controller(id: "", name: "") @AppStorage("JIT") var isJITEnabled: Bool = false - @AppStorage("ignoreJIT") var ignoreJIT: Bool = false // MARK: - Initialization init() { @@ -58,13 +59,6 @@ struct ContentView: View { mainMenuView } } - .onChange(of: isVirtualControllerActive) { newValue in - if newValue { - createVirtualController() - } else { - destroyVirtualController() - } - } } // MARK: - View Components @@ -79,7 +73,6 @@ struct ContentView: View { HStack { GameListView(startemu: $game) .onAppear { - createVirtualController() 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 + var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; private func initializeSDL() { - DispatchQueue.main.async { - setMoltenVKSettings() - SDL_SetMainReady() - SDL_iPhoneSetEventPump(SDL_TRUE) - SDL_Init(SDL_INIT_VIDEO) - } + setMoltenVKSettings() + SDL_SetMainReady() + SDL_iPhoneSetEventPump(SDL_TRUE) + SDL_Init(SdlInitFlags) } private func setupEmulation() { virtualController?.disconnect() + patchMakeKeyAndVisible() - controllerCallback = { - DispatchQueue.main.async { - - start(displayid: 1) - } + if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) { + + isVCA = true + + start(displayid: 1) + + + } else { + isVCA = false + + start(displayid: 1) + + } - - - showVirtualController() + // controllerCallback!() } private func refreshControllersList() { Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in 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 } diff --git a/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift b/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift new file mode 100644 index 000000000..bdbf1e846 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift @@ -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 "" + } + } +} + + diff --git a/src/MeloNX/MeloNX/Views/ControllerView/Haptics/Haptics.swift b/src/MeloNX/MeloNX/Views/ControllerView/Haptics/Haptics.swift new file mode 100644 index 000000000..5dd555815 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/Haptics/Haptics.swift @@ -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) + } +} + + diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/Contents.json b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/Contents.json b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/Contents.json new file mode 100644 index 000000000..3a763d2f9 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@1x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@1x.png new file mode 100644 index 000000000..2e3903652 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@1x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@2x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@2x.png new file mode 100644 index 000000000..49a14c122 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@2x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@3x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@3x.png new file mode 100644 index 000000000..35851e642 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultBase.imageset/JoyStickBase@3x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/Contents.json b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/Contents.json new file mode 100644 index 000000000..6c1bf409f --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@1x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@1x.png new file mode 100644 index 000000000..d4555a959 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@1x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@2x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@2x.png new file mode 100644 index 000000000..93c135334 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@2x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@3x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@3x.png new file mode 100644 index 000000000..25e602e3f Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/DefaultHandle.imageset/JoyStickHandle@3x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/Contents.json b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/Contents.json new file mode 100644 index 000000000..6f901e4e8 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@1x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@1x.png new file mode 100644 index 000000000..113ccadc4 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@1x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@2x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@2x.png new file mode 100644 index 000000000..bbdf7e4cd Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@2x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@3x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@3x.png new file mode 100644 index 000000000..949788e5f Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyBase.imageset/JoyStickBaseCustom@3x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/Contents.json b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/Contents.json new file mode 100644 index 000000000..4091d8b19 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@1x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@1x.png new file mode 100644 index 000000000..9fb451158 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@1x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@2x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@2x.png new file mode 100644 index 000000000..2c0f0d930 Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@2x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@3x.png b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@3x.png new file mode 100644 index 000000000..edc88f2ee Binary files /dev/null and b/src/MeloNX/MeloNX/Views/ControllerView/JoyStickView/Resources/Assets.xcassets/FancyHandle.imageset/JoyStickHandleCustom@3x.png differ diff --git a/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift b/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift new file mode 100644 index 000000000..70b190ea7 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift @@ -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) + } + } + } + } +}