Persists customized input mappings between app launches

This commit is contained in:
Riley Testut 2017-09-28 12:42:44 -07:00
parent d70105e30e
commit 94cbdbe159
10 changed files with 315 additions and 73 deletions

@ -1 +1 @@
Subproject commit f33f8bd91a9e0b41ba55b9aa8370397dd7e7f809 Subproject commit 7681a93515aea5c7642a56c888fe5da3aa4ea09f

View File

@ -79,6 +79,8 @@
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */; }; BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */; };
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */; }; BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */; };
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */; }; BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */; };
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */; };
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */; };
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; }; BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; }; BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
@ -109,6 +111,7 @@
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; }; BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; };
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */; };
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; }; BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; };
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; }; BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; };
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; };
@ -202,6 +205,7 @@
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ParentViewController.swift"; sourceTree = "<group>"; }; BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ParentViewController.swift"; sourceTree = "<group>"; };
BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ControllerSkin+Configuring.swift"; sourceTree = "<group>"; }; BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ControllerSkin+Configuring.swift"; sourceTree = "<group>"; };
BF696B7F1D9B2B02009639E0 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = Theming/Theme.swift; sourceTree = "<group>"; }; BF696B7F1D9B2B02009639E0 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Theme.swift; path = Theming/Theme.swift; sourceTree = "<group>"; };
BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GameControllerInputMappingTransformer.swift; path = Database/Model/Transformers/GameControllerInputMappingTransformer.swift; sourceTree = "<group>"; };
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BF6BF3121EB7E47F008E83CD /* ImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportOption.swift; path = Importing/ImportOption.swift; sourceTree = "<group>"; }; BF6BF3121EB7E47F008E83CD /* ImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportOption.swift; path = Importing/ImportOption.swift; sourceTree = "<group>"; };
BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iTunesImportOption.swift; path = "Importing/Import Options/iTunesImportOption.swift"; sourceTree = "<group>"; }; BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = iTunesImportOption.swift; path = "Importing/Import Options/iTunesImportOption.swift"; sourceTree = "<group>"; };
@ -209,6 +213,8 @@
BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseImportOption.swift; path = "Importing/Import Options/GamesDatabaseImportOption.swift"; sourceTree = "<group>"; }; BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseImportOption.swift; path = "Importing/Import Options/GamesDatabaseImportOption.swift"; sourceTree = "<group>"; };
BF6BF3201EB82362008E83CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/GamesDatabase.storyboard; sourceTree = "<group>"; }; BF6BF3201EB82362008E83CD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/GamesDatabase.storyboard; sourceTree = "<group>"; };
BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PhotoLibraryImportOption.swift; path = "Importing/Import Options/PhotoLibraryImportOption.swift"; sourceTree = "<group>"; }; BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PhotoLibraryImportOption.swift; path = "Importing/Import Options/PhotoLibraryImportOption.swift"; sourceTree = "<group>"; };
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = _GameControllerInputMapping.swift; path = Database/Model/Machine/_GameControllerInputMapping.swift; sourceTree = "<group>"; };
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GameControllerInputMapping.swift; path = Database/Model/Human/GameControllerInputMapping.swift; sourceTree = "<group>"; };
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; }; BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridMenuViewController.swift; path = "Pause Menu/GridMenuViewController.swift"; sourceTree = "<group>"; }; BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridMenuViewController.swift; path = "Pause Menu/GridMenuViewController.swift"; sourceTree = "<group>"; };
@ -378,6 +384,7 @@
BF5942721E09BC700051894B /* Model.xcdatamodel */, BF5942721E09BC700051894B /* Model.xcdatamodel */,
BF5942741E09BC740051894B /* Human */, BF5942741E09BC740051894B /* Human */,
BF5942751E09BC780051894B /* Machine */, BF5942751E09BC780051894B /* Machine */,
BF6B82A31F7CC29A00042BFB /* Transformers */,
BF5942761E09BC7C0051894B /* Misc */, BF5942761E09BC7C0051894B /* Misc */,
); );
name = Model; name = Model;
@ -390,6 +397,7 @@
BF5942781E09BC830051894B /* ControllerSkin.swift */, BF5942781E09BC830051894B /* ControllerSkin.swift */,
BF5942791E09BC830051894B /* Game.swift */, BF5942791E09BC830051894B /* Game.swift */,
BF59427A1E09BC830051894B /* GameCollection.swift */, BF59427A1E09BC830051894B /* GameCollection.swift */,
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */,
BF59427B1E09BC830051894B /* SaveState.swift */, BF59427B1E09BC830051894B /* SaveState.swift */,
); );
name = Human; name = Human;
@ -402,6 +410,7 @@
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */, BF5942821E09BC8B0051894B /* _ControllerSkin.swift */,
BF5942831E09BC8B0051894B /* _Game.swift */, BF5942831E09BC8B0051894B /* _Game.swift */,
BF5942841E09BC8B0051894B /* _GameCollection.swift */, BF5942841E09BC8B0051894B /* _GameCollection.swift */,
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */,
BF5942851E09BC8B0051894B /* _SaveState.swift */, BF5942851E09BC8B0051894B /* _SaveState.swift */,
); );
name = Machine; name = Machine;
@ -433,6 +442,14 @@
name = Theming; name = Theming;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF6B82A31F7CC29A00042BFB /* Transformers */ = {
isa = PBXGroup;
children = (
BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */,
);
name = Transformers;
sourceTree = "<group>";
};
BF6BF3161EB820F4008E83CD /* Import Options */ = { BF6BF3161EB820F4008E83CD /* Import Options */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -811,6 +828,7 @@
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */, BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */,
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */, BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */, BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */,
BF59427C1E09BC830051894B /* Cheat.swift in Sources */, BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */, BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */, BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
@ -859,9 +877,11 @@
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */, BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */, BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */, BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */, BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */, BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */, BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */, BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */, BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */,

View File

@ -0,0 +1,77 @@
//
// GameControllerInputMapping.swift
// Delta
//
// Created by Riley Testut on 8/30/16.
// Copyright (c) 2016 Riley Testut. All rights reserved.
//
import Foundation
import DeltaCore
@objc(GameControllerInputMapping)
public class GameControllerInputMapping: _GameControllerInputMapping
{
fileprivate var inputMapping: DeltaCore.GameControllerInputMapping {
get { return self.deltaCoreInputMapping as! DeltaCore.GameControllerInputMapping }
set { self.deltaCoreInputMapping = newValue }
}
public convenience init(inputMapping: DeltaCore.GameControllerInputMapping, context: NSManagedObjectContext)
{
self.init(entity: GameControllerInputMapping.entity(), insertInto: context)
self.inputMapping = inputMapping
}
}
extension GameControllerInputMapping
{
class func inputMapping(for gameController: GameController, gameType: GameType, in managedObjectContext: NSManagedObjectContext) -> GameControllerInputMapping?
{
guard let playerIndex = gameController.playerIndex else {
return nil
}
let fetchRequest: NSFetchRequest<GameControllerInputMapping> = GameControllerInputMapping.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %@ AND %K == %d", #keyPath(GameControllerInputMapping.gameControllerInputType), gameController.inputType.rawValue, #keyPath(GameControllerInputMapping.gameType), gameType.rawValue, #keyPath(GameControllerInputMapping.playerIndex), playerIndex)
do
{
let inputMappings = try managedObjectContext.fetch(fetchRequest)
let inputMapping = inputMappings.first
return inputMapping
}
catch
{
print(error)
return nil
}
}
}
extension GameControllerInputMapping: GameControllerInputMappingProtocol
{
var name: String? {
get { return self.inputMapping.name }
set { self.inputMapping.name = newValue }
}
var supportedControllerInputs: [Input] {
return self.inputMapping.supportedControllerInputs
}
public func input(forControllerInput controllerInput: Input) -> Input?
{
return self.inputMapping.input(forControllerInput: controllerInput)
}
func set(_ input: Input?, forControllerInput controllerInput: Input)
{
self.inputMapping.set(input, forControllerInput: controllerInput)
}
}

View File

@ -0,0 +1,28 @@
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to GameControllerInputMapping.swift instead.
import Foundation
import CoreData
import DeltaCore
public class _GameControllerInputMapping: NSManagedObject
{
@nonobjc public class func fetchRequest() -> NSFetchRequest<GameControllerInputMapping> {
return NSFetchRequest<GameControllerInputMapping>(entityName: "GameControllerInputMapping")
}
// MARK: - Properties
@NSManaged public var deltaCoreInputMapping: Any
@NSManaged public var gameControllerInputType: GameControllerInputType
@NSManaged public var gameType: GameType
@NSManaged public var playerIndex: Int16
// MARK: - Relationships
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11759" systemVersion="16C68" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0"> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13240" systemVersion="16G29" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<entity name="Cheat" representedClassName="Cheat" syncable="YES"> <entity name="Cheat" representedClassName="Cheat" syncable="YES">
<attribute name="code" attributeType="String" syncable="YES"/> <attribute name="code" attributeType="String" syncable="YES"/>
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> <attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
@ -79,6 +79,31 @@
</uniquenessConstraint> </uniquenessConstraint>
</uniquenessConstraints> </uniquenessConstraints>
</entity> </entity>
<entity name="GameControllerInputMapping" representedClassName="GameControllerInputMapping" syncable="YES">
<attribute name="deltaCoreInputMapping" attributeType="Transformable" valueTransformerName="GameControllerInputMappingTransformer" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="Any"/>
</userInfo>
</attribute>
<attribute name="gameControllerInputType" attributeType="Transformable" valueTransformerName="" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameControllerInputType"/>
</userInfo>
</attribute>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="playerIndex" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="gameControllerInputType"/>
<constraint value="gameType"/>
<constraint value="playerIndex"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="SaveState" representedClassName="SaveState" syncable="YES"> <entity name="SaveState" representedClassName="SaveState" syncable="YES">
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/> <attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="filename" attributeType="String" syncable="YES"> <attribute name="filename" attributeType="String" syncable="YES">
@ -107,6 +132,7 @@
<element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/> <element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/> <element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/> <element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="105"/>
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/> <element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
</elements> </elements>
</model> </model>

View File

@ -0,0 +1,61 @@
//
// GameControllerInputMappingTransformer.swift
// Delta
//
// Created by Riley Testut on 9/27/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import Foundation
import DeltaCore
@objc(GameControllerInputMappingTransformer)
class GameControllerInputMappingTransformer: ValueTransformer
{
override class func transformedValueClass() -> AnyClass {
return NSData.self
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func transformedValue(_ value: Any?) -> Any?
{
guard let inputMapping = value as? DeltaCore.GameControllerInputMapping else { return nil }
let plistEncoder = PropertyListEncoder()
do
{
let data = try plistEncoder.encode(inputMapping)
return data
}
catch
{
print(error)
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any?
{
guard let inputMappingData = value as? Data else { return nil }
let plistDecoder = PropertyListDecoder()
do
{
let inputMapping = try plistDecoder.decode(DeltaCore.GameControllerInputMapping.self, from: inputMappingData)
return inputMapping
}
catch
{
print(error)
return nil
}
}
}

View File

@ -32,12 +32,15 @@ private extension GameViewController
struct SustainInputsMapping: GameControllerInputMappingProtocol struct SustainInputsMapping: GameControllerInputMappingProtocol
{ {
let gameControllerInputType: GameControllerInputType let gameController: GameController
let previousInputMapping: GameControllerInputMappingProtocol?
var gameControllerInputType: GameControllerInputType {
return self.gameController.inputType
}
func input(forControllerInput controllerInput: Input) -> Input? func input(forControllerInput controllerInput: Input) -> Input?
{ {
if let mappedInput = self.previousInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu if let mappedInput = self.gameController.defaultInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu
{ {
return mappedInput return mappedInput
} }
@ -355,6 +358,8 @@ private extension GameViewController
{ {
@objc func updateControllers() @objc func updateControllers()
{ {
guard let emulatorCore = self.emulatorCore, let game = self.game else { return }
let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers
if let index = Settings.localControllerPlayerIndex if let index = Settings.localControllerPlayerIndex
@ -368,28 +373,25 @@ private extension GameViewController
self.controllerView.isHidden = true self.controllerView.isHidden = true
} }
// Removing all game controllers from EmulatorCore will reset each controller's playerIndex to nil for gameController in controllers
// We temporarily cache their playerIndexes, and then we reset them after removing all controllers
var controllerIndexes = [ObjectIdentifier: Int?]()
controllers.forEach { controllerIndexes[ObjectIdentifier($0)] = $0.playerIndex }
self.emulatorCore?.removeAllGameControllers()
// Reset each controller's playerIndex to what it was before removing all controllers from EmulatorCore
controllers.forEach { $0.playerIndex = controllerIndexes[ObjectIdentifier($0)] ?? nil }
for controller in controllers
{ {
if let index = controller.playerIndex if gameController.playerIndex != nil
{ {
// We need to place the underscore here to silence erroneous unused result warning despite annotating function with @discardableResult if let inputMapping = GameControllerInputMapping.inputMapping(for: gameController, gameType: game.type, in: DatabaseManager.shared.viewContext)
// Hopefully this bug won't be around for too long... {
_ = self.emulatorCore?.setGameController(controller, at: index) gameController.addReceiver(self, inputMapping: inputMapping)
controller.addReceiver(self) gameController.addReceiver(emulatorCore, inputMapping: inputMapping)
} }
else else
{ {
controller.removeReceiver(self) gameController.addReceiver(self)
gameController.addReceiver(emulatorCore)
}
}
else
{
gameController.removeReceiver(self)
gameController.removeReceiver(emulatorCore)
} }
} }
@ -623,8 +625,8 @@ private extension GameViewController
self.isSelectingSustainedButtons = true self.isSelectingSustainedButtons = true
self.sustainInputsMapping = SustainInputsMapping(gameControllerInputType: gameController.inputType, previousInputMapping: gameController.inputMapping) let sustainInputsMapping = SustainInputsMapping(gameController: gameController)
gameController.inputMapping = self.sustainInputsMapping gameController.addReceiver(self, inputMapping: sustainInputsMapping)
let blurEffect = self.sustainButtonsBlurView.effect let blurEffect = self.sustainButtonsBlurView.effect
self.sustainButtonsBlurView.effect = nil self.sustainButtonsBlurView.effect = nil
@ -643,7 +645,7 @@ private extension GameViewController
self.isSelectingSustainedButtons = false self.isSelectingSustainedButtons = false
gameController.inputMapping = self.sustainInputsMapping?.previousInputMapping self.updateControllers()
self.sustainInputsMapping = nil self.sustainInputsMapping = nil
// Reactivate all sustained inputs, since they will now be mapped to game inputs. // Reactivate all sustained inputs, since they will now be mapped to game inputs.

View File

@ -17,7 +17,7 @@ class ControllerInputsViewController: UIViewController
{ {
var gameController: GameController! { var gameController: GameController! {
didSet { didSet {
self.prepareGameController() self.gameController.addReceiver(self, inputMapping: nil)
} }
} }
@ -28,8 +28,8 @@ class ControllerInputsViewController: UIViewController
} }
} }
fileprivate var inputMapping: GameControllerInputMapping! fileprivate lazy var managedObjectContext: NSManagedObjectContext = DatabaseManager.shared.newBackgroundContext()
fileprivate var previousInputMapping: GameControllerInputMappingProtocol? fileprivate var inputMappings = [System: GameControllerInputMapping]()
fileprivate let supportedActionInputs: [ActionInput] = [.saveState, .loadState, .fastForward] fileprivate let supportedActionInputs: [ActionInput] = [.saveState, .loadState, .fastForward]
@ -91,8 +91,11 @@ extension ControllerInputsViewController
self.actionsMenuViewController = segue.destination as! GridMenuViewController self.actionsMenuViewController = segue.destination as! GridMenuViewController
self.prepareActionsMenuViewController() self.prepareActionsMenuViewController()
case "cancelControllerControls": self.gameController.inputMapping = self.previousInputMapping case "cancelControllerInputs": break
case "saveControllerControls": self.gameController.inputMapping = self.inputMapping case "saveControllerInputs":
self.managedObjectContext.performAndWait {
self.managedObjectContext.saveWithErrorLogging()
}
default: break default: break
} }
@ -105,6 +108,7 @@ private extension ControllerInputsViewController
{ {
guard self.isViewLoaded else { return } guard self.isViewLoaded else { return }
// Update popoverMenuButton to display correctly on iOS 10.
if let popoverMenuButton = self.navigationItem.popoverMenuController?.popoverMenuButton if let popoverMenuButton = self.navigationItem.popoverMenuController?.popoverMenuButton
{ {
popoverMenuButton.title = self.system.localizedShortName popoverMenuButton.title = self.system.localizedShortName
@ -113,8 +117,31 @@ private extension ControllerInputsViewController
self.navigationController?.navigationBar.layoutIfNeeded() self.navigationController?.navigationBar.layoutIfNeeded()
} }
// Update controller view's controller skin.
self.gameViewController.controllerView.controllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: self.system.gameType) self.gameViewController.controllerView.controllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: self.system.gameType)
// Fetch input mapping if it hasn't already been fetched.
if let gameController = self.gameController, let playerIndex = gameController.playerIndex, self.inputMappings[self.system] == nil
{
self.managedObjectContext.performAndWait {
let inputMapping = GameControllerInputMapping.inputMapping(for: gameController, gameType: self.system.gameType, in: self.managedObjectContext) ?? {
let deltaCoreInputMapping = gameController.defaultInputMapping as? DeltaCore.GameControllerInputMapping ?? DeltaCore.GameControllerInputMapping(gameControllerInputType: gameController.inputType)
let inputMapping = GameControllerInputMapping(inputMapping: deltaCoreInputMapping, context: self.managedObjectContext)
inputMapping.gameControllerInputType = gameController.inputType
inputMapping.gameType = self.system.gameType
inputMapping.playerIndex = Int16(playerIndex)
return inputMapping
}()
inputMapping.name = String.localizedStringWithFormat("Custom %@", gameController.name)
self.inputMappings[self.system] = inputMapping
}
}
// Update callouts, if view is already on screen.
if self.view.window != nil if self.view.window != nil
{ {
self.calloutViews.forEach { $1.dismissCallout(animated: true) } self.calloutViews.forEach { $1.dismissCallout(animated: true) }
@ -126,18 +153,6 @@ private extension ControllerInputsViewController
} }
} }
func prepareGameController()
{
self.gameController.addReceiver(self)
self.previousInputMapping = self.gameController.inputMapping
self.inputMapping = self.gameController.inputMapping as? GameControllerInputMapping ?? GameControllerInputMapping(gameControllerInputType: self.gameController.inputType)
self.inputMapping.name = String.localizedStringWithFormat("Custom %@", self.gameController.name)
self.gameController.inputMapping = nil
}
func preparePopoverMenuController() func preparePopoverMenuController()
{ {
let listMenuViewController = ListMenuViewController() let listMenuViewController = ListMenuViewController()
@ -186,7 +201,7 @@ private extension ControllerInputsViewController
text = NSLocalizedString("Fast Forward", comment: "") text = NSLocalizedString("Fast Forward", comment: "")
} }
let item = MenuItem(text: text, image: image) { (item) in let item = MenuItem(text: text, image: image) { [unowned self] (item) in
guard let calloutView = self.calloutViews[AnyInput(input)] else { return } guard let calloutView = self.calloutViews[AnyInput(input)] else { return }
self.toggle(calloutView) self.toggle(calloutView)
} }
@ -204,7 +219,8 @@ private extension ControllerInputsViewController
let controllerView = self.gameViewController.controllerView, let controllerView = self.gameViewController.controllerView,
let traits = controllerView.controllerSkinTraits, let traits = controllerView.controllerSkinTraits,
let items = controllerView.controllerSkin?.items(for: traits), let items = controllerView.controllerSkin?.items(for: traits),
let controllerViewInputMapping = controllerView.inputMapping let controllerViewInputMapping = controllerView.defaultInputMapping,
let inputMapping = self.inputMappings[self.system]
else { return } else { return }
// Implicit assumption that all skins used for controller input mapping don't have multiple items with same input. // Implicit assumption that all skins used for controller input mapping don't have multiple items with same input.
@ -218,8 +234,9 @@ private extension ControllerInputsViewController
self.calloutViews[AnyInput(input)] = calloutView self.calloutViews[AnyInput(input)] = calloutView
} }
self.managedObjectContext.performAndWait {
// Update callout views with controller inputs that map to callout views' associated controller skin inputs. // Update callout views with controller inputs that map to callout views' associated controller skin inputs.
for input in self.inputMapping.supportedControllerInputs for input in inputMapping.supportedControllerInputs
{ {
let mappedInput = self.mappedInput(for: input) let mappedInput = self.mappedInput(for: input)
@ -236,6 +253,7 @@ private extension ControllerInputsViewController
} }
} }
} }
}
// Present only callout views that are associated with a controller input. // Present only callout views that are associated with a controller input.
for calloutView in self.calloutViews.values for calloutView in self.calloutViews.values
@ -252,6 +270,8 @@ private extension ControllerInputsViewController
{ {
func updateActiveCalloutView(with controllerInput: Input?) func updateActiveCalloutView(with controllerInput: Input?)
{ {
guard let inputMapping = self.inputMappings[self.system] else { return }
guard let activeCalloutView = self.activeCalloutView else { return } guard let activeCalloutView = self.activeCalloutView else { return }
guard let input = self.calloutViews.first(where: { $0.value == activeCalloutView })?.key else { return } guard let input = self.calloutViews.first(where: { $0.value == activeCalloutView })?.key else { return }
@ -271,20 +291,22 @@ private extension ControllerInputsViewController
} }
} }
for supportedInput in self.inputMapping.supportedControllerInputs self.managedObjectContext.performAndWait {
for supportedInput in inputMapping.supportedControllerInputs
{ {
let mappedInput = self.mappedInput(for: supportedInput) let mappedInput = self.mappedInput(for: supportedInput)
if mappedInput == input if mappedInput == input
{ {
// Set all existing controller inputs that currently map to "input" to instead map to nil. // Set all existing controller inputs that currently map to "input" to instead map to nil.
self.inputMapping.set(nil, forControllerInput: supportedInput) inputMapping.set(nil, forControllerInput: supportedInput)
} }
} }
if let controllerInput = controllerInput if let controllerInput = controllerInput
{ {
self.inputMapping.set(input, forControllerInput: controllerInput) inputMapping.set(input, forControllerInput: controllerInput)
}
} }
activeCalloutView.input = controllerInput activeCalloutView.input = controllerInput
@ -352,7 +374,11 @@ private extension ControllerInputsViewController
{ {
func mappedInput(for input: Input) -> AnyInput func mappedInput(for input: Input) -> AnyInput
{ {
guard let mappedInput = self.inputMapping.input(forControllerInput: input) else { guard let inputMapping = self.inputMappings[self.system] else {
fatalError("Input mapping for current system does not exist.")
}
guard let mappedInput = inputMapping.input(forControllerInput: input) else {
fatalError("Mapped input for provided input does not exist.") fatalError("Mapped input for provided input does not exist.")
} }
@ -450,6 +476,8 @@ extension ControllerInputsViewController: GameControllerReceiver
{ {
func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input) func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input)
{ {
guard self.isViewLoaded else { return }
switch gameController switch gameController
{ {
case self.gameViewController.controllerView: case self.gameViewController.controllerView:

View File

@ -36,7 +36,7 @@ private class LocalDeviceController: NSObject, GameController
let inputType: GameControllerInputType = .standard let inputType: GameControllerInputType = .standard
var inputMapping: GameControllerInputMappingProtocol? var defaultInputMapping: GameControllerInputMappingProtocol?
} }
class ControllersSettingsViewController: UITableViewController class ControllersSettingsViewController: UITableViewController

View File

@ -286,12 +286,12 @@
<navigationItem key="navigationItem" id="UeP-Yr-9jA"> <navigationItem key="navigationItem" id="UeP-Yr-9jA">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="QfC-sf-WbP"> <barButtonItem key="leftBarButtonItem" systemItem="cancel" id="QfC-sf-WbP">
<connections> <connections>
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="cancelControllerControls" unwindAction="unwindFromControllerControlsViewController:" id="AkD-Lu-h5b"/> <segue destination="8l5-7I-Z7e" kind="unwind" identifier="cancelControllerInputs" unwindAction="unwindFromControllerControlsViewController:" id="AkD-Lu-h5b"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
<barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="WHh-7W-jpl"> <barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="WHh-7W-jpl">
<connections> <connections>
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="saveControllerControls" unwindAction="unwindFromControllerControlsViewController:" id="4xr-OB-4dx"/> <segue destination="8l5-7I-Z7e" kind="unwind" identifier="saveControllerInputs" unwindAction="unwindFromControllerControlsViewController:" id="4xr-OB-4dx"/>
</connections> </connections>
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>