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 471ce6b6f..faefc819f 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/Swift/Controller/VirtualController.swift b/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift index 6c451f2cd..7809ee7ec 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Controller/VirtualController.swift @@ -6,11 +6,15 @@ // import Foundation +import CoreHaptics +import UIKit class VirtualController { private var instanceID: SDL_JoystickID = -1 private var controller: OpaquePointer? + public let controllername = "MeloNX Touch Controller" + init() { setupVirtualController() } @@ -22,7 +26,45 @@ class VirtualController { } // Create virtual controller - instanceID = SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1) + var joystickDesc = SDL_VirtualJoystickDesc( + version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION), + type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), + naxes: 6, + nbuttons: 15, + nhats: 1, + vendor_id: 0, + product_id: 0, + padding: 0, + button_mask: 0, + axis_mask: 0, + name: controllername.withCString { $0 }, + userdata: nil, + Update: { userdata in + // Update joystick state here + }, + SetPlayerIndex: { userdata, playerIndex in + print("Player index set to \(playerIndex)") + }, + Rumble: { userdata, lowFreq, highFreq in + print("Rumble with \(lowFreq), \(highFreq)") + VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq)) + return 0 + }, + RumbleTriggers: { userdata, leftRumble, rightRumble in + print("Trigger rumble with \(leftRumble), \(rightRumble)") + return 0 + }, + SetLED: { userdata, red, green, blue in + print("Set LED to RGB(\(red), \(green), \(blue))") + return 0 + }, + SendEffect: { userdata, data, size in + print("Effect sent with size \(size)") + return 0 + } + ) + + instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// 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 @@ -38,16 +80,58 @@ class VirtualController { } } + static func rumble(lowFreq: Float, highFreq: Float) { + do { + // Low-frequency haptic pattern + let lowFreqPattern = try CHHapticPattern(events: [ + CHHapticEvent(eventType: .hapticTransient, parameters: [ + CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq), + CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) + ], relativeTime: 0, duration: 0.2) + ], parameters: []) + + // High-frequency haptic pattern + let highFreqPattern = try CHHapticPattern(events: [ + CHHapticEvent(eventType: .hapticTransient, parameters: [ + CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq), + CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) + ], relativeTime: 0.2, duration: 0.2) + ], parameters: []) + + // Create and start the haptic engine + let engine = try CHHapticEngine() + try engine.start() + + // Create and play the low-frequency player + let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern) + try lowFreqPlayer.start(atTime: 0) + + // Create and play the high-frequency player after a short delay + let highFreqPlayer = try engine.makePlayer(with: highFreqPattern) + try highFreqPlayer.start(atTime: 0.2) + + } catch { + print("Error creating haptic patterns: \(error)") + } + } + + 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) { + func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) { // 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) + var scaleFactor = 32767.0 + if UIDevice.current.systemName.contains("iPadOS") { + scaleFactor /= (160 * 1.2) + } else { + scaleFactor /= 160 + } + let scaledX = Int16(min(32767.0, max(-32768.0, x * scaleFactor))) + let scaledY = Int16(min(32767.0, max(-32768.0, y * scaleFactor))) if stick == .right { updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue)) diff --git a/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift b/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift index f5f0b91f7..eb02333eb 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Controller/WaitforVC.swift @@ -33,13 +33,16 @@ func waitforcontroller() { } let controllerView = ControllerView() - let hostingController = UIHostingController(rootView: controllerView) - - hostingController.view.frame = window.bounds // Set the frame of the SwiftUI view + + // Create the custom container + let containerView = TransparentHostingContainerView(frame: window.bounds) + containerView.backgroundColor = .clear + + hostingController.view.frame = containerView.bounds hostingController.view.backgroundColor = .clear - - + containerView.addSubview(hostingController.view) + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in if findGCControllerView(in: window) == nil { window.addSubview(hostingController.view) @@ -50,3 +53,14 @@ func waitforcontroller() { } } + + +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) + + // Return nil if the touch is outside visible content (passes through to views below) + return view === self ? nil : view + } +} diff --git a/src/MeloNX/MeloNX/Views/ContentView.swift b/src/MeloNX/MeloNX/Views/ContentView.swift index ae3e752b8..2bec99b62 100644 --- a/src/MeloNX/MeloNX/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/Views/ContentView.swift @@ -154,7 +154,7 @@ struct ContentView: View { Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in controllersList = Ryujinx.shared.getConnectedControllers() - if let onscreen = controllersList.first(where: { $0.name == "Virtual Controller" }) { + if let onscreen = controllersList.first(where: { $0.name == Ryujinx.shared.virtualController.controllername }) { self.onscreencontroller = onscreen } diff --git a/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift b/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift index bdbf1e846..0301b75cf 100644 --- a/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift +++ b/src/MeloNX/MeloNX/Views/ControllerView/ControllerView.swift @@ -87,6 +87,7 @@ struct ControllerView: View { } } } + .padding() } } diff --git a/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift b/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift index 70b190ea7..dc1db3d8c 100644 --- a/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift +++ b/src/MeloNX/MeloNX/Views/ControllerView/Joystick/JoystickView.swift @@ -39,13 +39,13 @@ public struct Joystick: View { 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 :/ + 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) + Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y) } else { - Ryujinx.shared.virtualController.thumbstickMoved(.left, x: scaledX, y: scaledY) + Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y) } } }