[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 */; };
|
D57D795629F300E100BB2CF8 /* CustomTintColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A817AF29DF4E6E00904AFE /* CustomTintColor.swift */; };
|
||||||
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54A4BB229E4D27E004C7D57 /* FeatureDetailView.swift */; };
|
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54A4BB229E4D27E004C7D57 /* FeatureDetailView.swift */; };
|
||||||
D57D796029F315F700BB2CF8 /* ExperimentalFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C00229DDED6D00A8D610 /* ExperimentalFeaturesView.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 */; };
|
D5864970297734280081477E /* CheatMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586496F297734280081477E /* CheatMetadata.swift */; };
|
||||||
D586497229774ABD0081477E /* CheatBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586497129774ABD0081477E /* CheatBase.swift */; };
|
D586497229774ABD0081477E /* CheatBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586497129774ABD0081477E /* CheatBase.swift */; };
|
||||||
D5864978297756CE0081477E /* CheatBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5864977297756CE0081477E /* CheatBaseView.swift */; };
|
D5864978297756CE0081477E /* CheatBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5864977297756CE0081477E /* CheatBaseView.swift */; };
|
||||||
@ -1647,7 +1646,6 @@
|
|||||||
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */,
|
D57D795F29F315F700BB2CF8 /* FeatureDetailView.swift in Sources */,
|
||||||
D51CB7A629EDC15900B59678 /* ExperimentalFeatures.swift in Sources */,
|
D51CB7A629EDC15900B59678 /* ExperimentalFeatures.swift in Sources */,
|
||||||
D57D796029F315F700BB2CF8 /* ExperimentalFeaturesView.swift in Sources */,
|
D57D796029F315F700BB2CF8 /* ExperimentalFeaturesView.swift in Sources */,
|
||||||
D57D796129F31E7500BB2CF8 /* VariableFastForward.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1108,9 +1108,18 @@ extension GameViewController
|
|||||||
guard let emulatorCore = self.emulatorCore else { return }
|
guard let emulatorCore = self.emulatorCore else { return }
|
||||||
|
|
||||||
if activate
|
if activate
|
||||||
|
{
|
||||||
|
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
|
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.upperBound
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.lowerBound
|
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.lowerBound
|
||||||
|
|||||||
@ -8,15 +8,32 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
import DeltaCore
|
||||||
import DeltaFeatures
|
import DeltaFeatures
|
||||||
|
|
||||||
enum FastForwardSpeed: Double, CaseIterable, CustomStringConvertible
|
struct FastForwardSpeed: RawRepresentable
|
||||||
{
|
{
|
||||||
case x2 = 2
|
let rawValue: Double
|
||||||
case x3 = 3
|
|
||||||
case x4 = 4
|
|
||||||
case x8 = 8
|
|
||||||
|
|
||||||
|
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 {
|
var description: String {
|
||||||
if #available(iOS 15, *)
|
if #available(iOS 15, *)
|
||||||
{
|
{
|
||||||
@ -28,10 +45,7 @@ enum FastForwardSpeed: Double, CaseIterable, CustomStringConvertible
|
|||||||
return "\(self.rawValue)x"
|
return "\(self.rawValue)x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension FastForwardSpeed: LocalizedOptionValue
|
|
||||||
{
|
|
||||||
var localizedDescription: Text {
|
var localizedDescription: Text {
|
||||||
Text(self.description)
|
Text(self.description)
|
||||||
}
|
}
|
||||||
@ -50,24 +64,56 @@ struct VariableFastForwardOptions
|
|||||||
// @Option // No name = hidden
|
// @Option // No name = hidden
|
||||||
// var preferredSpeedsBySystem: [String: Double] = [:]
|
// 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?
|
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?
|
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?
|
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?
|
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?
|
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?
|
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?
|
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 image: UIImage?
|
||||||
var action: ((MenuItem) -> Void)
|
var action: ((MenuItem) -> Void)
|
||||||
|
|
||||||
|
var menu: UIMenu?
|
||||||
|
|
||||||
@objc dynamic var isSelected = false
|
@objc dynamic var isSelected = false
|
||||||
|
|
||||||
init(text: String, image: UIImage?, action: @escaping ((MenuItem) -> Void))
|
init(text: String, image: UIImage?, action: @escaping ((MenuItem) -> Void))
|
||||||
|
|||||||
@ -161,7 +161,7 @@ private extension PauseViewController
|
|||||||
self.sustainButtonsItem = nil
|
self.sustainButtonsItem = nil
|
||||||
self.fastForwardItem = 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.saveStateItem = MenuItem(text: NSLocalizedString("Save State", comment: ""), image: #imageLiteral(resourceName: "SaveSaveState"), action: { [unowned self] _ in
|
||||||
self.saveStatesViewControllerMode = .saving
|
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.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 })
|
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()
|
func updateSafeAreaInsets()
|
||||||
@ -194,4 +200,56 @@ private extension PauseViewController
|
|||||||
self.additionalSafeAreaInsets.right = 0
|
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())
|
options: CustomTintColorOptions())
|
||||||
var customTintColor
|
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()
|
private init()
|
||||||
{
|
{
|
||||||
self.prepareFeatures()
|
self.prepareFeatures()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user