Path: blob/a-new-beginning/Folium-iOS/Controllers/EmulationControllers/CytrusController.swift
2 views
//
// CytrusController.swift
// Folium
//
// Created by Jarrod Norwell on 28/11/2025.
//
import Cytrus
import GameController
import StoreKit
import UIKit
class CytrusController : UIViewController {
var imageView: MTKView? = nil,
secondaryImageView: MTKView? = nil
var visualEffectView: UIVisualEffectView? = nil
var settingsButton: UIButton? = nil,
selectButton: UIButton? = nil,
startButton: UIButton? = nil
var upButton: UIButton? = nil,
downButton: UIButton? = nil,
leftButton: UIButton? = nil,
rightButton: UIButton? = nil
var bButton: UIButton? = nil,
aButton: UIButton? = nil,
xButton: UIButton? = nil,
yButton: UIButton? = nil
var lButton: UIButton? = nil,
rButton: UIButton? = nil,
zlButton: UIButton? = nil,
zrButton: UIButton? = nil
var leftThumbstick: LatestControllerThumbstick? = nil,
rightThumbstick: LatestControllerThumbstick? = nil
var constraints: (portrait: [NSLayoutConstraint], landscape: [NSLayoutConstraint]) = ([], [])
var applicationModel: ApplicationModel
var game: NewCytrusGame? = nil
init(_ applicationModel: ApplicationModel, _ game: NewCytrusGame? = nil) {
self.applicationModel = applicationModel
self.game = game
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let device: MTLDevice? = MTLCreateSystemDefaultDevice()
if #available(iOS 26, *) {
let effect: UIGlassContainerEffect = .init()
effect.spacing = 20
visualEffectView = .init(effect: effect)
guard let visualEffectView else {
return
}
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(visualEffectView)
visualEffectView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
let viewToAddSubviews: UIView = visualEffectView?.contentView ?? view
imageView = MTKView(frame: .zero, device: device)
guard let imageView else {
return
}
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .secondaryLabel
if #available(iOS 26, *) {
imageView.clipsToBounds = true
imageView.cornerConfiguration = .corners(radius: .fixed(15.0))
} else {
imageView.clipsToBounds = true
imageView.layer.cornerCurve = .continuous
imageView.layer.cornerRadius = 15.0
}
viewToAddSubviews.addSubview(imageView)
secondaryImageView = MTKView(frame: .zero, device: device)
guard let secondaryImageView else {
return
}
secondaryImageView.translatesAutoresizingMaskIntoConstraints = false
secondaryImageView.backgroundColor = .secondaryLabel
if #available(iOS 26, *) {
secondaryImageView.clipsToBounds = true
secondaryImageView.cornerConfiguration = .corners(radius: .fixed(15.0))
} else {
secondaryImageView.clipsToBounds = true
secondaryImageView.layer.cornerCurve = .continuous
secondaryImageView.layer.cornerRadius = 15.0
}
secondaryImageView.isUserInteractionEnabled = true
viewToAddSubviews.addSubview(secondaryImageView)
var menuChildren: [UIMenuElement] = [
UIAction(title: "Multiplayer", image: .init(systemName: "network"), handler: { _ in
guard let game = self.game else {
return
}
self.applicationModel.cytrus.pause(true)
let navigationController: UINavigationController = UINavigationController(rootViewController: CytrusMultiplayerRoomsController(self.applicationModel, game))
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: true)
/*
Task {
switch await Transaction.latest(for: "premium_month") {
case .unverified(let transaction, let error):
print(#line, transaction.expirationDate?.formatted() ?? "", error.localizedDescription)
case .verified(let transaction):
if let expirationDate = transaction.expirationDate, expirationDate.compare(Date()) == .orderedDescending {
} else {
await StoreModel(applicationModel: self.applicationModel).premium(controller: self)
}
case .none:
await StoreModel(applicationModel: self.applicationModel).premium(controller: self)
}
}*/
}),
UIMenu(title: "Amiibo", image: .init(systemName: "person.crop.circle"), children: [
UIAction(title: "Remove", image: .init(systemName: "minus.circle")) { _ in
self.applicationModel.cytrus.removeAmiibo()
},
UIAction(title: "Insert", image: .init(systemName: "plus.circle")) { _ in
let documentPicker: UIDocumentPickerViewController = .init(forOpeningContentTypes: [.item], asCopy: true)
documentPicker.delegate = self
self.present(documentPicker, animated: true)
}
])
]
if let connectedRoom = applicationModel.cytrus.multiplayer.connectedRoom {
menuChildren.prepend(UIAction(title: "Chat", image: .init(systemName: "bubble.left.and.bubble.right")) { _ in
let chatController: CytrusMultiplayerChatController = .init(applicationModel: self.applicationModel,
room: connectedRoom,
collectionViewLayout: self.applicationModel.layoutManager.list)
let navigationController: UINavigationController = .init(rootViewController: chatController)
if let sheetPresentationController = navigationController.sheetPresentationController {
let custom: UISheetPresentationController.Detent = .custom { context in self.view.safeAreaInsets.top + self.view.safeAreaInsets.bottom }
let medium: UISheetPresentationController.Detent = .medium()
let large: UISheetPresentationController.Detent = .large()
sheetPresentationController.detents = [custom, medium, large]
sheetPresentationController.largestUndimmedDetentIdentifier = custom.identifier
sheetPresentationController.prefersGrabberVisible = true
if #available(iOS 26.1, *) {
sheetPresentationController.backgroundEffect = UIGlassEffect(style: .regular)
}
self.present(navigationController, animated: true)
}
})
menuChildren.prepend(UIAction(title: "Disconnect", image: .init(systemName: "minus.circle"), attributes: .destructive) { _ in
self.applicationModel.cytrus.multiplayer.disconnect()
})
}
let settingsConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "ellipsis"), .medium)
settingsButton = .button(with: settingsConfiguration, actions: ({ _ in }, { _ in }), UIMenu(children: [
UIMenu(options: .displayInline, children: [
UIDeferredMenuElement.uncached { completion in
Task {
completion([
UIAction(title: "Stop & Exit", image: UIImage(systemName: "stop"), attributes: .destructive) { _ in
let alertController: UIAlertController = UIAlertController(title: "Stop & Exit",
message: "Are you sure you want to stop & exit? Unsaved progress will be lost",
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
alertController.addAction(UIAlertAction(title: "Stop & Exit", style: .destructive, handler: { _ in
self.applicationModel.cytrus.stop()
self.dismiss(animated: true)
}))
self.present(alertController, animated: true)
},
UIAction(title: self.applicationModel.cytrus.isPaused ? "Resume" : "Pause",
image: self.applicationModel.cytrus.isPaused ? UIImage(systemName: "play") : UIImage(systemName: "pause")) { _ in
self.applicationModel.cytrus.pause(!self.applicationModel.cytrus.isPaused)
}
])
}
}
]),
UIMenu(options: .displayInline, children: [
UIMenu(title: "Save State", image: .init(systemName: "arrow.down.circle"), preferredElementSize: .small, children: [
UIDeferredMenuElement.uncached { completion in
guard let game = self.game, let identifier: UInt64 = game.information.identifier else {
completion([])
return
}
let stateNumbers: [Int] = [0, 1, 2]
let elements: [UIAction] = stateNumbers.map { state in
return .init(image: UIImage(systemName: "\(state + 1).circle")) { _ in
if self.applicationModel.cytrus.stateExists(identifier, state) {
let alertController: UIAlertController = UIAlertController(title: "Overwrite?",
message: "Are you sure you want to overwrite? This action is irreversible",
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
alertController.addAction(UIAlertAction(title: "Overwrite", style: .destructive, handler: { _ in
self.applicationModel.cytrus.save(state)
}))
self.present(alertController, animated: true)
} else {
self.applicationModel.cytrus.save(state)
}
}
}
completion(elements)
}
]),
UIMenu(title: "Load State", image: .init(systemName: "arrow.up.circle"), preferredElementSize: .small, children: [
UIDeferredMenuElement.uncached { completion in
guard let game = self.game, let identifier: UInt64 = game.information.identifier else {
completion([])
return
}
let stateNumbers: [Int] = [0, 1, 2]
let elements: [UIAction] = stateNumbers.map { state in
let fileExists: Bool = self.applicationModel.cytrus.stateExists(identifier, state)
return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? [] : .disabled) { _ in
self.applicationModel.cytrus.load(state)
}
}
completion(elements)
}
]),
UIMenu(options: .displayInline, children: [
UIAction(title: "Capture Still Image", image: UIImage(systemName: "photo.badge.arrow.down")) { _ in
self.applicationModel.cytrus.pause(true)
// guard let imageView: UIImageView = self.imageView, let image: UIImage = imageView.image else {
// return
// }
//
// UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
self.applicationModel.cytrus.pause(false)
}
]),
UIMenu(options: .displayInline, children: menuChildren)
])
]))
guard let settingsButton else {
return
}
viewToAddSubviews.addSubview(settingsButton)
let selectConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "minus"), .medium)
selectButton = .button(with: selectConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .select, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .select, player: 0, pressed: false)
}))
guard let selectButton else {
return
}
viewToAddSubviews.addSubview(selectButton)
let startConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "plus"), .medium)
startButton = .button(with: startConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .start, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .start, player: 0, pressed: false)
}))
guard let startButton else {
return
}
viewToAddSubviews.addSubview(startButton)
let upConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.up"))
upButton = .button(with: upConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .up, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .up, player: 0, pressed: false)
}))
guard let upButton else {
return
}
viewToAddSubviews.addSubview(upButton)
let downConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.down"))
downButton = .button(with: downConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .down, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .down, player: 0, pressed: false)
}))
guard let downButton else {
return
}
viewToAddSubviews.addSubview(downButton)
let leftConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.left"))
leftButton = .button(with: leftConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .left, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .left, player: 0, pressed: false)
}))
guard let leftButton else {
return
}
viewToAddSubviews.addSubview(leftButton)
let rightConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "arrow.right"))
rightButton = .button(with: rightConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .right, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .right, player: 0, pressed: false)
}))
guard let rightButton else {
return
}
viewToAddSubviews.addSubview(rightButton)
let aConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "a.circle"))
aButton = .button(with: aConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .a, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .a, player: 0, pressed: false)
}))
guard let aButton else {
return
}
viewToAddSubviews.addSubview(aButton)
let bConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "b.circle"))
bButton = .button(with: bConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .b, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .b, player: 0, pressed: false)
}))
guard let bButton else {
return
}
viewToAddSubviews.addSubview(bButton)
let yConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "y.circle"))
yButton = .button(with: yConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .y, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .y, player: 0, pressed: false)
}))
guard let yButton else {
return
}
viewToAddSubviews.addSubview(yButton)
let xConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "x.circle"))
xButton = .button(with: xConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .x, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .x, player: 0, pressed: false)
}))
guard let xButton else {
return
}
viewToAddSubviews.addSubview(xButton)
let lConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "left"))
lButton = .button(with: lConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .l, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .l, player: 0, pressed: false)
}))
guard let lButton else {
return
}
viewToAddSubviews.addSubview(lButton)
let zlConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
zlButton = .button(with: zlConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .zl, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .zl, player: 0, pressed: false)
}))
guard let zlButton else {
return
}
viewToAddSubviews.addSubview(zlButton)
let rConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "right"))
rButton = .button(with: rConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .r, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .r, player: 0, pressed: false)
}))
guard let rButton else {
return
}
viewToAddSubviews.addSubview(rButton)
let zrConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "circle"), .medium)
zrButton = .button(with: zrConfiguration,
actions: ({ _ in
self.applicationModel.cytrus.button(button: .zr, player: 0, pressed: true)
}, { _ in
self.applicationModel.cytrus.button(button: .zr, player: 0, pressed: false)
}))
guard let zrButton else {
return
}
viewToAddSubviews.addSubview(zrButton)
leftThumbstick = .init(.left, self)
guard let leftThumbstick else {
return
}
leftThumbstick.translatesAutoresizingMaskIntoConstraints = false
viewToAddSubviews.insertSubview(leftThumbstick, belowSubview: upButton)
rightThumbstick = .init(.right, self)
guard let rightThumbstick else {
return
}
rightThumbstick.translatesAutoresizingMaskIntoConstraints = false
viewToAddSubviews.insertSubview(rightThumbstick, belowSubview: aButton)
if iPhone {
constraints.portrait.append(contentsOf: [
imageView.topAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.topAnchor,
constant: 20.0),
imageView.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
imageView.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
multiplier: 3.0 / 5.0),
secondaryImageView.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
constant: 20.0),
secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
secondaryImageView.heightAnchor.constraint(equalTo: secondaryImageView.safeAreaLayoutGuide.widthAnchor,
multiplier: 3.0 / 4.0),
settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
settingsButton.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
])
constraints.landscape.append(contentsOf: [
imageView.topAnchor.constraint(equalTo: viewToAddSubviews.topAnchor,
constant: 20.0),
imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
imageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
constant: -10.0),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor,
multiplier: 5.0 / 3.0),
secondaryImageView.topAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
constant: 10.0),
secondaryImageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
secondaryImageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
secondaryImageView.widthAnchor.constraint(equalTo: secondaryImageView.heightAnchor,
multiplier: 4.0 / 3.0),
startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
])
} else {
constraints.portrait.append(contentsOf: [
imageView.topAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.topAnchor,
constant: 20.0),
imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
imageView.widthAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.widthAnchor,
multiplier: 2.0 / 3.0),
imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
multiplier: 3.0 / 5.0),
secondaryImageView.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
constant: 20.0),
secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
secondaryImageView.heightAnchor.constraint(equalTo: secondaryImageView.safeAreaLayoutGuide.widthAnchor,
multiplier: 3.0 / 4.0),
settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
settingsButton.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
])
constraints.landscape.append(contentsOf: [
imageView.topAnchor.constraint(equalTo: viewToAddSubviews.topAnchor,
constant: 20.0),
imageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
imageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
constant: -10.0),
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor,
multiplier: 4.0 / 3.0),
secondaryImageView.topAnchor.constraint(equalTo: viewToAddSubviews.centerYAnchor,
constant: 10.0),
secondaryImageView.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor),
secondaryImageView.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
secondaryImageView.widthAnchor.constraint(equalTo: secondaryImageView.heightAnchor,
multiplier: 4.0 / 3.0),
startButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
selectButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
settingsButton.bottomAnchor.constraint(equalTo: viewToAddSubviews.bottomAnchor,
constant: -20.0),
settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
bButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
aButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
aButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor),
xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor),
yButton.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.topAnchor),
yButton.trailingAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.leadingAnchor),
upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor),
leftButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
leftButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor),
downButton.bottomAnchor.constraint(equalTo: selectButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
rightButton.leadingAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.trailingAnchor),
rightButton.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.topAnchor),
lButton.leadingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.leadingAnchor,
constant: 20.0),
lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor,
constant: 20.0),
zlButton.centerYAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.centerYAnchor),
rButton.trailingAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.trailingAnchor,
constant: -20.0),
rButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor,
constant: -20.0),
zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor,
constant: -20.0),
zrButton.centerYAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.centerYAnchor),
leftThumbstick.topAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor),
leftThumbstick.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor),
leftThumbstick.bottomAnchor.constraint(equalTo: downButton.safeAreaLayoutGuide.bottomAnchor),
leftThumbstick.trailingAnchor.constraint(equalTo: rightButton.safeAreaLayoutGuide.trailingAnchor),
rightThumbstick.topAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor),
rightThumbstick.leadingAnchor.constraint(equalTo: yButton.safeAreaLayoutGuide.leadingAnchor),
rightThumbstick.bottomAnchor.constraint(equalTo: bButton.safeAreaLayoutGuide.bottomAnchor),
rightThumbstick.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor)
])
}
switch interfaceOrientation() {
case .portrait:
view.addConstraints(constraints.portrait)
case .landscapeLeft, .landscapeRight:
view.addConstraints(constraints.landscape)
default:
break
}
applicationModel.cytrus.allocate()
applicationModel.cytrus.diskCacheCallback { stage, current, total in
print(stage, current, total)
}
Task {
await GCController.startWirelessControllerDiscovery()
}
NotificationCenter.default.addObserver(forName: .applicationStateDidChange, object: nil, queue: .current) { notification in
guard let applicationState: ApplicationState = notification.object as? ApplicationState else {
return
}
self.applicationModel.cytrus.pause(applicationState.shouldPause)
}
NotificationCenter.default.addObserver(forName: .init("openKeyboard"), object: nil, queue: .main) { notification in
guard let config = notification.object as? KeyboardConfig else {
return
}
let alertController = UIAlertController(title: "", message: nil, preferredStyle: .alert)
let cancelAction: UIAlertAction = .init(title: .localized(for: .dismiss), style: .cancel) { _ in
NotificationCenter.default.post(name: .init("closeKeyboard"), object: nil, userInfo: [
"buttonPressed" : 0,
"keyboardText" : ""
])
}
let okayButton: UIAlertAction = .init(title: "Okay", style: .default) { _ in
guard let textFields = alertController.textFields, let textField = textFields.first else {
return
}
NotificationCenter.default.post(name: .init("closeKeyboard"), object: nil, userInfo: [
"buttonPressed" : 0,
"keyboardText" : textField.text ?? ""
])
}
switch config.buttonConfig {
case .single:
alertController.addAction(okayButton)
case .dual:
alertController.addAction(cancelAction)
alertController.addAction(okayButton)
case .triple:
break
case .none:
break
@unknown default:
break
}
alertController.addTextField()
self.present(alertController, animated: true)
}
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .current) { notification in
guard let controller: GCController = notification.object as? GCController,
let extendedGamepad: GCExtendedGamepad = controller.extendedGamepad else {
return
}
self.view.subviews.filter { subview in
subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
}.forEach { button in
UIView.animate(withDuration: 1 / 3) {
button.alpha = 1 / 3
}
}
extendedGamepad.buttonA.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .a, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.buttonB.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .b, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.buttonX.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .x, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.buttonY.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .y, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.dpad.up.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .up, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.dpad.down.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .down, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.dpad.left.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .left, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.dpad.right.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .right, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.leftShoulder.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .l, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.leftTrigger.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .zl, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.rightShoulder.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .r, player: controller.playerIndex.rawValue, pressed: pressed)
}
extendedGamepad.rightTrigger.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .zr, player: controller.playerIndex.rawValue, pressed: pressed)
}
if let buttonOptions = extendedGamepad.buttonOptions {
buttonOptions.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .select, player: controller.playerIndex.rawValue, pressed: pressed)
}
}
extendedGamepad.buttonMenu.pressedChangedHandler = { element, value, pressed in
self.applicationModel.cytrus.button(button: .start, player: controller.playerIndex.rawValue, pressed: pressed)
}
}
NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .current) { notification in
self.view.subviews.filter { subview in
subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
}.forEach { button in
UIView.animate(withDuration: 1 / 3) {
button.alpha = 1
}
}
}
}
override var prefersHomeIndicatorAutoHidden: Bool { true }
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
.portrait
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let imageView, let secondaryImageView else {
return
}
if !applicationModel.cytrus.running() || !applicationModel.cytrus.stopped() {
if let layer = imageView.layer as? CAMetalLayer {
applicationModel.cytrus.initialize(layer, layer.frame.size)
}
if let layer = secondaryImageView.layer as? CAMetalLayer {
applicationModel.cytrus.initialize(layer, layer.frame.size, true)
}
#if !targetEnvironment(simulator)
if let game {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Thread.setThreadPriority(1)
Thread.detachNewThread {
self.applicationModel.cytrus.insert(game.details.url) {
self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: imageView)
self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: secondaryImageView, true)
}
}
}
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Thread.setThreadPriority(1)
Thread.detachNewThread {
let url: URL = self.applicationModel.cytrus.bootHome(3)
print(url.path)
self.applicationModel.cytrus.insert(url) {
self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: imageView)
self.applicationModel.cytrus.orientationChange(with: self.interfaceOrientation(), using: secondaryImageView, true)
}
}
}
}
#endif
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
applicationModel.cytrus.deallocate()
applicationModel.cytrus.deinitialize()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let game {
game.extras.update()
}
GCController.stopWirelessControllerDiscovery()
NotificationCenter.default.removeObserver(self)
}
override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { context in } completion: { context in
switch self.interfaceOrientation() {
case .portrait:
self.view.removeConstraints(self.constraints.landscape)
self.view.addConstraints(self.constraints.portrait)
case .landscapeLeft, .landscapeRight:
self.view.removeConstraints(self.constraints.portrait)
self.view.addConstraints(self.constraints.landscape)
default:
break
}
self.view.setNeedsUpdateConstraints()
}
}
}
extension CytrusController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else {
return
}
if let secondaryImageView {
applicationModel.cytrus.touchBegan(at: touch.location(in: secondaryImageView))
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
applicationModel.cytrus.touchEnded()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
guard let touch = touches.first else {
return
}
if let secondaryImageView {
applicationModel.cytrus.touchMoved(at: touch.location(in: secondaryImageView))
}
}
}
extension CytrusController : LatestControllerThumbstickDelegate {
func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
}
func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
}
func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {
}
func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
}
func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
}
func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
applicationModel.cytrus.thumbstickMoved(thumbstick == .left ? .circlePad : .cStick, position.x, position.y)
}
}
extension CytrusController {
@objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
if #available(iOS 17.5, *), let settingsButton {
UINotificationFeedbackGenerator(view: settingsButton).notificationOccurred(error == nil ? .success : .error)
} else {
UINotificationFeedbackGenerator().notificationOccurred(error == nil ? .success : .error)
}
}
}
extension CytrusController : UIDocumentPickerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else {
return
}
let result: Bool = self.applicationModel.cytrus.insertAmiibo(url)
UINotificationFeedbackGenerator().notificationOccurred(result ? .success : .error)
}
}