[Experimental Feature] Implements VariableFastForward feature
This commit is contained in:
parent
233ef7d418
commit
6fd7f9e1d5
@ -187,7 +187,6 @@
|
||||
D57D795629F300E100BB2CF8 /* CustomTintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A817AF29DF4E6E00904AFE /* CustomTintColor.swift */; };
|
||||
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54A4BB229E4D27E004C7D57 /* FeatureDetailView.swift */; };
|
||||
D57D796029F315F700BB2CF8 /* ExperimentalFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C00229DDED6D00A8D610 /* ExperimentalFeaturesView.swift */; };
|
||||
D57D796129F31E7500BB2CF8 /* VariableFastForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C01C29DE058C00A8D610 /* VariableFastForward.swift */; };
|
||||
D5864970297734280081477E /* CheatMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586496F297734280081477E /* CheatMetadata.swift */; };
|
||||
D586497229774ABD0081477E /* CheatBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586497129774ABD0081477E /* CheatBase.swift */; };
|
||||
D5864978297756CE0081477E /* CheatBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5864977297756CE0081477E /* CheatBaseView.swift */; };
|
||||
@ -1647,7 +1646,6 @@
|
||||
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */,
|
||||
D51CB7A629EDC15900B59678 /* ExperimentalFeatures.swift in Sources */,
|
||||
D57D796029F315F700BB2CF8 /* ExperimentalFeaturesView.swift in Sources */,
|
||||
D57D796129F31E7500BB2CF8 /* VariableFastForward.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@ -1109,7 +1109,16 @@ extension GameViewController
|
||||
|
||||
if activate
|
||||
{
|
||||
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.upperBound
|
||||
if ExperimentalFeatures.shared.variableFastForward.isEnabled,
|
||||
let preferredSpeed = ExperimentalFeatures.shared.variableFastForward[emulatorCore.game.type],
|
||||
preferredSpeed.rawValue <= emulatorCore.deltaCore.supportedRates.upperBound
|
||||
{
|
||||
emulatorCore.rate = preferredSpeed.rawValue
|
||||
}
|
||||
else
|
||||
{
|
||||
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.upperBound
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -8,15 +8,32 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import DeltaCore
|
||||
import DeltaFeatures
|
||||
|
||||
enum FastForwardSpeed: Double, CaseIterable, CustomStringConvertible
|
||||
struct FastForwardSpeed: RawRepresentable
|
||||
{
|
||||
case x2 = 2
|
||||
case x3 = 3
|
||||
case x4 = 4
|
||||
case x8 = 8
|
||||
let rawValue: Double
|
||||
|
||||
init(rawValue: Double)
|
||||
{
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static func speeds(in range: ClosedRange<Double>) -> [FastForwardSpeed]
|
||||
{
|
||||
// .dropFirst() to remove 1x speed.
|
||||
var speeds = stride(from: range.lowerBound, to: range.upperBound, by: 1.0).dropFirst().map { FastForwardSpeed(rawValue: $0) }
|
||||
|
||||
// Handles both integer and non-integer maximum speeds, because range.upperBound is not included in `speeds`.
|
||||
speeds.append(.init(rawValue: range.upperBound))
|
||||
|
||||
return speeds
|
||||
}
|
||||
}
|
||||
|
||||
extension FastForwardSpeed: CustomStringConvertible, LocalizedOptionValue
|
||||
{
|
||||
var description: String {
|
||||
if #available(iOS 15, *)
|
||||
{
|
||||
@ -28,10 +45,7 @@ enum FastForwardSpeed: Double, CaseIterable, CustomStringConvertible
|
||||
return "\(self.rawValue)x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FastForwardSpeed: LocalizedOptionValue
|
||||
{
|
||||
|
||||
var localizedDescription: Text {
|
||||
Text(self.description)
|
||||
}
|
||||
@ -50,24 +64,56 @@ struct VariableFastForwardOptions
|
||||
// @Option // No name = hidden
|
||||
// var preferredSpeedsBySystem: [String: Double] = [:]
|
||||
|
||||
@Option(name: "Nintendo", description: "Preferred NES fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Nintendo", description: "Preferred NES fast forward speed.", values: FastForwardSpeed.speeds(in: System.nes.deltaCore.supportedRates))
|
||||
var nes: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Super Nintendo", description: "Preferred SNES fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Super Nintendo", description: "Preferred SNES fast forward speed.", values: FastForwardSpeed.speeds(in: System.snes.deltaCore.supportedRates))
|
||||
var snes: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Sega Genesis", description: "Preferred Genesis fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Sega Genesis", description: "Preferred Genesis fast forward speed.", values: FastForwardSpeed.speeds(in: System.genesis.deltaCore.supportedRates))
|
||||
var genesis: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Nintendo 64", description: "Preferred N64 fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Nintendo 64", description: "Preferred N64 fast forward speed.", values: FastForwardSpeed.speeds(in: System.n64.deltaCore.supportedRates))
|
||||
var n64: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Game Boy Color", description: "Preferred GBC fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Game Boy Color", description: "Preferred GBC fast forward speed.", values: FastForwardSpeed.speeds(in: System.gbc.deltaCore.supportedRates))
|
||||
var gbc: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Game Boy Advance", description: "Preferred GBA fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Game Boy Advance", description: "Preferred GBA fast forward speed.", values: FastForwardSpeed.speeds(in: System.gba.deltaCore.supportedRates))
|
||||
var gba: FastForwardSpeed?
|
||||
|
||||
@Option(name: "Nintendo DS", description: "Preferred DS fast forward speed.", values: FastForwardSpeed.allCases)
|
||||
@Option(name: "Nintendo DS", description: "Preferred DS fast forward speed.", values: FastForwardSpeed.speeds(in: System.ds.deltaCore.supportedRates))
|
||||
var ds: FastForwardSpeed?
|
||||
}
|
||||
|
||||
extension Feature where Options == VariableFastForwardOptions
|
||||
{
|
||||
subscript(gameType: GameType) -> FastForwardSpeed? {
|
||||
get {
|
||||
guard let system = System(gameType: gameType) else { return nil }
|
||||
switch system
|
||||
{
|
||||
case .nes: return self.nes
|
||||
case .snes: return self.snes
|
||||
case .genesis: return self.genesis
|
||||
case .n64: return self.n64
|
||||
case .gbc: return self.gbc
|
||||
case .gba: return self.gba
|
||||
case .ds: return self.ds
|
||||
}
|
||||
}
|
||||
set {
|
||||
guard let system = System(gameType: gameType) else { return }
|
||||
switch system
|
||||
{
|
||||
case .nes: self.nes = newValue
|
||||
case .snes: self.snes = newValue
|
||||
case .genesis: self.genesis = newValue
|
||||
case .n64: self.n64 = newValue
|
||||
case .gbc: self.gbc = newValue
|
||||
case .gba: self.gba = newValue
|
||||
case .ds: self.ds = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,3 +174,31 @@ extension GridMenuViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension GridMenuViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
|
||||
{
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
guard let menu = item.menu else { return nil }
|
||||
|
||||
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { _ in menu }
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
|
||||
{
|
||||
guard let indexPath = configuration.identifier as? IndexPath else { return nil }
|
||||
guard let cell = collectionView.cellForItem(at: indexPath) as? GridCollectionViewCell else { return nil }
|
||||
|
||||
let parameters = UIPreviewParameters()
|
||||
parameters.backgroundColor = .clear
|
||||
parameters.visiblePath = UIBezierPath(rect: cell.contentView.bounds)
|
||||
|
||||
let preview = UITargetedPreview(view: cell.contentView, parameters: parameters)
|
||||
return preview
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
|
||||
{
|
||||
return self.collectionView(collectionView, previewForHighlightingContextMenuWithConfiguration: configuration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,8 @@ class MenuItem: NSObject
|
||||
var image: UIImage?
|
||||
var action: ((MenuItem) -> Void)
|
||||
|
||||
var menu: UIMenu?
|
||||
|
||||
@objc dynamic var isSelected = false
|
||||
|
||||
init(text: String, image: UIImage?, action: @escaping ((MenuItem) -> Void))
|
||||
|
||||
@ -161,7 +161,7 @@ private extension PauseViewController
|
||||
self.sustainButtonsItem = nil
|
||||
self.fastForwardItem = nil
|
||||
|
||||
guard self.emulatorCore != nil else { return }
|
||||
guard let emulatorCore = self.emulatorCore else { return }
|
||||
|
||||
self.saveStateItem = MenuItem(text: NSLocalizedString("Save State", comment: ""), image: #imageLiteral(resourceName: "SaveSaveState"), action: { [unowned self] _ in
|
||||
self.saveStatesViewControllerMode = .saving
|
||||
@ -179,6 +179,12 @@ private extension PauseViewController
|
||||
|
||||
self.fastForwardItem = MenuItem(text: NSLocalizedString("Fast Forward", comment: ""), image: #imageLiteral(resourceName: "FastForward"), action: { _ in })
|
||||
self.sustainButtonsItem = MenuItem(text: NSLocalizedString("Hold Buttons", comment: ""), image: #imageLiteral(resourceName: "SustainButtons"), action: { _ in })
|
||||
|
||||
if ExperimentalFeatures.shared.variableFastForward.isEnabled
|
||||
{
|
||||
let menu = self.makeFastForwardMenu(for: emulatorCore.game)
|
||||
self.fastForwardItem?.menu = menu
|
||||
}
|
||||
}
|
||||
|
||||
func updateSafeAreaInsets()
|
||||
@ -194,4 +200,56 @@ private extension PauseViewController
|
||||
self.additionalSafeAreaInsets.right = 0
|
||||
}
|
||||
}
|
||||
|
||||
func makeFastForwardMenu(for game: GameProtocol) -> UIMenu?
|
||||
{
|
||||
guard let deltaCore = Delta.core(for: game.type), #available(iOS 15, *) else { return nil }
|
||||
|
||||
let menu = UIMenu(title: NSLocalizedString("Change the Fast Forward speed for this system.", comment: ""), options: [.singleSelection], children: [
|
||||
UIDeferredMenuElement.uncached { [weak self] completion in
|
||||
let preferredSpeed = ExperimentalFeatures.shared.variableFastForward[game.type]
|
||||
|
||||
let supportedSpeeds = FastForwardSpeed.speeds(in: deltaCore.supportedRates)
|
||||
var actions = zip(0..., supportedSpeeds).map { (index, speed) in
|
||||
|
||||
let state: UIAction.State = (speed == preferredSpeed) ? .on : .off
|
||||
let action = UIAction(title: speed.description, state: state) { action in
|
||||
ExperimentalFeatures.shared.variableFastForward[game.type] = speed
|
||||
|
||||
if let fastForwardItem = self?.fastForwardItem
|
||||
{
|
||||
fastForwardItem.isSelected = true // Always enable FF after selecting speed.
|
||||
fastForwardItem.action(fastForwardItem)
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
let configuration = UIImage.SymbolConfiguration(hierarchicalColor: .deltaPurple)
|
||||
|
||||
let percentage = Double(index + 1) / Double(supportedSpeeds.count)
|
||||
action.image = UIImage(systemName: "timelapse", variableValue: percentage, configuration: configuration)
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
let state: UIAction.State = (preferredSpeed == nil) ? .on : .off
|
||||
let action = UIAction(title: NSLocalizedString("Maximum", comment: ""), state: state) { action in
|
||||
ExperimentalFeatures.shared.variableFastForward[game.type] = nil
|
||||
|
||||
if let fastForwardItem = self?.fastForwardItem
|
||||
{
|
||||
fastForwardItem.isSelected = true // Always enable FF after selecting speed.
|
||||
fastForwardItem.action(fastForwardItem)
|
||||
}
|
||||
}
|
||||
actions.append(action)
|
||||
|
||||
completion(actions)
|
||||
}
|
||||
])
|
||||
|
||||
return menu
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,11 +22,6 @@ struct ExperimentalFeatures: FeatureContainer
|
||||
options: CustomTintColorOptions())
|
||||
var customTintColor
|
||||
|
||||
@Feature(name: "Variable Fast Forward",
|
||||
description: "Change the preferred Fast Foward speed per-system. You can also change it by long-pressing the Fast Forward button from the Pause Menu.",
|
||||
options: VariableFastForwardOptions())
|
||||
var variableFastForward
|
||||
|
||||
private init()
|
||||
{
|
||||
self.prepareFeatures()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user