11 changed files with 487 additions and 575 deletions
-
5src/ios/CMakeLists.txt
-
432src/ios/ControllerView.swift
-
424src/ios/EmulationView.swift
-
51src/ios/FileManager.swift
-
12src/ios/GameButtonView.swift
-
1src/ios/GameListView.swift
-
1src/ios/LibraryView.swift
-
23src/ios/MetalView.swift
-
44src/ios/NavView.swift
-
51src/ios/PomeloApp.swift
-
18src/ios/SettingsView.swift
@ -1,432 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11 |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
import SwiftUI |
|||
import GameController |
|||
|
|||
import SwiftUIJoystick |
|||
|
|||
struct ControllerView: View { |
|||
let appui = AppUI.shared |
|||
@State var isPressed = false |
|||
@State var controllerconnected = false |
|||
@State private var x: CGFloat = 0.0 |
|||
@State private var y: CGFloat = 0.0 |
|||
@Environment(\.presentationMode) var presentationMode |
|||
|
|||
var body: some View { |
|||
GeometryReader { geometry in |
|||
ZStack { |
|||
if !controllerconnected { |
|||
OnScreenController(geometry: geometry) // i did this to clean it up as it was quite long lmfao |
|||
} |
|||
} |
|||
} |
|||
.onAppear { |
|||
print("checking for controller:") |
|||
controllerconnected = false |
|||
DispatchQueue.main.async { |
|||
setupControllers() // i dont know what half of this shit does |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Add a dictionary to track controller IDs |
|||
@State var controllerIDs: [GCController: Int] = [:] |
|||
|
|||
private func setupControllers() { |
|||
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .main) { notification in |
|||
if let controller = notification.object as? GCController { |
|||
print("wow controller onstart") // yippeeee |
|||
self.setupController(controller) |
|||
self.controllerconnected = true |
|||
} else { |
|||
print("not GCController :((((((") // wahhhhhhh |
|||
} |
|||
} |
|||
|
|||
|
|||
NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .main) { notification in |
|||
if let controller = notification.object as? GCController { |
|||
print("wow controller gone") |
|||
if self.controllerIDs.isEmpty { |
|||
controllerconnected = false |
|||
} |
|||
self.controllerIDs.removeValue(forKey: controller) // Remove the controller ID |
|||
} |
|||
} |
|||
|
|||
GCController.controllers().forEach { controller in |
|||
print("wow controller") |
|||
self.controllerconnected = true |
|||
self.setupController(controller) |
|||
} |
|||
} |
|||
|
|||
private func setupController(_ controller: GCController) { |
|||
// Assign a unique ID to the controller, max 5 controllers |
|||
if controllerIDs.count < 6, controllerIDs[controller] == nil { |
|||
controllerIDs[controller] = controllerIDs.count |
|||
} |
|||
|
|||
guard let controllerId = controllerIDs[controller] else { return } |
|||
|
|||
if let extendedGamepad = controller.extendedGamepad { |
|||
|
|||
// Handle extended gamepad |
|||
extendedGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId) |
|||
} |
|||
|
|||
extendedGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonOptions?.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.minus, controllerId: controllerId) : self.touchUpInside(.minus, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonMenu.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.plus, controllerId: controllerId) : self.touchUpInside(.plus, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonA.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonB.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.B, controllerId: controllerId) : self.touchUpInside(.B, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonX.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonY.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.Y, controllerId: controllerId) : self.touchUpInside(.Y, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.leftShoulder.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.triggerL, controllerId: controllerId) : self.touchUpInside(.L, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.leftTrigger.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.triggerZL, controllerId: controllerId) : self.touchUpInside(.triggerZL, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.rightShoulder.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.triggerR, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.leftThumbstickButton?.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.L, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.rightThumbstickButton?.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.R, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.rightTrigger.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.triggerZR, controllerId: controllerId) : self.touchUpInside(.triggerZR, controllerId: controllerId) |
|||
} |
|||
extendedGamepad.buttonHome?.pressedChangedHandler = { button, value, pressed in |
|||
if pressed { |
|||
appui.exit() |
|||
presentationMode.wrappedValue.dismiss() |
|||
} |
|||
} |
|||
extendedGamepad.leftThumbstick.valueChangedHandler = { dpad, x, y in |
|||
self.appui.thumbstickMoved(analog: .left, x: x, y: y, controllerid: controllerId) |
|||
} |
|||
|
|||
extendedGamepad.rightThumbstick.valueChangedHandler = { dpad, x, y in |
|||
self.appui.thumbstickMoved(analog: .right, x: x, y: y, controllerid: controllerId) |
|||
} |
|||
|
|||
if let motion = controller.motion { |
|||
var lastTimestamp = Date().timeIntervalSince1970 // Initialize timestamp when motion starts |
|||
|
|||
motion.valueChangedHandler = { motion in |
|||
// Get current time |
|||
let currentTimestamp = Date().timeIntervalSince1970 |
|||
let deltaTimestamp = Int32((currentTimestamp - lastTimestamp) * 1000) // Difference in milliseconds |
|||
|
|||
// Update last timestamp |
|||
lastTimestamp = currentTimestamp |
|||
|
|||
// Get gyroscope data |
|||
let gyroX = motion.rotationRate.x |
|||
let gyroY = motion.rotationRate.y |
|||
let gyroZ = motion.rotationRate.z |
|||
|
|||
// Get accelerometer data |
|||
let accelX = motion.gravity.x + motion.userAcceleration.x |
|||
let accelY = motion.gravity.y + motion.userAcceleration.y |
|||
let accelZ = motion.gravity.z + motion.userAcceleration.z |
|||
|
|||
print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)") |
|||
|
|||
// Call your gyroMoved function with the motion data |
|||
appui.gyroMoved(x: Float(gyroX), y: Float(gyroY), z: Float(gyroZ), accelX: Float(accelX), accelY: Float(accelY), accelZ: Float(accelZ), controllerId: Int32(controllerId), deltaTimestamp: Int32(lastTimestamp)) |
|||
} |
|||
} |
|||
} else if let microGamepad = controller.microGamepad { |
|||
// Handle micro gamepad |
|||
microGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId) |
|||
} |
|||
microGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId) |
|||
} |
|||
microGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId) |
|||
} |
|||
microGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId) |
|||
} |
|||
microGamepad.buttonA.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId) |
|||
} |
|||
microGamepad.buttonX.pressedChangedHandler = { button, value, pressed in |
|||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId) |
|||
} |
|||
} |
|||
} |
|||
|
|||
private func touchDown(_ button: VirtualControllerButtonType, controllerId: Int) { |
|||
appui.virtualControllerButtonDown(button: button, controllerid: controllerId) } |
|||
|
|||
private func touchUpInside(_ button: VirtualControllerButtonType, controllerId: Int) { |
|||
appui.virtualControllerButtonUp(button: button, controllerid: controllerId) |
|||
} |
|||
} |
|||
|
|||
struct OnScreenController: View { |
|||
@State var geometry: GeometryProxy |
|||
var body: some View { |
|||
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad { |
|||
// portrait |
|||
VStack { |
|||
Spacer() |
|||
VStack { |
|||
HStack { |
|||
VStack { |
|||
ShoulderButtonsViewLeft() |
|||
ZStack { |
|||
Joystick() |
|||
DPadView() |
|||
} |
|||
} |
|||
.padding() |
|||
VStack { |
|||
ShoulderButtonsViewRight() |
|||
ZStack { |
|||
Joystick(iscool: true) // hope this works |
|||
ABXYView() |
|||
} |
|||
} |
|||
.padding() |
|||
} |
|||
HStack { |
|||
ButtonView(button: .plus).padding(.horizontal, 40) |
|||
ButtonView(button: .minus).padding(.horizontal, 40) |
|||
} |
|||
} |
|||
.padding(.bottom, geometry.size.height / 3.2) // very broken |
|||
} |
|||
} else { |
|||
// could be landscape |
|||
VStack { |
|||
HStack { |
|||
Spacer() |
|||
ButtonView(button: .home) |
|||
.padding(.horizontal) |
|||
} |
|||
Spacer() |
|||
VStack { |
|||
HStack { |
|||
|
|||
// gotta fuckin add + and - now |
|||
VStack { |
|||
ShoulderButtonsViewLeft() |
|||
ZStack { |
|||
Joystick() |
|||
DPadView() |
|||
} |
|||
} |
|||
HStack { |
|||
Spacer() |
|||
VStack { |
|||
Spacer() |
|||
ButtonView(button: .plus) // Adding the + button |
|||
} |
|||
VStack { |
|||
Spacer() |
|||
ButtonView(button: .minus) // 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 { |
|||
var body: some View { |
|||
HStack { |
|||
ButtonView(button: .triggerZL) |
|||
.padding(.horizontal) |
|||
ButtonView(button: .triggerL) |
|||
.padding(.horizontal) |
|||
} |
|||
.frame(width: 160, height: 20) |
|||
} |
|||
} |
|||
|
|||
struct ShoulderButtonsViewRight: View { |
|||
var body: some View { |
|||
HStack { |
|||
ButtonView(button: .triggerR) |
|||
.padding(.horizontal) |
|||
ButtonView(button: .triggerZR) |
|||
.padding(.horizontal) |
|||
} |
|||
.frame(width: 160, height: 20) |
|||
|
|||
} |
|||
} |
|||
|
|||
struct DPadView: View { |
|||
var body: some View { |
|||
VStack { |
|||
ButtonView(button: .directionalPadUp) |
|||
HStack { |
|||
ButtonView(button: .directionalPadLeft) |
|||
Spacer(minLength: 20) |
|||
ButtonView(button: .directionalPadRight) |
|||
} |
|||
ButtonView(button: .directionalPadDown) |
|||
.padding(.horizontal) |
|||
} |
|||
.frame(width: 145, height: 145) |
|||
} |
|||
} |
|||
|
|||
struct ABXYView: View { |
|||
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: 145, height: 145) |
|||
} |
|||
} |
|||
|
|||
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) |
|||
} |
|||
} |
|||
|
|||
struct ButtonView: View { |
|||
var button: VirtualControllerButtonType |
|||
@StateObject private var viewModel: EmulationViewModel = EmulationViewModel(game: nil) |
|||
let appui = AppUI.shared |
|||
@State var mtkView: MTKView? |
|||
@State var width: CGFloat = 45 |
|||
@State var height: CGFloat = 45 |
|||
@State var isPressed = false |
|||
var id: Int { |
|||
if onscreenjoy { |
|||
return 8 |
|||
} |
|||
return 0 |
|||
} |
|||
@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.5 : 1) |
|||
.gesture( |
|||
DragGesture(minimumDistance: 0) |
|||
.onChanged { _ in |
|||
if !self.isPressed { |
|||
self.isPressed = true |
|||
DispatchQueue.main.async { |
|||
if button == .home { |
|||
presentationMode.wrappedValue.dismiss() |
|||
appui.exit() |
|||
} else { |
|||
appui.virtualControllerButtonDown(button: button, controllerid: id) |
|||
Haptics.shared.play(.heavy) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.onEnded { _ in |
|||
self.isPressed = false |
|||
DispatchQueue.main.async { |
|||
if button != .home { |
|||
appui.virtualControllerButtonUp(button: button, controllerid: id) |
|||
} |
|||
} |
|||
} |
|||
) |
|||
.onAppear() { |
|||
if button == .triggerL || button == .triggerZL || button == .triggerZR || button == .triggerR { |
|||
width = 65 |
|||
} |
|||
|
|||
|
|||
if button == .minus || button == .plus || button == .home { |
|||
width = 35 |
|||
height = 35 |
|||
} |
|||
} |
|||
} |
|||
|
|||
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 .directionalPadUp: return "arrowtriangle.up.circle.fill" |
|||
case .directionalPadDown: return "arrowtriangle.down.circle.fill" |
|||
case .directionalPadLeft: return "arrowtriangle.left.circle.fill" |
|||
case .directionalPadRight: return "arrowtriangle.right.circle.fill" |
|||
case .triggerZL: return"zl.rectangle.roundedtop.fill" |
|||
case .triggerZR: return "zr.rectangle.roundedtop.fill" |
|||
case .triggerL: return "l.rectangle.roundedbottom.fill" |
|||
case .triggerR: return "r.rectangle.roundedbottom.fill" |
|||
case .plus: return "plus.circle.fill" |
|||
case .minus: return "minus.circle.fill" |
|||
case .home: return "house.circle.fill" |
|||
default: return "" |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11 |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
import SwiftUI |
|||
import Metal |
|||
|
|||
|
|||
struct MetalView: UIViewRepresentable { |
|||
let device: MTLDevice? |
|||
let configure: (UIView) -> Void |
|||
|
|||
func makeUIView(context: Context) -> EmulationScreenView { |
|||
let view = EmulationScreenView() |
|||
configure(view.primaryScreen) |
|||
return view |
|||
} |
|||
|
|||
func updateUIView(_ uiView: EmulationScreenView, context: Context) { |
|||
// |
|||
} |
|||
} |
|||
@ -1,44 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11 |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
import SwiftUI |
|||
|
|||
struct BootOSView: View { |
|||
@Binding var core: Core |
|||
@Binding var currentnavigarion: Int |
|||
@State var appui = AppUI.shared |
|||
@AppStorage("cangetfullpath") var canGetFullPath: Bool = false |
|||
var body: some View { |
|||
if (appui.canGetFullPath() -- canGetFullPath) { |
|||
EmulationView(game: nil) |
|||
} else { |
|||
VStack { |
|||
Text("Unable Launch Switch OS") |
|||
.font(.largeTitle) |
|||
.padding() |
|||
Text("You do not have the Switch Home Menu Files Needed to launch the Ηome Menu") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
struct NavView: View { |
|||
@Binding var core: Core |
|||
@State private var selectedTab = 0 |
|||
var body: some View { |
|||
TabView(selection: $selectedTab) { |
|||
LibraryView(core: $core) |
|||
.tabItem { Label("Library", systemImage: "rectangle.on.rectangle") } |
|||
.tag(0) |
|||
BootOSView(core: $core, currentnavigarion: $selectedTab) |
|||
.toolbar(.hidden, for: .tabBar) |
|||
.tabItem { Label("Boot OS", systemImage: "house") } |
|||
.tag(1) |
|||
SettingsView(core: core) |
|||
.tabItem { Label("Settings", systemImage: "gear") } |
|||
.tag(2) |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11 |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
import SwiftUI |
|||
|
|||
struct SettingsView: View { |
|||
@State var core: Core |
|||
@State var showprompt = false |
|||
|
|||
@AppStorage("icon") var iconused = 1 |
|||
var body: some View { |
|||
NavigationStack { |
|||
|
|||
} |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue