forked from MeloNX/MeloNX
Implement new Virtual Controller Joystick. Add Game Requirements
This commit is contained in:
parent
8917ebf708
commit
2b7e29fa21
@ -24,7 +24,6 @@
|
|||||||
/* End PBXAggregateTarget section */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
|
||||||
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||||
@ -203,7 +202,6 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
|
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
|
||||||
@ -301,7 +299,6 @@
|
|||||||
);
|
);
|
||||||
name = MeloNX;
|
name = MeloNX;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
|
|
||||||
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
@ -393,7 +390,6 @@
|
|||||||
mainGroup = 4E80A9842CD6F54500029585;
|
mainGroup = 4E80A9842CD6F54500029585;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
|
|
||||||
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 56;
|
preferredProjectObjectVersion = 56;
|
||||||
@ -721,6 +717,12 @@
|
|||||||
"$(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",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -863,6 +865,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 = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -955,6 +969,12 @@
|
|||||||
"$(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",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1097,6 +1117,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 = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -1298,14 +1330,6 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 1.5.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
||||||
@ -1317,11 +1341,6 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
|
|
||||||
productName = SwiftUIJoystick;
|
|
||||||
};
|
|
||||||
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "d611b071fbe94fdc9900a07a218340eab4ce2c3c7168bf6542f2830c0400a72b",
|
"originHash" : "fedf09a893a63378a2e53f631cd833ae83a0c9ee7338eb8d153b04fd34aaf805",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "swiftsvg",
|
"identity" : "swiftsvg",
|
||||||
@ -9,15 +9,6 @@
|
|||||||
"branch" : "master",
|
"branch" : "master",
|
||||||
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swiftuijoystick",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
|
||||||
"version" : "1.5.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 3
|
||||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1620"
|
LastUpgradeVersion = "1620"
|
||||||
version = "1.7">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES"
|
||||||
@ -62,10 +62,11 @@
|
|||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
|
debugXPCServices = "NO"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
allowLocationSimulation = "YES"
|
allowLocationSimulation = "YES"
|
||||||
viewDebuggingEnabled = "No"
|
queueDebuggingEnabled = "No"
|
||||||
consoleMode = "0"
|
consoleMode = "0"
|
||||||
structuredConsoleMode = "2">
|
structuredConsoleMode = "2">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
|
@ -131,10 +131,8 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
|
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
|
||||||
let scaleFactor = 32767.0 / 160
|
let scaledX = Int16(min(32767.0, max(-32768.0, x * 32767.0)))
|
||||||
|
let scaledY = Int16(min(32767.0, max(-32768.0, y * 32767.0)))
|
||||||
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 {
|
if stick == .right {
|
||||||
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
||||||
|
@ -38,6 +38,7 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
@Published var metalLayer: CAMetalLayer? = nil
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
|
@Published var isPortrait = false
|
||||||
@Published var firmwareversion = "0"
|
@Published var firmwareversion = "0"
|
||||||
@Published var emulationUIView: MeloMTKView? = nil
|
@Published var emulationUIView: MeloMTKView? = nil
|
||||||
@Published var config: Ryujinx.Configuration? = nil
|
@Published var config: Ryujinx.Configuration? = nil
|
||||||
@ -510,9 +511,16 @@ class Ryujinx : ObservableObject {
|
|||||||
print("[Ryujinx] \(message)")
|
print("[Ryujinx] \(message)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateOrientation() -> Bool {
|
||||||
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let window = windowScene.windows.first {
|
||||||
|
return (window.bounds.size.height > window.bounds.size.width)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func ryuIsJITEnabled() {
|
func ryuIsJITEnabled() {
|
||||||
jitenabled = isJITEnabled()
|
jitenabled = isJITEnabled()
|
||||||
print("JIT \(jitenabled)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GameController
|
import GameController
|
||||||
import SwiftUIJoystick
|
|
||||||
import CoreMotion
|
import CoreMotion
|
||||||
|
|
||||||
struct ControllerView: View {
|
struct ControllerView: View {
|
||||||
@ -15,6 +14,8 @@ struct ControllerView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") private var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") private var controllerScale: Double = 1.0
|
||||||
@AppStorage("stick-button") private var stickButton = false
|
@AppStorage("stick-button") private var stickButton = false
|
||||||
@State private var isPortrait = true
|
@State private var isPortrait = true
|
||||||
|
@State var hideDpad = false
|
||||||
|
@State var hideABXY = false
|
||||||
@Environment(\.verticalSizeClass) var verticalSizeClass
|
@Environment(\.verticalSizeClass) var verticalSizeClass
|
||||||
|
|
||||||
|
|
||||||
@ -45,16 +46,22 @@ struct ControllerView: View {
|
|||||||
VStack(spacing: 15) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewLeft()
|
ShoulderButtonsViewLeft()
|
||||||
ZStack {
|
ZStack {
|
||||||
Joystick()
|
JoystickController(showBackground: $hideDpad)
|
||||||
DPadView()
|
if !hideDpad {
|
||||||
|
DPadView()
|
||||||
|
.animation(.easeInOut(duration: 0.2), value: hideDpad)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(spacing: 15) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewRight()
|
ShoulderButtonsViewRight()
|
||||||
ZStack {
|
ZStack {
|
||||||
Joystick(iscool: true)
|
JoystickController(iscool: true, showBackground: $hideABXY)
|
||||||
ABXYView()
|
if !hideABXY {
|
||||||
|
ABXYView()
|
||||||
|
.animation(.easeInOut(duration: 0.2), value: hideABXY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,11 +88,14 @@ struct ControllerView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
VStack(spacing: 15) {
|
VStack(spacing: 20) {
|
||||||
ShoulderButtonsViewLeft()
|
ShoulderButtonsViewLeft()
|
||||||
ZStack {
|
ZStack {
|
||||||
Joystick()
|
JoystickController(showBackground: $hideDpad)
|
||||||
DPadView()
|
if !hideDpad {
|
||||||
|
DPadView()
|
||||||
|
.animation(.easeInOut(duration: 0.2), value: hideDpad)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +105,14 @@ struct ControllerView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: 15) {
|
VStack(spacing: 20) {
|
||||||
ShoulderButtonsViewRight()
|
ShoulderButtonsViewRight()
|
||||||
ZStack {
|
ZStack {
|
||||||
Joystick(iscool: true)
|
JoystickController(iscool: true, showBackground: $hideABXY)
|
||||||
ABXYView()
|
if !hideABXY {
|
||||||
|
ABXYView()
|
||||||
|
.animation(.easeInOut(duration: 0.2), value: hideABXY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,11 +212,11 @@ struct DPadView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 5) {
|
VStack(spacing: 7) {
|
||||||
ButtonView(button: .dPadUp)
|
ButtonView(button: .dPadUp)
|
||||||
HStack(spacing: 20) {
|
HStack(spacing: 22) {
|
||||||
ButtonView(button: .dPadLeft)
|
ButtonView(button: .dPadLeft)
|
||||||
Spacer(minLength: 20)
|
Spacer(minLength: 22)
|
||||||
ButtonView(button: .dPadRight)
|
ButtonView(button: .dPadRight)
|
||||||
}
|
}
|
||||||
ButtonView(button: .dPadDown)
|
ButtonView(button: .dPadDown)
|
||||||
@ -224,11 +237,11 @@ struct ABXYView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 5) {
|
VStack(spacing: 7) {
|
||||||
ButtonView(button: .X)
|
ButtonView(button: .X)
|
||||||
HStack(spacing: 20) {
|
HStack(spacing: 22) {
|
||||||
ButtonView(button: .Y)
|
ButtonView(button: .Y)
|
||||||
Spacer(minLength: 20)
|
Spacer(minLength: 22)
|
||||||
ButtonView(button: .A)
|
ButtonView(button: .A)
|
||||||
}
|
}
|
||||||
ButtonView(button: .B)
|
ButtonView(button: .B)
|
||||||
@ -259,19 +272,25 @@ struct ButtonView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width, height: height)
|
.frame(width: width, height: height)
|
||||||
.foregroundColor(true ? Color.white.opacity(0.9) : Color.black.opacity(0.9))
|
.foregroundColor(true ? Color.white.opacity(0.5) : Color.black.opacity(0.5))
|
||||||
.background(
|
.background(
|
||||||
Group {
|
Group {
|
||||||
if !button.isTrigger {
|
if !button.isTrigger && button != .leftStick && button != .rightStick {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.fill(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: width * 1.25, height: height * 1.25)
|
||||||
} else {
|
} else if button == .leftStick || button == .rightStick {
|
||||||
Image(systemName: buttonText)
|
Image(systemName: buttonText)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: width * 1.25, height: height * 1.25)
|
||||||
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
||||||
|
} else if button.isTrigger {
|
||||||
|
Image(systemName: "" + String(turntobutton(buttonText)))
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: width * 1.25, height: height * 1.25)
|
||||||
|
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -286,10 +305,24 @@ struct ButtonView: View {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
print(String(buttonText.dropFirst(2)))
|
||||||
configureSizeForButton()
|
configureSizeForButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func turntobutton(_ string: String) -> String {
|
||||||
|
var sting = string
|
||||||
|
if string.hasPrefix("zl") || string.hasPrefix("zr") {
|
||||||
|
sting = String(string.dropFirst(3))
|
||||||
|
} else {
|
||||||
|
sting = String(string.dropFirst(2))
|
||||||
|
}
|
||||||
|
sting = sting.replacingOccurrences(of: "rectangle", with: "button")
|
||||||
|
sting = sting.replacingOccurrences(of: ".fill", with: ".horizontal.fill")
|
||||||
|
|
||||||
|
return sting
|
||||||
|
}
|
||||||
|
|
||||||
private func handleButtonPress() {
|
private func handleButtonPress() {
|
||||||
if !isPressed {
|
if !isPressed {
|
||||||
isPressed = true
|
isPressed = true
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
//
|
||||||
|
// Joystick.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct Joystick: View {
|
||||||
|
@Binding var position: CGPoint
|
||||||
|
@State var joystickSize: CGFloat
|
||||||
|
var boundarySize: CGFloat
|
||||||
|
|
||||||
|
@State private var offset: CGSize = .zero
|
||||||
|
@Binding var showBackground: Bool
|
||||||
|
|
||||||
|
var dragGesture: some Gesture {
|
||||||
|
DragGesture()
|
||||||
|
.onChanged { value in
|
||||||
|
withAnimation(.easeIn) {
|
||||||
|
showBackground = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let translation = value.translation
|
||||||
|
let distance = sqrt(translation.width * translation.width + translation.height * translation.height)
|
||||||
|
let maxRadius = (boundarySize - joystickSize) / 2
|
||||||
|
let extendedRadius = maxRadius + (joystickSize / 2)
|
||||||
|
|
||||||
|
if distance <= extendedRadius {
|
||||||
|
offset = translation
|
||||||
|
} else {
|
||||||
|
let angle = atan2(translation.height, translation.width)
|
||||||
|
offset = CGSize(width: cos(angle) * extendedRadius, height: sin(angle) * extendedRadius)
|
||||||
|
}
|
||||||
|
position = CGPoint(x: offset.width / extendedRadius, y: offset.height / extendedRadius)
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
offset = .zero // Reset to center
|
||||||
|
position = .zero
|
||||||
|
withAnimation(.easeOut) { // Smooth animation when hiding the background
|
||||||
|
showBackground = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.clear.opacity(0))
|
||||||
|
.frame(width: boundarySize, height: boundarySize)
|
||||||
|
|
||||||
|
|
||||||
|
if showBackground {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.gray.opacity(0.4))
|
||||||
|
.frame(width: boundarySize, height: boundarySize)
|
||||||
|
.animation(.easeInOut(duration: 0.05), value: showBackground)
|
||||||
|
.transition(.scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle()
|
||||||
|
.fill(Color.white.opacity(0.5))
|
||||||
|
.frame(width: joystickSize, height: joystickSize)
|
||||||
|
.background(
|
||||||
|
Circle()
|
||||||
|
.fill(Color.gray.opacity(0.3))
|
||||||
|
.frame(width: joystickSize * 1.25, height: joystickSize * 1.25)
|
||||||
|
)
|
||||||
|
.offset(offset)
|
||||||
|
.gesture(dragGesture)
|
||||||
|
}
|
||||||
|
.frame(width: boundarySize, height: boundarySize)
|
||||||
|
.onChange(of: showBackground) { newValue in
|
||||||
|
if newValue {
|
||||||
|
joystickSize *= 1.4
|
||||||
|
} else {
|
||||||
|
joystickSize = (boundarySize * 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftUIJoystick
|
|
||||||
|
|
||||||
public struct Joystick: View {
|
struct JoystickController: View {
|
||||||
@State var iscool: Bool? = nil
|
@State var iscool: Bool? = nil
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
@Binding var showBackground: Bool
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
@State var position: CGPoint = CGPoint(x: 0, y: 0)
|
||||||
var dragDiameter: CGFloat {
|
var dragDiameter: CGFloat {
|
||||||
var selfs = CGFloat(160)
|
var selfs = CGFloat(160)
|
||||||
selfs *= controllerScale
|
selfs *= controllerScale
|
||||||
@ -23,39 +23,21 @@ public struct Joystick: View {
|
|||||||
|
|
||||||
return selfs
|
return selfs
|
||||||
}
|
}
|
||||||
private let shape: JoystickShape = .circle
|
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack{
|
VStack {
|
||||||
JoystickBuilder(
|
Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
|
||||||
monitor: self.joystickMonitor,
|
.onChange(of: position) { newValue in
|
||||||
width: self.dragDiameter,
|
let scaledX = Float(newValue.x)
|
||||||
shape: .circle,
|
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||||
background: {
|
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||||
Text("")
|
|
||||||
.hidden()
|
if iscool != nil {
|
||||||
},
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||||
foreground: {
|
} else {
|
||||||
Circle()
|
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
||||||
.fill(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.7))
|
}
|
||||||
.background(
|
|
||||||
Circle()
|
|
||||||
.fill(colorScheme == .dark ? Color.gray.opacity(0.3) : Color.gray.opacity(0.2))
|
|
||||||
.frame(width: (dragDiameter / 4) * 1.2, height: (dragDiameter / 4) * 1.2)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
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: newValue.x, y: newValue.y)
|
|
||||||
} else {
|
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// GameRequirementsCache.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class GameCompatibiliryCache {
|
||||||
|
static let shared = GameCompatibiliryCache()
|
||||||
|
private let cacheKey = "gameRequirementsCache"
|
||||||
|
private let timestampKey = "gameRequirementsCacheTimestamp"
|
||||||
|
|
||||||
|
private let cacheDuration: TimeInterval = Double.random(in: 3...5) * 24 * 60 * 60 // Randomly pick 3-5 days
|
||||||
|
|
||||||
|
func getCachedData() -> [GameRequirements]? {
|
||||||
|
guard let cachedData = UserDefaults.standard.data(forKey: cacheKey),
|
||||||
|
let timestamp = UserDefaults.standard.object(forKey: timestampKey) as? Date else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeElapsed = Date().timeIntervalSince(timestamp)
|
||||||
|
if timeElapsed > cacheDuration {
|
||||||
|
clearCache()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return try? JSONDecoder().decode([GameRequirements].self, from: cachedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCachedData(_ data: [GameRequirements]) {
|
||||||
|
if let encodedData = try? JSONEncoder().encode(data) {
|
||||||
|
UserDefaults.standard.set(encodedData, forKey: cacheKey)
|
||||||
|
UserDefaults.standard.set(Date(), forKey: timestampKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearCache() {
|
||||||
|
UserDefaults.standard.removeObject(forKey: cacheKey)
|
||||||
|
UserDefaults.standard.removeObject(forKey: timestampKey)
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ struct GameLibraryView: View {
|
|||||||
@State var isSelectingGameDLC: Bool = false
|
@State var isSelectingGameDLC: Bool = false
|
||||||
@StateObject var ryujinx = Ryujinx.shared
|
@StateObject var ryujinx = Ryujinx.shared
|
||||||
@State var gameInfo: Game?
|
@State var gameInfo: Game?
|
||||||
|
@State var gameRequirements: [GameRequirements] = []
|
||||||
var games: Binding<[Game]> {
|
var games: Binding<[Game]> {
|
||||||
Binding(
|
Binding(
|
||||||
get: { Ryujinx.shared.games },
|
get: { Ryujinx.shared.games },
|
||||||
@ -79,7 +80,7 @@ struct GameLibraryView: View {
|
|||||||
if !isSearching && !realRecentGames.isEmpty {
|
if !isSearching && !realRecentGames.isEmpty {
|
||||||
Section {
|
Section {
|
||||||
ForEach(realRecentGames) { game in
|
ForEach(realRecentGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameRequirements: $gameRequirements, gameInfo: $gameInfo)
|
||||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
removeFromRecentGames(game)
|
removeFromRecentGames(game)
|
||||||
@ -94,14 +95,14 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
Section {
|
Section {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameRequirements: $gameRequirements, gameInfo: $gameInfo)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Others")
|
Text("Others")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameRequirements: $gameRequirements, gameInfo: $gameInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +114,15 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
firmwareversion = (firmware == "" ? "0" : firmware)
|
firmwareversion = (firmware == "" ? "0" : firmware)
|
||||||
|
|
||||||
|
pullGameCompatibility() { game in
|
||||||
|
switch game {
|
||||||
|
case .success(let sucees):
|
||||||
|
gameRequirements = sucees
|
||||||
|
case .failure(_):
|
||||||
|
print("uhohh stinki")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
||||||
switch result {
|
switch result {
|
||||||
@ -367,7 +377,6 @@ extension Game: Codable {
|
|||||||
version = try container.decode(String.self, forKey: .version)
|
version = try container.decode(String.self, forKey: .version)
|
||||||
fileURL = try container.decode(URL.self, forKey: .fileURL)
|
fileURL = try container.decode(URL.self, forKey: .fileURL)
|
||||||
|
|
||||||
// Initialize other properties
|
|
||||||
self.containerFolder = fileURL.deletingLastPathComponent()
|
self.containerFolder = fileURL.deletingLastPathComponent()
|
||||||
self.fileType = .item
|
self.fileType = .item
|
||||||
}
|
}
|
||||||
@ -386,10 +395,11 @@ extension Game: Codable {
|
|||||||
struct GameListRow: View {
|
struct GameListRow: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@Binding var startemu: Game?
|
@Binding var startemu: Game?
|
||||||
@Binding var games: [Game] // Add this binding
|
@Binding var games: [Game]
|
||||||
@Binding var isViewingGameInfo: Bool
|
@Binding var isViewingGameInfo: Bool
|
||||||
@Binding var isSelectingGameUpdate: Bool
|
@Binding var isSelectingGameUpdate: Bool
|
||||||
@Binding var isSelectingGameDLC: Bool
|
@Binding var isSelectingGameDLC: Bool
|
||||||
|
@Binding var gameRequirements: [GameRequirements]
|
||||||
@Binding var gameInfo: Game?
|
@Binding var gameInfo: Game?
|
||||||
@State var gametoDelete: Game?
|
@State var gametoDelete: Game?
|
||||||
@State var showGameDeleteConfirmation: Bool = false
|
@State var showGameDeleteConfirmation: Bool = false
|
||||||
@ -433,8 +443,31 @@ struct GameListRow: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if let game = gameRequirements.first(where: { $0.game_id == game.titleId }) {
|
||||||
|
let totalMemory = ProcessInfo.processInfo.physicalMemory
|
||||||
|
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Capsule().fill(game.memoryInt <= Int(String(format: "%.0f", Double(totalMemory) / 1_000_000_000)) ?? 0 ? Color.green : Color.red)
|
||||||
|
.frame(width: 70 / 1.5, height: 35 / 1.5)
|
||||||
|
.overlay {
|
||||||
|
Text(game.device_memory)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.font(.system(size: 11))
|
||||||
|
}
|
||||||
|
|
||||||
|
Capsule().fill(game.color)
|
||||||
|
.frame(width: 70 / 1.5, height: 35 / 1.5)
|
||||||
|
.overlay {
|
||||||
|
Text(game.compatibility)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.font(.system(size: 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Image(systemName: "play.circle.fill")
|
Image(systemName: "play.circle.fill")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
@ -500,6 +533,7 @@ struct GameListRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func deleteGame(game: Game) {
|
private func deleteGame(game: Game) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
do {
|
do {
|
||||||
@ -510,3 +544,66 @@ struct GameListRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GameRequirements: Codable {
|
||||||
|
var game_id: String
|
||||||
|
var compatibility: String
|
||||||
|
var device_memory: String
|
||||||
|
var memoryInt: Int {
|
||||||
|
var devicemem = device_memory
|
||||||
|
devicemem.removeLast(2)
|
||||||
|
print(devicemem)
|
||||||
|
return Int(devicemem) ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var color: Color {
|
||||||
|
switch compatibility {
|
||||||
|
case "Perfect":
|
||||||
|
return .green
|
||||||
|
case "Playable":
|
||||||
|
return .yellow
|
||||||
|
case "Menu":
|
||||||
|
return .orange
|
||||||
|
case "Boots":
|
||||||
|
return .red
|
||||||
|
case "Nothing":
|
||||||
|
return .black
|
||||||
|
default:
|
||||||
|
return .clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullGameCompatibility(completion: @escaping (Result<[GameRequirements], Error>) -> Void) {
|
||||||
|
if let cachedData = GameCompatibiliryCache.shared.getCachedData() {
|
||||||
|
completion(.success(cachedData))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = URL(string: "https://melonx.org/api/game_entries") else {
|
||||||
|
completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data else {
|
||||||
|
completion(.failure(NSError(domain: "No data", code: 0, userInfo: nil)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let decodedData = try JSONDecoder().decode([GameRequirements].self, from: data)
|
||||||
|
GameCompatibiliryCache.shared.setCachedData(decodedData)
|
||||||
|
completion(.success(decodedData))
|
||||||
|
} catch {
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
@ -51,6 +51,8 @@ struct SettingsView: View {
|
|||||||
@AppStorage("stick-button") var stickButton = false
|
@AppStorage("stick-button") var stickButton = false
|
||||||
@AppStorage("waitForVPN") var waitForVPN = false
|
@AppStorage("waitForVPN") var waitForVPN = false
|
||||||
|
|
||||||
|
@AppStorage("HideButtons") var hideButtonsJoy = false
|
||||||
|
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var showControllerInfo = false
|
@State private var showControllerInfo = false
|
||||||
|
@ -606,26 +606,67 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel);
|
return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetData(int x, int y, int width, int height)
|
public byte[] GetData(int x, int y, int width, int height)
|
||||||
{
|
{
|
||||||
|
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
||||||
|
|
||||||
int size = width * height * Info.BytesPerPixel;
|
int size = width * height * Info.BytesPerPixel;
|
||||||
using var bufferHolder = _gd.BufferManager.Create(_gd, size);
|
|
||||||
|
|
||||||
using (var cbs = _gd.CommandBufferPool.Rent())
|
|
||||||
{
|
|
||||||
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
|
||||||
var image = GetImage().Get(cbs).Value;
|
|
||||||
|
|
||||||
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferHolder.WaitForFences();
|
|
||||||
byte[] bitmap = new byte[size];
|
byte[] bitmap = new byte[size];
|
||||||
GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
|
|
||||||
|
if (size <= MaxChunkSize)
|
||||||
|
{
|
||||||
|
using var bufferHolder = _gd.BufferManager.Create(_gd, size);
|
||||||
|
using (var cbs = _gd.CommandBufferPool.Rent())
|
||||||
|
{
|
||||||
|
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
||||||
|
var image = GetImage().Get(cbs).Value;
|
||||||
|
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferHolder.WaitForFences();
|
||||||
|
GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int dataPerPixel = Info.BytesPerPixel;
|
||||||
|
int rowStride = width * dataPerPixel;
|
||||||
|
int rowsPerChunk = Math.Max(1, MaxChunkSize / rowStride);
|
||||||
|
int originalHeight = height;
|
||||||
|
int currentY = y;
|
||||||
|
int bitmapOffset = 0;
|
||||||
|
|
||||||
|
while (currentY < y + originalHeight)
|
||||||
|
{
|
||||||
|
int chunkHeight = Math.Min(rowsPerChunk, y + originalHeight - currentY);
|
||||||
|
|
||||||
|
if (chunkHeight <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int chunkSize = chunkHeight * rowStride;
|
||||||
|
|
||||||
|
// Process this chunk
|
||||||
|
using var bufferHolder = _gd.BufferManager.Create(_gd, chunkSize);
|
||||||
|
using (var cbs = _gd.CommandBufferPool.Rent())
|
||||||
|
{
|
||||||
|
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
||||||
|
var image = GetImage().Get(cbs).Value;
|
||||||
|
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, chunkSize, true, 0, 0, x, currentY, width, chunkHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferHolder.WaitForFences();
|
||||||
|
GetDataFromBuffer(bufferHolder.GetDataStorage(0, chunkSize), chunkSize, Span<byte>.Empty)
|
||||||
|
.CopyTo(new Span<byte>(bitmap, bitmapOffset, chunkSize));
|
||||||
|
|
||||||
|
currentY += chunkHeight;
|
||||||
|
bitmapOffset += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public PinnedSpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||||
@ -738,14 +779,28 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return GetDataFromBuffer(result, size, result);
|
return GetDataFromBuffer(result, size, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
|
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer = 0, int level = 0)
|
||||||
{
|
{
|
||||||
|
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
||||||
|
|
||||||
int size = GetBufferDataLength(Info.GetMipSize(level));
|
int size = GetBufferDataLength(Info.GetMipSize(level));
|
||||||
|
|
||||||
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
if (size <= MaxChunkSize)
|
||||||
return GetDataFromBuffer(result, size, result);
|
{
|
||||||
|
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
||||||
|
return GetDataFromBuffer(result, size, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fullResult = new byte[size];
|
||||||
|
|
||||||
|
Span<byte> fullTextureData = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
||||||
|
|
||||||
|
GetDataFromBuffer(fullTextureData, size, fullTextureData).CopyTo(fullResult);
|
||||||
|
|
||||||
|
return fullResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(MemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
@ -769,7 +824,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
||||||
{
|
{
|
||||||
const int MaxChunkSize = 1024 * 1024;
|
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
||||||
|
|
||||||
int bufferDataLength = GetBufferDataLength(data.Length);
|
int bufferDataLength = GetBufferDataLength(data.Length);
|
||||||
|
|
||||||
|
@ -9,8 +9,18 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
||||||
private readonly List<string> _gamepadsIds;
|
private readonly List<string> _gamepadsIds;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
public ReadOnlySpan<string> GamepadsIds => _gamepadsIds.ToArray();
|
public ReadOnlySpan<string> GamepadsIds
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return _gamepadsIds.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string DriverName => "SDL2";
|
public string DriverName => "SDL2";
|
||||||
|
|
||||||
@ -35,7 +45,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateGamepadId(int joystickIndex)
|
private string GenerateGamepadId(int joystickIndex)
|
||||||
{
|
{
|
||||||
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
|
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
|
||||||
|
|
||||||
@ -44,14 +54,16 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include joystickIndex at the start of the ID to maintain compatibility with GetJoystickIndexByGamepadId
|
||||||
return joystickIndex + "-" + guid;
|
return joystickIndex + "-" + guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetJoystickIndexByGamepadId(string id)
|
private int GetJoystickIndexByGamepadId(string id)
|
||||||
{
|
{
|
||||||
string[] data = id.Split("-");
|
string[] data = id.Split("-");
|
||||||
|
|
||||||
if (data.Length != 6 || !int.TryParse(data[0], out int joystickIndex))
|
// Parse the joystick index from the ID string
|
||||||
|
if (data.Length < 2 || !int.TryParse(data[0], out int joystickIndex))
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -64,7 +76,11 @@ namespace Ryujinx.Input.SDL2
|
|||||||
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
|
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
|
||||||
{
|
{
|
||||||
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
|
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
|
||||||
_gamepadsIds.Remove(id);
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
@ -74,6 +90,13 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||||
{
|
{
|
||||||
|
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
||||||
|
{
|
||||||
|
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||||
|
// so it is rejected to avoid doubling the entries.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string id = GenerateGamepadId(joystickDeviceId);
|
string id = GenerateGamepadId(joystickDeviceId);
|
||||||
|
|
||||||
if (id == null)
|
if (id == null)
|
||||||
@ -81,16 +104,21 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
// Check if we already have this gamepad ID in our list
|
||||||
// so it is rejected to avoid doubling the entries.
|
lock (_lock)
|
||||||
if (_gamepadsIds.Contains(id))
|
|
||||||
{
|
{
|
||||||
return;
|
if (_gamepadsIds.Contains(id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
||||||
{
|
{
|
||||||
_gamepadsIds.Add(id);
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_gamepadsIds.Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
OnGamepadConnected?.Invoke(id);
|
OnGamepadConnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
@ -103,13 +131,17 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
|
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
|
||||||
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
|
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
|
||||||
|
|
||||||
|
// Simulate a full disconnect when disposing
|
||||||
foreach (string id in _gamepadsIds)
|
foreach (string id in _gamepadsIds)
|
||||||
{
|
{
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gamepadsIds.Clear();
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_gamepadsIds.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
SDL2Driver.Instance.Dispose();
|
SDL2Driver.Instance.Dispose();
|
||||||
}
|
}
|
||||||
@ -130,11 +162,6 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != GenerateGamepadId(joystickIndex))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
||||||
|
|
||||||
if (gamepadHandle == IntPtr.Zero)
|
if (gamepadHandle == IntPtr.Zero)
|
||||||
@ -145,4 +172,4 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return new SDL2Gamepad(gamepadHandle, id);
|
return new SDL2Gamepad(gamepadHandle, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user