Merge branch 'release/beta5'
This commit is contained in:
commit
93ffb83e94
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "Cores/GBADeltaCore"]
|
||||
path = Cores/GBADeltaCore
|
||||
url = git@github.com:rileytestut/GBADeltaCore.git
|
||||
[submodule "Cores/GBCDeltaCore"]
|
||||
path = Cores/GBCDeltaCore
|
||||
url = git@github.com:rileytestut/GBCDeltaCore.git
|
||||
|
||||
5168
Artwork/Icon.ai
Normal file
5168
Artwork/Icon.ai
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
||||
Subproject commit 99da200c440a8dad93d9b92314f91b933b659bf8
|
||||
Subproject commit d7213260852683ea5a3f6c2274733bbd65d2ec92
|
||||
@ -1 +1 @@
|
||||
Subproject commit ae5ab38dbe6a5fc995f4f3a8cb7075dad3f8f6d5
|
||||
Subproject commit 7babfcf93a32916ad06989c9a63681acf7f07d50
|
||||
1
Cores/GBCDeltaCore
Submodule
1
Cores/GBCDeltaCore
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9d714254ace7bd5d63805d434f4ccd1f2a947951
|
||||
@ -1 +1 @@
|
||||
Subproject commit e49d318e2e18034b2bccea34047800956298de4a
|
||||
Subproject commit a354c3700fc4576bbefb6a8324bcd7a121d9b934
|
||||
@ -27,12 +27,12 @@
|
||||
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF0418131D01E93400E85BCF /* GBADeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */; };
|
||||
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF107EC31BF413F000E0C32C /* GamesViewController.swift */; };
|
||||
BF11734D1DA32A5200047DF8 /* GameType+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734C1DA32A5200047DF8 /* GameType+Localization.swift */; };
|
||||
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
|
||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */; };
|
||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */; };
|
||||
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15AF831F54B43B009B6AAB /* ActionInput.swift */; };
|
||||
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
|
||||
BF1DAD5D1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */; };
|
||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
|
||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
|
||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF31878A1D489AAA00BD020D /* CheatValidator.swift */; };
|
||||
@ -40,19 +40,20 @@
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
||||
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; };
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF81C5D870B00C1184C /* PauseItem.swift */; };
|
||||
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF81C5D870B00C1184C /* MenuItem.swift */; };
|
||||
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */; };
|
||||
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
|
||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
|
||||
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */; };
|
||||
BF59425C1E09BB810051894B /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942581E09BB810051894B /* Action.swift */; };
|
||||
BF4828841F9027B600028B97 /* Delta.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4828811F9027B600028B97 /* Delta.xcdatamodeld */; };
|
||||
BF4828861F9028F500028B97 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828851F9028F500028B97 /* System.swift */; };
|
||||
BF4828881F90290F00028B97 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828871F90290F00028B97 /* Action.swift */; };
|
||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */; };
|
||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; };
|
||||
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; };
|
||||
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */; };
|
||||
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426D1E09BC5D0051894B /* DatabaseManager.swift */; };
|
||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426E1E09BC5D0051894B /* GamesDatabase.swift */; };
|
||||
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = BF5942721E09BC700051894B /* Model.xcdatamodel */; };
|
||||
BF59427C1E09BC830051894B /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942771E09BC830051894B /* Cheat.swift */; };
|
||||
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942781E09BC830051894B /* ControllerSkin.swift */; };
|
||||
BF59427E1E09BC830051894B /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942791E09BC830051894B /* Game.swift */; };
|
||||
@ -69,13 +70,26 @@
|
||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */; };
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
|
||||
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; };
|
||||
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */; };
|
||||
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */; };
|
||||
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
|
||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.swift */; };
|
||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3121EB7E47F008E83CD /* ImportOption.swift */; };
|
||||
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */; };
|
||||
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */; };
|
||||
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */; };
|
||||
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */; };
|
||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */; };
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; };
|
||||
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */; };
|
||||
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */; };
|
||||
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */; };
|
||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */; };
|
||||
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */; };
|
||||
@ -85,14 +99,21 @@
|
||||
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63BDE91D389EEB00FCB040 /* GameViewController.swift */; };
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
|
||||
BFC314771E0C8CFC0056E3A8 /* GameType+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */; };
|
||||
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */; };
|
||||
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC6F7B71F435BC500221B96 /* Input+Display.swift */; };
|
||||
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
|
||||
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; };
|
||||
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
|
||||
BFDC7B9F1F7DE0CF0052A7C5 /* Delta.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */; };
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
|
||||
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; };
|
||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
|
||||
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
|
||||
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */; };
|
||||
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, ); }; };
|
||||
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */; };
|
||||
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; };
|
||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; };
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; };
|
||||
@ -113,6 +134,7 @@
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */,
|
||||
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */,
|
||||
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */,
|
||||
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */,
|
||||
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */,
|
||||
@ -129,88 +151,109 @@
|
||||
A19FF50F55441BC2B2248241 /* Pods-Delta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.release.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.release.xcconfig"; sourceTree = "<group>"; };
|
||||
BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = openvgdb.sqlite; sourceTree = "<group>"; };
|
||||
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBADeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkinsViewController.swift; path = "Controller Skins/ControllerSkinsViewController.swift"; sourceTree = "<group>"; };
|
||||
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinsViewController.swift; sourceTree = "<group>"; };
|
||||
BF090CF11B490D8300DCAB45 /* Delta-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Delta-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
BF107EC31BF413F000E0C32C /* GamesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = "<group>"; };
|
||||
BF11734C1DA32A5200047DF8 /* GameType+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GameType+Localization.swift"; sourceTree = "<group>"; };
|
||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllersSettingsViewController.swift; path = Controllers/ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewGameViewController.swift; sourceTree = "<group>"; };
|
||||
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EmulatorCore+Cheats.swift"; sourceTree = "<group>"; };
|
||||
BF15AF831F54B43B009B6AAB /* ActionInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionInput.swift; sourceTree = "<group>"; };
|
||||
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Importing.swift"; sourceTree = "<group>"; };
|
||||
BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameTypeControllerSkinsViewController.swift; path = "Controller Skins/GameTypeControllerSkinsViewController.swift"; sourceTree = "<group>"; };
|
||||
BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemControllerSkinsViewController.swift; sourceTree = "<group>"; };
|
||||
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
||||
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
|
||||
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = "<group>"; };
|
||||
BF31878A1D489AAA00BD020D /* CheatValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatValidator.swift; path = "Pause Menu/Cheats/CheatValidator.swift"; sourceTree = "<group>"; };
|
||||
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditCheatViewController.swift; path = "Pause Menu/Cheats/EditCheatViewController.swift"; sourceTree = "<group>"; };
|
||||
BF34FA101CF1899D006624C7 /* CheatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTextView.swift; path = "Pause Menu/Cheats/CheatTextView.swift"; sourceTree = "<group>"; };
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = "<group>"; };
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesCollectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
BF31878A1D489AAA00BD020D /* CheatValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatValidator.swift; sourceTree = "<group>"; };
|
||||
BF34FA061CF0F510006624C7 /* EditCheatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditCheatViewController.swift; sourceTree = "<group>"; };
|
||||
BF34FA101CF1899D006624C7 /* CheatTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatTextView.swift; sourceTree = "<group>"; };
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseViewController.swift; sourceTree = "<group>"; };
|
||||
BF353FF51C5D837600C1184C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PauseMenu.storyboard; sourceTree = "<group>"; };
|
||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseItem.swift; path = "Pause Menu/PauseItem.swift"; sourceTree = "<group>"; };
|
||||
BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PausePresentationController.swift; path = "Pause Menu/Presentation Controller/PausePresentationController.swift"; sourceTree = "<group>"; };
|
||||
BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PausePresentationControllerContentView.xib; path = "Pause Menu/Presentation Controller/PausePresentationControllerContentView.xib"; sourceTree = "<group>"; };
|
||||
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseStoryboardSegue.swift; path = "Pause Menu/Segues/PauseStoryboardSegue.swift"; sourceTree = "<group>"; };
|
||||
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesViewController.swift; path = "Pause Menu/Save States/SaveStatesViewController.swift"; sourceTree = "<group>"; };
|
||||
BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseTransitionCoordinator.swift; path = "Pause Menu/Segues/PauseTransitionCoordinator.swift"; sourceTree = "<group>"; };
|
||||
BF5942581E09BB810051894B /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Action.swift; path = Components/Action.swift; sourceTree = "<group>"; };
|
||||
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadControllerSkinImageOperation.swift; path = Components/Loading/LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadImageURLOperation.swift; path = Components/Loading/LoadImageURLOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewCell.swift; path = "Components/Collection View/GridCollectionViewCell.swift"; sourceTree = "<group>"; };
|
||||
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewLayout.swift; path = "Components/Collection View/GridCollectionViewLayout.swift"; sourceTree = "<group>"; };
|
||||
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseManager.swift; path = Database/DatabaseManager.swift; sourceTree = "<group>"; };
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabase.swift; path = Database/OpenVGDB/GamesDatabase.swift; sourceTree = "<group>"; };
|
||||
BF5942721E09BC700051894B /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; name = Model.xcdatamodel; path = Database/Model/Model.xcdatamodel; sourceTree = "<group>"; };
|
||||
BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Cheat.swift; path = Database/Model/Human/Cheat.swift; sourceTree = "<group>"; };
|
||||
BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkin.swift; path = Database/Model/Human/ControllerSkin.swift; sourceTree = "<group>"; };
|
||||
BF5942791E09BC830051894B /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Game.swift; path = Database/Model/Human/Game.swift; sourceTree = "<group>"; };
|
||||
BF59427A1E09BC830051894B /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollection.swift; path = Database/Model/Human/GameCollection.swift; sourceTree = "<group>"; };
|
||||
BF59427B1E09BC830051894B /* SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveState.swift; path = Database/Model/Human/SaveState.swift; sourceTree = "<group>"; };
|
||||
BF5942811E09BC8B0051894B /* _Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _Cheat.swift; path = Database/Model/Machine/_Cheat.swift; sourceTree = "<group>"; };
|
||||
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _ControllerSkin.swift; path = Database/Model/Machine/_ControllerSkin.swift; sourceTree = "<group>"; };
|
||||
BF5942831E09BC8B0051894B /* _Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _Game.swift; path = Database/Model/Machine/_Game.swift; sourceTree = "<group>"; };
|
||||
BF5942841E09BC8B0051894B /* _GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _GameCollection.swift; path = Database/Model/Machine/_GameCollection.swift; sourceTree = "<group>"; };
|
||||
BF5942851E09BC8B0051894B /* _SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _SaveState.swift; path = Database/Model/Machine/_SaveState.swift; sourceTree = "<group>"; };
|
||||
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ControllerSkinConfigurations.h; path = Database/Model/Misc/ControllerSkinConfigurations.h; sourceTree = "<group>"; };
|
||||
BF59428D1E09BCFB0051894B /* ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportController.swift; path = Components/Importing/ImportController.swift; sourceTree = "<group>"; };
|
||||
BF353FF81C5D870B00C1184C /* MenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuItem.swift; sourceTree = "<group>"; };
|
||||
BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PausePresentationController.swift; sourceTree = "<group>"; };
|
||||
BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = PausePresentationControllerContentView.xib; path = Delta/Base.lproj/PausePresentationControllerContentView.xib; sourceTree = SOURCE_ROOT; };
|
||||
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesViewController.swift; sourceTree = "<group>"; };
|
||||
BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseTransitionCoordinator.swift; sourceTree = "<group>"; };
|
||||
BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 2.xcdatamodel"; sourceTree = "<group>"; };
|
||||
BF4828831F9027B600028B97 /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Delta.xcdatamodel; sourceTree = "<group>"; };
|
||||
BF4828851F9028F500028B97 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
|
||||
BF4828871F90290F00028B97 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = "<group>"; };
|
||||
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadImageURLOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewLayout.swift; sourceTree = "<group>"; };
|
||||
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesDatabase.swift; sourceTree = "<group>"; };
|
||||
BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cheat.swift; sourceTree = "<group>"; };
|
||||
BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkin.swift; sourceTree = "<group>"; };
|
||||
BF5942791E09BC830051894B /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = "<group>"; };
|
||||
BF59427A1E09BC830051894B /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = "<group>"; };
|
||||
BF59427B1E09BC830051894B /* SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveState.swift; sourceTree = "<group>"; };
|
||||
BF5942811E09BC8B0051894B /* _Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Cheat.swift; sourceTree = "<group>"; };
|
||||
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _ControllerSkin.swift; sourceTree = "<group>"; };
|
||||
BF5942831E09BC8B0051894B /* _Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Game.swift; sourceTree = "<group>"; };
|
||||
BF5942841E09BC8B0051894B /* _GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _GameCollection.swift; sourceTree = "<group>"; };
|
||||
BF5942851E09BC8B0051894B /* _SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _SaveState.swift; sourceTree = "<group>"; };
|
||||
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ControllerSkinConfigurations.h; sourceTree = "<group>"; };
|
||||
BF59428D1E09BCFB0051894B /* ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportController.swift; sourceTree = "<group>"; };
|
||||
BF59428F1E09BD1A0051894B /* NSFetchedResultsController+Conveniences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFetchedResultsController+Conveniences.h"; sourceTree = "<group>"; };
|
||||
BF5942901E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFetchedResultsController+Conveniences.m"; sourceTree = "<group>"; };
|
||||
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
|
||||
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Settings.storyboard; path = Delta/Base.lproj/Settings.storyboard; sourceTree = SOURCE_ROOT; };
|
||||
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerInputsViewController.swift; sourceTree = "<group>"; };
|
||||
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
|
||||
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListMenuViewController.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>"; };
|
||||
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; path = Theme.swift; sourceTree = "<group>"; };
|
||||
BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerInputMappingTransformer.swift; 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; path = ImportOption.swift; sourceTree = "<group>"; };
|
||||
BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iTunesImportOption.swift; sourceTree = "<group>"; };
|
||||
BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClipboardImportOption.swift; sourceTree = "<group>"; };
|
||||
BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesDatabaseImportOption.swift; 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; path = PhotoLibraryImportOption.swift; sourceTree = "<group>"; };
|
||||
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _GameControllerInputMapping.swift; sourceTree = "<group>"; };
|
||||
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerInputMapping.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMenuViewController.swift; sourceTree = "<group>"; };
|
||||
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = "<group>"; };
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadata.swift; path = Database/OpenVGDB/GameMetadata.swift; sourceTree = "<group>"; };
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseBrowserViewController.swift; path = Database/OpenVGDB/GamesDatabaseBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkinTableViewCell.swift; path = "Controller Skins/ControllerSkinTableViewCell.swift"; sourceTree = "<group>"; };
|
||||
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuController.swift; sourceTree = "<group>"; };
|
||||
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCalloutView.swift; sourceTree = "<group>"; };
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadata.swift; sourceTree = "<group>"; };
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesDatabaseBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinTableViewCell.swift; sourceTree = "<group>"; };
|
||||
BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DeltaCoreProtocol+Delta.swift"; sourceTree = "<group>"; };
|
||||
BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GameType+Delta.swift"; sourceTree = "<group>"; };
|
||||
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = "<group>"; };
|
||||
BFC6F7B71F435BC500221B96 /* Input+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Input+Display.swift"; sourceTree = "<group>"; };
|
||||
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatsViewController.swift; sourceTree = "<group>"; };
|
||||
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Delta.xcmappingmodel; sourceTree = "<group>"; };
|
||||
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesStoryboardSegue.swift; path = Segues/SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuButton.swift; sourceTree = "<group>"; };
|
||||
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputStreamOutputWriter.swift; path = Components/Importing/InputStreamOutputWriter.swift; sourceTree = "<group>"; };
|
||||
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadataTableViewCell.swift; path = Database/OpenVGDB/GameMetadataTableViewCell.swift; sourceTree = "<group>"; };
|
||||
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputStreamOutputWriter.swift; sourceTree = "<group>"; };
|
||||
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadataTableViewCell.swift; sourceTree = "<group>"; };
|
||||
BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFFA71DB1AAC406100EE9DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
BFFA71E11AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesPresentationController.swift; path = Segues/GamesPresentationController.swift; sourceTree = "<group>"; };
|
||||
BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesStoryboardSegue.swift; path = Segues/GamesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InitialGamesStoryboardSegue.swift; path = Segues/InitialGamesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LaunchViewController.swift; path = Launch/LaunchViewController.swift; sourceTree = "<group>"; };
|
||||
BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesPresentationController.swift; sourceTree = "<group>"; };
|
||||
BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialGamesStoryboardSegue.swift; sourceTree = "<group>"; };
|
||||
BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
||||
BFFC46451D59861000AF2CC6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
BFFC464B1D5998D600AF2CC6 /* CheatTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTableViewCell.swift; path = "Pause Menu/Cheats/CheatTableViewCell.swift"; sourceTree = "<group>"; };
|
||||
BFFC464B1D5998D600AF2CC6 /* CheatTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatTableViewCell.swift; sourceTree = "<group>"; };
|
||||
C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -224,6 +267,7 @@
|
||||
BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */,
|
||||
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */,
|
||||
BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */,
|
||||
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */,
|
||||
4FE8465FD28810191C3E5212 /* Pods_Delta.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -238,14 +282,15 @@
|
||||
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */,
|
||||
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */,
|
||||
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */,
|
||||
BF11734C1DA32A5200047DF8 /* GameType+Localization.swift */,
|
||||
BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */,
|
||||
BF59428F1E09BD1A0051894B /* NSFetchedResultsController+Conveniences.h */,
|
||||
BF5942901E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m */,
|
||||
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */,
|
||||
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
|
||||
BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */,
|
||||
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */,
|
||||
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
|
||||
BFC6F7B71F435BC500221B96 /* Input+Display.swift */,
|
||||
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -254,18 +299,20 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */,
|
||||
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */,
|
||||
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */,
|
||||
);
|
||||
name = Controllers;
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF1DAD5B1D9F574900E752A7 /* Controller Skins */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */,
|
||||
BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */,
|
||||
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */,
|
||||
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */,
|
||||
);
|
||||
name = "Controller Skins";
|
||||
path = "Controller Skins";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF353FFB1C5DA2F600C1184C /* Presentation Controller */ = {
|
||||
@ -274,7 +321,7 @@
|
||||
BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */,
|
||||
BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */,
|
||||
);
|
||||
name = "Presentation Controller";
|
||||
path = "Presentation Controller";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF3540031C5DA6D800C1184C /* Save States */ = {
|
||||
@ -283,7 +330,7 @@
|
||||
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */,
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */,
|
||||
);
|
||||
name = "Save States";
|
||||
path = "Save States";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF46894D1AAC469800A2586D /* Game Selection */ = {
|
||||
@ -299,12 +346,12 @@
|
||||
BF5942571E09BB5D0051894B /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF5942581E09BB810051894B /* Action.swift */,
|
||||
BF59428C1E09BCE50051894B /* Importing */,
|
||||
BF4828871F90290F00028B97 /* Action.swift */,
|
||||
BFE0229C1F5B56840052D888 /* Popover Menu */,
|
||||
BF5942671E09BBB70051894B /* Collection View */,
|
||||
BF5942601E09BBA80051894B /* Loading */,
|
||||
);
|
||||
name = Components;
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942601E09BBA80051894B /* Loading */ = {
|
||||
@ -313,7 +360,7 @@
|
||||
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */,
|
||||
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */,
|
||||
);
|
||||
name = Loading;
|
||||
path = Loading;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942671E09BBB70051894B /* Collection View */ = {
|
||||
@ -322,28 +369,31 @@
|
||||
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */,
|
||||
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */,
|
||||
);
|
||||
name = "Collection View";
|
||||
path = "Collection View";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF59426C1E09BC450051894B /* Database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */,
|
||||
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */,
|
||||
BF5942711E09BC690051894B /* Model */,
|
||||
BF95E2751E49763D0030E7AD /* OpenVGDB */,
|
||||
);
|
||||
name = Database;
|
||||
path = Database;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942711E09BC690051894B /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF5942721E09BC700051894B /* Model.xcdatamodel */,
|
||||
BF4828811F9027B600028B97 /* Delta.xcdatamodeld */,
|
||||
BF5942741E09BC740051894B /* Human */,
|
||||
BF5942751E09BC780051894B /* Machine */,
|
||||
BF6B82A31F7CC29A00042BFB /* Transformers */,
|
||||
BFEF24F01F7DD4B600454C62 /* Migrations */,
|
||||
BF5942761E09BC7C0051894B /* Misc */,
|
||||
);
|
||||
name = Model;
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942741E09BC740051894B /* Human */ = {
|
||||
@ -353,9 +403,10 @@
|
||||
BF5942781E09BC830051894B /* ControllerSkin.swift */,
|
||||
BF5942791E09BC830051894B /* Game.swift */,
|
||||
BF59427A1E09BC830051894B /* GameCollection.swift */,
|
||||
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */,
|
||||
BF59427B1E09BC830051894B /* SaveState.swift */,
|
||||
);
|
||||
name = Human;
|
||||
path = Human;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942751E09BC780051894B /* Machine */ = {
|
||||
@ -365,9 +416,10 @@
|
||||
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */,
|
||||
BF5942831E09BC8B0051894B /* _Game.swift */,
|
||||
BF5942841E09BC8B0051894B /* _GameCollection.swift */,
|
||||
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */,
|
||||
BF5942851E09BC8B0051894B /* _SaveState.swift */,
|
||||
);
|
||||
name = Machine;
|
||||
path = Machine;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942761E09BC7C0051894B /* Misc */ = {
|
||||
@ -375,16 +427,17 @@
|
||||
children = (
|
||||
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */,
|
||||
);
|
||||
name = Misc;
|
||||
path = Misc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF59428C1E09BCE50051894B /* Importing */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF59428D1E09BCFB0051894B /* ImportController.swift */,
|
||||
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */,
|
||||
BF6BF3121EB7E47F008E83CD /* ImportOption.swift */,
|
||||
BF6BF3161EB820F4008E83CD /* Import Options */,
|
||||
);
|
||||
name = Importing;
|
||||
path = Importing;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF696B7E1D9B2AE6009639E0 /* Theming */ = {
|
||||
@ -392,7 +445,26 @@
|
||||
children = (
|
||||
BF696B7F1D9B2B02009639E0 /* Theme.swift */,
|
||||
);
|
||||
name = Theming;
|
||||
path = Theming;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF6B82A31F7CC29A00042BFB /* Transformers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */,
|
||||
);
|
||||
path = Transformers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF6BF3161EB820F4008E83CD /* Import Options */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF6BF3171EB82111008E83CD /* iTunesImportOption.swift */,
|
||||
BF6BF3191EB82146008E83CD /* ClipboardImportOption.swift */,
|
||||
BF6BF3261EB87EB8008E83CD /* PhotoLibraryImportOption.swift */,
|
||||
BF6BF31B1EB821A0008E83CD /* GamesDatabaseImportOption.swift */,
|
||||
);
|
||||
path = "Import Options";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = {
|
||||
@ -400,14 +472,14 @@
|
||||
children = (
|
||||
BF353FF41C5D837600C1184C /* PauseMenu.storyboard */,
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */,
|
||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */,
|
||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */,
|
||||
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */,
|
||||
BF353FF81C5D870B00C1184C /* MenuItem.swift */,
|
||||
BF3540031C5DA6D800C1184C /* Save States */,
|
||||
BFC9B7371CEFCD08008629BB /* Cheats */,
|
||||
BF353FFB1C5DA2F600C1184C /* Presentation Controller */,
|
||||
BF912E481C5CB5D50041527C /* Segues */,
|
||||
);
|
||||
name = "Pause Menu";
|
||||
path = "Pause Menu";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF912E481C5CB5D50041527C /* Segues */ = {
|
||||
@ -416,23 +488,33 @@
|
||||
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */,
|
||||
BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */,
|
||||
);
|
||||
name = Segues;
|
||||
path = Segues;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF930FFB1EB6D6EC00E8DBA0 /* Systems */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF4828851F9028F500028B97 /* System.swift */,
|
||||
);
|
||||
path = Systems;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF95E2751E49763D0030E7AD /* OpenVGDB */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */,
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
|
||||
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */,
|
||||
);
|
||||
name = OpenVGDB;
|
||||
path = OpenVGDB;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */,
|
||||
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */,
|
||||
BF27CC941BCB7B7A00A20D89 /* GameController.framework */,
|
||||
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */,
|
||||
@ -467,7 +549,17 @@
|
||||
BF34FA101CF1899D006624C7 /* CheatTextView.swift */,
|
||||
BF31878A1D489AAA00BD020D /* CheatValidator.swift */,
|
||||
);
|
||||
name = Cheats;
|
||||
path = Cheats;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFE0229C1F5B56840052D888 /* Popover Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */,
|
||||
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */,
|
||||
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */,
|
||||
);
|
||||
path = "Popover Menu";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFEC732F1AAECCBD00650035 /* Resources */ = {
|
||||
@ -479,12 +571,29 @@
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFEF24F01F7DD4B600454C62 /* Migrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFDC7B9E1F7DE0CF0052A7C5 /* Delta.xcmappingmodel */,
|
||||
BFEF24F11F7DD4BE00454C62 /* Policies */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFEF24F11F7DD4BE00454C62 /* Policies */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */,
|
||||
);
|
||||
path = Policies;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFFA71CE1AAC406100EE9DD1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFFA71D91AAC406100EE9DD1 /* Delta */,
|
||||
BFEC732F1AAECCBD00650035 /* Resources */,
|
||||
BF9F4FCD1AAD7B25004C9500 /* Frameworks */,
|
||||
BFEC732F1AAECCBD00650035 /* Resources */,
|
||||
BFFA71D81AAC406100EE9DD1 /* Products */,
|
||||
FD1E8AE87FA2DB8793F7B937 /* Pods */,
|
||||
);
|
||||
@ -509,6 +618,8 @@
|
||||
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */,
|
||||
BFAA1FEB1B8AA4E800495943 /* Settings */,
|
||||
BF59426C1E09BC450051894B /* Database */,
|
||||
BF59428C1E09BCE50051894B /* Importing */,
|
||||
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
|
||||
BF5942571E09BB5D0051894B /* Components */,
|
||||
BF696B7E1D9B2AE6009639E0 /* Theming */,
|
||||
BF090CEE1B490C1A00DCAB45 /* Extensions */,
|
||||
@ -532,6 +643,7 @@
|
||||
children = (
|
||||
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
|
||||
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */,
|
||||
BF15AF831F54B43B009B6AAB /* ActionInput.swift */,
|
||||
);
|
||||
path = Emulation;
|
||||
sourceTree = "<group>";
|
||||
@ -544,7 +656,7 @@
|
||||
BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */,
|
||||
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */,
|
||||
);
|
||||
name = Segues;
|
||||
path = Segues;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFFC46211D59848000AF2CC6 /* Launch */ = {
|
||||
@ -553,7 +665,7 @@
|
||||
BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */,
|
||||
BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */,
|
||||
);
|
||||
name = Launch;
|
||||
path = Launch;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD1E8AE87FA2DB8793F7B937 /* Pods */ = {
|
||||
@ -579,6 +691,7 @@
|
||||
BF9F4FCC1AAD7AEE004C9500 /* Embed Frameworks */,
|
||||
B444B2BB31CBCEE7D86E943D /* [CP] Embed Pods Frameworks */,
|
||||
3521C8FA1BB6004A6E9FE324 /* [CP] Copy Pods Resources */,
|
||||
BF6BF3281EB897F6008E83CD /* Fabric */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -596,7 +709,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0820;
|
||||
LastUpgradeCheck = 0910;
|
||||
ORGANIZATIONNAME = "Riley Testut";
|
||||
TargetAttributes = {
|
||||
BF14D8941DE7A512002CA1BE = {
|
||||
@ -607,7 +720,8 @@
|
||||
BFFA71D61AAC406100EE9DD1 = {
|
||||
CreatedOnToolsVersion = 6.3;
|
||||
DevelopmentTeam = 6XVY5G3U44;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 0900;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.iCloud = {
|
||||
enabled = 1;
|
||||
@ -641,6 +755,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */,
|
||||
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */,
|
||||
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
|
||||
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
|
||||
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
|
||||
@ -697,6 +812,20 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/usr/local/bin/mogenerator -m \"Delta/Database/Model/Model.xcdatamodel\" --human-dir \"Delta/Database/Model/Human\" --machine-dir \"Delta/Database/Model/Machine\" --swift --template-var scalarsWhenNonOptional=true --template-path \"Delta/Database/Model/mogenerator/templates\"";
|
||||
};
|
||||
BF6BF3281EB897F6008E83CD /* Fabric */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = Fabric;
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Fabric/run\" d542629b4f6625cfd5564d27318550321272076d 333118df9345dcec21e4ba0bb7fa8f6c67c4eb41734374e24f6c71a8dcd5c870";
|
||||
};
|
||||
DBD91E7D7EC2729786B4C5B1 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -709,7 +838,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
@ -719,8 +848,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */,
|
||||
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
|
||||
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
|
||||
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */,
|
||||
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
|
||||
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
|
||||
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
|
||||
BF59427E1E09BC830051894B /* Game.swift in Sources */,
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
|
||||
@ -729,27 +862,34 @@
|
||||
BF5942801E09BC830051894B /* SaveState.swift in Sources */,
|
||||
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
|
||||
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
|
||||
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
|
||||
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
|
||||
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
|
||||
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */,
|
||||
BF5942731E09BC700051894B /* Model.xcdatamodel in Sources */,
|
||||
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */,
|
||||
BF4828841F9027B600028B97 /* Delta.xcdatamodeld in Sources */,
|
||||
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
|
||||
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
|
||||
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
|
||||
BFDC7B9F1F7DE0CF0052A7C5 /* Delta.xcmappingmodel in Sources */,
|
||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
|
||||
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
||||
BF1DAD5D1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift in Sources */,
|
||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
|
||||
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
|
||||
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
|
||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
|
||||
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
|
||||
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
|
||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
||||
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
|
||||
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
|
||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */,
|
||||
BF11734D1DA32A5200047DF8 /* GameType+Localization.swift in Sources */,
|
||||
BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */,
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
|
||||
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */,
|
||||
@ -758,14 +898,17 @@
|
||||
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
||||
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
|
||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
|
||||
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
|
||||
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
|
||||
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
|
||||
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
|
||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
|
||||
BFC314771E0C8CFC0056E3A8 /* GameType+Delta.swift in Sources */,
|
||||
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
|
||||
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */,
|
||||
BF4828861F9028F500028B97 /* System.swift in Sources */,
|
||||
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */,
|
||||
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */,
|
||||
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */,
|
||||
@ -775,12 +918,16 @@
|
||||
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */,
|
||||
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
||||
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
|
||||
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */,
|
||||
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
|
||||
BF4828881F90290F00028B97 /* Action.swift in Sources */,
|
||||
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
|
||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
||||
BF59425C1E09BB810051894B /* Action.swift in Sources */,
|
||||
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */,
|
||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */,
|
||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */,
|
||||
BF6BF31A1EB82146008E83CD /* ClipboardImportOption.swift in Sources */,
|
||||
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
|
||||
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
|
||||
);
|
||||
@ -795,7 +942,17 @@
|
||||
BF353FF51C5D837600C1184C /* Base */,
|
||||
);
|
||||
name = PauseMenu.storyboard;
|
||||
sourceTree = "<group>";
|
||||
path = Delta;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
BF6BF3201EB82362008E83CD /* Base */,
|
||||
);
|
||||
name = GamesDatabase.storyboard;
|
||||
path = Delta;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
BFFA71E01AAC406100EE9DD1 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@ -803,8 +960,8 @@
|
||||
BFFA71E11AAC406100EE9DD1 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
path = .;
|
||||
sourceTree = "<group>";
|
||||
path = Delta;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@ -812,7 +969,8 @@
|
||||
BFFC46451D59861000AF2CC6 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
path = Delta;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
@ -841,20 +999,26 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
@ -891,14 +1055,20 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@ -934,12 +1104,17 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
||||
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -950,12 +1125,18 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = 6XVY5G3U44;
|
||||
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DIMPACTOR";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -990,6 +1171,20 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
BF4828811F9027B600028B97 /* Delta.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */,
|
||||
BF4828831F9027B600028B97 /* Delta.xcdatamodel */,
|
||||
);
|
||||
currentVersion = BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */;
|
||||
path = Delta.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
};
|
||||
rootObject = BFFA71CF1AAC406100EE9DD1 /* Project object */;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0820"
|
||||
LastUpgradeVersion = "0910"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
@ -62,6 +62,20 @@
|
||||
ReferencedContainer = "container:Cores/GBADeltaCore/GBADeltaCore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "BF8F2AAD1E9C879300F89F15"
|
||||
BuildableName = "GBCDeltaCore.framework"
|
||||
BlueprintName = "GBCDeltaCore"
|
||||
ReferencedContainer = "container:Cores/GBCDeltaCore/GBCDeltaCore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
@ -82,6 +96,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@ -101,6 +116,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
3
Delta.xcworkspace/contents.xcworkspacedata
generated
3
Delta.xcworkspace/contents.xcworkspacedata
generated
@ -13,6 +13,9 @@
|
||||
<FileRef
|
||||
location = "group:Cores/GBADeltaCore/GBADeltaCore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Cores/GBCDeltaCore/GBCDeltaCore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:External/Roxas/Roxas.xcodeproj">
|
||||
</FileRef>
|
||||
|
||||
@ -9,8 +9,9 @@
|
||||
import UIKit
|
||||
|
||||
import DeltaCore
|
||||
import SNESDeltaCore
|
||||
import GBADeltaCore
|
||||
|
||||
import Fabric
|
||||
import Crashlytics
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
@ -19,10 +20,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
|
||||
{
|
||||
Fabric.with([Crashlytics.self])
|
||||
|
||||
Settings.registerDefaults()
|
||||
|
||||
Delta.register(SNES.core)
|
||||
Delta.register(GBA.core)
|
||||
System.supportedSystems.forEach { Delta.register($0.deltaCore) }
|
||||
|
||||
self.configureAppearance()
|
||||
|
||||
@ -38,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
}
|
||||
|
||||
// Controllers
|
||||
ExternalControllerManager.shared.startMonitoringExternalControllers()
|
||||
ExternalGameControllerManager.shared.startMonitoring()
|
||||
|
||||
return true
|
||||
}
|
||||
@ -77,7 +79,7 @@ extension AppDelegate
|
||||
{
|
||||
self.window?.tintColor = UIColor.deltaPurple
|
||||
|
||||
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes[NSForegroundColorAttributeName] = UIColor.white
|
||||
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = UIColor.white
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,12 +90,11 @@ extension AppDelegate
|
||||
return self.openURL(url)
|
||||
}
|
||||
|
||||
@discardableResult fileprivate func openURL(_ url: URL) -> Bool
|
||||
@discardableResult private func openURL(_ url: URL) -> Bool
|
||||
{
|
||||
guard url.isFileURL else { return false }
|
||||
|
||||
let gameType = GameType.gameType(forFileExtension: url.pathExtension)
|
||||
if gameType != .unknown || url.pathExtension.lowercased() == "zip"
|
||||
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
|
||||
{
|
||||
return self.importGame(at: url)
|
||||
}
|
||||
|
||||
107
Delta/Base.lproj/GamesDatabase.storyboard
Normal file
107
Delta/Base.lproj/GamesDatabase.storyboard
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="6bq-zy-UZU">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Games Database-->
|
||||
<scene sceneID="S7I-gw-igt">
|
||||
<objects>
|
||||
<tableViewController id="SB6-jW-dhZ" customClass="GamesDatabaseBrowserViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="97" sectionHeaderHeight="28" sectionFooterHeight="28" id="bJf-Sa-ZOX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="4cJ-4B-Kgt" customClass="GameMetadataTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="97"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4cJ-4B-Kgt" id="7ze-s0-mpI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DSH-Hk-snb" userLabel="Selected Background View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="97.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="tNY-2F-llo">
|
||||
<rect key="frame" x="15" y="8" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="tNY-2F-llo" secondAttribute="height" multiplier="1:1" id="f4E-bV-L96"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DND-Fv-FyB">
|
||||
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="leading" secondItem="tNY-2F-llo" secondAttribute="trailing" constant="15" id="71e-t3-7Av"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="DND-Fv-FyB" secondAttribute="bottom" constant="1" id="9No-RE-0xx"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="top" relation="greaterThanOrEqual" secondItem="7ze-s0-mpI" secondAttribute="top" constant="1" id="F9q-6H-sqC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="DND-Fv-FyB" secondAttribute="trailing" constant="15" id="KFv-7n-LrD"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="centerY" secondItem="7ze-s0-mpI" secondAttribute="centerY" id="YBX-t4-jkR"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="top" secondItem="7ze-s0-mpI" secondAttribute="top" constant="8" id="bYX-gA-QvB"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tNY-2F-llo" secondAttribute="bottom" constant="8" id="fxr-wr-I6X"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="leading" secondItem="7ze-s0-mpI" secondAttribute="leading" constant="15" id="hX2-Gr-Bnz"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="artworkImageView" destination="tNY-2F-llo" id="GqY-jv-rso"/>
|
||||
<outlet property="artworkImageViewLeadingConstraint" destination="hX2-Gr-Bnz" id="be8-dr-c8K"/>
|
||||
<outlet property="artworkImageViewTrailingConstraint" destination="71e-t3-7Av" id="y62-KO-y1r"/>
|
||||
<outlet property="nameLabel" destination="DND-Fv-FyB" id="LhN-cA-8Hy"/>
|
||||
<outlet property="selectedBackgroundView" destination="DSH-Hk-snb" id="hLY-4k-VxU"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="SB6-jW-dhZ" id="2aq-ZA-84E"/>
|
||||
<outlet property="delegate" destination="SB6-jW-dhZ" id="WgY-cp-m7K"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Games Database" id="rwF-kd-avR">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="BnB-5n-Rff">
|
||||
<connections>
|
||||
<segue destination="mUU-ug-yNs" kind="unwind" unwindAction="unwindFromGamesDatabaseBrowserWith:" id="zdg-Az-WwQ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="f3a-hX-Qnu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="mUU-ug-yNs" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2652" y="1001.649175412294"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="REv-V5-eEz">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="6bq-zy-UZU" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="uzY-vR-coL">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="SB6-jW-dhZ" kind="relationship" relationship="rootViewController" id="b0w-Fq-hrk"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Hr9-N6-XXA" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1854" y="1002"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="BoxArt" width="100" height="100"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11760" systemVersion="16B2657" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="dkK-ii-Bx4">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="dkK-ii-Bx4">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11755"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@ -47,6 +47,6 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Delta" width="1280" height="1140"/>
|
||||
<image name="Delta" width="1342" height="1196"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C68" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -32,13 +31,13 @@
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="tmn-gd-5UN" secondAttribute="trailing" id="9Rq-HM-vqk"/>
|
||||
<constraint firstItem="0om-QB-N5a" firstAttribute="top" secondItem="tmn-gd-5UN" secondAttribute="bottom" id="DV5-hh-1VN"/>
|
||||
<constraint firstItem="tmn-gd-5UN" firstAttribute="leading" secondItem="3Bk-k3-7J9" secondAttribute="leading" id="f1f-sa-dBA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tmn-gd-5UN" secondAttribute="bottom" id="ifM-Wa-u9y"/>
|
||||
<constraint firstItem="tmn-gd-5UN" firstAttribute="top" secondItem="3Bk-k3-7J9" secondAttribute="top" id="nhS-aC-rUR"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Games" id="pFk-as-3k4">
|
||||
<barButtonItem key="leftBarButtonItem" image="Settings_Button" id="2gg-lC-FhX">
|
||||
<barButtonItem key="leftBarButtonItem" image="SettingsButton" id="2gg-lC-FhX">
|
||||
<connections>
|
||||
<segue destination="xMK-Cs-fAS" kind="presentation" id="uN5-PN-7FK"/>
|
||||
</connections>
|
||||
@ -49,91 +48,16 @@
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="7TT-mP-bjt"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1036" y="1002"/>
|
||||
</scene>
|
||||
<!--Games Database-->
|
||||
<scene sceneID="S7I-gw-igt">
|
||||
<objects>
|
||||
<tableViewController id="SB6-jW-dhZ" customClass="GamesDatabaseBrowserViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="97" sectionHeaderHeight="28" sectionFooterHeight="28" id="bJf-Sa-ZOX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="4cJ-4B-Kgt" customClass="GameMetadataTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="97"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4cJ-4B-Kgt" id="7ze-s0-mpI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DSH-Hk-snb" userLabel="Selected Background View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="97"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="tNY-2F-llo">
|
||||
<rect key="frame" x="15" y="8" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="tNY-2F-llo" secondAttribute="height" multiplier="1:1" id="f4E-bV-L96"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DND-Fv-FyB">
|
||||
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="leading" secondItem="tNY-2F-llo" secondAttribute="trailing" constant="15" id="71e-t3-7Av"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="DND-Fv-FyB" secondAttribute="bottom" constant="1" id="9No-RE-0xx"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="top" relation="greaterThanOrEqual" secondItem="7ze-s0-mpI" secondAttribute="top" constant="1" id="F9q-6H-sqC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="DND-Fv-FyB" secondAttribute="trailing" constant="15" id="KFv-7n-LrD"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="centerY" secondItem="7ze-s0-mpI" secondAttribute="centerY" id="YBX-t4-jkR"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="top" secondItem="7ze-s0-mpI" secondAttribute="top" constant="8" id="bYX-gA-QvB"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tNY-2F-llo" secondAttribute="bottom" constant="8" id="fxr-wr-I6X"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="leading" secondItem="7ze-s0-mpI" secondAttribute="leading" constant="15" id="hX2-Gr-Bnz"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="artworkImageView" destination="tNY-2F-llo" id="GqY-jv-rso"/>
|
||||
<outlet property="artworkImageViewLeadingConstraint" destination="hX2-Gr-Bnz" id="be8-dr-c8K"/>
|
||||
<outlet property="artworkImageViewTrailingConstraint" destination="71e-t3-7Av" id="y62-KO-y1r"/>
|
||||
<outlet property="nameLabel" destination="DND-Fv-FyB" id="LhN-cA-8Hy"/>
|
||||
<outlet property="selectedBackgroundView" destination="DSH-Hk-snb" id="hLY-4k-VxU"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="SB6-jW-dhZ" id="2aq-ZA-84E"/>
|
||||
<outlet property="delegate" destination="SB6-jW-dhZ" id="WgY-cp-m7K"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Games Database" id="rwF-kd-avR">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="BnB-5n-Rff">
|
||||
<connections>
|
||||
<segue destination="mUU-ug-yNs" kind="unwind" unwindAction="unwindFromGamesDatabaseBrowserWith:" id="zdg-Az-WwQ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="f3a-hX-Qnu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="mUU-ug-yNs" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2652" y="1001.649175412294"/>
|
||||
</scene>
|
||||
<!--Game Collection View Controller-->
|
||||
<scene sceneID="qNA-NP-TiF">
|
||||
<objects>
|
||||
<collectionViewController storyboardIdentifier="gameCollectionViewController" clearsSelectionOnViewWillAppear="NO" id="kqu-75-owz" customClass="GameCollectionViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="OIq-Z8-kxO">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="white" dataMode="prototypes" id="OIq-Z8-kxO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -161,7 +85,6 @@
|
||||
<connections>
|
||||
<segue destination="X2o-q6-XD5" kind="unwind" identifier="unwindFromGames" unwindAction="unwindFromGamesViewControllerWith:" id="k8C-Xn-maU"/>
|
||||
<segue destination="MPk-bF-nkj" kind="presentation" identifier="saveStates" customClass="SaveStatesStoryboardSegue" customModule="Delta" customModuleProvider="target" id="1Xp-2J-0cq"/>
|
||||
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="mzX-Bb-MaX"/>
|
||||
</connections>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@ -260,12 +183,11 @@
|
||||
<toolbarItems/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wj9-1e-eev">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="M4r-sO-G4H">
|
||||
<rect key="frame" x="0.0" y="556" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</toolbar>
|
||||
<connections>
|
||||
@ -290,7 +212,7 @@
|
||||
<navigationController storyboardIdentifier="saveStatesNavigationController" automaticallyAdjustsScrollViewInsets="NO" id="MPk-bF-nkj" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="twH-3X-6DV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@ -314,31 +236,11 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3409" y="1716"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="REv-V5-eEz">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="6bq-zy-UZU" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" barStyle="black" id="uzY-vR-coL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="SB6-jW-dhZ" kind="relationship" relationship="rootViewController" id="b0w-Fq-hrk"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Hr9-N6-XXA" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1854" y="1002"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="BoxArt" width="100" height="100"/>
|
||||
<image name="Settings_Button" width="22" height="22"/>
|
||||
<image name="SettingsButton" width="16" height="16"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="mzX-Bb-MaX"/>
|
||||
<segue reference="Tey-6Z-UHp"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11185.3" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Dt0-nV-isV">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.19" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Dt0-nV-isV">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11151.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.16"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="Menlo.ttc">
|
||||
<string>Menlo-Regular</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--Pause View Controller-->
|
||||
<scene sceneID="Wst-Dv-TjM">
|
||||
@ -16,15 +20,17 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="oOH-ea-jcb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p2M-dE-BJs" userLabel="Blur View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="eyD-0d-RHe" userLabel="Blur Content View">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rqN-NB-jbb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<connections>
|
||||
<segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/>
|
||||
</connections>
|
||||
@ -63,7 +69,7 @@
|
||||
<objects>
|
||||
<navigationController id="sWv-Ky-VGs" sceneMemberID="viewController">
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" id="Snh-Z0-9kC">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" misplaced="YES" barStyle="black" id="Snh-Z0-9kC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
@ -78,9 +84,9 @@
|
||||
<!--Paused-->
|
||||
<scene sceneID="1md-hu-g0J">
|
||||
<objects>
|
||||
<collectionViewController id="0jA-NY-mvB" customClass="PauseMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<collectionViewController id="0jA-NY-mvB" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="10" id="yXv-zl-idO" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
@ -91,7 +97,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="84" width="60" height="80"/>
|
||||
<rect key="frame" x="0.0" y="20" width="60" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="80"/>
|
||||
@ -128,7 +134,7 @@
|
||||
<objects>
|
||||
<collectionViewController storyboardIdentifier="saveStatesViewController" id="OOk-k7-INg" customClass="SaveStatesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="XgF-OF-CVf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
@ -139,7 +145,7 @@
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="c3N-1A-ryV" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minX="20" minY="124" width="50" height="50"/>
|
||||
<rect key="frame" x="20" y="60" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
@ -148,7 +154,7 @@
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Header" id="YeY-W9-CC6" customClass="SaveStatesCollectionHeaderView" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="64" width="375" height="50"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
<connections>
|
||||
@ -174,22 +180,22 @@
|
||||
<objects>
|
||||
<tableViewController id="wb8-5o-1jE" customClass="CheatsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="f5S-hV-1yV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="e8g-ZW-5lQ" customClass="CheatTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<frame key="frameInset" minY="92" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="28" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="emc-gw-KkJ" userLabel="Selected Background View">
|
||||
<frame key="frameInset" maxY="-0.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" id="9bA-Tg-Bko">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
@ -198,8 +204,9 @@
|
||||
</vibrancyEffect>
|
||||
</visualEffectView>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R4A-9d-DGb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="EdX-fU-x54">
|
||||
<frame key="frameInset"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<vibrancyEffect>
|
||||
@ -245,20 +252,21 @@
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="editCheatViewController" id="jTR-Oe-YUJ" customClass="EditCheatViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="BV9-ff-x83">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Name" id="QT6-DZ-g70">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ZeC-rg-QFa">
|
||||
<rect key="frame" x="0.0" y="119.5" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="55.5" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="ZeC-rg-QFa" id="UIF-fK-ApW">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Cheat Name" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="DD1-X0-hg7">
|
||||
<rect key="frame" x="20" y="0.0" width="560" height="43.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
|
||||
<connections>
|
||||
@ -280,13 +288,14 @@
|
||||
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tst-zn-e04">
|
||||
<rect key="frame" x="0.0" y="227" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="163" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Tst-zn-e04" id="gwV-zS-RWQ">
|
||||
<frame key="frameInset" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xrD-ue-96Q">
|
||||
<rect key="frame" x="20" y="8" width="560" height="29"/>
|
||||
<segments>
|
||||
<segment title="First"/>
|
||||
<segment title="Second"/>
|
||||
@ -308,13 +317,14 @@
|
||||
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="210" id="xxc-cz-sb7">
|
||||
<rect key="frame" x="0.0" y="346.5" width="375" height="210"/>
|
||||
<rect key="frame" x="0.0" y="282.5" width="600" height="210"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="xxc-cz-sb7" id="agU-SE-fNa">
|
||||
<frame key="frameInset" width="375" height="209.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="XXXXXXXX YYYYYYYY" translatesAutoresizingMaskIntoConstraints="NO" id="a17-LB-QXD" customClass="CheatTextView" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="209.5"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="26"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/>
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PausePresentationController" customModule="Delta" customModuleProvider="target">
|
||||
@ -13,14 +18,14 @@
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="qFz-hB-77X">
|
||||
<rect key="frame" x="15" y="236" width="570" height="127.5"/>
|
||||
<rect key="frame" x="15" y="270.5" width="375" height="126.5"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="500" image="Pause" translatesAutoresizingMaskIntoConstraints="NO" id="cWa-Ht-i5m">
|
||||
<rect key="frame" x="0.0" y="0.0" width="570" height="80"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="900" constant="44" id="xki-9P-sHi">
|
||||
<variation key="heightClass=compact" constant="25"/>
|
||||
@ -28,9 +33,9 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="800" text="Super Mario World" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="ZcT-qE-aES">
|
||||
<rect key="frame" x="0.0" y="95" width="570" height="32.5"/>
|
||||
<rect key="frame" x="0.0" y="95" width="375" height="31.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11760" systemVersion="16B2657" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11755"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@ -21,15 +21,15 @@
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Inputs" id="c6K-sJ-0vW">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="tls-Hv-Rx2" detailTextLabel="vJP-Ie-a9H" style="IBUITableViewCellStyleValue1" id="jvV-ZB-Rq1">
|
||||
<rect key="frame" x="0.0" y="56" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="tls-Hv-Rx2" detailTextLabel="vJP-Ie-a9H" style="IBUITableViewCellStyleValue1" id="jvV-ZB-Rq1">
|
||||
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="jvV-ZB-Rq1" id="AVi-6C-eIS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tls-Hv-Rx2">
|
||||
<rect key="frame" x="15" y="12" width="56" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="56" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -45,15 +45,15 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="e3u-x9-IEC" detailTextLabel="2OP-A1-VYo" style="IBUITableViewCellStyleValue1" id="1Fv-H5-0oH">
|
||||
<rect key="frame" x="0.0" y="100" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="e3u-x9-IEC" detailTextLabel="2OP-A1-VYo" style="IBUITableViewCellStyleValue1" id="1Fv-H5-0oH">
|
||||
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1Fv-H5-0oH" id="kFJ-zK-MLZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e3u-x9-IEC">
|
||||
<rect key="frame" x="15" y="12" width="58" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="58" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -69,15 +69,15 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Cdn-11-xZe" detailTextLabel="wWc-NY-Bsd" style="IBUITableViewCellStyleValue1" id="EcC-Be-jV5">
|
||||
<rect key="frame" x="0.0" y="144" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="Cdn-11-xZe" detailTextLabel="wWc-NY-Bsd" style="IBUITableViewCellStyleValue1" id="EcC-Be-jV5">
|
||||
<rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="EcC-Be-jV5" id="9ZS-um-scR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Cdn-11-xZe">
|
||||
<rect key="frame" x="15" y="12" width="58.5" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="58.5" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -93,15 +93,15 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Hls-3b-EaS" detailTextLabel="hNf-uc-PLR" style="IBUITableViewCellStyleValue1" id="hO9-Ov-vsA">
|
||||
<rect key="frame" x="0.0" y="188" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="Hls-3b-EaS" detailTextLabel="hNf-uc-PLR" style="IBUITableViewCellStyleValue1" id="hO9-Ov-vsA">
|
||||
<rect key="frame" x="0.0" y="187.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hO9-Ov-vsA" id="MRi-re-XI7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 4" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hls-3b-EaS">
|
||||
<rect key="frame" x="15" y="12" width="59" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="59" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -121,15 +121,15 @@
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Controller Skins" id="Nch-k1-6pR">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="mBC-YU-BVK" style="IBUITableViewCellStyleDefault" id="ICf-ug-NwS">
|
||||
<rect key="frame" x="0.0" y="289" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="mBC-YU-BVK" style="IBUITableViewCellStyleDefault" id="ICf-ug-NwS">
|
||||
<rect key="frame" x="0.0" y="287.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ICf-ug-NwS" id="7se-sE-x9e">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Super Nintendo" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mBC-YU-BVK">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mBC-YU-BVK">
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -138,15 +138,32 @@
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="MFD-lC-Vfk" style="IBUITableViewCellStyleDefault" id="4Sh-Mb-vqu">
|
||||
<rect key="frame" x="0.0" y="333" width="375" height="44"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="Dxs-Me-IVU" style="IBUITableViewCellStyleDefault" id="Hqy-yc-Jef">
|
||||
<rect key="frame" x="0.0" y="331.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4Sh-Mb-vqu" id="cJG-Gr-n6q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Hqy-yc-Jef" id="wJL-kh-qW0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Game Boy Advance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MFD-lC-Vfk">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Dxs-Me-IVU">
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="o9x-Kn-6bC" style="IBUITableViewCellStyleDefault" id="jFa-Qk-1cj">
|
||||
<rect key="frame" x="0.0" y="375.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="jFa-Qk-1cj" id="rFR-qL-fNQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="System Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="o9x-Kn-6bC">
|
||||
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -160,14 +177,14 @@
|
||||
<tableViewSection headerTitle="Controller Opacity" footerTitle="Determines how translucent the controller appears, if supported by the controller skin." id="SwK-m9-8gt">
|
||||
<cells>
|
||||
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Xxk-vo-eu4">
|
||||
<rect key="frame" x="0.0" y="441" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="483" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Xxk-vo-eu4" id="vxt-Ex-b4b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="whi-If-wFf">
|
||||
<rect key="frame" x="74" y="7" width="288" height="31"/>
|
||||
<rect key="frame" x="75" y="7" width="286" height="31"/>
|
||||
<connections>
|
||||
<action selector="beginChangingControllerOpacityWith:" destination="eHi-aO-uGS" eventType="touchDown" id="NG9-FX-62d"/>
|
||||
<action selector="changeControllerOpacityWith:" destination="eHi-aO-uGS" eventType="valueChanged" id="Zci-tN-4uU"/>
|
||||
@ -177,7 +194,7 @@
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="50%" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zaz-yD-CYG">
|
||||
<rect key="frame" x="15" y="11" width="46" height="21"/>
|
||||
<rect key="frame" x="16" y="11" width="46" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/>
|
||||
<constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/>
|
||||
@ -223,6 +240,78 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1555" y="471"/>
|
||||
</scene>
|
||||
<!--Controls-->
|
||||
<scene sceneID="Gi9-m1-y9x">
|
||||
<objects>
|
||||
<viewController title="Controls" id="x1g-pH-DnF" customClass="ControllerInputsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="cH1-gu-g2u"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Z6c-bc-h6l"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="cPg-qa-ERT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6Wl-el-X30" userLabel="GameViewController">
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||
<connections>
|
||||
<segue destination="LIv-AL-s86" kind="embed" identifier="embedGameViewController" id="2Qg-Jw-0mM"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" placeholderIntrinsicWidth="375" placeholderIntrinsicHeight="200" translatesAutoresizingMaskIntoConstraints="NO" id="KkE-ji-6Y8" userLabel="GridMenuViewController">
|
||||
<rect key="frame" x="0.0" y="233.5" width="375" height="200"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="200" id="MWA-T4-ROi"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<segue destination="Jpj-e9-6XW" kind="embed" identifier="embedActionsMenuViewController" id="kfu-fO-l6Z"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="KkE-ji-6Y8" firstAttribute="centerY" secondItem="cPg-qa-ERT" secondAttribute="centerY" placeholder="YES" id="4wi-cL-aCQ"/>
|
||||
<constraint firstItem="Z6c-bc-h6l" firstAttribute="top" secondItem="6Wl-el-X30" secondAttribute="bottom" id="Bmp-yB-Yf1"/>
|
||||
<constraint firstAttribute="trailing" secondItem="KkE-ji-6Y8" secondAttribute="trailing" id="Jeb-8K-VYw"/>
|
||||
<constraint firstItem="6Wl-el-X30" firstAttribute="top" secondItem="cH1-gu-g2u" secondAttribute="bottom" id="TD2-bx-DJC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6Wl-el-X30" secondAttribute="trailing" id="Xph-DL-tBk"/>
|
||||
<constraint firstItem="6Wl-el-X30" firstAttribute="leading" secondItem="cPg-qa-ERT" secondAttribute="leading" id="gcd-77-5wR"/>
|
||||
<constraint firstItem="KkE-ji-6Y8" firstAttribute="leading" secondItem="cPg-qa-ERT" secondAttribute="leading" id="z7N-Cn-hGs"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="4p8-OB-LsR" appends="YES" id="4k4-Oj-XtP"/>
|
||||
</connections>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="UeP-Yr-9jA">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="QfC-sf-WbP">
|
||||
<connections>
|
||||
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="cancelControllerInputs" unwindAction="unwindFromControllerControlsViewController:" id="AkD-Lu-h5b"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="save" id="WHh-7W-jpl">
|
||||
<connections>
|
||||
<segue destination="8l5-7I-Z7e" kind="unwind" identifier="saveControllerInputs" unwindAction="unwindFromControllerControlsViewController:" id="4xr-OB-4dx"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="actionsMenuViewControllerHeightConstraint" destination="MWA-T4-ROi" id="itx-dZ-m76"/>
|
||||
<outlet property="cancelTapGestureRecognizer" destination="4p8-OB-LsR" id="coO-FL-pbp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="DqP-Jn-rth" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="8l5-7I-Z7e" userLabel="Exit" sceneMemberID="exit"/>
|
||||
<tapGestureRecognizer delaysTouchesBegan="YES" id="4p8-OB-LsR">
|
||||
<connections>
|
||||
<action selector="handleTapGesture:" destination="x1g-pH-DnF" id="8KO-75-4Iy"/>
|
||||
<outlet property="delegate" destination="x1g-pH-DnF" id="GDY-v6-naf"/>
|
||||
</connections>
|
||||
</tapGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3809" y="471"/>
|
||||
</scene>
|
||||
<!--Controllers-->
|
||||
<scene sceneID="swa-DT-VKS">
|
||||
<objects>
|
||||
@ -240,14 +329,14 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Controller Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VBO-V1-Wfu">
|
||||
<rect key="frame" x="15" y="12" width="118.5" height="19.5"/>
|
||||
<rect key="frame" x="16" y="12" width="118.5" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tqn-1q-p53">
|
||||
<rect key="frame" x="304" y="12" width="56" height="19.5"/>
|
||||
<rect key="frame" x="303" y="12" width="56" height="19.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@ -255,9 +344,6 @@
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="HzL-SB-qry" kind="unwind" identifier="unwindControllersSegue" unwindAction="unwindFromControllersSettingsViewController:" id="WJe-ZI-clo"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
@ -267,16 +353,18 @@
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<segue destination="0QR-U9-gtx" kind="presentation" identifier="controllerInputsSegue" id="E3Y-yV-zT5"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="HzL-SB-qry" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2221" y="471"/>
|
||||
</scene>
|
||||
<!--Game Boy Advance-->
|
||||
<scene sceneID="pkL-Te-puh">
|
||||
<objects>
|
||||
<tableViewController id="56e-ul-z6v" customClass="GameTypeControllerSkinsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController id="56e-ul-z6v" customClass="SystemControllerSkinsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="fdQ-n7-kUL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -399,7 +487,7 @@
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Ckw-ES-lkE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@ -411,5 +499,76 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="889" y="471"/>
|
||||
</scene>
|
||||
<!--Grid Menu View Controller-->
|
||||
<scene sceneID="Lgi-Ii-M1W">
|
||||
<objects>
|
||||
<collectionViewController id="Jpj-e9-6XW" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="yGk-jU-wZQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="tLr-UM-1BH" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="Hef-IR-nMO" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Jpj-e9-6XW" id="iAK-8A-KXA"/>
|
||||
<outlet property="delegate" destination="Jpj-e9-6XW" id="sbi-az-9kr"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<size key="freeformSize" width="375" height="667"/>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="pRg-BA-3KK" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4566" y="-216"/>
|
||||
</scene>
|
||||
<!--Game View Controller-->
|
||||
<scene sceneID="qAz-yz-iOc">
|
||||
<objects>
|
||||
<viewController id="LIv-AL-s86" customClass="GameViewController" customModule="DeltaCore" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="9u3-RP-Qcj"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="XGZ-ro-kQv"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="57g-cn-rbZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="uQK-ch-9AG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4566" y="471"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="bwW-s2-fcE">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="0QR-U9-gtx" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" id="Y5H-O6-CQ5">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="x1g-pH-DnF" kind="relationship" relationship="rootViewController" id="EOa-ao-vBI"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="D4f-Fb-zfa" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2977" y="471"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@ -45,15 +45,15 @@ class GridCollectionViewCell: UICollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark)))
|
||||
private var vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark)))
|
||||
|
||||
fileprivate var imageViewWidthConstraint: NSLayoutConstraint!
|
||||
fileprivate var imageViewHeightConstraint: NSLayoutConstraint!
|
||||
private var imageViewWidthConstraint: NSLayoutConstraint!
|
||||
private var imageViewHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
fileprivate var textLabelBottomAnchorConstraint: NSLayoutConstraint!
|
||||
private var textLabelBottomAnchorConstraint: NSLayoutConstraint!
|
||||
|
||||
fileprivate var textLabelVerticalSpacingConstraint: NSLayoutConstraint!
|
||||
fileprivate var textLabelFocusedVerticalSpacingConstraint: NSLayoutConstraint?
|
||||
private var textLabelVerticalSpacingConstraint: NSLayoutConstraint!
|
||||
private var textLabelFocusedVerticalSpacingConstraint: NSLayoutConstraint?
|
||||
|
||||
override init(frame: CGRect)
|
||||
{
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
//
|
||||
// ImportController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 10/10/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ObjectiveC
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import MobileCoreServices
|
||||
|
||||
protocol ImportControllerDelegate
|
||||
{
|
||||
func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
|
||||
func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
|
||||
|
||||
/** Optional **/
|
||||
func importControllerDidCancel(_ importController: ImportController)
|
||||
}
|
||||
|
||||
extension ImportControllerDelegate
|
||||
{
|
||||
func importControllerDidCancel(_ importController: ImportController)
|
||||
{
|
||||
// Empty Implementation
|
||||
}
|
||||
}
|
||||
|
||||
class ImportController: NSObject
|
||||
{
|
||||
var delegate: ImportControllerDelegate?
|
||||
|
||||
fileprivate weak var presentingViewController: UIViewController?
|
||||
|
||||
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completion: ((Void) -> Void)?)
|
||||
{
|
||||
self.presentingViewController = presentingViewController
|
||||
|
||||
var documentTypes = GameType.supportedTypes.map { $0.rawValue }
|
||||
documentTypes.append(kUTTypeDeltaControllerSkin as String)
|
||||
documentTypes.append(kUTTypeZipArchive as String)
|
||||
|
||||
// Add GBA4iOS's exported UTIs in case user has GBA4iOS installed (which may override Delta's UTI declarations)
|
||||
documentTypes.append("com.rileytestut.gba")
|
||||
documentTypes.append("com.rileytestut.gbc")
|
||||
documentTypes.append("com.rileytestut.gb")
|
||||
|
||||
#if os(iOS)
|
||||
let documentMenuController = UIDocumentMenuViewController(documentTypes: documentTypes, in: .import)
|
||||
documentMenuController.delegate = self
|
||||
documentMenuController.addOption(withTitle: NSLocalizedString("iTunes", comment: ""), image: nil, order: .first) { self.importFromiTunes(nil) }
|
||||
self.presentingViewController?.present(documentMenuController, animated: true, completion: nil)
|
||||
#else
|
||||
self.importFromiTunes(completion)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func importFromiTunes(_ completion: ((Void) -> Void)?)
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Import from iTunes?", comment: ""), message: NSLocalizedString("Delta will import the games and controller skins copied over via iTunes.", comment: ""), preferredStyle: .alert)
|
||||
|
||||
let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in
|
||||
|
||||
let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent()
|
||||
|
||||
do
|
||||
{
|
||||
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||
let controllerSkinURLs = contents.filter { $0.pathExtension.lowercased() == "deltaskin" }
|
||||
self.importControllerSkins(at: Set(controllerSkinURLs))
|
||||
|
||||
let gameURLs = contents.filter { GameType.gameType(forFileExtension: $0.pathExtension) != .unknown || $0.pathExtension.lowercased() == "zip" }
|
||||
self.importGames(at: Set(gameURLs))
|
||||
}
|
||||
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
self.presentingViewController?.importController = nil
|
||||
|
||||
}
|
||||
alertController.addAction(importAction)
|
||||
|
||||
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { action in
|
||||
self.delegate?.importControllerDidCancel(self)
|
||||
self.presentingViewController?.importController = nil
|
||||
}
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
self.presentingViewController?.present(alertController, animated: true, completion: completion)
|
||||
}
|
||||
|
||||
fileprivate func importGames(at urls: Set<URL>)
|
||||
{
|
||||
DatabaseManager.shared.importGames(at: urls) { (games, errors) in
|
||||
self.delegate?.importController(self, didImport: games, with: errors)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func importControllerSkins(at urls: Set<URL>)
|
||||
{
|
||||
DatabaseManager.shared.importControllerSkins(at: urls) { (controllerSkins, errors) in
|
||||
self.delegate?.importController(self, didImport: controllerSkins, with: errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
extension ImportController: UIDocumentMenuDelegate
|
||||
{
|
||||
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController)
|
||||
{
|
||||
documentPicker.delegate = self
|
||||
self.presentingViewController?.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController)
|
||||
{
|
||||
self.delegate?.importControllerDidCancel(self)
|
||||
|
||||
self.presentingViewController?.importController = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ImportController: UIDocumentPickerDelegate
|
||||
{
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
|
||||
{
|
||||
if url.pathExtension.lowercased() == "deltaskin"
|
||||
{
|
||||
self.importControllerSkins(at: [url])
|
||||
}
|
||||
else
|
||||
{
|
||||
self.importGames(at: [url])
|
||||
}
|
||||
|
||||
self.presentingViewController?.importController = nil
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
|
||||
{
|
||||
self.delegate?.importControllerDidCancel(self)
|
||||
|
||||
self.presentingViewController?.importController = nil
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private var ImportControllerKey: UInt8 = 0
|
||||
|
||||
extension UIViewController
|
||||
{
|
||||
fileprivate(set) var importController: ImportController?
|
||||
{
|
||||
set
|
||||
{
|
||||
objc_setAssociatedObject(self, &ImportControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
get
|
||||
{
|
||||
return objc_getAssociatedObject(self, &ImportControllerKey) as? ImportController
|
||||
}
|
||||
}
|
||||
|
||||
func present(_ importController: ImportController, animated: Bool, completion: ((Void) -> Void)?)
|
||||
{
|
||||
self.importController = importController
|
||||
|
||||
importController.presentImportController(from: self, animated: animated, completion: completion)
|
||||
}
|
||||
}
|
||||
@ -45,6 +45,11 @@ class LoadImageURLOperation: RSTLoadOperation<UIImage, NSURL>
|
||||
super.cancel()
|
||||
|
||||
self.downloadOperation?.cancel()
|
||||
|
||||
if self.isAsynchronous
|
||||
{
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override func loadResult(completion: @escaping (UIImage?, Swift.Error?) -> Void)
|
||||
@ -74,14 +79,12 @@ class LoadImageURLOperation: RSTLoadOperation<UIImage, NSURL>
|
||||
|
||||
private func loadLocalImage(completion: @escaping (UIImage?, Error?) -> Void)
|
||||
{
|
||||
let options: NSDictionary = [kCGImageSourceShouldCache as NSString: true]
|
||||
|
||||
guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, options) else {
|
||||
guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, nil) else {
|
||||
completion(nil, .doesNotExist)
|
||||
return
|
||||
}
|
||||
|
||||
guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else {
|
||||
guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
|
||||
completion(nil, .invalid)
|
||||
return
|
||||
}
|
||||
|
||||
64
Delta/Components/Popover Menu/ListMenuViewController.swift
Normal file
64
Delta/Components/Popover Menu/ListMenuViewController.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// PopoverMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class ListMenuViewController: UITableViewController
|
||||
{
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue }
|
||||
}
|
||||
|
||||
private let dataSource = RSTArrayTableViewDataSource<MenuItem>(items: [])
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
let navigationBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0.0
|
||||
return CGSize(width: 0, height: (self.tableView.rowHeight * CGFloat(self.items.count)) + navigationBarHeight)
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
super.init(style: .plain)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { (cell, item, indexPath) in
|
||||
cell.textLabel?.text = item.text
|
||||
cell.accessoryType = item.isSelected ? .checkmark : .none
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.rowHeight = 44
|
||||
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension ListMenuViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
item.isSelected = !item.isSelected
|
||||
item.action(item)
|
||||
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
121
Delta/Components/Popover Menu/PopoverMenuButton.swift
Normal file
121
Delta/Components/Popover Menu/PopoverMenuButton.swift
Normal file
@ -0,0 +1,121 @@
|
||||
//
|
||||
// PopoverMenuButton.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UINavigationBar
|
||||
{
|
||||
fileprivate var defaultTitleTextAttributes: [NSAttributedStringKey: Any]? {
|
||||
if let textAttributes = self._defaultTitleTextAttributes
|
||||
{
|
||||
return textAttributes
|
||||
}
|
||||
|
||||
// Make "copy" of self.
|
||||
let navigationBar = UINavigationBar(frame: .zero)
|
||||
navigationBar.barStyle = self.barStyle
|
||||
|
||||
// Set item with title so we can retrieve default text attributes.
|
||||
let navigationItem = UINavigationItem(title: "Testut")
|
||||
navigationBar.items = [navigationItem]
|
||||
navigationBar.isHidden = true
|
||||
|
||||
// Must be added to window hierarchy for it to create title UILabel.
|
||||
self.addSubview(navigationBar)
|
||||
defer { navigationBar.removeFromSuperview() }
|
||||
|
||||
navigationBar.layoutIfNeeded()
|
||||
|
||||
let textAttributes = navigationBar._defaultTitleTextAttributes
|
||||
return textAttributes
|
||||
}
|
||||
|
||||
private var _defaultTitleTextAttributes: [NSAttributedStringKey: Any]? {
|
||||
guard self.titleTextAttributes == nil else { return self.titleTextAttributes }
|
||||
|
||||
guard
|
||||
let contentView = self.subviews.first(where: { NSStringFromClass(type(of: $0)).contains("ContentView") || NSStringFromClass(type(of: $0)).contains("ItemView") }),
|
||||
let titleLabel = contentView.subviews.first(where: { $0 is UILabel }) as? UILabel
|
||||
else { return nil }
|
||||
|
||||
let textAttributes = titleLabel.attributedText?.attributes(at: 0, effectiveRange: nil)
|
||||
return textAttributes
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverMenuButton: UIControl
|
||||
{
|
||||
var title: String {
|
||||
get { return self.textLabel.text ?? "" }
|
||||
set {
|
||||
self.textLabel.text = newValue
|
||||
self.updateTextAttributes()
|
||||
self.invalidateIntrinsicContentSize()
|
||||
}
|
||||
}
|
||||
|
||||
private let textLabel: UILabel
|
||||
private let arrowLabel: UILabel
|
||||
private let stackView: UIStackView
|
||||
|
||||
private var parentNavigationBar: UINavigationBar? {
|
||||
guard let navigationController = self.parentViewController as? UINavigationController ?? self.parentViewController?.navigationController else { return nil }
|
||||
guard self.isDescendant(of: navigationController.navigationBar) else { return nil }
|
||||
|
||||
return navigationController.navigationBar
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return self.stackView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
self.textLabel = UILabel()
|
||||
self.textLabel.textColor = .black
|
||||
|
||||
self.arrowLabel = UILabel()
|
||||
self.arrowLabel.text = "▾"
|
||||
self.arrowLabel.textColor = .black
|
||||
|
||||
self.stackView = UIStackView(arrangedSubviews: [self.textLabel, self.arrowLabel])
|
||||
self.stackView.axis = .horizontal
|
||||
self.stackView.distribution = .fillProportionally
|
||||
self.stackView.alignment = .center
|
||||
self.stackView.spacing = 4.0
|
||||
self.stackView.isUserInteractionEnabled = false
|
||||
|
||||
let intrinsicContentSize = self.stackView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
super.init(frame: CGRect(origin: .zero, size: intrinsicContentSize))
|
||||
|
||||
self.addSubview(self.stackView, pinningEdgesWith: .zero)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func didMoveToSuperview()
|
||||
{
|
||||
self.updateTextAttributes()
|
||||
}
|
||||
}
|
||||
|
||||
private extension PopoverMenuButton
|
||||
{
|
||||
func updateTextAttributes()
|
||||
{
|
||||
guard let parentNavigationBar = self.parentNavigationBar else { return }
|
||||
guard let textAttributes = parentNavigationBar.defaultTitleTextAttributes else { return }
|
||||
|
||||
for label in [self.textLabel, self.arrowLabel]
|
||||
{
|
||||
label.attributedText = NSAttributedString(string: label.text ?? "", attributes: textAttributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
Delta/Components/Popover Menu/PopoverMenuController.swift
Normal file
102
Delta/Components/Popover Menu/PopoverMenuController.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// PopoverMenuController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/5/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private var popoverMenuControllerKey: UInt8 = 0
|
||||
|
||||
extension UINavigationItem
|
||||
{
|
||||
var popoverMenuController: PopoverMenuController? {
|
||||
get { return objc_getAssociatedObject(self, &popoverMenuControllerKey) as? PopoverMenuController }
|
||||
set {
|
||||
self.titleView = newValue?.popoverMenuButton
|
||||
objc_setAssociatedObject(self, &popoverMenuControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverMenuController: NSObject
|
||||
{
|
||||
let popoverViewController: UIViewController
|
||||
|
||||
let popoverMenuButton: PopoverMenuButton
|
||||
|
||||
var isActive: Bool = false {
|
||||
willSet {
|
||||
guard newValue != self.isActive else { return }
|
||||
|
||||
if newValue
|
||||
{
|
||||
self.presentPopoverViewController()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.dismissPopoverViewController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(popoverViewController: UIViewController)
|
||||
{
|
||||
self.popoverViewController = popoverViewController
|
||||
|
||||
self.popoverMenuButton = PopoverMenuButton()
|
||||
|
||||
super.init()
|
||||
|
||||
self.popoverMenuButton.addTarget(self, action: #selector(PopoverMenuController.pressedPopoverMenuButton(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private extension PopoverMenuController
|
||||
{
|
||||
@objc func pressedPopoverMenuButton(_ button: PopoverMenuButton)
|
||||
{
|
||||
self.isActive = !self.isActive
|
||||
}
|
||||
|
||||
func presentPopoverViewController()
|
||||
{
|
||||
guard !self.isActive else { return }
|
||||
|
||||
guard let presentingViewController = self.popoverMenuButton.parentViewController else { return }
|
||||
|
||||
self.popoverViewController.modalPresentationStyle = .popover
|
||||
self.popoverViewController.popoverPresentationController?.delegate = self
|
||||
self.popoverViewController.popoverPresentationController?.sourceView = self.popoverMenuButton.superview
|
||||
self.popoverViewController.popoverPresentationController?.sourceRect = self.popoverMenuButton.frame
|
||||
|
||||
presentingViewController.present(self.popoverViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func dismissPopoverViewController()
|
||||
{
|
||||
guard self.isActive else { return }
|
||||
|
||||
self.popoverViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension PopoverMenuController: UIPopoverPresentationControllerDelegate
|
||||
{
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
|
||||
{
|
||||
// Force popover presentation, regardless of trait collection.
|
||||
return .none
|
||||
}
|
||||
|
||||
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController)
|
||||
{
|
||||
self.isActive = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
//
|
||||
// PopoverMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Roxas
|
||||
|
||||
class ListMenuViewController: UITableViewController
|
||||
{
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue; }
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTArrayTableViewDataSource<MenuItem>(items: [])
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
get {
|
||||
let navigationBarHeight = self.navigationController?.navigationBar.bounds.height ?? 0.0
|
||||
return CGSize(width: 0, height: (self.tableView.rowHeight * CGFloat(self.items.count)) + navigationBarHeight)
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
init()
|
||||
{
|
||||
super.init(style: .plain)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { (cell, item, indexPath) in
|
||||
cell.textLabel?.text = item.text
|
||||
cell.accessoryType = item.isSelected ? .checkmark : .none
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.rowHeight = 44
|
||||
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension ListMenuViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
self.items.forEach { $0.isSelected = false }
|
||||
|
||||
let item = self.dataSource.item(at: indexPath)
|
||||
item.isSelected = true
|
||||
item.action(item)
|
||||
|
||||
self.tableView.reloadData()
|
||||
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ extension DatabaseManager
|
||||
{
|
||||
case doesNotExist(URL)
|
||||
case invalid(URL)
|
||||
case unsupported(URL)
|
||||
case unknown(URL, NSError)
|
||||
case saveFailed(Set<URL>, NSError)
|
||||
|
||||
@ -30,8 +31,9 @@ extension DatabaseManager
|
||||
{
|
||||
case .doesNotExist: return 0
|
||||
case .invalid: return 1
|
||||
case .unknown: return 2
|
||||
case .saveFailed: return 3
|
||||
case .unsupported: return 2
|
||||
case .unknown: return 3
|
||||
case .saveFailed: return 4
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,15 +43,16 @@ extension DatabaseManager
|
||||
{
|
||||
case (let .doesNotExist(url1), let .doesNotExist(url2)) where url1 == url2: return true
|
||||
case (let .invalid(url1), let .invalid(url2)) where url1 == url2: return true
|
||||
case (let .unsupported(url1), let .unsupported(url2)) where url1 == url2: return true
|
||||
case (let .unknown(url1, error1), let .unknown(url2, error2)) where url1 == url2 && error1 == error2: return true
|
||||
case (let .saveFailed(urls1, error1), let .saveFailed(urls2, error2)) where urls1 == urls2 && error1 == error2: return true
|
||||
case (.doesNotExist, _): return false
|
||||
case (.invalid, _): return false
|
||||
case (.unsupported, _): return false
|
||||
case (.unknown, _): return false
|
||||
case (.saveFailed, _): return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,16 +60,15 @@ final class DatabaseManager: NSPersistentContainer
|
||||
{
|
||||
static let shared = DatabaseManager()
|
||||
|
||||
fileprivate var gamesDatabase: GamesDatabase? = nil
|
||||
private var gamesDatabase: GamesDatabase? = nil
|
||||
|
||||
private init()
|
||||
{
|
||||
guard
|
||||
let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Model", withExtension: "mom"),
|
||||
let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Delta", withExtension: "momd"),
|
||||
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
|
||||
else { fatalError("Core Data model cannot be found. Aborting.") }
|
||||
|
||||
|
||||
super.init(name: "Delta", managedObjectModel: managedObjectModel)
|
||||
|
||||
self.viewContext.automaticallyMergesChangesFromParent = true
|
||||
@ -95,13 +97,13 @@ extension DatabaseManager
|
||||
//MARK: - Preparation -
|
||||
private extension DatabaseManager
|
||||
{
|
||||
func prepareDatabase(completion: @escaping (Void) -> Void)
|
||||
func prepareDatabase(completion: @escaping () -> Void)
|
||||
{
|
||||
self.performBackgroundTask { (context) in
|
||||
|
||||
for gameType in GameType.supportedTypes
|
||||
for system in System.supportedSystems
|
||||
{
|
||||
guard let deltaControllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: gameType) else { continue }
|
||||
guard let deltaControllerSkin = DeltaCore.ControllerSkin.standardControllerSkin(for: system.gameType) else { continue }
|
||||
|
||||
let controllerSkin = ControllerSkin(context: context)
|
||||
controllerSkin.isStandard = true
|
||||
@ -173,21 +175,27 @@ extension DatabaseManager
|
||||
continue
|
||||
}
|
||||
|
||||
let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String
|
||||
guard let gameType = GameType(fileExtension: url.pathExtension), let system = System(gameType: gameType) else {
|
||||
errors.insert(.unsupported(url))
|
||||
continue
|
||||
}
|
||||
|
||||
let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String
|
||||
let filename = identifier + "." + url.pathExtension
|
||||
|
||||
let game = Game.insertIntoManagedObjectContext(context)
|
||||
let game = Game(context: context)
|
||||
game.identifier = identifier
|
||||
game.type = gameType
|
||||
game.filename = filename
|
||||
|
||||
let databaseMetadata = self.gamesDatabase?.metadata(for: game)
|
||||
game.name = databaseMetadata?.name ?? url.deletingPathExtension().lastPathComponent
|
||||
game.artworkURL = databaseMetadata?.artworkURL
|
||||
|
||||
let gameCollection = GameCollection.gameSystemCollectionForPathExtension(url.pathExtension, inManagedObjectContext: context)
|
||||
game.type = GameType(rawValue: gameCollection.identifier)
|
||||
game.gameCollections.insert(gameCollection)
|
||||
|
||||
let gameCollection = GameCollection(context: context)
|
||||
gameCollection.identifier = gameType.rawValue
|
||||
gameCollection.index = Int16(system.year)
|
||||
gameCollection.games.insert(game)
|
||||
|
||||
do
|
||||
{
|
||||
@ -212,7 +220,6 @@ extension DatabaseManager
|
||||
|
||||
errors.insert(.unknown(url, error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
do
|
||||
@ -328,9 +335,8 @@ extension DatabaseManager
|
||||
guard !entry.fileName.contains("/") else { continue }
|
||||
|
||||
let fileExtension = (entry.fileName as NSString).pathExtension
|
||||
let gameType = GameType.gameType(forFileExtension: fileExtension)
|
||||
|
||||
guard gameType != .unknown else { continue }
|
||||
|
||||
guard GameType(fileExtension: fileExtension) != nil else { continue }
|
||||
|
||||
// At least one entry is a valid game file, so we set archiveContainsValidGameFile to true
|
||||
// This will result in this archive being considered valid, and thus we will not return an ImportError.invalid error for the archive
|
||||
@ -486,6 +492,14 @@ extension DatabaseManager
|
||||
|
||||
return gameTypeDirectoryURL
|
||||
}
|
||||
|
||||
class func artworkURL(for game: Game) -> URL
|
||||
{
|
||||
let gameURL = game.fileURL
|
||||
|
||||
let artworkURL = gameURL.deletingPathExtension().appendingPathExtension("jpg")
|
||||
return artworkURL
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Private -
|
||||
|
||||
@ -15,9 +15,9 @@ class InputStreamOutputWriter: NSObject
|
||||
let inputStream: InputStream
|
||||
let outputStream: OutputStream
|
||||
|
||||
fileprivate var completion: ((Error?) -> Void)?
|
||||
private var completion: ((Error?) -> Void)?
|
||||
|
||||
fileprivate var dataBuffer = Data(capacity: MaximumBufferLength * 2)
|
||||
private var dataBuffer = Data(capacity: MaximumBufferLength * 2)
|
||||
|
||||
init(inputStream: InputStream, outputStream: OutputStream)
|
||||
{
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Delta 2.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13532" systemVersion="16G29" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
|
||||
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
|
||||
<attribute name="code" attributeType="String" syncable="YES"/>
|
||||
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="isEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="CheatType"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="cheats" inverseEntity="Game" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="ControllerSkin" representedClassName="ControllerSkin" syncable="YES">
|
||||
<attribute name="filename" attributeType="String" syncable="YES"/>
|
||||
<attribute name="gameType" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="GameType"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="isStandard" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="supportedConfigurations" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueScalarType" value="ControllerSkinConfigurations"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
<constraint value="gameType"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Game" representedClassName="Game" syncable="YES">
|
||||
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="URL"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="filename" attributeType="String" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="NSURL"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="GameType"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<relationship name="cheats" toMany="YES" deletionRule="Cascade" destinationEntity="Cheat" inverseName="game" inverseEntity="Cheat" syncable="YES"/>
|
||||
<relationship name="gameCollections" toMany="YES" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
|
||||
<relationship name="previewSaveState" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SaveState" inverseName="previewGame" inverseEntity="SaveState" syncable="YES"/>
|
||||
<relationship name="saveStates" toMany="YES" deletionRule="Cascade" destinationEntity="SaveState" inverseName="game" inverseEntity="SaveState" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="GameCollection" representedClassName="GameCollection" syncable="YES">
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="index" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<relationship name="games" toMany="YES" deletionRule="Nullify" destinationEntity="Game" inverseName="gameCollections" inverseEntity="Game" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</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" versionHashModifier="quick" syncable="YES">
|
||||
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="filename" attributeType="String" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="NSURL"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueScalarType" value="SaveStateType"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
|
||||
<relationship name="previewGame" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="previewSaveState" inverseEntity="Game" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="165"/>
|
||||
<element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/>
|
||||
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
|
||||
<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"/>
|
||||
</elements>
|
||||
</model>
|
||||
@ -1,5 +1,5 @@
|
||||
<?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">
|
||||
<attribute name="code" attributeType="String" syncable="YES"/>
|
||||
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
@ -79,6 +79,31 @@
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</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">
|
||||
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" 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="Game" positionX="-378" positionY="-54" width="128" height="180"/>
|
||||
<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"/>
|
||||
</elements>
|
||||
</model>
|
||||
@ -61,7 +61,7 @@ public class ControllerSkin: _ControllerSkin
|
||||
return self.controllerSkin?.isDebugModeEnabled ?? false
|
||||
}
|
||||
|
||||
fileprivate lazy var controllerSkin: DeltaCore.ControllerSkin? = {
|
||||
private lazy var controllerSkin: DeltaCore.ControllerSkin? = {
|
||||
let controllerSkin = self.isStandard ? DeltaCore.ControllerSkin.standardControllerSkin(for: self.gameType) : DeltaCore.ControllerSkin(fileURL: self.fileURL)
|
||||
return controllerSkin
|
||||
}()
|
||||
@ -88,9 +88,9 @@ extension ControllerSkin: ControllerSkinProtocol
|
||||
return self.controllerSkin?.image(for: traits, preferredSize: preferredSize)
|
||||
}
|
||||
|
||||
public func inputs(for traits: DeltaCore.ControllerSkin.Traits, point: CGPoint) -> [Input]?
|
||||
public func inputs(for traits: DeltaCore.ControllerSkin.Traits, at point: CGPoint) -> [Input]?
|
||||
{
|
||||
return self.controllerSkin?.inputs(for: traits, point: point)
|
||||
return self.controllerSkin?.inputs(for: traits, at: point)
|
||||
}
|
||||
|
||||
public func items(for traits: DeltaCore.ControllerSkin.Traits) -> [DeltaCore.ControllerSkin.Item]?
|
||||
|
||||
@ -22,6 +22,37 @@ public class Game: _Game, GameProtocol
|
||||
|
||||
return fileURL
|
||||
}
|
||||
|
||||
public override var artworkURL: URL? {
|
||||
get {
|
||||
self.willAccessValue(forKey: #keyPath(Game.artworkURL))
|
||||
var artworkURL = self.primitiveValue(forKey: #keyPath(Game.artworkURL)) as? URL
|
||||
self.didAccessValue(forKey: #keyPath(Game.artworkURL))
|
||||
|
||||
if let unwrappedArtworkURL = artworkURL, unwrappedArtworkURL.isFileURL
|
||||
{
|
||||
// Recreate the stored URL relative to current sandbox location.
|
||||
artworkURL = URL(fileURLWithPath: unwrappedArtworkURL.relativePath, relativeTo: DatabaseManager.gamesDirectoryURL)
|
||||
}
|
||||
|
||||
return artworkURL
|
||||
}
|
||||
set {
|
||||
self.willChangeValue(forKey: #keyPath(Game.artworkURL))
|
||||
|
||||
var artworkURL = newValue
|
||||
|
||||
if let newValue = newValue, newValue.isFileURL
|
||||
{
|
||||
// Store a relative URL, since the sandbox location changes.
|
||||
artworkURL = URL(fileURLWithPath: newValue.lastPathComponent, relativeTo: DatabaseManager.gamesDirectoryURL)
|
||||
}
|
||||
|
||||
self.setPrimitiveValue(artworkURL, forKey: #keyPath(Game.artworkURL))
|
||||
|
||||
self.didChangeValue(forKey: #keyPath(Game.artworkURL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Game
|
||||
|
||||
@ -9,48 +9,22 @@
|
||||
import CoreData
|
||||
|
||||
import DeltaCore
|
||||
import SNESDeltaCore
|
||||
import GBADeltaCore
|
||||
|
||||
@objc(GameCollection)
|
||||
public class GameCollection: _GameCollection
|
||||
public class GameCollection: _GameCollection
|
||||
{
|
||||
var name: String
|
||||
{
|
||||
let gameType = GameType(rawValue: self.identifier)
|
||||
return gameType.localizedName
|
||||
var name: String {
|
||||
return self.system?.localizedName ?? NSLocalizedString("Unknown", comment: "")
|
||||
}
|
||||
|
||||
var shortName: String
|
||||
{
|
||||
let gameType = GameType(rawValue: self.identifier)
|
||||
return gameType.localizedShortName
|
||||
var shortName: String {
|
||||
return self.system?.localizedShortName ?? NSLocalizedString("Unknown", comment: "")
|
||||
}
|
||||
|
||||
class func gameSystemCollectionForPathExtension(_ pathExtension: String?, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> GameCollection
|
||||
{
|
||||
let gameType = GameType.gameType(forFileExtension: pathExtension ?? "")
|
||||
let identifier = gameType.rawValue
|
||||
var system: System? {
|
||||
let gameType = GameType(rawValue: self.identifier)
|
||||
|
||||
let index: Int16
|
||||
|
||||
switch gameType
|
||||
{
|
||||
case GameType.snes: index = 1990
|
||||
case GameType.gba: index = 2001
|
||||
default: index = Int16(INT16_MAX)
|
||||
}
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(GameCollection.identifier), identifier)
|
||||
|
||||
var gameCollection = GameCollection.instancesWithPredicate(predicate, inManagedObjectContext: managedObjectContext, type: GameCollection.self).first
|
||||
if gameCollection == nil
|
||||
{
|
||||
gameCollection = GameCollection.insertIntoManagedObjectContext(managedObjectContext)
|
||||
gameCollection?.identifier = identifier
|
||||
gameCollection?.index = index
|
||||
}
|
||||
|
||||
return gameCollection!
|
||||
let system = System(gameType: gameType)
|
||||
return system
|
||||
}
|
||||
}
|
||||
|
||||
77
Delta/Database/Model/Human/GameControllerInputMapping.swift
Normal file
77
Delta/Database/Model/Human/GameControllerInputMapping.swift
Normal 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
|
||||
{
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,7 @@ import DeltaCore
|
||||
@objc public enum SaveStateType: Int16
|
||||
{
|
||||
case auto
|
||||
case quick
|
||||
case general
|
||||
case locked
|
||||
}
|
||||
@ -78,4 +79,14 @@ public class SaveState: _SaveState, SaveStateProtocol
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
class func fetchRequest(for game: Game, type: SaveStateType) -> NSFetchRequest<SaveState>
|
||||
{
|
||||
let predicate = NSPredicate(format: "%K == %@ AND %K == %d", #keyPath(SaveState.game), game, #keyPath(SaveState.type), type.rawValue)
|
||||
|
||||
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
|
||||
return fetchRequest
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@
|
||||
//
|
||||
// SaveStateMigrationPolicy.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/28/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc(SaveStateToSaveStateMigrationPolicy)
|
||||
class SaveStateToSaveStateMigrationPolicy: NSEntityMigrationPolicy
|
||||
{
|
||||
@objc(migrateSaveStateType:)
|
||||
func migrateSaveStateType(_ rawValue: NSNumber) -> NSNumber
|
||||
{
|
||||
switch rawValue.intValue
|
||||
{
|
||||
case 0: return NSNumber(value: SaveStateType.auto.rawValue)
|
||||
case 1: return NSNumber(value: SaveStateType.general.rawValue)
|
||||
case 2: return NSNumber(value: SaveStateType.locked.rawValue)
|
||||
default: return rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,9 +8,32 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// Must be a class (not struct) so it can be used with Objective-C generics
|
||||
class GameMetadata
|
||||
// Must be an NSObject subclass so it can be used with RSTCellContentDataSource.
|
||||
class GameMetadata: NSObject
|
||||
{
|
||||
var name: String?
|
||||
var artworkURL: URL?
|
||||
let identifier: Int
|
||||
|
||||
let name: String?
|
||||
let artworkURL: URL?
|
||||
|
||||
init(identifier: Int, name: String?, artworkURL: URL?)
|
||||
{
|
||||
self.name = name
|
||||
self.identifier = identifier
|
||||
self.artworkURL = artworkURL
|
||||
}
|
||||
}
|
||||
|
||||
extension GameMetadata
|
||||
{
|
||||
override var hash: Int {
|
||||
return self.identifier.hashValue
|
||||
}
|
||||
|
||||
override func isEqual(_ object: Any?) -> Bool
|
||||
{
|
||||
guard let metadata = object as? GameMetadata else { return false }
|
||||
|
||||
return self.identifier == metadata.identifier
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,11 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
private extension UserDefaults
|
||||
{
|
||||
@NSManaged var previousGamesDatabaseVersion: Int
|
||||
}
|
||||
|
||||
extension ExpressionType
|
||||
{
|
||||
static var name: SQLite.Expression<String?> {
|
||||
@ -19,13 +24,17 @@ extension ExpressionType
|
||||
return SQLite.Expression<String?>("releaseCoverFront")
|
||||
}
|
||||
|
||||
static var hash: SQLite.Expression<String> {
|
||||
static var sha1Hash: SQLite.Expression<String> {
|
||||
return SQLite.Expression<String>("romHashSHA1")
|
||||
}
|
||||
|
||||
static var romID: SQLite.Expression<Int> {
|
||||
return SQLite.Expression<Int>("romID")
|
||||
}
|
||||
|
||||
static var releaseID: SQLite.Expression<Int> {
|
||||
return SQLite.Expression<Int>("releaseID")
|
||||
}
|
||||
}
|
||||
|
||||
extension Table
|
||||
@ -57,7 +66,9 @@ extension GamesDatabase
|
||||
|
||||
class GamesDatabase
|
||||
{
|
||||
fileprivate let connection: Connection
|
||||
static let version = -1
|
||||
|
||||
private let connection: Connection
|
||||
|
||||
init() throws
|
||||
{
|
||||
@ -71,28 +82,36 @@ class GamesDatabase
|
||||
{
|
||||
throw Error.connection(error)
|
||||
}
|
||||
|
||||
self.invalidateVirtualTableIfNeeded()
|
||||
}
|
||||
|
||||
func metadataResults(forGameName gameName: String) -> [GameMetadata]
|
||||
{
|
||||
let releaseID = Expression<Any>.releaseID
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
|
||||
let query = VirtualTable.search.select(name, artworkAddress).filter(name.match(gameName + "*"))
|
||||
let query = VirtualTable.search.select(releaseID, name, artworkAddress).filter(name.match(gameName + "*"))
|
||||
|
||||
do
|
||||
{
|
||||
let rows = try self.connection.prepare(query)
|
||||
|
||||
let results = rows.map { row -> GameMetadata in
|
||||
let metadata = GameMetadata()
|
||||
metadata.name = row[name]
|
||||
|
||||
let results = rows.map { (row) -> GameMetadata in
|
||||
|
||||
let artworkURL: URL?
|
||||
if let address = row[artworkAddress]
|
||||
{
|
||||
metadata.artworkURL = URL(string: address)
|
||||
artworkURL = URL(string: address)
|
||||
}
|
||||
else
|
||||
{
|
||||
artworkURL = nil
|
||||
}
|
||||
|
||||
|
||||
let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL)
|
||||
return metadata
|
||||
}
|
||||
|
||||
@ -118,26 +137,31 @@ class GamesDatabase
|
||||
|
||||
func metadata(for game: Game) -> GameMetadata?
|
||||
{
|
||||
let releaseID = Expression<Any>.releaseID
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
let hash = Expression<Any>.hash
|
||||
|
||||
let sha1Hash = Expression<Any>.sha1Hash
|
||||
let romID = Expression<Any>.romID
|
||||
|
||||
let gameHash = game.identifier.uppercased()
|
||||
let query = Table.roms.select(name, artworkAddress).filter(hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID])
|
||||
let query = Table.roms.select(releaseID, name, artworkAddress).filter(sha1Hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID])
|
||||
|
||||
do
|
||||
{
|
||||
if let row = try self.connection.pluck(query)
|
||||
{
|
||||
let metadata = GameMetadata()
|
||||
metadata.name = row[name]
|
||||
|
||||
let artworkURL: URL?
|
||||
if let address = row[artworkAddress]
|
||||
{
|
||||
metadata.artworkURL = URL(string: address)
|
||||
artworkURL = URL(string: address)
|
||||
}
|
||||
else
|
||||
{
|
||||
artworkURL = nil
|
||||
}
|
||||
|
||||
let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL)
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
@ -152,16 +176,33 @@ class GamesDatabase
|
||||
|
||||
private extension GamesDatabase
|
||||
{
|
||||
func invalidateVirtualTableIfNeeded()
|
||||
{
|
||||
guard UserDefaults.standard.previousGamesDatabaseVersion != GamesDatabase.version else { return }
|
||||
|
||||
do
|
||||
{
|
||||
try self.connection.run(VirtualTable.search.drop(ifExists: true))
|
||||
|
||||
UserDefaults.standard.previousGamesDatabaseVersion = GamesDatabase.version
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareFTS() -> Bool
|
||||
{
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
let releaseID = Expression<Any>.releaseID
|
||||
|
||||
do
|
||||
{
|
||||
try self.connection.run(VirtualTable.search.create(.FTS4([name, artworkAddress], tokenize: .Unicode61())))
|
||||
try self.connection.run(VirtualTable.search.create(.FTS4([releaseID, name, artworkAddress], tokenize: .Unicode61())))
|
||||
|
||||
let update = VirtualTable.search.insert(Table.releases.select(name, artworkAddress))
|
||||
let update = VirtualTable.search.insert(Table.releases.select(releaseID, name, artworkAddress))
|
||||
_ = try self.connection.run(update)
|
||||
}
|
||||
catch
|
||||
|
||||
@ -15,11 +15,9 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
{
|
||||
var selectionHandler: ((GameMetadata) -> Void)?
|
||||
|
||||
fileprivate let database: GamesDatabase?
|
||||
fileprivate let dataSource: RSTArrayTableViewDataSource<GameMetadata>
|
||||
private let database: GamesDatabase?
|
||||
|
||||
fileprivate let operationQueue = RSTOperationQueue()
|
||||
fileprivate let imageCache = NSCache<NSURL, UIImage>()
|
||||
private let dataSource: RSTArrayTableViewPrefetchingDataSource<GameMetadata, UIImage>
|
||||
|
||||
override init(style: UITableViewStyle) {
|
||||
fatalError()
|
||||
@ -37,39 +35,13 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
print(error)
|
||||
}
|
||||
|
||||
self.dataSource = RSTArrayTableViewDataSource<GameMetadata>(items: [])
|
||||
|
||||
let placeholderView = RSTPlaceholderView()
|
||||
placeholderView.textLabel.textColor = UIColor.lightText
|
||||
placeholderView.detailTextLabel.textColor = UIColor.lightText
|
||||
|
||||
self.dataSource.placeholderView = placeholderView
|
||||
self.dataSource = RSTArrayTableViewPrefetchingDataSource<GameMetadata, UIImage>(items: [])
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { (cell, metadata, indexPath) in
|
||||
self.configure(cell: cell as! GameMetadataTableViewCell, with: metadata, for: indexPath)
|
||||
}
|
||||
|
||||
if let database = self.database
|
||||
{
|
||||
self.dataSource.searchController.searchHandler = { [unowned database, unowned dataSource] (searchValue, previousSearchValue) in
|
||||
|
||||
return RSTBlockOperation(executionBlock: { [unowned database, unowned dataSource] (operation) in
|
||||
let results = database.metadataResults(forGameName: searchValue.text)
|
||||
|
||||
guard !operation.isCancelled else { return }
|
||||
|
||||
dataSource.items = results
|
||||
|
||||
rst_dispatch_sync_on_main_thread {
|
||||
self.updatePlaceholderView()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.definesPresentationContext = true
|
||||
|
||||
self.prepareDataSource()
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
@ -83,6 +55,8 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
self.view.backgroundColor = UIColor.deltaDarkGray
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.prefetchDataSource = self.dataSource
|
||||
|
||||
self.tableView.indicatorStyle = .white
|
||||
self.tableView.separatorColor = UIColor.gray
|
||||
|
||||
@ -99,7 +73,73 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesDatabaseBrowserViewController
|
||||
private extension GamesDatabaseBrowserViewController
|
||||
{
|
||||
func prepareDataSource()
|
||||
{
|
||||
/* Placeholder View */
|
||||
let placeholderView = RSTPlaceholderView()
|
||||
placeholderView.textLabel.textColor = UIColor.lightText
|
||||
placeholderView.detailTextLabel.textColor = UIColor.lightText
|
||||
self.dataSource.placeholderView = placeholderView
|
||||
|
||||
|
||||
/* Cell Configuration */
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, metadata, indexPath) in
|
||||
self.configure(cell: cell as! GameMetadataTableViewCell, with: metadata, for: indexPath)
|
||||
}
|
||||
|
||||
|
||||
/* Prefetching */
|
||||
self.dataSource.prefetchHandler = { (metadata, indexPath, completionHandler) in
|
||||
guard let artworkURL = metadata.artworkURL else { return nil }
|
||||
|
||||
let operation = LoadImageURLOperation(url: artworkURL)
|
||||
operation.resultHandler = { (image, error) in
|
||||
completionHandler(image, error)
|
||||
}
|
||||
return operation
|
||||
}
|
||||
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard let image = image else { return }
|
||||
|
||||
let cell = cell as! GameMetadataTableViewCell
|
||||
|
||||
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
|
||||
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
|
||||
|
||||
// Offset artworkImageViewLeadingConstraint and artworkImageViewTrailingConstraint to right-align artworkImageView
|
||||
cell.artworkImageViewLeadingConstraint.constant += offset
|
||||
cell.artworkImageViewTrailingConstraint.constant -= offset
|
||||
|
||||
cell.artworkImageView.image = image
|
||||
cell.artworkImageView.superview?.layoutIfNeeded()
|
||||
}
|
||||
|
||||
|
||||
/* Searching */
|
||||
if let database = self.database
|
||||
{
|
||||
self.dataSource.searchController.searchHandler = { [unowned self, unowned database] (searchValue, previousSearchValue) in
|
||||
return RSTBlockOperation() { [unowned self, unowned database] (operation) in
|
||||
let results = database.metadataResults(forGameName: searchValue.text)
|
||||
|
||||
guard !operation.isCancelled else { return }
|
||||
|
||||
self.dataSource.items = results
|
||||
|
||||
rst_dispatch_sync_on_main_thread {
|
||||
self.resetTableViewContentOffset()
|
||||
self.updatePlaceholderView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension GamesDatabaseBrowserViewController
|
||||
{
|
||||
func configure(cell: GameMetadataTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
|
||||
{
|
||||
@ -112,30 +152,6 @@ extension GamesDatabaseBrowserViewController
|
||||
cell.artworkImageViewTrailingConstraint.constant = 15
|
||||
|
||||
cell.separatorInset.left = cell.nameLabel.frame.minX
|
||||
|
||||
if let artworkURL = metadata.artworkURL
|
||||
{
|
||||
let operation = LoadImageURLOperation(url: artworkURL)
|
||||
operation.resultsCache = self.imageCache
|
||||
operation.resultHandler = { (image, error) in
|
||||
if let image = image
|
||||
{
|
||||
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
|
||||
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Offset artworkImageViewLeadingConstraint and artworkImageViewTrailingConstraint to right-align artworkImageView
|
||||
cell.artworkImageViewLeadingConstraint.constant += offset
|
||||
cell.artworkImageViewTrailingConstraint.constant -= offset
|
||||
|
||||
cell.artworkImageView.image = image
|
||||
cell.artworkImageView.superview?.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.operationQueue.addOperation(operation, forKey: indexPath as NSIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePlaceholderView()
|
||||
@ -153,6 +169,12 @@ extension GamesDatabaseBrowserViewController
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("Please make sure the name is correct, or try searching for another game.", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
func resetTableViewContentOffset()
|
||||
{
|
||||
self.tableView.setContentOffset(CGPoint.zero, animated: false)
|
||||
self.tableView.setContentOffset(CGPoint(x: 0, y: -self.topLayoutGuide.length), animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesDatabaseBrowserViewController
|
||||
@ -167,12 +189,6 @@ extension GamesDatabaseBrowserViewController
|
||||
let metadata = self.dataSource.item(at: indexPath)
|
||||
self.selectionHandler?(metadata)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
|
||||
{
|
||||
let operation = self.operationQueue[indexPath as NSIndexPath]
|
||||
operation?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesDatabaseBrowserViewController: UISearchControllerDelegate
|
||||
@ -193,7 +209,6 @@ extension GamesDatabaseBrowserViewController: UISearchControllerDelegate
|
||||
func didDismissSearchController(_ searchController: UISearchController)
|
||||
{
|
||||
// Fix potentially incorrect offset if user dismisses searchController while scrolling
|
||||
self.tableView.setContentOffset(CGPoint.zero, animated: false)
|
||||
self.tableView.setContentOffset(CGPoint(x: 0, y: -self.topLayoutGuide.length), animated: false)
|
||||
self.resetTableViewContentOffset()
|
||||
}
|
||||
}
|
||||
|
||||
28
Delta/Emulation/ActionInput.swift
Normal file
28
Delta/Emulation/ActionInput.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// ActionInput.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 8/28/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
public extension GameControllerInputType
|
||||
{
|
||||
static let action = GameControllerInputType("com.rileytestut.Delta.input.action")
|
||||
}
|
||||
|
||||
enum ActionInput: String
|
||||
{
|
||||
case quickSave
|
||||
case quickLoad
|
||||
case fastForward
|
||||
}
|
||||
|
||||
extension ActionInput: Input
|
||||
{
|
||||
var type: InputType {
|
||||
return .controller(.action)
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,25 @@ private extension GameViewController
|
||||
self.gameType = gameType
|
||||
}
|
||||
}
|
||||
|
||||
struct SustainInputsMapping: GameControllerInputMappingProtocol
|
||||
{
|
||||
let gameController: GameController
|
||||
|
||||
var gameControllerInputType: GameControllerInputType {
|
||||
return self.gameController.inputType
|
||||
}
|
||||
|
||||
func input(forControllerInput controllerInput: Input) -> Input?
|
||||
{
|
||||
if let mappedInput = self.gameController.defaultInputMapping?.input(forControllerInput: controllerInput), mappedInput == StandardGameControllerInput.menu
|
||||
{
|
||||
return mappedInput
|
||||
}
|
||||
|
||||
return controllerInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GameViewController: DeltaCore.GameViewController
|
||||
@ -53,11 +72,11 @@ class GameViewController: DeltaCore.GameViewController
|
||||
}
|
||||
|
||||
//MARK: - Private Properties -
|
||||
fileprivate var pauseViewController: PauseViewController?
|
||||
fileprivate var pausingGameController: GameController?
|
||||
private var pauseViewController: PauseViewController?
|
||||
private var pausingGameController: GameController?
|
||||
|
||||
// Prevents the same save state from being saved multiple times
|
||||
fileprivate var pausedSaveState: PausedSaveState? {
|
||||
private var pausedSaveState: PausedSaveState? {
|
||||
didSet
|
||||
{
|
||||
if let saveState = oldValue, self.pausedSaveState == nil
|
||||
@ -74,25 +93,20 @@ class GameViewController: DeltaCore.GameViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var _isLoadingSaveState = false
|
||||
private var _isLoadingSaveState = false
|
||||
|
||||
fileprivate var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
||||
private var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
||||
|
||||
// Sustain Buttons
|
||||
fileprivate var updateSemaphores = Set<DispatchSemaphore>()
|
||||
fileprivate var sustainedInputs = [ObjectIdentifier: [Input]]()
|
||||
fileprivate var reactivateSustainedInputsQueue: OperationQueue
|
||||
fileprivate var selectingSustainedButtons = false
|
||||
private var isSelectingSustainedButtons = false
|
||||
private var sustainInputsMapping: SustainInputsMapping?
|
||||
|
||||
fileprivate var sustainButtonsContentView: UIView!
|
||||
fileprivate var sustainButtonsBlurView: UIVisualEffectView!
|
||||
fileprivate var sustainButtonsBackgroundView: RSTPlaceholderView!
|
||||
private var sustainButtonsContentView: UIView!
|
||||
private var sustainButtonsBlurView: UIVisualEffectView!
|
||||
private var sustainButtonsBackgroundView: RSTPlaceholderView!
|
||||
|
||||
required init()
|
||||
{
|
||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
super.init()
|
||||
|
||||
self.initialize()
|
||||
@ -100,9 +114,6 @@ class GameViewController: DeltaCore.GameViewController
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
self.reactivateSustainedInputsQueue = OperationQueue()
|
||||
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.initialize()
|
||||
@ -112,8 +123,8 @@ class GameViewController: DeltaCore.GameViewController
|
||||
{
|
||||
self.delegate = self
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
|
||||
}
|
||||
@ -128,19 +139,42 @@ class GameViewController: DeltaCore.GameViewController
|
||||
{
|
||||
super.gameController(gameController, didActivate: input)
|
||||
|
||||
guard (input as? ControllerInput) != .menu else { return }
|
||||
|
||||
if self.selectingSustainedButtons
|
||||
if self.isSelectingSustainedButtons
|
||||
{
|
||||
self.addSustainedInput(input, for: gameController)
|
||||
}
|
||||
else if let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)], sustainedInputs.contains(where: { $0.isEqual(input) })
|
||||
{
|
||||
// Perform on next run loop
|
||||
DispatchQueue.main.async {
|
||||
self.reactivateSustainedInput(input, for: gameController)
|
||||
guard let pausingGameController = self.pausingGameController, gameController == pausingGameController else { return }
|
||||
|
||||
if input != StandardGameControllerInput.menu
|
||||
{
|
||||
gameController.sustain(input)
|
||||
}
|
||||
}
|
||||
else if self.emulatorCore?.state == .running
|
||||
{
|
||||
guard let actionInput = ActionInput(input: input) else { return }
|
||||
|
||||
switch actionInput
|
||||
{
|
||||
case .quickSave: self.performQuickSaveAction()
|
||||
case .quickLoad: self.performQuickLoadAction()
|
||||
case .fastForward: self.performFastForwardAction(activate: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func gameController(_ gameController: GameController, didDeactivate input: Input)
|
||||
{
|
||||
super.gameController(gameController, didDeactivate: input)
|
||||
|
||||
guard !self.isSelectingSustainedButtons else { return }
|
||||
|
||||
guard let actionInput = ActionInput(input: input) else { return }
|
||||
|
||||
switch actionInput
|
||||
{
|
||||
case .quickSave: break
|
||||
case .quickLoad: break
|
||||
case .fastForward: self.performFastForwardAction(activate: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,10 +187,15 @@ extension GameViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
// Lays out self.gameView, so we can pin self.sustainButtonsContentView to it without resulting in a temporary "cannot satisfy constraints".
|
||||
self.view.layoutIfNeeded()
|
||||
|
||||
let gameViewContainerView = self.gameView.superview!
|
||||
|
||||
self.sustainButtonsContentView = UIView(frame: CGRect(x: 0, y: 0, width: self.gameView.bounds.width, height: self.gameView.bounds.height))
|
||||
self.sustainButtonsContentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.sustainButtonsContentView.isHidden = true
|
||||
self.view.insertSubview(self.sustainButtonsContentView, aboveSubview: self.gameView)
|
||||
self.view.insertSubview(self.sustainButtonsContentView, aboveSubview: gameViewContainerView)
|
||||
|
||||
let blurEffect = UIBlurEffect(style: .dark)
|
||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
|
||||
@ -182,10 +221,10 @@ extension GameViewController
|
||||
vibrancyView.contentView.addSubview(self.sustainButtonsBackgroundView)
|
||||
|
||||
// Auto Layout
|
||||
self.sustainButtonsContentView.leadingAnchor.constraint(equalTo: self.gameView.leadingAnchor).isActive = true
|
||||
self.sustainButtonsContentView.trailingAnchor.constraint(equalTo: self.gameView.trailingAnchor).isActive = true
|
||||
self.sustainButtonsContentView.topAnchor.constraint(equalTo: self.gameView.topAnchor).isActive = true
|
||||
self.sustainButtonsContentView.bottomAnchor.constraint(equalTo: self.gameView.bottomAnchor).isActive = true
|
||||
self.sustainButtonsContentView.leadingAnchor.constraint(equalTo: gameViewContainerView.leadingAnchor).isActive = true
|
||||
self.sustainButtonsContentView.trailingAnchor.constraint(equalTo: gameViewContainerView.trailingAnchor).isActive = true
|
||||
self.sustainButtonsContentView.topAnchor.constraint(equalTo: gameViewContainerView.topAnchor).isActive = true
|
||||
self.sustainButtonsContentView.bottomAnchor.constraint(equalTo: gameViewContainerView.bottomAnchor).isActive = true
|
||||
|
||||
self.updateControllerSkin()
|
||||
self.updateControllers()
|
||||
@ -220,7 +259,7 @@ extension GameViewController
|
||||
|
||||
if let game = self.game
|
||||
{
|
||||
let fileURL = FileManager.uniqueTemporaryURL()
|
||||
let fileURL = FileManager.default.uniqueTemporaryURL()
|
||||
self.pausedSaveState = PausedSaveState(fileURL: fileURL, gameType: game.type)
|
||||
|
||||
self.emulatorCore?.saveSaveState(to: fileURL)
|
||||
@ -238,22 +277,27 @@ extension GameViewController
|
||||
pauseViewController.saveStatesViewControllerDelegate = self
|
||||
pauseViewController.cheatsViewControllerDelegate = self
|
||||
|
||||
pauseViewController.fastForwardItem?.selected = (self.emulatorCore?.rate != self.emulatorCore?.configuration.supportedRates.lowerBound)
|
||||
pauseViewController.fastForwardItem?.isSelected = (self.emulatorCore?.rate != self.emulatorCore?.deltaCore.supportedRates.lowerBound)
|
||||
pauseViewController.fastForwardItem?.action = { [unowned self] item in
|
||||
guard let emulatorCore = self.emulatorCore else { return }
|
||||
emulatorCore.rate = item.selected ? emulatorCore.configuration.supportedRates.upperBound : emulatorCore.configuration.supportedRates.lowerBound
|
||||
self.performFastForwardAction(activate: item.isSelected)
|
||||
}
|
||||
|
||||
pauseViewController.sustainButtonsItem?.selected = (self.sustainedInputs[ObjectIdentifier(gameController)]?.count ?? 0) > 0
|
||||
pauseViewController.sustainButtonsItem?.isSelected = gameController.sustainedInputs.count > 0
|
||||
pauseViewController.sustainButtonsItem?.action = { [unowned self, unowned pauseViewController] item in
|
||||
|
||||
self.resetSustainedInputs(for: gameController)
|
||||
for input in gameController.sustainedInputs
|
||||
{
|
||||
gameController.unsustain(input)
|
||||
}
|
||||
|
||||
if item.selected
|
||||
if item.isSelected
|
||||
{
|
||||
self.showSustainButtonView()
|
||||
pauseViewController.dismiss()
|
||||
}
|
||||
|
||||
// Re-set gameController as pausingGameController.
|
||||
self.pausingGameController = gameController
|
||||
}
|
||||
|
||||
self.pauseViewController = pauseViewController
|
||||
@ -341,13 +385,14 @@ private extension GameViewController
|
||||
{
|
||||
@objc func updateControllers()
|
||||
{
|
||||
var controllers = [GameController]()
|
||||
controllers.append(self.controllerView)
|
||||
let isExternalGameControllerConnected = ExternalGameControllerManager.shared.connectedControllers.contains(where: { $0.playerIndex != nil })
|
||||
if !isExternalGameControllerConnected && Settings.localControllerPlayerIndex == nil
|
||||
{
|
||||
Settings.localControllerPlayerIndex = 0
|
||||
}
|
||||
|
||||
// We need to map each item as a GameControllerProtocol due to a Swift bug
|
||||
controllers.append(contentsOf: ExternalControllerManager.shared.connectedControllers.map { $0 as GameController })
|
||||
|
||||
if let index = Settings.localControllerPlayerIndex
|
||||
// If Settings.localControllerPlayerIndex is non-nil, and there isn't a connected controller with same playerIndex, show controller view.
|
||||
if let index = Settings.localControllerPlayerIndex, !ExternalGameControllerManager.shared.connectedControllers.contains { $0.playerIndex == index }
|
||||
{
|
||||
self.controllerView.playerIndex = index
|
||||
self.controllerView.isHidden = false
|
||||
@ -356,44 +401,48 @@ private extension GameViewController
|
||||
{
|
||||
self.controllerView.playerIndex = nil
|
||||
self.controllerView.isHidden = true
|
||||
}
|
||||
|
||||
// Removing all game controllers from EmulatorCore will reset each controller's playerIndex to nil
|
||||
// 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
|
||||
{
|
||||
// We need to place the underscore here to silence erroneous unused result warning despite annotating function with @discardableResult
|
||||
// Hopefully this bug won't be around for too long...
|
||||
_ = self.emulatorCore?.setGameController(controller, at: index)
|
||||
controller.addReceiver(self)
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.removeReceiver(self)
|
||||
}
|
||||
|
||||
Settings.localControllerPlayerIndex = nil
|
||||
}
|
||||
|
||||
self.view.setNeedsLayout()
|
||||
self.view.layoutIfNeeded()
|
||||
|
||||
if let emulatorCore = self.emulatorCore, let game = self.game
|
||||
{
|
||||
let controllers = [self.controllerView as GameController] + ExternalGameControllerManager.shared.connectedControllers
|
||||
|
||||
for gameController in controllers
|
||||
{
|
||||
if gameController.playerIndex != nil
|
||||
{
|
||||
if let inputMapping = GameControllerInputMapping.inputMapping(for: gameController, gameType: game.type, in: DatabaseManager.shared.viewContext)
|
||||
{
|
||||
gameController.addReceiver(self, inputMapping: inputMapping)
|
||||
gameController.addReceiver(emulatorCore, inputMapping: inputMapping)
|
||||
}
|
||||
else
|
||||
{
|
||||
gameController.addReceiver(self)
|
||||
gameController.addReceiver(emulatorCore)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gameController.removeReceiver(self)
|
||||
gameController.removeReceiver(emulatorCore)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateControllerSkin()
|
||||
{
|
||||
guard let game = self.game else { return }
|
||||
guard let game = self.game, let system = System(gameType: game.type) else { return }
|
||||
|
||||
let traits = DeltaCore.ControllerSkin.Traits.defaults(for: self.view)
|
||||
|
||||
let controllerSkin = Settings.preferredControllerSkin(for: game.type, traits: traits)
|
||||
let controllerSkin = Settings.preferredControllerSkin(for: system, traits: traits)
|
||||
self.controllerView.controllerSkin = controllerSkin
|
||||
|
||||
if controllerSkin?.isTranslucent(for: traits) ?? false
|
||||
@ -411,7 +460,7 @@ private extension GameViewController
|
||||
/// Save States
|
||||
extension GameViewController: SaveStatesViewControllerDelegate
|
||||
{
|
||||
fileprivate func updateAutoSaveState()
|
||||
private func updateAutoSaveState()
|
||||
{
|
||||
// Ensures game is non-nil and also a Game subclass
|
||||
guard let game = self.game as? Game else { return }
|
||||
@ -429,47 +478,42 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
||||
|
||||
let game = backgroundContext.object(with: game.objectID) as! Game
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %d AND %K == %@", #keyPath(SaveState.type), SaveStateType.auto.rawValue, #keyPath(SaveState.game), game)
|
||||
|
||||
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
|
||||
fetchRequest.predicate = predicate
|
||||
let fetchRequest = SaveState.fetchRequest(for: game, type: .auto)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)]
|
||||
|
||||
var saveStates: [SaveState]? = nil
|
||||
|
||||
do
|
||||
{
|
||||
saveStates = try fetchRequest.execute()
|
||||
let saveStates = try fetchRequest.execute()
|
||||
|
||||
if let saveState = saveStates.first, saveStates.count >= 2
|
||||
{
|
||||
// If there are two or more auto save states, update the oldest one
|
||||
self.update(saveState, with: self.pausedSaveState)
|
||||
|
||||
// Tiny hack: SaveStatesViewController sorts save states by creation date, so we update the creation date too
|
||||
// Simpler than deleting old save states ¯\_(ツ)_/¯
|
||||
saveState.creationDate = saveState.modifiedDate
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, create a new one
|
||||
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
|
||||
saveState.type = .auto
|
||||
saveState.game = game
|
||||
|
||||
self.update(saveState, with: self.pausedSaveState)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
if let saveStates = saveStates, let saveState = saveStates.first, saveStates.count >= 2
|
||||
{
|
||||
// If there are two or more auto save states, update the oldest one
|
||||
self.update(saveState, with: self.pausedSaveState)
|
||||
|
||||
// Tiny hack; SaveStatesViewController sorts save states by creation date, so we update the creation date too
|
||||
// Simpler than deleting old save states ¯\_(ツ)_/¯
|
||||
saveState.creationDate = saveState.modifiedDate
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, create a new one
|
||||
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
|
||||
saveState.type = .auto
|
||||
saveState.game = game
|
||||
|
||||
self.update(saveState, with: self.pausedSaveState)
|
||||
}
|
||||
|
||||
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func update(_ saveState: SaveState, with replacementSaveState: SaveStateProtocol? = nil)
|
||||
private func update(_ saveState: SaveState, with replacementSaveState: SaveStateProtocol? = nil)
|
||||
{
|
||||
let isRunning = (self.emulatorCore?.state == .running)
|
||||
|
||||
@ -523,25 +567,14 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - SaveStatesViewControllerDelegate
|
||||
|
||||
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
|
||||
private func load(_ saveState: SaveStateProtocol)
|
||||
{
|
||||
let updatingExistingSaveState = FileManager.default.fileExists(atPath: saveState.fileURL.path)
|
||||
let isRunning = (self.emulatorCore?.state == .running)
|
||||
|
||||
self.update(saveState)
|
||||
|
||||
// Dismiss if updating an existing save state.
|
||||
// If creating a new one, don't dismiss.
|
||||
if updatingExistingSaveState
|
||||
if isRunning
|
||||
{
|
||||
self.pauseViewController?.dismiss()
|
||||
self.pauseEmulation()
|
||||
}
|
||||
}
|
||||
|
||||
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateProtocol)
|
||||
{
|
||||
self._isLoadingSaveState = true
|
||||
|
||||
// If we're loading the auto save state, we need to create a temporary copy of saveState.
|
||||
// Then, we update the auto save state, but load our copy so everything works out.
|
||||
@ -549,7 +582,7 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
||||
|
||||
if let autoSaveState = saveState as? SaveState, autoSaveState.type == .auto
|
||||
{
|
||||
let temporaryURL = FileManager.uniqueTemporaryURL()
|
||||
let temporaryURL = FileManager.default.uniqueTemporaryURL()
|
||||
|
||||
do
|
||||
{
|
||||
@ -585,16 +618,33 @@ extension GameViewController: SaveStatesViewControllerDelegate
|
||||
print(error)
|
||||
}
|
||||
|
||||
// Reactivate sustained inputs
|
||||
for gameController in self.emulatorCore?.gameControllers ?? []
|
||||
if isRunning
|
||||
{
|
||||
guard let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)] else { continue }
|
||||
|
||||
for input in sustainedInputs
|
||||
{
|
||||
self.reactivateSustainedInput(input, for: gameController)
|
||||
}
|
||||
self.resumeEmulation()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - SaveStatesViewControllerDelegate
|
||||
|
||||
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
|
||||
{
|
||||
let updatingExistingSaveState = FileManager.default.fileExists(atPath: saveState.fileURL.path)
|
||||
|
||||
self.update(saveState)
|
||||
|
||||
// Dismiss if updating an existing save state.
|
||||
// If creating a new one, don't dismiss.
|
||||
if updatingExistingSaveState
|
||||
{
|
||||
self.pauseViewController?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveStateProtocol)
|
||||
{
|
||||
self._isLoadingSaveState = true
|
||||
|
||||
self.load(saveState)
|
||||
|
||||
self.pauseViewController?.dismiss()
|
||||
}
|
||||
@ -620,7 +670,12 @@ private extension GameViewController
|
||||
{
|
||||
func showSustainButtonView()
|
||||
{
|
||||
self.selectingSustainedButtons = true
|
||||
guard let gameController = self.pausingGameController else { return }
|
||||
|
||||
self.isSelectingSustainedButtons = true
|
||||
|
||||
let sustainInputsMapping = SustainInputsMapping(gameController: gameController)
|
||||
gameController.addReceiver(self, inputMapping: sustainInputsMapping)
|
||||
|
||||
let blurEffect = self.sustainButtonsBlurView.effect
|
||||
self.sustainButtonsBlurView.effect = nil
|
||||
@ -635,7 +690,18 @@ private extension GameViewController
|
||||
|
||||
func hideSustainButtonView()
|
||||
{
|
||||
self.selectingSustainedButtons = false
|
||||
guard let gameController = self.pausingGameController else { return }
|
||||
|
||||
self.isSelectingSustainedButtons = false
|
||||
|
||||
self.updateControllers()
|
||||
self.sustainInputsMapping = nil
|
||||
|
||||
// Reactivate all sustained inputs, since they will now be mapped to game inputs.
|
||||
for input in gameController.sustainedInputs
|
||||
{
|
||||
gameController.activate(input)
|
||||
}
|
||||
|
||||
let blurEffect = self.sustainButtonsBlurView.effect
|
||||
|
||||
@ -647,94 +713,76 @@ private extension GameViewController
|
||||
self.sustainButtonsBlurView.effect = blurEffect
|
||||
}
|
||||
}
|
||||
|
||||
func resetSustainedInputs(for gameController: GameController)
|
||||
}
|
||||
|
||||
//MARK: - Action Inputs -
|
||||
/// Action Inputs
|
||||
extension GameViewController
|
||||
{
|
||||
func performQuickSaveAction()
|
||||
{
|
||||
if let previousInputs = self.sustainedInputs[ObjectIdentifier(gameController)]
|
||||
{
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Activate previousInputs without notifying anyone so we can then deactivate them
|
||||
// We do this because deactivating an already deactivated input has no effect
|
||||
previousInputs.forEach { gameController.activate($0) }
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
|
||||
// Deactivate previously sustained inputs
|
||||
previousInputs.forEach { gameController.deactivate($0) }
|
||||
}
|
||||
guard let game = self.game as? Game else { return }
|
||||
|
||||
self.sustainedInputs[ObjectIdentifier(gameController)] = []
|
||||
}
|
||||
|
||||
func addSustainedInput(_ input: Input, for gameController: GameController)
|
||||
{
|
||||
var inputs = self.sustainedInputs[ObjectIdentifier(gameController)] ?? []
|
||||
|
||||
guard !inputs.contains(where: { $0.isEqual(input) }) else { return }
|
||||
|
||||
inputs.append(input)
|
||||
self.sustainedInputs[ObjectIdentifier(gameController)] = inputs
|
||||
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Causes input to be considered deactivated, so gameController won't send a subsequent message to observers when user actually deactivates
|
||||
// However, at this point the core still thinks it is activated, and is temporarily not a receiver, thus sustaining it
|
||||
gameController.deactivate(input)
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
}
|
||||
|
||||
func reactivateSustainedInput(_ input: Input, for gameController: GameController)
|
||||
{
|
||||
// These MUST be performed serially, or else Bad Things Happen™ if multiple inputs are reactivated at once
|
||||
self.reactivateSustainedInputsQueue.addOperation {
|
||||
let backgroundContext = DatabaseManager.shared.newBackgroundContext()
|
||||
backgroundContext.performAndWait {
|
||||
|
||||
// The manual activations/deactivations here are hidden implementation details, so we won't notify ourselves about them
|
||||
gameController.removeReceiver(self)
|
||||
let game = backgroundContext.object(with: game.objectID) as! Game
|
||||
let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
|
||||
|
||||
// Must deactivate first so core recognizes a secondary activation
|
||||
gameController.deactivate(input)
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "com.rileytestut.Delta.sustainButtonsQueue")
|
||||
dispatchQueue.async {
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
self.updateSemaphores.insert(semaphore)
|
||||
|
||||
// To ensure the emulator core recognizes us activating the input again, we need to wait at least two frames
|
||||
// Unfortunately we cannot init DispatchSemaphore with value less than 0
|
||||
// To compensate, we simply wait twice; once the first wait returns, we wait again
|
||||
semaphore.wait()
|
||||
semaphore.wait()
|
||||
|
||||
// These MUST be performed serially, or else Bad Things Happen™ if multiple inputs are reactivated at once
|
||||
self.reactivateSustainedInputsQueue.addOperation {
|
||||
|
||||
self.updateSemaphores.remove(semaphore)
|
||||
|
||||
// Ensure we still are not a receiver (to prevent rare race conditions)
|
||||
gameController.removeReceiver(self)
|
||||
|
||||
gameController.activate(input)
|
||||
|
||||
let receivers = gameController.receivers
|
||||
receivers.forEach { gameController.removeReceiver($0) }
|
||||
|
||||
// Causes input to be considered deactivated, so gameController won't send a subsequent message to observers when user actually deactivates
|
||||
// However, at this point the core still thinks it is activated, and is temporarily not a receiver, thus sustaining it
|
||||
gameController.deactivate(input)
|
||||
|
||||
receivers.forEach { gameController.addReceiver($0) }
|
||||
do
|
||||
{
|
||||
if let quickSaveState = try fetchRequest.execute().first
|
||||
{
|
||||
self.update(quickSaveState)
|
||||
}
|
||||
else
|
||||
{
|
||||
let saveState = SaveState(context: backgroundContext)
|
||||
saveState.type = .quick
|
||||
saveState.game = game
|
||||
|
||||
self.update(saveState)
|
||||
}
|
||||
|
||||
// More Bad Things Happen™ if we add self as observer before ALL reactivations have occurred (notable, infinite loops)
|
||||
self.reactivateSustainedInputsQueue.waitUntilAllOperationsAreFinished()
|
||||
|
||||
gameController.addReceiver(self)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
|
||||
func performQuickLoadAction()
|
||||
{
|
||||
guard let game = self.game as? Game else { return }
|
||||
|
||||
let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
|
||||
|
||||
do
|
||||
{
|
||||
if let quickSaveState = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first
|
||||
{
|
||||
self.load(quickSaveState)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func performFastForwardAction(activate: Bool)
|
||||
{
|
||||
guard let emulatorCore = self.emulatorCore else { return }
|
||||
|
||||
if activate
|
||||
{
|
||||
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.upperBound
|
||||
}
|
||||
else
|
||||
{
|
||||
emulatorCore.rate = emulatorCore.deltaCore.supportedRates.lowerBound
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -745,12 +793,17 @@ extension GameViewController: GameViewControllerDelegate
|
||||
{
|
||||
func gameViewController(_ gameViewController: DeltaCore.GameViewController, handleMenuInputFrom gameController: GameController)
|
||||
{
|
||||
if self.selectingSustainedButtons
|
||||
if let pausingGameController = self.pausingGameController
|
||||
{
|
||||
guard pausingGameController == gameController else { return }
|
||||
}
|
||||
|
||||
if self.isSelectingSustainedButtons
|
||||
{
|
||||
self.hideSustainButtonView()
|
||||
}
|
||||
|
||||
if let pauseViewController = self.pauseViewController, !self.selectingSustainedButtons
|
||||
if let pauseViewController = self.pauseViewController, !self.isSelectingSustainedButtons
|
||||
{
|
||||
pauseViewController.dismiss()
|
||||
}
|
||||
@ -763,15 +816,7 @@ extension GameViewController: GameViewControllerDelegate
|
||||
|
||||
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
||||
{
|
||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.selectingSustainedButtons && self.view.window != nil
|
||||
}
|
||||
|
||||
func gameViewControllerDidUpdate(_ gameViewController: DeltaCore.GameViewController)
|
||||
{
|
||||
for semaphore in self.updateSemaphores
|
||||
{
|
||||
semaphore.signal()
|
||||
}
|
||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -805,12 +850,12 @@ private extension GameViewController
|
||||
|
||||
case .preferredControllerSkin:
|
||||
guard
|
||||
let gameType = notification.userInfo?[Settings.NotificationUserInfoKey.gameType] as? GameType,
|
||||
let system = notification.userInfo?[Settings.NotificationUserInfoKey.system] as? System,
|
||||
let traits = notification.userInfo?[Settings.NotificationUserInfoKey.traits] as? DeltaCore.ControllerSkin.Traits
|
||||
else { return }
|
||||
|
||||
let currentTraits = DeltaCore.ControllerSkin.Traits.defaults(for: self.view)
|
||||
if gameType == self.game?.type && traits == currentTraits
|
||||
if system.gameType == self.game?.type && traits == currentTraits
|
||||
{
|
||||
self.updateControllerSkin()
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ class PreviewGameViewController: DeltaCore.GameViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var emulatorCoreQueue = DispatchQueue(label: "com.rileytestut.Delta.PreviewGameViewController.emulatorCoreQueue", qos: .userInitiated)
|
||||
private var emulatorCoreQueue = DispatchQueue(label: "com.rileytestut.Delta.PreviewGameViewController.emulatorCoreQueue", qos: .userInitiated)
|
||||
|
||||
override var game: GameProtocol? {
|
||||
willSet {
|
||||
|
||||
23
Delta/Extensions/DeltaCoreProtocol+Delta.swift
Normal file
23
Delta/Extensions/DeltaCoreProtocol+Delta.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// DeltaCoreProtocol+Delta.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 4/30/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension DeltaCoreProtocol
|
||||
{
|
||||
var supportedRates: ClosedRange<Double> {
|
||||
guard let system = System(gameType: self.gameType) else { return 1...1 }
|
||||
|
||||
switch system
|
||||
{
|
||||
case .snes: return 1...4
|
||||
case .gba: return 1...3
|
||||
case .gbc: return 1...4
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// GameType+Delta.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/22/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension GameType
|
||||
{
|
||||
static var supportedTypes: Set<GameType>
|
||||
{
|
||||
return [GameType.snes, GameType.gba]
|
||||
}
|
||||
|
||||
static func gameType(forFileExtension fileExtension: String) -> GameType
|
||||
{
|
||||
let gameType: GameType
|
||||
|
||||
switch fileExtension.lowercased()
|
||||
{
|
||||
case "smc", "sfc", "fig": gameType = GameType.snes
|
||||
case "gba": gameType = GameType.gba
|
||||
default: gameType = GameType.unknown
|
||||
}
|
||||
|
||||
return gameType
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
//
|
||||
// GameType+Localization.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 10/3/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension GameType
|
||||
{
|
||||
var localizedName: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case GameType.snes: return NSLocalizedString("Super Nintendo Entertainment System", comment: "")
|
||||
case GameType.gba: return NSLocalizedString("Game Boy Advance", comment: "")
|
||||
case GameType.unknown: return NSLocalizedString("Unsupported System", comment: "")
|
||||
default: return NSLocalizedString("Unknown", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
var localizedShortName: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case GameType.snes: return NSLocalizedString("SNES", comment: "")
|
||||
case GameType.gba: return NSLocalizedString("GBA", comment: "")
|
||||
case GameType.unknown: return NSLocalizedString("Unsupported", comment: "")
|
||||
default: return NSLocalizedString("Unknown", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Delta/Extensions/Input+Display.swift
Normal file
112
Delta/Extensions/Input+Display.swift
Normal file
@ -0,0 +1,112 @@
|
||||
//
|
||||
// Input+Display.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 8/15/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension Input
|
||||
{
|
||||
// With the default GameControllerInputMapping files, multiple controller inputs may map to the same game input.
|
||||
// This is because each controller input maps to a unique standard input, but then multiple standard inputs may map to same game input.
|
||||
// To ensure we only show the most "important" controller input for a game input, we define general "display priorities" for each input.
|
||||
//
|
||||
// For example, MFiGameController.down and MFiGameController.leftThumbstickDown both map to a "down" game input.
|
||||
// However, .down has a higher priority than .leftThumbstickDown, so we show .down instead of .leftThumbstickDown.
|
||||
var displayPriority: Int {
|
||||
switch self.type
|
||||
{
|
||||
case .game: break
|
||||
case .controller(.standard): break
|
||||
case .controller(.mfi):
|
||||
let input = MFiGameController.Input(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .leftThumbstickUp: return 750
|
||||
case .leftThumbstickDown: return 750
|
||||
case .leftThumbstickLeft: return 750
|
||||
case .leftThumbstickRight: return 750
|
||||
case .leftShoulder: return 750
|
||||
case .leftTrigger: return 500
|
||||
case .rightShoulder: return 750
|
||||
case .rightTrigger: return 500
|
||||
default: break
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
return 1000
|
||||
}
|
||||
|
||||
var localizedName: String {
|
||||
switch self.type
|
||||
{
|
||||
case .game: break
|
||||
case .controller(.standard):
|
||||
let input = StandardGameControllerInput(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .menu: return NSLocalizedString("Menu", comment: "")
|
||||
case .up: return NSLocalizedString("Up", comment: "")
|
||||
case .down: return NSLocalizedString("Down", comment: "")
|
||||
case .left: return NSLocalizedString("Left", comment: "")
|
||||
case .right: return NSLocalizedString("Right", comment: "")
|
||||
case .leftThumbstickUp: return NSLocalizedString("L🕹↑", comment: "")
|
||||
case .leftThumbstickDown: return NSLocalizedString("L🕹↓", comment: "")
|
||||
case .leftThumbstickLeft: return NSLocalizedString("L🕹←", comment: "")
|
||||
case .leftThumbstickRight: return NSLocalizedString("L🕹→", comment: "")
|
||||
case .rightThumbstickUp: return NSLocalizedString("R🕹↑", comment: "")
|
||||
case .rightThumbstickDown: return NSLocalizedString("R🕹↓", comment: "")
|
||||
case .rightThumbstickLeft: return NSLocalizedString("R🕹←", comment: "")
|
||||
case .rightThumbstickRight: return NSLocalizedString("R🕹→", comment: "")
|
||||
case .a: return NSLocalizedString("A", comment: "")
|
||||
case .b: return NSLocalizedString("B", comment: "")
|
||||
case .x: return NSLocalizedString("X", comment: "")
|
||||
case .y: return NSLocalizedString("Y", comment: "")
|
||||
case .start: return NSLocalizedString("Start", comment: "Start button")
|
||||
case .select: return NSLocalizedString("Select", comment: "Select button")
|
||||
case .l1: return NSLocalizedString("L1", comment: "")
|
||||
case .l2: return NSLocalizedString("L2", comment: "")
|
||||
case .l3: return NSLocalizedString("L3", comment: "")
|
||||
case .r1: return NSLocalizedString("R1", comment: "")
|
||||
case .r2: return NSLocalizedString("R2", comment: "")
|
||||
case .r3: return NSLocalizedString("R3", comment: "")
|
||||
}
|
||||
|
||||
case .controller(.mfi):
|
||||
let input = MFiGameController.Input(input: self)!
|
||||
switch input
|
||||
{
|
||||
case .menu: return NSLocalizedString("Menu", comment: "")
|
||||
case .up: return NSLocalizedString("Up", comment: "")
|
||||
case .down: return NSLocalizedString("Down", comment: "")
|
||||
case .left: return NSLocalizedString("Left", comment: "")
|
||||
case .right: return NSLocalizedString("Right", comment: "")
|
||||
case .leftThumbstickUp: return NSLocalizedString("L🕹↑", comment: "")
|
||||
case .leftThumbstickDown: return NSLocalizedString("L🕹↓", comment: "")
|
||||
case .leftThumbstickLeft: return NSLocalizedString("L🕹←", comment: "")
|
||||
case .leftThumbstickRight: return NSLocalizedString("L🕹→", comment: "")
|
||||
case .rightThumbstickUp: return NSLocalizedString("R🕹↑", comment: "")
|
||||
case .rightThumbstickDown: return NSLocalizedString("R🕹↓", comment: "")
|
||||
case .rightThumbstickLeft: return NSLocalizedString("R🕹←", comment: "")
|
||||
case .rightThumbstickRight: return NSLocalizedString("R🕹→", comment: "")
|
||||
case .a: return NSLocalizedString("A", comment: "")
|
||||
case .b: return NSLocalizedString("B", comment: "")
|
||||
case .x: return NSLocalizedString("X", comment: "")
|
||||
case .y: return NSLocalizedString("Y", comment: "")
|
||||
case .leftShoulder: return NSLocalizedString("L1", comment: "")
|
||||
case .leftTrigger: return NSLocalizedString("L2", comment: "")
|
||||
case .rightShoulder: return NSLocalizedString("R1", comment: "")
|
||||
case .rightTrigger: return NSLocalizedString("R2", comment: "")
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ extension UIAlertController
|
||||
{
|
||||
case .doesNotExist(let url): urls.insert(url)
|
||||
case .invalid(let url): urls.insert(url)
|
||||
case .unsupported(let url): urls.insert(url)
|
||||
case .unknown(let url, _): urls.insert(url)
|
||||
case .saveFailed(let errorURLs, _): urls.formUnion(errorURLs)
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ extension UIColor
|
||||
{
|
||||
class var deltaPurple: UIColor
|
||||
{
|
||||
return UIColor(red: 140.0/255.0, green: 26.0/255.0, blue: 233.0/255.0, alpha: 1.0)
|
||||
return UIColor(red: 139.0/255.0, green: 40.0/255.0, blue: 247.0/255.0, alpha: 1.0)
|
||||
}
|
||||
|
||||
class var deltaDarkGray: UIColor
|
||||
|
||||
@ -18,7 +18,7 @@ internal extension UILabel
|
||||
context.minimumScaleFactor = self.minimumScaleFactor
|
||||
|
||||
// Using self.attributedString returns incorrect calculations, so we create our own attributed string
|
||||
let attributedString = NSAttributedString(string: text, attributes: [NSFontAttributeName: self.font])
|
||||
let attributedString = NSAttributedString(string: text, attributes: [.font: self.font])
|
||||
attributedString.boundingRect(with: self.bounds.size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: context)
|
||||
|
||||
let scaleFactor = context.actualScaleFactor
|
||||
|
||||
28
Delta/Extensions/UIView+ParentViewController.swift
Normal file
28
Delta/Extensions/UIView+ParentViewController.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// UIView+ParentViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/3/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView
|
||||
{
|
||||
var parentViewController: UIViewController? {
|
||||
var nextResponder = self.next
|
||||
|
||||
while nextResponder != nil
|
||||
{
|
||||
if let parentViewController = nextResponder as? UIViewController
|
||||
{
|
||||
return parentViewController
|
||||
}
|
||||
|
||||
nextResponder = nextResponder?.next
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -21,11 +21,11 @@ extension UIViewControllerContextTransitioning
|
||||
|
||||
/// UIViews
|
||||
var sourceView: UIView {
|
||||
return self.sourceViewController.view
|
||||
return self.view(forKey: .from) ?? self.sourceViewController.view
|
||||
}
|
||||
|
||||
var destinationView: UIView {
|
||||
return self.destinationViewController.view
|
||||
return self.view(forKey: .to) ?? self.destinationViewController.view
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
|
||||
import DeltaCore
|
||||
|
||||
@ -16,9 +17,9 @@ import SDWebImage
|
||||
|
||||
class GameCollectionViewController: UICollectionViewController
|
||||
{
|
||||
var gameCollection: GameCollection! {
|
||||
var gameCollection: GameCollection? {
|
||||
didSet {
|
||||
self.title = self.gameCollection.shortName
|
||||
self.title = self.gameCollection?.shortName
|
||||
self.updateDataSource()
|
||||
}
|
||||
}
|
||||
@ -40,17 +41,28 @@ class GameCollectionViewController: UICollectionViewController
|
||||
}
|
||||
}
|
||||
|
||||
internal let dataSource: RSTFetchedResultsCollectionViewPrefetchingDataSource<Game, UIImage>
|
||||
|
||||
weak var activeEmulatorCore: EmulatorCore?
|
||||
|
||||
fileprivate var activeSaveState: SaveStateProtocol?
|
||||
private var activeSaveState: SaveStateProtocol?
|
||||
|
||||
fileprivate let dataSource = RSTFetchedResultsCollectionViewDataSource<Game>(fetchedResultsController: NSFetchedResultsController())
|
||||
fileprivate let prototypeCell = GridCollectionViewCell()
|
||||
private let prototypeCell = GridCollectionViewCell()
|
||||
|
||||
fileprivate var _performing3DTouchTransition = false
|
||||
fileprivate weak var _destination3DTouchTransitionViewController: UIViewController?
|
||||
private var _performing3DTouchTransition = false
|
||||
private weak var _destination3DTouchTransitionViewController: UIViewController?
|
||||
|
||||
fileprivate var _renameAction: UIAlertAction?
|
||||
private var _renameAction: UIAlertAction?
|
||||
private var _changingArtworkGame: Game?
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
self.dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<Game, UIImage>(fetchedResultsController: NSFetchedResultsController())
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.prepareDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UIViewController -
|
||||
@ -61,11 +73,8 @@ extension GameCollectionViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
self.collectionView?.prefetchDataSource = self.dataSource
|
||||
self.collectionView?.delegate = self
|
||||
|
||||
let layout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
@ -122,25 +131,7 @@ extension GameCollectionViewController
|
||||
saveStatesViewController.game = game
|
||||
saveStatesViewController.mode = .loading
|
||||
saveStatesViewController.theme = self.theme
|
||||
|
||||
case "gamesDatabaseBrowser":
|
||||
let game = sender as! Game
|
||||
|
||||
let gamesDatabaseBrowserViewController = (segue.destination as! UINavigationController).topViewController as! GamesDatabaseBrowserViewController
|
||||
gamesDatabaseBrowserViewController.selectionHandler = { (metadata) in
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask({ (context) in
|
||||
let temporaryGame = context.object(with: game.objectID) as! Game
|
||||
temporaryGame.artworkURL = metadata.artworkURL
|
||||
context.saveWithErrorLogging()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
gamesDatabaseBrowserViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
case "unwindFromGames":
|
||||
let destinationViewController = segue.destination as! GameViewController
|
||||
let cell = sender as! UICollectionViewCell
|
||||
@ -195,11 +186,42 @@ extension GameCollectionViewController
|
||||
//MARK: - Private Methods -
|
||||
private extension GameCollectionViewController
|
||||
{
|
||||
//MARK: - Update
|
||||
//MARK: - Data Source
|
||||
func prepareDataSource()
|
||||
{
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
|
||||
self.dataSource.prefetchHandler = { (game, indexPath, completionHandler) in
|
||||
guard let artworkURL = game.artworkURL else { return nil }
|
||||
|
||||
let imageOperation = LoadImageURLOperation(url: artworkURL)
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
completionHandler(image, error)
|
||||
}
|
||||
|
||||
return imageOperation
|
||||
}
|
||||
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard let image = image else { return }
|
||||
|
||||
let cell = cell as! GridCollectionViewCell
|
||||
cell.imageView.image = image
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "ANY %K == %@", #keyPath(Game.gameCollections), self.gameCollection)
|
||||
|
||||
if let gameCollection = self.gameCollection
|
||||
{
|
||||
fetchRequest.predicate = NSPredicate(format: "ANY %K == %@", #keyPath(Game.gameCollections), gameCollection)
|
||||
}
|
||||
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
@ -207,7 +229,7 @@ private extension GameCollectionViewController
|
||||
}
|
||||
|
||||
//MARK: - Configure Cells
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath, ignoreImageOperations: Bool = false)
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let game = self.dataSource.item(at: indexPath)
|
||||
|
||||
@ -222,29 +244,11 @@ private extension GameCollectionViewController
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
}
|
||||
|
||||
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
|
||||
|
||||
cell.maximumImageSize = CGSize(width: 90, height: 90)
|
||||
cell.textLabel.text = game.name
|
||||
cell.textLabel.textColor = UIColor.gray
|
||||
|
||||
if let artworkURL = game.artworkURL, !ignoreImageOperations
|
||||
{
|
||||
cell.imageView.sd_setImage(with: artworkURL, placeholderImage: #imageLiteral(resourceName: "BoxArt"), options: .continueInBackground) { (image, error, type, url) in
|
||||
|
||||
if let error = error
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
if image != nil
|
||||
{
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Emulation
|
||||
@ -342,7 +346,7 @@ private extension GameCollectionViewController
|
||||
|
||||
func rename(_ game: Game, with name: String)
|
||||
{
|
||||
guard name.characters.count > 0 else { return }
|
||||
guard name.count > 0 else { return }
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||
let game = context.object(with: game.objectID) as! Game
|
||||
@ -356,7 +360,16 @@ private extension GameCollectionViewController
|
||||
|
||||
func changeArtwork(for game: Game)
|
||||
{
|
||||
self.performSegue(withIdentifier: "gamesDatabaseBrowser", sender: game)
|
||||
self._changingArtworkGame = game
|
||||
|
||||
let clipboardImportOption = ClipboardImportOption()
|
||||
let photoLibraryImportOption = PhotoLibraryImportOption(presentingViewController: self)
|
||||
let gamesDatabaseImportOption = GamesDatabaseImportOption(presentingViewController: self)
|
||||
|
||||
let importController = ImportController(documentTypes: [kUTTypeImage as String])
|
||||
importController.delegate = self
|
||||
importController.importOptions = [clipboardImportOption, photoLibraryImportOption, gamesDatabaseImportOption]
|
||||
self.present(importController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func share(_ game: Game)
|
||||
@ -394,7 +407,7 @@ private extension GameCollectionViewController
|
||||
@objc func textFieldTextDidChange(_ textField: UITextField)
|
||||
{
|
||||
let text = textField.text ?? ""
|
||||
self._renameAction?.isEnabled = text.characters.count > 0
|
||||
self._renameAction?.isEnabled = text.count > 0
|
||||
}
|
||||
|
||||
@objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer)
|
||||
@ -417,7 +430,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
|
||||
{
|
||||
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController?
|
||||
{
|
||||
guard self.gameCollection.identifier != GameType.unknown.rawValue else { return nil }
|
||||
guard self.gameCollection?.identifier != GameType.unknown.rawValue else { return nil }
|
||||
|
||||
guard
|
||||
let collectionView = self.collectionView,
|
||||
@ -452,7 +465,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
|
||||
let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: game)!
|
||||
let cell = self.collectionView?.cellForItem(at: indexPath)
|
||||
|
||||
let fileURL = FileManager.uniqueTemporaryURL()
|
||||
let fileURL = FileManager.default.uniqueTemporaryURL()
|
||||
self.activeSaveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL)
|
||||
|
||||
gameViewController.emulatorCore?.stop()
|
||||
@ -493,13 +506,105 @@ extension GameCollectionViewController: SaveStatesViewControllerDelegate
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - ImportControllerDelegate -
|
||||
/// ImportControllerDelegate
|
||||
extension GameCollectionViewController: ImportControllerDelegate
|
||||
{
|
||||
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
|
||||
{
|
||||
guard let game = self._changingArtworkGame else { return }
|
||||
|
||||
var errors = errors
|
||||
|
||||
var imageURL: URL?
|
||||
|
||||
if let url = urls.first
|
||||
{
|
||||
if url.isFileURL
|
||||
{
|
||||
do
|
||||
{
|
||||
let imageData = try Data(contentsOf: url)
|
||||
|
||||
if let image = UIImage(data: imageData)
|
||||
{
|
||||
let resizedImage = image.resizing(toFit: CGSize(width: 300, height: 300))
|
||||
|
||||
if let resizedData = UIImageJPEGRepresentation(resizedImage, 0.85)
|
||||
{
|
||||
let destinationURL = DatabaseManager.artworkURL(for: game)
|
||||
try resizedData.write(to: destinationURL, options: .atomic)
|
||||
|
||||
imageURL = destinationURL
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
imageURL = url
|
||||
}
|
||||
}
|
||||
|
||||
for error in errors
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
if let imageURL = imageURL
|
||||
{
|
||||
// Remove previous artwork from cache.
|
||||
self.dataSource.prefetchItemCache.removeObject(forKey: game)
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||
let temporaryGame = context.object(with: game.objectID) as! Game
|
||||
temporaryGame.artworkURL = imageURL
|
||||
context.saveWithErrorLogging()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
func presentAlertController()
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change Artwork", comment: ""), message: NSLocalizedString("The image might be corrupted or in an unsupported format.", comment: ""), preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .cancel, handler: nil))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
if let presentedViewController = self.presentedViewController
|
||||
{
|
||||
presentedViewController.dismiss(animated: true) {
|
||||
presentAlertController()
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presentAlertController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importControllerDidCancel(_ importController: ImportController)
|
||||
{
|
||||
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UICollectionViewDelegate -
|
||||
/// UICollectionViewDelegate
|
||||
extension GameCollectionViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
guard self.gameCollection.identifier != GameType.unknown.rawValue else { return }
|
||||
guard self.gameCollection?.identifier != GameType.unknown.rawValue else { return }
|
||||
|
||||
let cell = collectionView.cellForItem(at: indexPath)
|
||||
let game = self.dataSource.item(at: indexPath)
|
||||
@ -542,12 +647,6 @@ extension GameCollectionViewController
|
||||
self.launchGame(withSender: cell, clearScreen: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
|
||||
{
|
||||
let cell = cell as! GridCollectionViewCell
|
||||
cell.imageView.sd_cancelCurrentImageLoad()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UICollectionViewDelegateFlowLayout -
|
||||
@ -562,7 +661,7 @@ extension GameCollectionViewController: UICollectionViewDelegateFlowLayout
|
||||
widthConstraint.isActive = true
|
||||
defer { widthConstraint.isActive = false }
|
||||
|
||||
self.configure(self.prototypeCell, for: indexPath, ignoreImageOperations: true)
|
||||
self.configure(self.prototypeCell, for: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import MobileCoreServices
|
||||
|
||||
import DeltaCore
|
||||
|
||||
@ -34,11 +35,17 @@ class GamesViewController: UIViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var pageViewController: UIPageViewController!
|
||||
fileprivate var placeholderView: RSTPlaceholderView!
|
||||
fileprivate var pageControl: UIPageControl!
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
|
||||
fileprivate let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>
|
||||
private var pageViewController: UIPageViewController!
|
||||
private var placeholderView: RSTPlaceholderView!
|
||||
private var pageControl: UIPageControl!
|
||||
|
||||
private let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>
|
||||
|
||||
private var searchController: RSTSearchController?
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
fatalError("initWithNibName: not implemented")
|
||||
@ -87,6 +94,11 @@ extension GamesViewController
|
||||
self.navigationController?.navigationBar.barStyle = .blackTranslucent
|
||||
self.navigationController?.toolbar.barStyle = .blackTranslucent
|
||||
|
||||
if #available(iOS 11.0, *)
|
||||
{
|
||||
self.prepareSearchController()
|
||||
}
|
||||
|
||||
self.updateTheme()
|
||||
}
|
||||
|
||||
@ -108,11 +120,15 @@ extension GamesViewController
|
||||
{
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if let viewControllers = self.pageViewController.viewControllers as? [GameCollectionViewController]
|
||||
if #available(iOS 11.0, *) {}
|
||||
else
|
||||
{
|
||||
for viewController in viewControllers
|
||||
if let viewControllers = self.pageViewController.viewControllers as? [GameCollectionViewController]
|
||||
{
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
for viewController in viewControllers
|
||||
{
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,6 +164,48 @@ extension GamesViewController
|
||||
/// UI
|
||||
private extension GamesViewController
|
||||
{
|
||||
@available(iOS 11.0, *)
|
||||
func prepareSearchController()
|
||||
{
|
||||
let searchResultsController = self.storyboard?.instantiateViewController(withIdentifier: "gameCollectionViewController") as! GameCollectionViewController
|
||||
searchResultsController.gameCollection = nil
|
||||
searchResultsController.theme = self.theme
|
||||
searchResultsController.activeEmulatorCore = self.activeEmulatorCore
|
||||
|
||||
let placeholderView = RSTPlaceholderView()
|
||||
placeholderView.textLabel.text = NSLocalizedString("No Games Found", comment: "")
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("Please make sure the name is correct, or try searching for another game.", comment: "")
|
||||
|
||||
switch self.theme
|
||||
{
|
||||
case .opaque: searchResultsController.dataSource.placeholderView = placeholderView
|
||||
case .translucent:
|
||||
let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark)))
|
||||
vibrancyView.contentView.addSubview(placeholderView, pinningEdgesWith: .zero)
|
||||
searchResultsController.dataSource.placeholderView = vibrancyView
|
||||
}
|
||||
|
||||
self.searchController = RSTSearchController(searchResultsController: searchResultsController)
|
||||
self.searchController?.searchableKeyPaths = [#keyPath(Game.name)]
|
||||
self.searchController?.searchHandler = { [weak searchController, weak searchResultsController] (searchValue, _) in
|
||||
if searchController?.searchBar.text?.isEmpty == false
|
||||
{
|
||||
self.pageViewController.view.isHidden = true
|
||||
}
|
||||
else
|
||||
{
|
||||
self.pageViewController.view.isHidden = false
|
||||
}
|
||||
|
||||
searchResultsController?.dataSource.predicate = searchValue.predicate
|
||||
return nil
|
||||
}
|
||||
self.navigationItem.searchController = self.searchController
|
||||
self.navigationItem.hidesSearchBarWhenScrolling = false
|
||||
|
||||
self.definesPresentationContext = true
|
||||
}
|
||||
|
||||
func updateTheme()
|
||||
{
|
||||
switch self.theme
|
||||
@ -185,12 +243,16 @@ private extension GamesViewController
|
||||
let indexPath = IndexPath(row: safeIndex, section: 0)
|
||||
|
||||
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "gameCollectionViewController") as! GameCollectionViewController
|
||||
viewController.gameCollection = self.fetchedResultsController.object(at: indexPath) as! GameCollection
|
||||
viewController.gameCollection = self.fetchedResultsController.object(at: indexPath) as? GameCollection
|
||||
viewController.theme = self.theme
|
||||
viewController.activeEmulatorCore = self.activeEmulatorCore
|
||||
|
||||
// Need to set content inset here AND willTransitionTo callback to ensure its correct for all edge cases
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
if #available(iOS 11.0, *) {}
|
||||
else
|
||||
{
|
||||
// Need to set content inset here AND willTransitionTo callback to ensure its correct for all edge cases
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
}
|
||||
|
||||
return viewController
|
||||
}
|
||||
@ -264,39 +326,57 @@ private extension GamesViewController
|
||||
/// Importing
|
||||
extension GamesViewController: ImportControllerDelegate
|
||||
{
|
||||
@IBAction fileprivate func importFiles()
|
||||
@IBAction private func importFiles()
|
||||
{
|
||||
let importController = ImportController()
|
||||
var documentTypes = Set(System.supportedSystems.map { $0.gameType.rawValue })
|
||||
documentTypes.insert(kUTTypeZipArchive as String)
|
||||
|
||||
// Add GBA4iOS's exported UTIs in case user has GBA4iOS installed (which may override Delta's UTI declarations)
|
||||
documentTypes.insert("com.rileytestut.gba")
|
||||
documentTypes.insert("com.rileytestut.gbc")
|
||||
documentTypes.insert("com.rileytestut.gb")
|
||||
|
||||
let itunesImportOption = iTunesImportOption(presentingViewController: self)
|
||||
|
||||
let importController = ImportController(documentTypes: documentTypes)
|
||||
importController.delegate = self
|
||||
importController.importOptions = [itunesImportOption]
|
||||
self.present(importController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
//MARK: - ImportControllerDelegate
|
||||
@nonobjc func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
|
||||
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
|
||||
{
|
||||
if errors.count > 0
|
||||
for error in errors
|
||||
{
|
||||
let alertController = UIAlertController.alertController(for: .games, with: errors)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
print(error)
|
||||
}
|
||||
|
||||
if games.count > 0
|
||||
{
|
||||
print("Imported Games:", games.map { $0.name })
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>)
|
||||
{
|
||||
if errors.count > 0
|
||||
{
|
||||
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
let gameURLs = urls.filter { $0.pathExtension.lowercased() != "deltaskin" }
|
||||
DatabaseManager.shared.importGames(at: Set(gameURLs)) { (games, errors) in
|
||||
if errors.count > 0
|
||||
{
|
||||
let alertController = UIAlertController.alertController(for: .games, with: errors)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
if games.count > 0
|
||||
{
|
||||
print("Imported Games:", games.map { $0.name })
|
||||
}
|
||||
}
|
||||
|
||||
if controllerSkins.count > 0
|
||||
{
|
||||
print("Imported Controller Skins:", controllerSkins.map { $0.name })
|
||||
let controllerSkinURLs = urls.filter { $0.pathExtension.lowercased() == "deltaskin" }
|
||||
DatabaseManager.shared.importControllerSkins(at: Set(controllerSkinURLs)) { (controllerSkins, errors) in
|
||||
if errors.count > 0
|
||||
{
|
||||
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
if controllerSkins.count > 0
|
||||
{
|
||||
print("Imported Controller Skins:", controllerSkins.map { $0.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,9 +427,13 @@ extension GamesViewController: UIPageViewControllerDataSource, UIPageViewControl
|
||||
{
|
||||
guard let viewControllers = pendingViewControllers as? [GameCollectionViewController] else { return }
|
||||
|
||||
for viewController in viewControllers
|
||||
if #available(iOS 11.0, *) {}
|
||||
else
|
||||
{
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
for viewController in viewControllers
|
||||
{
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,6 +455,21 @@ extension GamesViewController: UIPageViewControllerDataSource, UIPageViewControl
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesViewController: UISearchResultsUpdating
|
||||
{
|
||||
func updateSearchResults(for searchController: UISearchController)
|
||||
{
|
||||
if searchController.searchBar.text?.isEmpty == false
|
||||
{
|
||||
self.pageViewController.view.isHidden = true
|
||||
}
|
||||
else
|
||||
{
|
||||
self.pageViewController.view.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - NSFetchedResultsControllerDelegate -
|
||||
/// NSFetchedResultsControllerDelegate
|
||||
extension GamesViewController: NSFetchedResultsControllerDelegate
|
||||
|
||||
@ -12,8 +12,12 @@ class GamesPresentationController: UIPresentationController
|
||||
{
|
||||
private let blurView: UIVisualEffectView
|
||||
|
||||
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?)
|
||||
private let animator: UIViewPropertyAnimator
|
||||
|
||||
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, animator: UIViewPropertyAnimator)
|
||||
{
|
||||
self.animator = animator
|
||||
|
||||
self.blurView = UIVisualEffectView(effect: nil)
|
||||
self.blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
@ -26,17 +30,17 @@ class GamesPresentationController: UIPresentationController
|
||||
|
||||
self.blurView.frame = CGRect(x: 0, y: 0, width: containerView.bounds.width, height: containerView.bounds.height)
|
||||
containerView.addSubview(self.blurView)
|
||||
|
||||
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
|
||||
|
||||
self.animator.addAnimations {
|
||||
self.blurView.effect = UIBlurEffect(style: .dark)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin()
|
||||
{
|
||||
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
|
||||
self.animator.addAnimations {
|
||||
self.blurView.effect = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool)
|
||||
|
||||
@ -10,9 +10,9 @@ import UIKit
|
||||
|
||||
class GamesStoryboardSegue: UIStoryboardSegue
|
||||
{
|
||||
fileprivate let animator: UIViewPropertyAnimator
|
||||
private let animator: UIViewPropertyAnimator
|
||||
|
||||
fileprivate var isPresenting: Bool = true
|
||||
private var isPresenting: Bool = true
|
||||
|
||||
override init(identifier: String?, source: UIViewController, destination: UIViewController)
|
||||
{
|
||||
@ -48,7 +48,7 @@ extension GamesStoryboardSegue: UIViewControllerTransitioningDelegate
|
||||
|
||||
func presentationController(forPresented presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, source: UIViewController) -> UIPresentationController?
|
||||
{
|
||||
let presentationController = GamesPresentationController(presentedViewController: presentedViewController, presenting: presentingViewController)
|
||||
let presentationController = GamesPresentationController(presentedViewController: presentedViewController, presenting: presentingViewController, animator: self.animator)
|
||||
return presentationController
|
||||
}
|
||||
}
|
||||
@ -85,48 +85,51 @@ extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning
|
||||
snapshotView.alpha = 1.0
|
||||
transitionContext.containerView.addSubview(snapshotView)
|
||||
|
||||
// Ensures navigation controller toolbar (if visible) has been added to view heirachy, allowing us to add constraints
|
||||
transitionContext.containerView.layoutIfNeeded()
|
||||
|
||||
// We add extra padding around the existing navigation bar and toolbar so they never appear to be detached from the edges of the screen during the overshooting of the spring animation
|
||||
var topPaddingToolbar: UIToolbar? = nil
|
||||
var bottomPaddingToolbar: UIToolbar? = nil
|
||||
|
||||
if let navigationController = transitionContext.destinationViewController as? UINavigationController
|
||||
{
|
||||
let padding: CGFloat = 44
|
||||
// Must be wrapped in no-animation block to prevent iOS 11 search bar from not appearing.
|
||||
UIView.performWithoutAnimation {
|
||||
// Ensures navigation controller toolbar (if visible) has been added to view heirachy, allowing us to add constraints
|
||||
transitionContext.containerView.layoutIfNeeded()
|
||||
|
||||
if !navigationController.isNavigationBarHidden
|
||||
if let navigationController = transitionContext.destinationViewController as? UINavigationController
|
||||
{
|
||||
let topToolbar = UIToolbar(frame: CGRect.zero)
|
||||
topToolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
topToolbar.barStyle = navigationController.toolbar.barStyle
|
||||
transitionContext.destinationView.insertSubview(topToolbar, belowSubview: navigationController.navigationBar)
|
||||
let padding: CGFloat = 44
|
||||
|
||||
topToolbar.bottomAnchor.constraint(equalTo: navigationController.navigationBar.bottomAnchor).isActive = true
|
||||
topToolbar.centerXAnchor.constraint(equalTo: navigationController.navigationBar.centerXAnchor).isActive = true
|
||||
topToolbar.widthAnchor.constraint(equalTo: navigationController.navigationBar.widthAnchor, constant: padding * 2).isActive = true
|
||||
topToolbar.heightAnchor.constraint(equalTo: navigationController.navigationBar.heightAnchor, constant: padding).isActive = true
|
||||
if !navigationController.isNavigationBarHidden
|
||||
{
|
||||
let topToolbar = UIToolbar(frame: CGRect.zero)
|
||||
topToolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
topToolbar.barStyle = navigationController.toolbar.barStyle
|
||||
transitionContext.destinationView.insertSubview(topToolbar, at: 1)
|
||||
|
||||
topToolbar.topAnchor.constraint(equalTo: navigationController.navigationBar.topAnchor, constant: -padding).isActive = true
|
||||
topToolbar.bottomAnchor.constraint(equalTo: navigationController.topViewController!.topLayoutGuide.bottomAnchor).isActive = true
|
||||
topToolbar.leftAnchor.constraint(equalTo: navigationController.navigationBar.leftAnchor, constant: -padding).isActive = true
|
||||
topToolbar.rightAnchor.constraint(equalTo: navigationController.navigationBar.rightAnchor, constant: padding).isActive = true
|
||||
|
||||
topPaddingToolbar = topToolbar
|
||||
}
|
||||
|
||||
topPaddingToolbar = topToolbar
|
||||
}
|
||||
|
||||
if !navigationController.isToolbarHidden
|
||||
{
|
||||
let bottomToolbar = UIToolbar(frame: CGRect.zero)
|
||||
bottomToolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
bottomToolbar.barStyle = navigationController.toolbar.barStyle
|
||||
transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar)
|
||||
|
||||
bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true
|
||||
bottomToolbar.centerXAnchor.constraint(equalTo: navigationController.toolbar.centerXAnchor).isActive = true
|
||||
bottomToolbar.widthAnchor.constraint(equalTo: navigationController.toolbar.widthAnchor, constant: padding * 2).isActive = true
|
||||
bottomToolbar.heightAnchor.constraint(equalTo: navigationController.toolbar.heightAnchor, constant: padding).isActive = true
|
||||
|
||||
bottomPaddingToolbar = bottomToolbar
|
||||
if !navigationController.isToolbarHidden
|
||||
{
|
||||
let bottomToolbar = UIToolbar(frame: CGRect.zero)
|
||||
bottomToolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
bottomToolbar.barStyle = navigationController.toolbar.barStyle
|
||||
transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar)
|
||||
|
||||
bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true
|
||||
bottomToolbar.bottomAnchor.constraint(equalTo: navigationController.toolbar.bottomAnchor, constant: padding).isActive = true
|
||||
bottomToolbar.leftAnchor.constraint(equalTo: navigationController.toolbar.leftAnchor, constant: -padding).isActive = true
|
||||
bottomToolbar.rightAnchor.constraint(equalTo: navigationController.toolbar.rightAnchor, constant: padding).isActive = true
|
||||
|
||||
bottomPaddingToolbar = bottomToolbar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.animator.addAnimations {
|
||||
snapshotView.alpha = 0.0
|
||||
transitionContext.destinationView.transform = CGAffineTransform.identity
|
||||
@ -163,3 +166,4 @@ extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning
|
||||
self.animator.startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,9 +10,9 @@ import UIKit
|
||||
|
||||
class InitialGamesStoryboardSegue: UIStoryboardSegue
|
||||
{
|
||||
fileprivate let animator: UIViewPropertyAnimator
|
||||
private let animator: UIViewPropertyAnimator
|
||||
|
||||
fileprivate var isPresenting: Bool = true
|
||||
private var isPresenting: Bool = true
|
||||
|
||||
override init(identifier: String?, source: UIViewController, destination: UIViewController)
|
||||
{
|
||||
|
||||
39
Delta/Importing/Import Options/ClipboardImportOption.swift
Normal file
39
Delta/Importing/Import Options/ClipboardImportOption.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// ClipboardImportOption.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 5/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
|
||||
import Roxas
|
||||
|
||||
struct ClipboardImportOption: ImportOption
|
||||
{
|
||||
let title = NSLocalizedString("Clipboard", comment: "")
|
||||
let image: UIImage? = nil
|
||||
|
||||
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
guard UIPasteboard.general.hasImages else { return completionHandler([]) }
|
||||
|
||||
guard let data = UIPasteboard.general.data(forPasteboardType: kUTTypeImage as String) else { return completionHandler([]) }
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryURL = FileManager.default.uniqueTemporaryURL()
|
||||
try data.write(to: temporaryURL, options: .atomic)
|
||||
|
||||
completionHandler([temporaryURL])
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
|
||||
completionHandler([])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
//
|
||||
// GamesDatabaseImportOption.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 5/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
struct GamesDatabaseImportOption: ImportOption
|
||||
{
|
||||
let title = NSLocalizedString("Games Database", comment: "")
|
||||
let image: UIImage? = nil
|
||||
|
||||
private let presentingViewController: UIViewController
|
||||
|
||||
init(presentingViewController: UIViewController)
|
||||
{
|
||||
self.presentingViewController = presentingViewController
|
||||
}
|
||||
|
||||
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
let storyboard = UIStoryboard(name: "GamesDatabase", bundle: nil)
|
||||
let navigationController = (storyboard.instantiateInitialViewController() as! UINavigationController)
|
||||
|
||||
let gamesDatabaseBrowserViewController = navigationController.topViewController as! GamesDatabaseBrowserViewController
|
||||
gamesDatabaseBrowserViewController.selectionHandler = { (metadata) in
|
||||
if let artworkURL = metadata.artworkURL
|
||||
{
|
||||
completionHandler([artworkURL])
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
self.presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
//
|
||||
// PhotoLibraryImportOption.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 5/2/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
|
||||
class PhotoLibraryImportOption: NSObject, ImportOption
|
||||
{
|
||||
let title = NSLocalizedString("Photo Library", comment: "")
|
||||
let image: UIImage? = nil
|
||||
|
||||
private let presentingViewController: UIViewController
|
||||
private var completionHandler: ((Set<URL>?) -> Void)?
|
||||
|
||||
init(presentingViewController: UIViewController)
|
||||
{
|
||||
self.presentingViewController = presentingViewController
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
self.completionHandler = completionHandler
|
||||
|
||||
let imagePickerController = UIImagePickerController()
|
||||
imagePickerController.delegate = self
|
||||
imagePickerController.sourceType = .photoLibrary
|
||||
imagePickerController.mediaTypes = [kUTTypeImage as String]
|
||||
imagePickerController.view.backgroundColor = .white
|
||||
self.presentingViewController.present(imagePickerController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoLibraryImportOption: UIImagePickerControllerDelegate, UINavigationControllerDelegate
|
||||
{
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
|
||||
{
|
||||
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage, let data = UIImageJPEGRepresentation(image, 0.85) else {
|
||||
self.completionHandler?([])
|
||||
return
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryURL = FileManager.default.uniqueTemporaryURL()
|
||||
try data.write(to: temporaryURL, options: .atomic)
|
||||
|
||||
self.completionHandler?([temporaryURL])
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.completionHandler?([])
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Delta/Importing/Import Options/iTunesImportOption.swift
Normal file
73
Delta/Importing/Import Options/iTunesImportOption.swift
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// iTunesImportOption.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 5/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import DeltaCore
|
||||
|
||||
struct iTunesImportOption: ImportOption
|
||||
{
|
||||
let title = NSLocalizedString("iTunes", comment: "")
|
||||
let image: UIImage? = nil
|
||||
|
||||
private let presentingViewController: UIViewController
|
||||
|
||||
init(presentingViewController: UIViewController)
|
||||
{
|
||||
self.presentingViewController = presentingViewController
|
||||
}
|
||||
|
||||
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Import from iTunes?", comment: ""), message: NSLocalizedString("Delta will import the games and controller skins copied over via iTunes.", comment: ""), preferredStyle: .alert)
|
||||
|
||||
let importAction = UIAlertAction(title: NSLocalizedString("Import", comment: ""), style: .default) { action in
|
||||
|
||||
var importedURLs = Set<URL>()
|
||||
|
||||
let documentsDirectoryURL = DatabaseManager.defaultDirectoryURL().deletingLastPathComponent()
|
||||
|
||||
do
|
||||
{
|
||||
let contents = try FileManager.default.contentsOfDirectory(at: documentsDirectoryURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
|
||||
|
||||
let itemURLs = contents.filter { GameType(fileExtension: $0.pathExtension) != nil || $0.pathExtension.lowercased() == "zip" || $0.pathExtension.lowercased() == "deltaskin" }
|
||||
|
||||
for url in itemURLs
|
||||
{
|
||||
let destinationURL = FileManager.default.uniqueTemporaryURL().appendingPathExtension(url.pathExtension)
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.moveItem(at: url, to: destinationURL)
|
||||
importedURLs.insert(destinationURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Error importing file at URL", url, error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
completionHandler(importedURLs)
|
||||
}
|
||||
alertController.addAction(importAction)
|
||||
|
||||
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { action in
|
||||
completionHandler(nil)
|
||||
}
|
||||
alertController.addAction(cancelAction)
|
||||
|
||||
self.presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
250
Delta/Importing/ImportController.swift
Normal file
250
Delta/Importing/ImportController.swift
Normal file
@ -0,0 +1,250 @@
|
||||
//
|
||||
// ImportController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 10/10/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
import ObjectiveC
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
protocol ImportControllerDelegate
|
||||
{
|
||||
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
|
||||
|
||||
/** Optional **/
|
||||
func importControllerDidCancel(_ importController: ImportController)
|
||||
}
|
||||
|
||||
extension ImportControllerDelegate
|
||||
{
|
||||
func importControllerDidCancel(_ importController: ImportController)
|
||||
{
|
||||
// Empty Implementation
|
||||
}
|
||||
}
|
||||
|
||||
class ImportController: NSObject
|
||||
{
|
||||
let documentTypes: Set<String>
|
||||
|
||||
var delegate: ImportControllerDelegate?
|
||||
var importOptions: [ImportOption]?
|
||||
|
||||
private weak var presentingViewController: UIViewController?
|
||||
|
||||
// Store presentedViewController separately, since when we dismiss we don't know if it has already been dismissed.
|
||||
// Calling dismiss on presentingViewController in that case would dismiss presentingViewController, which is bad.
|
||||
private weak var presentedViewController: UIViewController?
|
||||
|
||||
private let importQueue: OperationQueue
|
||||
private let fileCoordinator: NSFileCoordinator
|
||||
|
||||
init(documentTypes: Set<String>)
|
||||
{
|
||||
self.documentTypes = documentTypes
|
||||
|
||||
let dispatchQueue = DispatchQueue(label: "com.rileytestut.Delta.ImportController.dispatchQueue", qos: .userInitiated, attributes: .concurrent)
|
||||
|
||||
self.importQueue = OperationQueue()
|
||||
self.importQueue.name = "com.rileytestut.Delta.ImportController.importQueue"
|
||||
self.importQueue.underlyingQueue = dispatchQueue
|
||||
|
||||
self.fileCoordinator = NSFileCoordinator(filePresenter: nil)
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?)
|
||||
{
|
||||
self.presentingViewController = presentingViewController
|
||||
|
||||
#if IMPACTOR
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction.cancel)
|
||||
|
||||
if let importOptions = self.importOptions
|
||||
{
|
||||
for importOption in importOptions
|
||||
{
|
||||
alertController.add(importOption) { [unowned self] (urls) in
|
||||
self.finish(with: urls, errors: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.presentedViewController = alertController
|
||||
self.presentingViewController?.present(alertController, animated: true, completion: nil)
|
||||
|
||||
#else
|
||||
|
||||
let documentMenuController = UIDocumentMenuViewController(documentTypes: Array(self.documentTypes), in: .import)
|
||||
documentMenuController.delegate = self
|
||||
|
||||
if let reversedImportOptions = self.importOptions?.reversed()
|
||||
{
|
||||
for importOption in reversedImportOptions
|
||||
{
|
||||
documentMenuController.add(importOption, order: .first) { [unowned self] (urls) in
|
||||
self.finish(with: urls, errors: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.presentedViewController = documentMenuController
|
||||
self.presentingViewController?.present(documentMenuController, animated: true, completion: nil)
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc private func cancel()
|
||||
{
|
||||
self.finish(with: nil, errors: [])
|
||||
}
|
||||
|
||||
private func finish(with urls: Set<URL>?, errors: [Error])
|
||||
{
|
||||
if let urls = urls
|
||||
{
|
||||
self.delegate?.importController(self, didImportItemsAt: urls, errors: errors)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.delegate?.importControllerDidCancel(self)
|
||||
}
|
||||
|
||||
self.presentedViewController?.dismiss(animated: true)
|
||||
|
||||
self.presentingViewController?.importController = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ImportController: UIDocumentMenuDelegate
|
||||
{
|
||||
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController)
|
||||
{
|
||||
if #available(iOS 11.0, *)
|
||||
{
|
||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(ImportController.cancel))
|
||||
|
||||
let documentBrowserViewController = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes: Array(self.documentTypes))
|
||||
documentBrowserViewController.delegate = self
|
||||
documentBrowserViewController.browserUserInterfaceStyle = .dark
|
||||
documentBrowserViewController.allowsPickingMultipleItems = true
|
||||
documentBrowserViewController.allowsDocumentCreation = false
|
||||
documentBrowserViewController.additionalTrailingNavigationBarButtonItems = [cancelButton]
|
||||
|
||||
self.presentedViewController = documentBrowserViewController
|
||||
self.presentingViewController?.present(documentBrowserViewController, animated: true, completion: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
documentPicker.delegate = self
|
||||
|
||||
self.presentedViewController = documentPicker
|
||||
self.presentingViewController?.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController)
|
||||
{
|
||||
self.finish(with: nil, errors: [])
|
||||
}
|
||||
}
|
||||
|
||||
extension ImportController: UIDocumentPickerDelegate
|
||||
{
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
|
||||
{
|
||||
self.finish(with: [url], errors: [])
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
|
||||
{
|
||||
self.finish(with: Set(urls), errors: [])
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
|
||||
{
|
||||
self.finish(with: nil, errors: [])
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
extension ImportController: UIDocumentBrowserViewControllerDelegate
|
||||
{
|
||||
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL])
|
||||
{
|
||||
var coordinatedURLs = Set<URL>()
|
||||
var errors = [Error]()
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
for url in documentURLs
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
let intent = NSFileAccessIntent.readingIntent(with: url)
|
||||
self.fileCoordinator.coordinate(with: [intent], queue: self.importQueue) { (error) in
|
||||
if let error = error
|
||||
{
|
||||
errors.append(error)
|
||||
}
|
||||
else
|
||||
{
|
||||
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
|
||||
do
|
||||
{
|
||||
// Always access intent.url, as the system may have updated it when requesting access.
|
||||
try FileManager.default.copyItem(at: intent.url, to: temporaryURL)
|
||||
|
||||
coordinatedURLs.insert(temporaryURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: self.importQueue.underlyingQueue!) {
|
||||
self.finish(with: coordinatedURLs, errors: errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var ImportControllerKey: UInt8 = 0
|
||||
|
||||
extension UIViewController
|
||||
{
|
||||
fileprivate(set) var importController: ImportController?
|
||||
{
|
||||
set
|
||||
{
|
||||
objc_setAssociatedObject(self, &ImportControllerKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
get
|
||||
{
|
||||
return objc_getAssociatedObject(self, &ImportControllerKey) as? ImportController
|
||||
}
|
||||
}
|
||||
|
||||
func present(_ importController: ImportController, animated: Bool, completion: (() -> Void)?)
|
||||
{
|
||||
self.importController = importController
|
||||
|
||||
importController.presentImportController(from: self, animated: animated, completionHandler: completion)
|
||||
}
|
||||
}
|
||||
40
Delta/Importing/ImportOption.swift
Normal file
40
Delta/Importing/ImportOption.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// ImportOption.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 5/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension UIDocumentMenuViewController
|
||||
{
|
||||
func add(_ importOption: ImportOption, order: UIDocumentMenuOrder, completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
self.addOption(withTitle: importOption.title, image: importOption.image, order: order) {
|
||||
importOption.import(withCompletionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIAlertController
|
||||
{
|
||||
func add(_ importOption: ImportOption, completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
{
|
||||
let action = UIAlertAction(title: importOption.title, style: .default, handler: { action in
|
||||
importOption.import(withCompletionHandler: completionHandler)
|
||||
})
|
||||
self.addAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
protocol ImportOption
|
||||
{
|
||||
var title: String { get }
|
||||
var image: UIImage? { get }
|
||||
|
||||
func `import`(withCompletionHandler completionHandler: @escaping (Set<URL>?) -> Void)
|
||||
}
|
||||
@ -10,10 +10,10 @@ import UIKit
|
||||
|
||||
class LaunchViewController: UIViewController
|
||||
{
|
||||
@IBOutlet fileprivate var gameViewContainerView: UIView!
|
||||
fileprivate var gameViewController: GameViewController!
|
||||
@IBOutlet private var gameViewContainerView: UIView!
|
||||
private var gameViewController: GameViewController!
|
||||
|
||||
fileprivate var presentedGameViewController: Bool = false
|
||||
private var presentedGameViewController: Bool = false
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return self.gameViewController?.preferredStatusBarStyle ?? .lightContent
|
||||
|
||||
@ -13,7 +13,10 @@ import DeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
private let CheatPrefixAttribute = "prefix"
|
||||
private extension NSAttributedStringKey
|
||||
{
|
||||
static let cheatPrefix = NSAttributedStringKey("CheatPrefix")
|
||||
}
|
||||
|
||||
class CheatTextView: UITextView
|
||||
{
|
||||
@ -23,7 +26,7 @@ class CheatTextView: UITextView
|
||||
}
|
||||
}
|
||||
|
||||
@NSCopying fileprivate var attributedFormat: NSAttributedString?
|
||||
@NSCopying private var attributedFormat: NSAttributedString?
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
@ -44,9 +47,9 @@ extension CheatTextView
|
||||
|
||||
if let format = self.cheatFormat, let font = self.font
|
||||
{
|
||||
let characterWidth = ("A" as NSString).size(attributes: [NSFontAttributeName: font]).width
|
||||
let characterWidth = ("A" as NSString).size(withAttributes: [.font: font]).width
|
||||
|
||||
let width = characterWidth * CGFloat(format.format.characters.count)
|
||||
let width = characterWidth * CGFloat(format.format.count)
|
||||
self.textContainer.size = CGSize(width: width, height: 0)
|
||||
}
|
||||
}
|
||||
@ -79,7 +82,7 @@ private extension CheatTextView
|
||||
|
||||
if let prefixString = prefixString, prefixString.length > 0
|
||||
{
|
||||
attributedString.addAttribute(CheatPrefixAttribute, value: prefixString, range: NSRange(location: 0, length: 1))
|
||||
attributedString.addAttribute(.cheatPrefix, value: prefixString, range: NSRange(location: 0, length: 1))
|
||||
}
|
||||
|
||||
attributedFormat.append(attributedString)
|
||||
@ -105,7 +108,7 @@ private extension CheatTextView
|
||||
|
||||
extension CheatTextView: NSLayoutManagerDelegate
|
||||
{
|
||||
func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSGlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: UIFont, forGlyphRange glyphRange: NSRange) -> Int
|
||||
func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: UIFont, forGlyphRange glyphRange: NSRange) -> Int
|
||||
{
|
||||
// Returning 0 = let the layoutManager do the normal logic
|
||||
guard let attributedFormat = self.attributedFormat else { return 0 }
|
||||
@ -118,7 +121,7 @@ extension CheatTextView: NSLayoutManagerDelegate
|
||||
|
||||
// Allocate our replacement buffers
|
||||
let glyphBuffer = UnsafeMutablePointer<CGGlyph>.allocate(capacity: bufferSize)
|
||||
let propertyBuffer = UnsafeMutablePointer<NSGlyphProperty>.allocate(capacity: bufferSize)
|
||||
let propertyBuffer = UnsafeMutablePointer<NSLayoutManager.GlyphProperty>.allocate(capacity: bufferSize)
|
||||
let characterBuffer = UnsafeMutablePointer<Int>.allocate(capacity: bufferSize)
|
||||
|
||||
var offset = 0
|
||||
@ -128,10 +131,10 @@ extension CheatTextView: NSLayoutManagerDelegate
|
||||
// The index the actual character maps to in the cheat format
|
||||
let characterIndex = charIndexes[i] % attributedFormat.length
|
||||
|
||||
if let prefix = attributedFormat.attributes(at: characterIndex, effectiveRange: nil)[CheatPrefixAttribute] as? String
|
||||
if let prefix = attributedFormat.attributes(at: characterIndex, effectiveRange: nil)[.cheatPrefix] as? String
|
||||
{
|
||||
// If there is a prefix string, we insert the glyphs (and associated properties/character indexes) first
|
||||
let prefixCount = prefix.characters.count
|
||||
let prefixCount = prefix.count
|
||||
|
||||
for j in 0 ..< prefixCount
|
||||
{
|
||||
|
||||
@ -51,7 +51,7 @@ struct CheatValidator
|
||||
// Remove newline characters (code should already be formatted)
|
||||
let sanitizedCode = (cheat.code as NSString).replacingOccurrences(of: "\n", with: "")
|
||||
|
||||
if sanitizedCode.characters.count % self.format.format.characters.count != 0
|
||||
if sanitizedCode.count % self.format.format.count != 0
|
||||
{
|
||||
throw Error.invalidCode
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ class CheatsViewController: UITableViewController
|
||||
|
||||
weak var delegate: CheatsViewControllerDelegate?
|
||||
|
||||
fileprivate let dataSource = RSTFetchedResultsTableViewDataSource<Cheat>(fetchedResultsController: NSFetchedResultsController())
|
||||
private let dataSource = RSTFetchedResultsTableViewDataSource<Cheat>(fetchedResultsController: NSFetchedResultsController())
|
||||
}
|
||||
|
||||
extension CheatsViewController
|
||||
|
||||
@ -33,7 +33,7 @@ class EditCheatViewController: UITableViewController
|
||||
var game: Game! {
|
||||
didSet {
|
||||
let deltaCore = Delta.core(for: self.game.type)!
|
||||
self.supportedCheatFormats = deltaCore.emulatorConfiguration.supportedCheatFormats
|
||||
self.supportedCheatFormats = deltaCore.supportedCheatFormats.sorted() { $0.name < $1.name }
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,19 +43,19 @@ class EditCheatViewController: UITableViewController
|
||||
|
||||
var isPreviewing = false
|
||||
|
||||
fileprivate var supportedCheatFormats: [CheatFormat]!
|
||||
private var supportedCheatFormats: [CheatFormat]!
|
||||
|
||||
fileprivate var selectedCheatFormat: CheatFormat {
|
||||
private var selectedCheatFormat: CheatFormat {
|
||||
let cheatFormat = self.supportedCheatFormats[self.typeSegmentedControl.selectedSegmentIndex]
|
||||
return cheatFormat
|
||||
}
|
||||
|
||||
fileprivate var mutableCheat: Cheat!
|
||||
fileprivate var managedObjectContext = DatabaseManager.shared.newBackgroundContext()
|
||||
private var mutableCheat: Cheat!
|
||||
private var managedObjectContext = DatabaseManager.shared.newBackgroundContext()
|
||||
|
||||
@IBOutlet fileprivate var nameTextField: UITextField!
|
||||
@IBOutlet fileprivate var typeSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet fileprivate var codeTextView: CheatTextView!
|
||||
@IBOutlet private var nameTextField: UITextField!
|
||||
@IBOutlet private var typeSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet private var codeTextView: CheatTextView!
|
||||
|
||||
override var previewActionItems: [UIPreviewActionItem]
|
||||
{
|
||||
@ -132,7 +132,7 @@ extension EditCheatViewController
|
||||
|
||||
// Update UI
|
||||
|
||||
if name.characters.count == 0
|
||||
if name.count == 0
|
||||
{
|
||||
self.title = NSLocalizedString("Cheat", comment: "")
|
||||
}
|
||||
@ -224,7 +224,7 @@ private extension EditCheatViewController
|
||||
@IBAction func updateCheatName(_ sender: UITextField)
|
||||
{
|
||||
var title = sender.text ?? ""
|
||||
if title.characters.count == 0
|
||||
if title.count == 0
|
||||
{
|
||||
title = NSLocalizedString("Cheat", comment: "")
|
||||
}
|
||||
@ -325,7 +325,7 @@ private extension EditCheatViewController
|
||||
sender.resignFirstResponder()
|
||||
}
|
||||
|
||||
func presentErrorAlert(title: String, message: String, handler: ((Void) -> Void)?)
|
||||
func presentErrorAlert(title: String, message: String, handler: (() -> Void)?)
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
@ -382,7 +382,9 @@ extension EditCheatViewController: UITextViewDelegate
|
||||
|
||||
// We need to manually add back the attributes when manually modifying the underlying text storage
|
||||
// Otherwise, pasting text into an empty text view will result in the wrong font being used
|
||||
let attributedString = NSAttributedString(string: sanitizedText, attributes: textView.typingAttributes)
|
||||
let attributes = Dictionary(uniqueKeysWithValues: textView.typingAttributes.map { (key, value) in (NSAttributedStringKey(key), value) })
|
||||
|
||||
let attributedString = NSAttributedString(string: sanitizedText, attributes: attributes)
|
||||
textView.textStorage.replaceCharacters(in: range, with: attributedString)
|
||||
|
||||
// We must add attributedString.length, not range.length, in case the attributed string's length differs
|
||||
|
||||
176
Delta/Pause Menu/GridMenuViewController.swift
Normal file
176
Delta/Pause Menu/GridMenuViewController.swift
Normal file
@ -0,0 +1,176 @@
|
||||
//
|
||||
// GridMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/21/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class GridMenuViewController: UICollectionViewController
|
||||
{
|
||||
var items: [MenuItem] {
|
||||
get { return self.dataSource.items }
|
||||
set { self.dataSource.items = newValue; self.updateItems() }
|
||||
}
|
||||
|
||||
var isVibrancyEnabled = true
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
set { }
|
||||
get { return self.collectionView?.contentSize ?? CGSize.zero }
|
||||
}
|
||||
|
||||
private let dataSource = RSTArrayCollectionViewDataSource<MenuItem>(items: [])
|
||||
|
||||
private var prototypeCell = GridCollectionViewCell()
|
||||
private var previousIndexPath: IndexPath? = nil
|
||||
|
||||
private var registeredKVOObservers = Set<NSKeyValueObservation>()
|
||||
|
||||
init()
|
||||
{
|
||||
let collectionViewLayout = GridCollectionViewLayout()
|
||||
collectionViewLayout.itemSize = CGSize(width: 60, height: 80)
|
||||
collectionViewLayout.minimumLineSpacing = 20
|
||||
collectionViewLayout.minimumInteritemSpacing = 10
|
||||
|
||||
super.init(collectionViewLayout: collectionViewLayout)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
deinit
|
||||
{
|
||||
// Crashes on iOS 10 if not explicitly invalidated.
|
||||
self.registeredKVOObservers.forEach { $0.invalidate() }
|
||||
}
|
||||
}
|
||||
|
||||
extension GridMenuViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.collectionView?.register(GridCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
|
||||
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
collectionViewLayout.itemWidth = 90
|
||||
collectionViewLayout.usesEqualHorizontalSpacingDistributionForSingleRow = true
|
||||
|
||||
// Manually update prototype cell properties
|
||||
self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth).isActive = true
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let indexPath = self.previousIndexPath
|
||||
{
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension GridMenuViewController
|
||||
{
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
|
||||
cell.maximumImageSize = CGSize(width: 60, height: 60)
|
||||
|
||||
cell.imageView.image = pauseItem.image
|
||||
cell.imageView.contentMode = .center
|
||||
cell.imageView.layer.borderWidth = 2
|
||||
cell.imageView.layer.borderColor = self.view.tintColor.cgColor
|
||||
cell.imageView.layer.cornerRadius = 10
|
||||
|
||||
cell.textLabel.text = pauseItem.text
|
||||
cell.textLabel.textColor = self.view.tintColor
|
||||
|
||||
if pauseItem.isSelected
|
||||
{
|
||||
cell.imageView.tintColor = UIColor.black
|
||||
cell.imageView.backgroundColor = self.view.tintColor
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.tintColor = self.view.tintColor
|
||||
cell.imageView.backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
cell.isImageViewVibrancyEnabled = self.isVibrancyEnabled
|
||||
cell.isTextLabelVibrancyEnabled = self.isVibrancyEnabled
|
||||
}
|
||||
|
||||
func updateItems()
|
||||
{
|
||||
self.registeredKVOObservers.removeAll()
|
||||
|
||||
for (index, item) in self.items.enumerated()
|
||||
{
|
||||
let observer = item.observe(\.isSelected, changeHandler: { [unowned self] (item, change) in
|
||||
let indexPath = IndexPath(item: index, section: 0)
|
||||
|
||||
if let cell = self.collectionView?.cellForItem(at: indexPath) as? GridCollectionViewCell
|
||||
{
|
||||
self.configure(cell, for: indexPath)
|
||||
}
|
||||
})
|
||||
|
||||
self.registeredKVOObservers.insert(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GridMenuViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
self.configure(self.prototypeCell, for: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
extension GridMenuViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.previousIndexPath = indexPath
|
||||
|
||||
let item = self.items[indexPath.item]
|
||||
item.isSelected = !item.isSelected
|
||||
item.action(item)
|
||||
}
|
||||
}
|
||||
|
||||
35
Delta/Pause Menu/MenuItem.swift
Normal file
35
Delta/Pause Menu/MenuItem.swift
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// MenuItem.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/30/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Must be class for use with Objective-C generics :(
|
||||
class MenuItem: NSObject
|
||||
{
|
||||
var text: String
|
||||
var image: UIImage?
|
||||
var action: ((MenuItem) -> Void)
|
||||
|
||||
@objc dynamic var isSelected = false
|
||||
|
||||
init(text: String, image: UIImage?, action: @escaping ((MenuItem) -> Void))
|
||||
{
|
||||
self.image = image
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
extension MenuItem
|
||||
{
|
||||
override func isEqual(_ object: Any?) -> Bool
|
||||
{
|
||||
guard let item = object as? MenuItem else { return false }
|
||||
return item.image == self.image && item.text == self.text
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// PauseItem.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/30/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// Must be class for use with Objective-C generics :(
|
||||
class PauseItem: Equatable
|
||||
{
|
||||
var image: UIImage
|
||||
var text: String
|
||||
var action: ((PauseItem) -> Void)
|
||||
|
||||
var selected = false
|
||||
|
||||
init(image: UIImage, text: String, action: @escaping ((PauseItem) -> Void))
|
||||
{
|
||||
self.image = image
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
func ==(lhs: PauseItem, rhs: PauseItem) -> Bool
|
||||
{
|
||||
return (lhs.image == rhs.image) && (lhs.text == rhs.text)
|
||||
}
|
||||
@ -1,146 +0,0 @@
|
||||
//
|
||||
// PauseMenuViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/21/15.
|
||||
// Copyright © 2015 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class PauseMenuViewController: UICollectionViewController
|
||||
{
|
||||
var items = [PauseItem]() {
|
||||
didSet
|
||||
{
|
||||
guard oldValue != self.items else { return }
|
||||
|
||||
if self.items.count > 8
|
||||
{
|
||||
fatalError("PauseViewController only supports up to 8 items (for my sanity when laying out on a landscape iPhone 4s")
|
||||
}
|
||||
|
||||
self.dataSource.items = self.items
|
||||
}
|
||||
}
|
||||
|
||||
override var preferredContentSize: CGSize {
|
||||
set { }
|
||||
get { return self.collectionView?.contentSize ?? CGSize.zero }
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTArrayCollectionViewDataSource<PauseItem>(items: [])
|
||||
|
||||
fileprivate var prototypeCell = GridCollectionViewCell()
|
||||
fileprivate var previousIndexPath: IndexPath? = nil
|
||||
}
|
||||
|
||||
extension PauseMenuViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
|
||||
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
collectionViewLayout.itemWidth = 90
|
||||
collectionViewLayout.usesEqualHorizontalSpacingDistributionForSingleRow = true
|
||||
|
||||
// Manually update prototype cell properties
|
||||
self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth).isActive = true
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let indexPath = self.previousIndexPath
|
||||
{
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PauseMenuViewController
|
||||
{
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let pauseItem = self.items[(indexPath as NSIndexPath).item]
|
||||
|
||||
cell.maximumImageSize = CGSize(width: 60, height: 60)
|
||||
|
||||
cell.imageView.image = pauseItem.image
|
||||
cell.imageView.contentMode = .center
|
||||
cell.imageView.layer.borderWidth = 2
|
||||
cell.imageView.layer.borderColor = UIColor.white.cgColor
|
||||
cell.imageView.layer.cornerRadius = 10
|
||||
|
||||
cell.textLabel.text = pauseItem.text
|
||||
cell.textLabel.textColor = UIColor.white
|
||||
|
||||
if pauseItem.selected
|
||||
{
|
||||
cell.imageView.tintColor = UIColor.black
|
||||
cell.imageView.backgroundColor = UIColor.white
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.tintColor = UIColor.white
|
||||
cell.imageView.backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
cell.isTextLabelVibrancyEnabled = true
|
||||
}
|
||||
|
||||
func toggleSelectedStateForPauseItemAtIndexPath(_ indexPath: IndexPath)
|
||||
{
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
pauseItem.selected = !pauseItem.selected
|
||||
|
||||
let cell = self.collectionView!.cellForItem(at: indexPath) as! GridCollectionViewCell
|
||||
self.configure(cell, for: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
extension PauseMenuViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
self.configure(self.prototypeCell, for: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
extension PauseMenuViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
self.previousIndexPath = indexPath
|
||||
|
||||
self.toggleSelectedStateForPauseItemAtIndexPath(indexPath)
|
||||
|
||||
let pauseItem = self.items[indexPath.item]
|
||||
pauseItem.action(pauseItem)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,16 +18,16 @@ class PauseViewController: UIViewController, PauseInfoProviding
|
||||
}
|
||||
}
|
||||
|
||||
var pauseItems: [PauseItem] {
|
||||
var pauseItems: [MenuItem] {
|
||||
return [self.saveStateItem, self.loadStateItem, self.cheatCodesItem, self.fastForwardItem, self.sustainButtonsItem].flatMap { $0 }
|
||||
}
|
||||
|
||||
/// Pause Items
|
||||
var saveStateItem: PauseItem?
|
||||
var loadStateItem: PauseItem?
|
||||
var cheatCodesItem: PauseItem?
|
||||
var fastForwardItem: PauseItem?
|
||||
var sustainButtonsItem: PauseItem?
|
||||
var saveStateItem: MenuItem?
|
||||
var loadStateItem: MenuItem?
|
||||
var cheatCodesItem: MenuItem?
|
||||
var fastForwardItem: MenuItem?
|
||||
var sustainButtonsItem: MenuItem?
|
||||
|
||||
/// PauseInfoProviding
|
||||
var pauseText: String?
|
||||
@ -38,9 +38,9 @@ class PauseViewController: UIViewController, PauseInfoProviding
|
||||
/// Save States
|
||||
weak var saveStatesViewControllerDelegate: SaveStatesViewControllerDelegate?
|
||||
|
||||
fileprivate var saveStatesViewControllerMode = SaveStatesViewController.Mode.loading
|
||||
private var saveStatesViewControllerMode = SaveStatesViewController.Mode.loading
|
||||
|
||||
fileprivate var pauseNavigationController: UINavigationController!
|
||||
private var pauseNavigationController: UINavigationController!
|
||||
|
||||
/// UIViewController
|
||||
override var preferredContentSize: CGSize {
|
||||
@ -85,8 +85,8 @@ extension PauseViewController
|
||||
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple
|
||||
self.pauseNavigationController.view.backgroundColor = UIColor.clear
|
||||
|
||||
let pauseMenuViewController = self.pauseNavigationController.topViewController as! PauseMenuViewController
|
||||
pauseMenuViewController.items = self.pauseItems
|
||||
let gridMenuViewController = self.pauseNavigationController.topViewController as! GridMenuViewController
|
||||
gridMenuViewController.items = self.pauseItems
|
||||
|
||||
case "saveStates":
|
||||
let saveStatesViewController = segue.destination as! SaveStatesViewController
|
||||
@ -135,21 +135,21 @@ private extension PauseViewController
|
||||
|
||||
guard self.emulatorCore != nil else { return }
|
||||
|
||||
self.saveStateItem = PauseItem(image: #imageLiteral(resourceName: "SaveSaveState"), text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in
|
||||
self.saveStateItem = MenuItem(text: NSLocalizedString("Save State", comment: ""), image: #imageLiteral(resourceName: "SaveSaveState"), action: { [unowned self] _ in
|
||||
self.saveStatesViewControllerMode = .saving
|
||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
||||
})
|
||||
|
||||
self.loadStateItem = PauseItem(image: #imageLiteral(resourceName: "LoadSaveState"), text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in
|
||||
self.loadStateItem = MenuItem(text: NSLocalizedString("Load State", comment: ""), image: #imageLiteral(resourceName: "LoadSaveState"), action: { [unowned self] _ in
|
||||
self.saveStatesViewControllerMode = .loading
|
||||
self.performSegue(withIdentifier: "saveStates", sender: self)
|
||||
})
|
||||
|
||||
self.cheatCodesItem = PauseItem(image: #imageLiteral(resourceName: "CheatCodes"), text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in
|
||||
self.cheatCodesItem = MenuItem(text: NSLocalizedString("Cheat Codes", comment: ""), image: #imageLiteral(resourceName: "CheatCodes"), action: { [unowned self] _ in
|
||||
self.performSegue(withIdentifier: "cheats", sender: self)
|
||||
})
|
||||
|
||||
self.fastForwardItem = PauseItem(image: #imageLiteral(resourceName: "FastForward"), text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in })
|
||||
self.sustainButtonsItem = PauseItem(image: #imageLiteral(resourceName: "SustainButtons"), text: NSLocalizedString("Sustain Buttons", comment: ""), action: { _ in })
|
||||
self.fastForwardItem = MenuItem(text: NSLocalizedString("Fast Forward", comment: ""), image: #imageLiteral(resourceName: "FastForward"), action: { _ in })
|
||||
self.sustainButtonsItem = MenuItem(text: NSLocalizedString("Sustain Buttons", comment: ""), image: #imageLiteral(resourceName: "SustainButtons"), action: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,13 +17,17 @@ protocol PauseInfoProviding
|
||||
|
||||
class PausePresentationController: UIPresentationController
|
||||
{
|
||||
let presentationAnimator: UIViewPropertyAnimator
|
||||
|
||||
private let blurringView: UIVisualEffectView
|
||||
private let vibrancyView: UIVisualEffectView
|
||||
|
||||
private var contentView: UIView!
|
||||
@IBOutlet private weak var pauseLabel: UILabel!
|
||||
@IBOutlet private weak var pauseIconImageView: UIImageView!
|
||||
@IBOutlet private weak var stackView: UIStackView!
|
||||
|
||||
// Must not be weak, or else may result in crash when deallocating.
|
||||
@IBOutlet private var pauseLabel: UILabel!
|
||||
@IBOutlet private var pauseIconImageView: UIImageView!
|
||||
@IBOutlet private var stackView: UIStackView!
|
||||
|
||||
override var frameOfPresentedViewInContainerView: CGRect
|
||||
{
|
||||
@ -45,8 +49,10 @@ class PausePresentationController: UIPresentationController
|
||||
return frame
|
||||
}
|
||||
|
||||
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?)
|
||||
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, presentationAnimator: UIViewPropertyAnimator)
|
||||
{
|
||||
self.presentationAnimator = presentationAnimator
|
||||
|
||||
self.blurringView = UIVisualEffectView(effect: nil)
|
||||
self.vibrancyView = UIVisualEffectView(effect: nil)
|
||||
|
||||
@ -83,16 +89,18 @@ class PausePresentationController: UIPresentationController
|
||||
self.contentView.alpha = 0.0
|
||||
self.vibrancyView.contentView.addSubview(self.contentView)
|
||||
|
||||
self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { context in
|
||||
|
||||
self.presentationAnimator.addAnimations {
|
||||
let blurEffect = UIBlurEffect(style: .dark)
|
||||
|
||||
self.blurringView.effect = blurEffect
|
||||
self.vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect)
|
||||
|
||||
self.contentView.alpha = 1.0
|
||||
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
// I have absolutely no clue why animating with transition coordinator results in no animation on iOS 11.
|
||||
// Spent far too long trying to fix it, so just use the presentation animator.
|
||||
// self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { context in }, completion: nil)
|
||||
}
|
||||
|
||||
override func dismissalTransitionWillBegin()
|
||||
|
||||
@ -12,7 +12,7 @@ class SaveStatesCollectionHeaderView: UICollectionReusableView
|
||||
{
|
||||
let textLabel = UILabel()
|
||||
|
||||
fileprivate let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark)))
|
||||
private let vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .dark)))
|
||||
|
||||
var isTextLabelVibrancyEnabled = true {
|
||||
didSet {
|
||||
|
||||
@ -29,6 +29,7 @@ extension SaveStatesViewController
|
||||
enum Section: Int
|
||||
{
|
||||
case auto
|
||||
case quick
|
||||
case general
|
||||
case locked
|
||||
}
|
||||
@ -58,29 +59,30 @@ class SaveStatesViewController: UICollectionViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var vibrancyView: UIVisualEffectView!
|
||||
fileprivate var placeholderView: RSTPlaceholderView!
|
||||
private var vibrancyView: UIVisualEffectView!
|
||||
private var placeholderView: RSTPlaceholderView!
|
||||
|
||||
fileprivate var prototypeCell = GridCollectionViewCell()
|
||||
fileprivate var prototypeCellWidthConstraint: NSLayoutConstraint!
|
||||
fileprivate var prototypeHeader = SaveStatesCollectionHeaderView()
|
||||
private var prototypeCell = GridCollectionViewCell()
|
||||
private var prototypeCellWidthConstraint: NSLayoutConstraint!
|
||||
private var prototypeHeader = SaveStatesCollectionHeaderView()
|
||||
|
||||
fileprivate let dataSource = RSTFetchedResultsCollectionViewDataSource<SaveState>(fetchedResultsController: NSFetchedResultsController())
|
||||
private let dataSource: RSTFetchedResultsCollectionViewPrefetchingDataSource<SaveState, UIImage>
|
||||
|
||||
fileprivate let imageOperationQueue = RSTOperationQueue()
|
||||
fileprivate let imageCache = NSCache<NSURL, UIImage>()
|
||||
private var emulatorCoreSaveState: SaveStateProtocol?
|
||||
|
||||
fileprivate var emulatorCoreSaveState: SaveStateProtocol?
|
||||
|
||||
fileprivate let dateFormatter: DateFormatter
|
||||
private let dateFormatter: DateFormatter
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
self.dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<SaveState, UIImage>(fetchedResultsController: NSFetchedResultsController())
|
||||
|
||||
self.dateFormatter = DateFormatter()
|
||||
self.dateFormatter.timeStyle = .short
|
||||
self.dateFormatter.dateStyle = .short
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.prepareDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,21 +92,8 @@ extension SaveStatesViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.vibrancyView = UIVisualEffectView(effect: nil)
|
||||
|
||||
self.placeholderView = RSTPlaceholderView(frame: CGRect(x: 0, y: 0, width: self.vibrancyView.bounds.width, height: self.vibrancyView.bounds.height))
|
||||
self.placeholderView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("No Save States", comment: "")
|
||||
self.placeholderView.textLabel.textColor = UIColor.white
|
||||
self.placeholderView.detailTextLabel.textColor = UIColor.white
|
||||
self.vibrancyView.contentView.addSubview(self.placeholderView)
|
||||
|
||||
self.dataSource.proxy = self
|
||||
self.dataSource.placeholderView = self.vibrancyView
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
self.collectionView?.dataSource = self.dataSource
|
||||
self.collectionView?.prefetchDataSource = self.dataSource
|
||||
|
||||
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
|
||||
@ -118,11 +107,11 @@ extension SaveStatesViewController
|
||||
{
|
||||
case .saving:
|
||||
self.title = NSLocalizedString("Save State", comment: "")
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("You can create a new save state by pressing the + button in the top right.", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("You can create a new save state by pressing the + button in the top right.", comment: "")
|
||||
|
||||
case .loading:
|
||||
self.title = NSLocalizedString("Load State", comment: "")
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("You can create a new save state by pressing the Save State option in the pause menu.", comment: "")
|
||||
self.placeholderView.detailTextLabel.text = NSLocalizedString("You can create a new save state by pressing the Save State option in the pause menu.", comment: "")
|
||||
self.navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
|
||||
@ -156,10 +145,57 @@ extension SaveStatesViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension SaveStatesViewController
|
||||
{
|
||||
func prepareDataSource()
|
||||
{
|
||||
self.dataSource.proxy = self
|
||||
|
||||
self.vibrancyView = UIVisualEffectView(effect: nil)
|
||||
|
||||
self.placeholderView = RSTPlaceholderView(frame: CGRect(x: 0, y: 0, width: self.vibrancyView.bounds.width, height: self.vibrancyView.bounds.height))
|
||||
self.placeholderView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.placeholderView.textLabel.text = NSLocalizedString("No Save States", comment: "")
|
||||
self.placeholderView.textLabel.textColor = UIColor.white
|
||||
self.placeholderView.detailTextLabel.textColor = UIColor.white
|
||||
self.vibrancyView.contentView.addSubview(self.placeholderView)
|
||||
|
||||
self.dataSource.placeholderView = self.vibrancyView
|
||||
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! GridCollectionViewCell, for: indexPath)
|
||||
}
|
||||
|
||||
self.dataSource.prefetchHandler = { [unowned self] (saveState, indexPath, completionHandler) in
|
||||
let imageOperation = LoadImageURLOperation(url: saveState.imageFileURL)
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
completionHandler(image, error)
|
||||
}
|
||||
|
||||
if self.isAppearing
|
||||
{
|
||||
imageOperation.start()
|
||||
imageOperation.waitUntilFinished()
|
||||
return nil
|
||||
}
|
||||
|
||||
return imageOperation
|
||||
}
|
||||
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard let image = image, let cell = cell as? GridCollectionViewCell else { return }
|
||||
|
||||
cell.imageView.backgroundColor = nil
|
||||
cell.imageView.image = image
|
||||
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SaveStatesViewController
|
||||
{
|
||||
//MARK: - Update -
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
|
||||
@ -194,7 +230,7 @@ private extension SaveStatesViewController
|
||||
|
||||
//MARK: - Configure Views -
|
||||
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath, ignoreExpensiveOperations ignoreOperations: Bool = false)
|
||||
func configure(_ cell: GridCollectionViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let saveState = self.dataSource.item(at: indexPath)
|
||||
|
||||
@ -213,35 +249,9 @@ private extension SaveStatesViewController
|
||||
cell.isImageViewVibrancyEnabled = true
|
||||
}
|
||||
|
||||
if !ignoreOperations
|
||||
{
|
||||
let imageOperation = LoadImageURLOperation(url: saveState.imageFileURL)
|
||||
imageOperation.resultsCache = self.imageCache
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
|
||||
if let image = image
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
cell.imageView.backgroundColor = nil
|
||||
cell.imageView.image = image
|
||||
|
||||
cell.isImageViewVibrancyEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure initially visible cells have loaded their image before they appear to prevent potential flickering from placeholder to thumbnail
|
||||
if self.isAppearing
|
||||
{
|
||||
imageOperation.isImmediate = true
|
||||
}
|
||||
|
||||
self.imageOperationQueue.addOperation(imageOperation, forKey: indexPath as NSCopying)
|
||||
}
|
||||
|
||||
let deltaCore = Delta.core(for: self.game.type)!
|
||||
|
||||
let dimensions = deltaCore.emulatorConfiguration.videoBufferInfo.outputDimensions
|
||||
let dimensions = deltaCore.videoFormat.dimensions
|
||||
cell.maximumImageSize = CGSize(width: self.prototypeCellWidthConstraint.constant, height: (self.prototypeCellWidthConstraint.constant / dimensions.width) * dimensions.height)
|
||||
|
||||
cell.textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
|
||||
@ -259,6 +269,7 @@ private extension SaveStatesViewController
|
||||
switch section
|
||||
{
|
||||
case .auto: title = NSLocalizedString("Auto Save", comment: "")
|
||||
case .quick: title = NSLocalizedString("Quick Save", comment: "")
|
||||
case .general: title = NSLocalizedString("General", comment: "")
|
||||
case .locked: title = NSLocalizedString("Locked", comment: "")
|
||||
}
|
||||
@ -301,6 +312,7 @@ private extension SaveStatesViewController
|
||||
let game = backgroundContext.object(with: self.game.objectID) as! Game
|
||||
|
||||
saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
|
||||
saveState.type = .general
|
||||
saveState.game = game
|
||||
}
|
||||
|
||||
@ -365,7 +377,7 @@ private extension SaveStatesViewController
|
||||
func rename(_ saveState: SaveState, with name: String?)
|
||||
{
|
||||
var name = name
|
||||
if (name ?? "").characters.count == 0
|
||||
if (name ?? "").count == 0
|
||||
{
|
||||
// When text is nil, we know to show the timestamp instead
|
||||
name = nil
|
||||
@ -471,6 +483,7 @@ private extension SaveStatesViewController
|
||||
switch saveState.type
|
||||
{
|
||||
case .auto: break
|
||||
case .quick: break
|
||||
case .general:
|
||||
let lockAction = Action(title: NSLocalizedString("Lock", comment: ""), style: .default, action: { [unowned self] action in
|
||||
self.lockSaveState(saveState)
|
||||
@ -549,13 +562,13 @@ private extension SaveStatesViewController
|
||||
//MARK: - 3D Touch -
|
||||
extension SaveStatesViewController: UIViewControllerPreviewingDelegate
|
||||
{
|
||||
fileprivate func prepareEmulatorCoreSaveState()
|
||||
private func prepareEmulatorCoreSaveState()
|
||||
{
|
||||
guard let emulatorCore = self.emulatorCore else { return }
|
||||
|
||||
// Store reference to current game state before we stop emulation so we can resume it if user decides to not load a save state
|
||||
|
||||
let fileURL = FileManager.uniqueTemporaryURL()
|
||||
let fileURL = FileManager.default.uniqueTemporaryURL()
|
||||
self.emulatorCoreSaveState = emulatorCore.saveSaveState(to: fileURL)
|
||||
|
||||
if self.emulatorCoreSaveState != nil
|
||||
@ -577,7 +590,7 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
|
||||
|
||||
let saveState = self.dataSource.item(at: indexPath)
|
||||
let actions = self.actionsForSaveState(saveState)?.previewActions ?? []
|
||||
let previewImage = self.imageCache.object(forKey: saveState.imageFileURL as NSURL) ?? UIImage(contentsOfFile: saveState.imageFileURL.path)
|
||||
let previewImage = self.dataSource.prefetchItemCache.object(forKey: saveState) ?? UIImage(contentsOfFile: saveState.imageFileURL.path)
|
||||
|
||||
let previewGameViewController = PreviewGameViewController()
|
||||
previewGameViewController.game = self.game
|
||||
@ -593,7 +606,7 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
|
||||
let gameViewController = viewControllerToCommit as! PreviewGameViewController
|
||||
gameViewController.emulatorCore?.pause()
|
||||
|
||||
let fileURL = FileManager.uniqueTemporaryURL()
|
||||
let fileURL = FileManager.default.uniqueTemporaryURL()
|
||||
if let saveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL)
|
||||
{
|
||||
gameViewController.emulatorCore?.stop()
|
||||
@ -634,11 +647,11 @@ extension SaveStatesViewController
|
||||
{
|
||||
case .saving:
|
||||
|
||||
let section = self.correctedSectionForSectionIndex((indexPath as NSIndexPath).section)
|
||||
let section = self.correctedSectionForSectionIndex(indexPath.section)
|
||||
switch section
|
||||
{
|
||||
case .auto: break
|
||||
case .general:
|
||||
case .quick, .general:
|
||||
let backgroundContext = DatabaseManager.shared.newBackgroundContext()
|
||||
backgroundContext.performAndWait() {
|
||||
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState
|
||||
@ -655,12 +668,6 @@ extension SaveStatesViewController
|
||||
case .loading: self.loadSaveState(saveState)
|
||||
}
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
|
||||
{
|
||||
let operation = self.imageOperationQueue[indexPath as NSCopying]
|
||||
operation?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <UICollectionViewDelegateFlowLayout> -
|
||||
@ -668,8 +675,7 @@ extension SaveStatesViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
// No need to load images from disk just to determine size, so we pass true for ignoreExpensiveOperations
|
||||
self.configure(self.prototypeCell, for: indexPath, ignoreExpensiveOperations: true)
|
||||
self.configure(self.prototypeCell, for: indexPath)
|
||||
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
|
||||
@ -10,15 +10,15 @@ import UIKit
|
||||
|
||||
class PauseStoryboardSegue: UIStoryboardSegue
|
||||
{
|
||||
fileprivate let animator: UIViewPropertyAnimator
|
||||
fileprivate let presentationController: PausePresentationController
|
||||
private let animator: UIViewPropertyAnimator
|
||||
private let presentationController: PausePresentationController
|
||||
|
||||
override init(identifier: String?, source: UIViewController, destination: UIViewController)
|
||||
{
|
||||
let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0))
|
||||
self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters)
|
||||
|
||||
self.presentationController = PausePresentationController(presentedViewController: destination, presenting: source)
|
||||
self.presentationController = PausePresentationController(presentedViewController: destination, presenting: source, presentationAnimator: self.animator)
|
||||
|
||||
super.init(identifier: identifier, source: source, destination: destination)
|
||||
}
|
||||
@ -29,6 +29,9 @@ class PauseStoryboardSegue: UIStoryboardSegue
|
||||
self.destination.modalPresentationStyle = .custom
|
||||
self.destination.modalPresentationCapturesStatusBarAppearance = true
|
||||
|
||||
// Manually set tint color, since calling layoutIfNeeded will cause view to load, but with default system tint color.
|
||||
self.destination.view.tintColor = .white
|
||||
|
||||
// We need to force layout of destinationViewController.view _before_ animateTransition(using:)
|
||||
// Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors
|
||||
self.destination.view.frame = self.source.view.frame
|
||||
|
||||
@ -37,6 +37,13 @@ class PauseTransitionCoordinator: NSObject, UIViewControllerAnimatedTransitionin
|
||||
|
||||
destinationViewController.view.layoutIfNeeded()
|
||||
|
||||
if let navigationController = destinationViewController.navigationController
|
||||
{
|
||||
// Layout before animation to prevent strange bar button item layout during animation.
|
||||
navigationController.view.setNeedsLayout()
|
||||
navigationController.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
UIView.animate(withDuration: self.transitionDuration(using: transitionContext), delay:0, options:RSTSystemTransitionAnimationCurve, animations: {
|
||||
|
||||
sourceViewController.view.frame.origin.y = self.presenting ? -sourceViewController.view.bounds.height : transitionContext.containerView.bounds.height
|
||||
|
||||
@ -12,18 +12,9 @@ import DeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
extension ControllerSkinsViewController
|
||||
{
|
||||
enum Section: Int
|
||||
{
|
||||
case standard
|
||||
case custom
|
||||
}
|
||||
}
|
||||
|
||||
class ControllerSkinsViewController: UITableViewController
|
||||
{
|
||||
var gameType: GameType! {
|
||||
var system: System! {
|
||||
didSet {
|
||||
self.updateDataSource()
|
||||
}
|
||||
@ -35,11 +26,16 @@ class ControllerSkinsViewController: UITableViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let dataSource = RSTFetchedResultsTableViewDataSource<ControllerSkin>(fetchedResultsController: NSFetchedResultsController())
|
||||
private let dataSource: RSTFetchedResultsTableViewPrefetchingDataSource<ControllerSkin, UIImage>
|
||||
|
||||
fileprivate let imageOperationQueue = RSTOperationQueue()
|
||||
|
||||
fileprivate let imageCache = NSCache<ControllerSkinImageCacheKey, UIImage>()
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
self.dataSource = RSTFetchedResultsTableViewPrefetchingDataSource<ControllerSkin, UIImage>(fetchedResultsController: NSFetchedResultsController())
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.prepareDataSource()
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerSkinsViewController
|
||||
@ -48,11 +44,8 @@ extension ControllerSkinsViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.dataSource.proxy = self
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
self.configure(cell as! ControllerSkinTableViewCell, for: indexPath)
|
||||
}
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.prefetchDataSource = self.dataSource
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning()
|
||||
@ -65,57 +58,53 @@ extension ControllerSkinsViewController
|
||||
private extension ControllerSkinsViewController
|
||||
{
|
||||
//MARK: - Update
|
||||
func prepareDataSource()
|
||||
{
|
||||
self.dataSource.proxy = self
|
||||
self.dataSource.cellConfigurationHandler = { (cell, item, indexPath) in
|
||||
let cell = cell as! ControllerSkinTableViewCell
|
||||
|
||||
cell.controllerSkinImageView.image = nil
|
||||
cell.activityIndicatorView.startAnimating()
|
||||
}
|
||||
|
||||
self.dataSource.prefetchHandler = { [unowned self] (controllerSkin, indexPath, completionHandler) in
|
||||
let imageOperation = LoadControllerSkinImageOperation(controllerSkin: controllerSkin, traits: self.traits, size: UIScreen.main.defaultControllerSkinSize)
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
completionHandler(image, error)
|
||||
}
|
||||
|
||||
// Ensure initially visible cells have loaded their image before they appear to prevent potential flickering from placeholder to thumbnail
|
||||
if self.isAppearing
|
||||
{
|
||||
imageOperation.start()
|
||||
imageOperation.waitUntilFinished()
|
||||
return nil
|
||||
}
|
||||
|
||||
return imageOperation
|
||||
}
|
||||
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard let image = image, let cell = cell as? ControllerSkinTableViewCell else { return }
|
||||
|
||||
cell.controllerSkinImageView.image = image
|
||||
cell.activityIndicatorView.stopAnimating()
|
||||
}
|
||||
}
|
||||
|
||||
func updateDataSource()
|
||||
{
|
||||
guard let gameType = self.gameType, let traits = self.traits else { return }
|
||||
guard let system = self.system, let traits = self.traits else { return }
|
||||
|
||||
let configuration = ControllerSkinConfigurations(traits: traits)
|
||||
|
||||
let fetchRequest: NSFetchRequest<ControllerSkin> = ControllerSkin.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND (%K & %d) == %d", #keyPath(ControllerSkin.gameType), gameType.rawValue, #keyPath(ControllerSkin.supportedConfigurations), configuration.rawValue, configuration.rawValue)
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND (%K & %d) == %d", #keyPath(ControllerSkin.gameType), system.gameType.rawValue, #keyPath(ControllerSkin.supportedConfigurations), configuration.rawValue, configuration.rawValue)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(ControllerSkin.isStandard), ascending: false), NSSortDescriptor(key: #keyPath(ControllerSkin.name), ascending: true)]
|
||||
|
||||
self.dataSource.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(ControllerSkin.name), cacheName: nil)
|
||||
}
|
||||
|
||||
//MARK: - Configure Cells
|
||||
func configure(_ cell: ControllerSkinTableViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
let controllerSkin = self.dataSource.item(at: indexPath)
|
||||
|
||||
cell.controllerSkinImageView.image = nil
|
||||
cell.activityIndicatorView.startAnimating()
|
||||
|
||||
let size = UIScreen.main.defaultControllerSkinSize
|
||||
|
||||
let imageOperation = LoadControllerSkinImageOperation(controllerSkin: controllerSkin, traits: self.traits, size: size)
|
||||
imageOperation.resultsCache = self.imageCache
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
|
||||
guard let image = image else { return }
|
||||
|
||||
if !imageOperation.isImmediate
|
||||
{
|
||||
UIView.transition(with: cell.controllerSkinImageView, duration: 0.2, options: .transitionCrossDissolve, animations: {
|
||||
cell.controllerSkinImageView.image = image
|
||||
}, completion: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.controllerSkinImageView.image = image
|
||||
}
|
||||
|
||||
cell.activityIndicatorView.stopAnimating()
|
||||
}
|
||||
|
||||
// Ensure initially visible cells have loaded their image before they appear to prevent potential flickering from placeholder to thumbnail
|
||||
if self.isAppearing
|
||||
{
|
||||
imageOperation.isImmediate = true
|
||||
}
|
||||
|
||||
self.imageOperationQueue.addOperation(imageOperation, forKey: indexPath as NSCopying)
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerSkinsViewController
|
||||
@ -127,39 +116,12 @@ extension ControllerSkinsViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerSkinsViewController: UITableViewDataSourcePrefetching
|
||||
{
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
|
||||
{
|
||||
for indexPath in indexPaths
|
||||
{
|
||||
let controllerSkin = self.dataSource.item(at: indexPath)
|
||||
|
||||
let size = UIScreen.main.defaultControllerSkinSize
|
||||
|
||||
let imageOperation = LoadControllerSkinImageOperation(controllerSkin: controllerSkin, traits: self.traits, size: size)
|
||||
imageOperation.resultsCache = self.imageCache
|
||||
|
||||
self.imageOperationQueue.addOperation(imageOperation, forKey: indexPath as NSCopying)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath])
|
||||
{
|
||||
for indexPath in indexPaths
|
||||
{
|
||||
let operation = self.imageOperationQueue[indexPath as NSCopying]
|
||||
operation?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerSkinsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
let controllerSkin = self.dataSource.item(at: indexPath)
|
||||
Settings.setPreferredControllerSkin(controllerSkin, for: self.gameType, traits: self.traits)
|
||||
Settings.setPreferredControllerSkin(controllerSkin, for: self.system, traits: self.traits)
|
||||
|
||||
_ = self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
@ -176,10 +138,4 @@ extension ControllerSkinsViewController
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
|
||||
{
|
||||
let operation = self.imageOperationQueue[indexPath as NSCopying]
|
||||
operation?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// GameTypeControllerSkinsViewController.swift
|
||||
// SystemControllerSkinsViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 9/30/16.
|
||||
@ -10,30 +10,30 @@ import UIKit
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension GameTypeControllerSkinsViewController
|
||||
extension SystemControllerSkinsViewController
|
||||
{
|
||||
fileprivate enum Section: Int
|
||||
private enum Section: Int
|
||||
{
|
||||
case portrait
|
||||
case landscape
|
||||
}
|
||||
}
|
||||
|
||||
class GameTypeControllerSkinsViewController: UITableViewController
|
||||
class SystemControllerSkinsViewController: UITableViewController
|
||||
{
|
||||
var gameType: GameType!
|
||||
var system: System!
|
||||
|
||||
@IBOutlet fileprivate var portraitImageView: UIImageView!
|
||||
@IBOutlet fileprivate var landscapeImageView: UIImageView!
|
||||
@IBOutlet private var portraitImageView: UIImageView!
|
||||
@IBOutlet private var landscapeImageView: UIImageView!
|
||||
}
|
||||
|
||||
extension GameTypeControllerSkinsViewController
|
||||
extension SystemControllerSkinsViewController
|
||||
{
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.title = self.gameType.localizedShortName
|
||||
self.title = self.system.localizedShortName
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
@ -53,7 +53,7 @@ extension GameTypeControllerSkinsViewController
|
||||
guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return }
|
||||
|
||||
let controllerSkinsViewController = segue.destination as! ControllerSkinsViewController
|
||||
controllerSkinsViewController.gameType = self.gameType
|
||||
controllerSkinsViewController.system = self.system
|
||||
|
||||
var traits = DeltaCore.ControllerSkin.Traits.defaults(for: self.view)
|
||||
|
||||
@ -68,7 +68,7 @@ extension GameTypeControllerSkinsViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension GameTypeControllerSkinsViewController
|
||||
extension SystemControllerSkinsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
||||
{
|
||||
@ -98,15 +98,15 @@ extension GameTypeControllerSkinsViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension GameTypeControllerSkinsViewController
|
||||
private extension SystemControllerSkinsViewController
|
||||
{
|
||||
func updateControllerSkins()
|
||||
{
|
||||
let portraitTraits = DeltaCore.ControllerSkin.Traits(deviceType: .iphone, displayMode: DeltaCore.ControllerSkin.DisplayMode.fullScreen, orientation: .portrait)
|
||||
let landscapeTraits = DeltaCore.ControllerSkin.Traits(deviceType: .iphone, displayMode: DeltaCore.ControllerSkin.DisplayMode.fullScreen, orientation: .landscape)
|
||||
|
||||
let portraitControllerSkin = Settings.preferredControllerSkin(for: self.gameType, traits: portraitTraits)
|
||||
let landscapeControllerSkin = Settings.preferredControllerSkin(for: self.gameType, traits: landscapeTraits)
|
||||
let portraitControllerSkin = Settings.preferredControllerSkin(for: self.system, traits: portraitTraits)
|
||||
let landscapeControllerSkin = Settings.preferredControllerSkin(for: self.system, traits: landscapeTraits)
|
||||
|
||||
self.portraitImageView.image = portraitControllerSkin?.image(for: portraitTraits, preferredSize: UIScreen.main.defaultControllerSkinSize)
|
||||
self.landscapeImageView.image = landscapeControllerSkin?.image(for: landscapeTraits, preferredSize: UIScreen.main.defaultControllerSkinSize)
|
||||
508
Delta/Settings/Controllers/ControllerInputsViewController.swift
Normal file
508
Delta/Settings/Controllers/ControllerInputsViewController.swift
Normal file
@ -0,0 +1,508 @@
|
||||
//
|
||||
// ControllerInputsViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 7/1/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import SMCalloutView
|
||||
|
||||
class ControllerInputsViewController: UIViewController
|
||||
{
|
||||
var gameController: GameController! {
|
||||
didSet {
|
||||
self.gameController.addReceiver(self, inputMapping: nil)
|
||||
}
|
||||
}
|
||||
|
||||
var system: System = .snes {
|
||||
didSet {
|
||||
guard self.system != oldValue else { return }
|
||||
self.updateSystem()
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var managedObjectContext: NSManagedObjectContext = DatabaseManager.shared.newBackgroundContext()
|
||||
private var inputMappings = [System: GameControllerInputMapping]()
|
||||
|
||||
private let supportedActionInputs: [ActionInput] = [.quickSave, .quickLoad, .fastForward]
|
||||
|
||||
private var gameViewController: DeltaCore.GameViewController!
|
||||
private var actionsMenuViewController: GridMenuViewController!
|
||||
|
||||
private var calloutViews = [AnyInput: InputCalloutView]()
|
||||
|
||||
private var activeCalloutView: InputCalloutView?
|
||||
|
||||
@IBOutlet private var actionsMenuViewControllerHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet private var cancelTapGestureRecognizer: UITapGestureRecognizer!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.gameViewController.controllerView.addReceiver(self)
|
||||
|
||||
self.navigationController?.navigationBar.barStyle = .black
|
||||
|
||||
NSLayoutConstraint.activate([self.gameViewController.gameView.centerYAnchor.constraint(equalTo: self.actionsMenuViewController.view.centerYAnchor)])
|
||||
|
||||
self.preparePopoverMenuController()
|
||||
self.updateSystem()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
{
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if self.actionsMenuViewController.preferredContentSize.height > 0
|
||||
{
|
||||
self.actionsMenuViewControllerHeightConstraint.constant = self.actionsMenuViewController.preferredContentSize.height
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if self.calloutViews.isEmpty
|
||||
{
|
||||
self.prepareCallouts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController
|
||||
{
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
switch identifier
|
||||
{
|
||||
case "embedGameViewController": self.gameViewController = segue.destination as! DeltaCore.GameViewController
|
||||
case "embedActionsMenuViewController":
|
||||
self.actionsMenuViewController = segue.destination as! GridMenuViewController
|
||||
self.prepareActionsMenuViewController()
|
||||
|
||||
case "cancelControllerInputs": break
|
||||
case "saveControllerInputs":
|
||||
self.managedObjectContext.performAndWait {
|
||||
self.managedObjectContext.saveWithErrorLogging()
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func updateSystem()
|
||||
{
|
||||
guard self.isViewLoaded else { return }
|
||||
|
||||
// Update popoverMenuButton to display correctly on iOS 10.
|
||||
if let popoverMenuButton = self.navigationItem.popoverMenuController?.popoverMenuButton
|
||||
{
|
||||
popoverMenuButton.title = self.system.localizedShortName
|
||||
popoverMenuButton.bounds.size = popoverMenuButton.intrinsicContentSize
|
||||
|
||||
self.navigationController?.navigationBar.layoutIfNeeded()
|
||||
}
|
||||
|
||||
// Update controller view's controller skin.
|
||||
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
|
||||
{
|
||||
self.calloutViews.forEach { $1.dismissCallout(animated: true) }
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
self.calloutViews = [:]
|
||||
self.prepareCallouts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func preparePopoverMenuController()
|
||||
{
|
||||
let listMenuViewController = ListMenuViewController()
|
||||
listMenuViewController.title = NSLocalizedString("Game System", comment: "")
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: listMenuViewController)
|
||||
|
||||
let popoverMenuController = PopoverMenuController(popoverViewController: navigationController)
|
||||
self.navigationItem.popoverMenuController = popoverMenuController
|
||||
|
||||
let items = System.supportedSystems.map { [unowned self, weak popoverMenuController, weak listMenuViewController] system -> MenuItem in
|
||||
let item = MenuItem(text: system.localizedShortName, image: #imageLiteral(resourceName: "CheatCodes")) { [weak popoverMenuController, weak listMenuViewController] item in
|
||||
listMenuViewController?.items.forEach { $0.isSelected = ($0 == item) }
|
||||
popoverMenuController?.isActive = false
|
||||
|
||||
self.system = system
|
||||
}
|
||||
item.isSelected = (system == self.system)
|
||||
|
||||
return item
|
||||
}
|
||||
listMenuViewController.items = items
|
||||
}
|
||||
|
||||
func prepareActionsMenuViewController()
|
||||
{
|
||||
var items = [MenuItem]()
|
||||
|
||||
for input in self.supportedActionInputs
|
||||
{
|
||||
let image: UIImage
|
||||
let text: String
|
||||
|
||||
switch input
|
||||
{
|
||||
case .quickSave:
|
||||
image = #imageLiteral(resourceName: "SaveSaveState")
|
||||
text = NSLocalizedString("Quick Save", comment: "")
|
||||
|
||||
case .quickLoad:
|
||||
image = #imageLiteral(resourceName: "LoadSaveState")
|
||||
text = NSLocalizedString("Quick Load", comment: "")
|
||||
|
||||
case .fastForward:
|
||||
image = #imageLiteral(resourceName: "FastForward")
|
||||
text = NSLocalizedString("Fast Forward", comment: "")
|
||||
}
|
||||
|
||||
let item = MenuItem(text: text, image: image) { [unowned self] (item) in
|
||||
guard let calloutView = self.calloutViews[AnyInput(input)] else { return }
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
self.actionsMenuViewController.items = items
|
||||
self.actionsMenuViewController.isVibrancyEnabled = false
|
||||
}
|
||||
|
||||
func prepareCallouts()
|
||||
{
|
||||
guard
|
||||
let controllerView = self.gameViewController.controllerView,
|
||||
let traits = controllerView.controllerSkinTraits,
|
||||
let items = controllerView.controllerSkin?.items(for: traits),
|
||||
let controllerViewInputMapping = controllerView.defaultInputMapping,
|
||||
let inputMapping = self.inputMappings[self.system]
|
||||
else { return }
|
||||
|
||||
// Implicit assumption that all skins used for controller input mapping don't have multiple items with same input.
|
||||
let mappedInputs = items.flatMap { $0.inputs.allInputs.flatMap(controllerViewInputMapping.input(forControllerInput:)) } + (self.supportedActionInputs as [Input])
|
||||
|
||||
// Create callout view for each on-screen input.
|
||||
for input in mappedInputs
|
||||
{
|
||||
let calloutView = InputCalloutView()
|
||||
calloutView.delegate = self
|
||||
self.calloutViews[AnyInput(input)] = calloutView
|
||||
}
|
||||
|
||||
self.managedObjectContext.performAndWait {
|
||||
// Update callout views with controller inputs that map to callout views' associated controller skin inputs.
|
||||
for input in inputMapping.supportedControllerInputs
|
||||
{
|
||||
let mappedInput = self.mappedInput(for: input)
|
||||
|
||||
if let calloutView = self.calloutViews[mappedInput]
|
||||
{
|
||||
if let previousInput = calloutView.input
|
||||
{
|
||||
// Ensure the input we display has a higher priority.
|
||||
calloutView.input = (input.displayPriority > previousInput.displayPriority) ? input : previousInput
|
||||
}
|
||||
else
|
||||
{
|
||||
calloutView.input = input
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Present only callout views that are associated with a controller input.
|
||||
for calloutView in self.calloutViews.values
|
||||
{
|
||||
if let presentationRect = self.presentationRect(for: calloutView), calloutView.input != nil
|
||||
{
|
||||
calloutView.presentCallout(from: presentationRect, in: self.view, constrainedTo: self.view, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func updateActiveCalloutView(with controllerInput: Input?)
|
||||
{
|
||||
guard let inputMapping = self.inputMappings[self.system] else { return }
|
||||
|
||||
guard let activeCalloutView = self.activeCalloutView else { return }
|
||||
|
||||
guard let input = self.calloutViews.first(where: { $0.value == activeCalloutView })?.key else { return }
|
||||
|
||||
if let controllerInput = controllerInput
|
||||
{
|
||||
for (_, calloutView) in self.calloutViews
|
||||
{
|
||||
guard let calloutInput = calloutView.input else { continue }
|
||||
|
||||
if calloutInput == controllerInput
|
||||
{
|
||||
// Hide callout views that previously displayed the controller input.
|
||||
calloutView.input = nil
|
||||
calloutView.dismissCallout(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.managedObjectContext.performAndWait {
|
||||
for supportedInput in inputMapping.supportedControllerInputs
|
||||
{
|
||||
let mappedInput = self.mappedInput(for: supportedInput)
|
||||
|
||||
if mappedInput == input
|
||||
{
|
||||
// Set all existing controller inputs that currently map to "input" to instead map to nil.
|
||||
inputMapping.set(nil, forControllerInput: supportedInput)
|
||||
}
|
||||
}
|
||||
|
||||
if let controllerInput = controllerInput
|
||||
{
|
||||
inputMapping.set(input, forControllerInput: controllerInput)
|
||||
}
|
||||
}
|
||||
|
||||
activeCalloutView.input = controllerInput
|
||||
|
||||
self.toggle(activeCalloutView)
|
||||
}
|
||||
|
||||
func toggle(_ calloutView: InputCalloutView)
|
||||
{
|
||||
if let activeCalloutView = self.activeCalloutView, activeCalloutView != calloutView
|
||||
{
|
||||
self.toggle(activeCalloutView)
|
||||
}
|
||||
|
||||
let menuItem: MenuItem?
|
||||
|
||||
if let input = self.calloutViews.first(where: { $0.value == calloutView })?.key, let index = self.supportedActionInputs.index(where: { $0 == input })
|
||||
{
|
||||
menuItem = self.actionsMenuViewController.items[index]
|
||||
}
|
||||
else
|
||||
{
|
||||
menuItem = nil
|
||||
}
|
||||
|
||||
switch calloutView.state
|
||||
{
|
||||
case .normal:
|
||||
calloutView.state = .listening
|
||||
menuItem?.isSelected = true
|
||||
self.activeCalloutView = calloutView
|
||||
|
||||
case .listening:
|
||||
calloutView.state = .normal
|
||||
menuItem?.isSelected = false
|
||||
self.activeCalloutView = nil
|
||||
}
|
||||
|
||||
calloutView.dismissCallout(animated: true)
|
||||
|
||||
if let presentationRect = self.presentationRect(for: calloutView)
|
||||
{
|
||||
if calloutView.state == .listening || calloutView.input != nil
|
||||
{
|
||||
calloutView.presentCallout(from: presentationRect, in: self.view, constrainedTo: self.view, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: UIGestureRecognizerDelegate
|
||||
{
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
|
||||
{
|
||||
return self.activeCalloutView != nil
|
||||
}
|
||||
|
||||
@IBAction private func handleTapGesture(_ tapGestureRecognizer: UITapGestureRecognizer)
|
||||
{
|
||||
self.updateActiveCalloutView(with: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllerInputsViewController
|
||||
{
|
||||
func mappedInput(for input: Input) -> AnyInput
|
||||
{
|
||||
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.")
|
||||
}
|
||||
|
||||
if let standardInput = StandardGameControllerInput(input: mappedInput)
|
||||
{
|
||||
if let gameInput = standardInput.input(for: self.system.gameType)
|
||||
{
|
||||
return AnyInput(gameInput)
|
||||
}
|
||||
}
|
||||
|
||||
return AnyInput(mappedInput)
|
||||
}
|
||||
|
||||
func presentationRect(for calloutView: InputCalloutView) -> CGRect?
|
||||
{
|
||||
guard let input = self.calloutViews.first(where: { $0.value == calloutView })?.key else { return nil }
|
||||
|
||||
guard
|
||||
let controllerView = self.gameViewController.controllerView,
|
||||
let traits = controllerView.controllerSkinTraits,
|
||||
let items = controllerView.controllerSkin?.items(for: traits)
|
||||
else { return nil }
|
||||
|
||||
if let item = items.first(where: { $0.inputs.allInputs.contains(where: { $0.stringValue == input.stringValue })})
|
||||
{
|
||||
// Input is a controller skin input.
|
||||
|
||||
let itemFrame: CGRect?
|
||||
|
||||
switch item.inputs
|
||||
{
|
||||
case .standard: itemFrame = item.frame
|
||||
case let .directional(up, down, left, right):
|
||||
|
||||
switch input.stringValue
|
||||
{
|
||||
case up.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + item.frame.width / 3,
|
||||
y: item.frame.minY,
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
case down.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + item.frame.width / 3,
|
||||
y: item.frame.minY + (item.frame.height / 3) * 2,
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
case left.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX,
|
||||
y: item.frame.minY + (item.frame.height / 3),
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
case right.stringValue:
|
||||
itemFrame = CGRect(x: item.frame.minX + (item.frame.width / 3) * 2,
|
||||
y: item.frame.minY + (item.frame.height / 3),
|
||||
width: item.frame.width / 3,
|
||||
height: item.frame.height / 3)
|
||||
|
||||
default: itemFrame = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let itemFrame = itemFrame
|
||||
{
|
||||
var presentationFrame = itemFrame.applying(CGAffineTransform(scaleX: controllerView.bounds.width, y: controllerView.bounds.height))
|
||||
presentationFrame = self.view.convert(presentationFrame, from: controllerView)
|
||||
|
||||
return presentationFrame
|
||||
}
|
||||
}
|
||||
else if let index = self.supportedActionInputs.index(where: { $0 == input })
|
||||
{
|
||||
// Input is an ActionInput.
|
||||
|
||||
let indexPath = IndexPath(item: index, section: 0)
|
||||
|
||||
if let attributes = self.actionsMenuViewController.collectionViewLayout.layoutAttributesForItem(at: indexPath)
|
||||
{
|
||||
let presentationFrame = self.view.convert(attributes.frame, from: self.actionsMenuViewController.view)
|
||||
return presentationFrame
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Input is not an on-screen input.
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: GameControllerReceiver
|
||||
{
|
||||
func gameController(_ gameController: GameController, didActivate controllerInput: DeltaCore.Input)
|
||||
{
|
||||
guard self.isViewLoaded else { return }
|
||||
|
||||
switch gameController
|
||||
{
|
||||
case self.gameViewController.controllerView:
|
||||
if let calloutView = self.calloutViews[AnyInput(controllerInput)]
|
||||
{
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
|
||||
case self.gameController: self.updateActiveCalloutView(with: controllerInput)
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func gameController(_ gameController: GameController, didDeactivate input: DeltaCore.Input)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllerInputsViewController: SMCalloutViewDelegate
|
||||
{
|
||||
func calloutViewClicked(_ calloutView: SMCalloutView)
|
||||
{
|
||||
guard let calloutView = calloutView as? InputCalloutView else { return }
|
||||
|
||||
self.toggle(calloutView)
|
||||
}
|
||||
}
|
||||
@ -9,39 +9,62 @@
|
||||
import UIKit
|
||||
import DeltaCore
|
||||
|
||||
private enum ControllersSettingsSection: Int
|
||||
import Roxas
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
case none
|
||||
case localDevice
|
||||
case externalControllers
|
||||
private enum Section: Int
|
||||
{
|
||||
case none
|
||||
case localDevice
|
||||
case externalControllers
|
||||
case customizeControls
|
||||
}
|
||||
}
|
||||
|
||||
private class LocalDeviceController: ExternalController
|
||||
private class LocalDeviceController: NSObject, GameController
|
||||
{
|
||||
override var name: String {
|
||||
var name: String {
|
||||
return UIDevice.current.name
|
||||
}
|
||||
|
||||
var playerIndex: Int? {
|
||||
set { Settings.localControllerPlayerIndex = newValue }
|
||||
get { return Settings.localControllerPlayerIndex }
|
||||
}
|
||||
|
||||
let inputType: GameControllerInputType = .standard
|
||||
|
||||
var defaultInputMapping: GameControllerInputMappingProtocol?
|
||||
}
|
||||
|
||||
class ControllersSettingsViewController: UITableViewController
|
||||
{
|
||||
var playerIndex: Int? {
|
||||
didSet
|
||||
{
|
||||
if let playerIndex = self.playerIndex
|
||||
var playerIndex: Int! {
|
||||
didSet {
|
||||
self.title = NSLocalizedString("Player \(self.playerIndex + 1)", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
private var gameController: GameController? {
|
||||
didSet {
|
||||
// Order matters since localDeviceController changes Settings.localControllerPlayerIndex, which sends out NSNotification.
|
||||
if oldValue == self.localDeviceController
|
||||
{
|
||||
self.title = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
||||
self.gameController?.playerIndex = self.playerIndex
|
||||
oldValue?.playerIndex = nil
|
||||
}
|
||||
else
|
||||
{
|
||||
self.title = NSLocalizedString("Controllers", comment: "")
|
||||
oldValue?.playerIndex = nil
|
||||
self.gameController?.playerIndex = self.playerIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var connectedControllers = ExternalControllerManager.shared.connectedControllers.sorted(by: { $0.playerIndex ?? NSIntegerMax < $1.playerIndex ?? NSIntegerMax })
|
||||
private var connectedControllers = ExternalGameControllerManager.shared.connectedControllers.sorted(by: { $0.playerIndex ?? NSIntegerMax < $1.playerIndex ?? NSIntegerMax })
|
||||
|
||||
fileprivate lazy var localDeviceController: LocalDeviceController = {
|
||||
private lazy var localDeviceController: LocalDeviceController = {
|
||||
let device = LocalDeviceController()
|
||||
device.playerIndex = Settings.localControllerPlayerIndex
|
||||
|
||||
@ -52,146 +75,76 @@ class ControllersSettingsViewController: UITableViewController
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
//MARK: - Storyboards -
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
guard let indexPath = self.tableView.indexPathForSelectedRow else { return }
|
||||
|
||||
var controllers = self.connectedControllers
|
||||
controllers.append(self.localDeviceController)
|
||||
|
||||
// Reset previous controller
|
||||
if let playerIndex = self.playerIndex, let index = controllers.index(where: { $0.playerIndex == playerIndex })
|
||||
let gameControllers = [self.localDeviceController as GameController] + self.connectedControllers
|
||||
for gameController in gameControllers
|
||||
{
|
||||
let controller = controllers[index]
|
||||
controller.playerIndex = nil
|
||||
}
|
||||
|
||||
switch ControllersSettingsSection(rawValue: (indexPath as NSIndexPath).section)!
|
||||
{
|
||||
case .none: break
|
||||
case .localDevice: self.localDeviceController.playerIndex = self.playerIndex
|
||||
case .externalControllers:
|
||||
let controller = self.connectedControllers[(indexPath as NSIndexPath).row]
|
||||
controller.playerIndex = self.playerIndex
|
||||
}
|
||||
|
||||
// Updates in case we reset it above, as well as if we updated in the switch statement
|
||||
Settings.localControllerPlayerIndex = self.localDeviceController.playerIndex
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
dynamic func externalControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? ExternalController else { return }
|
||||
|
||||
if let playerIndex = controller.playerIndex
|
||||
{
|
||||
self.connectedControllers.insert(controller, at: playerIndex)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.connectedControllers.append(controller)
|
||||
}
|
||||
|
||||
if let index = self.connectedControllers.index(of: controller)
|
||||
{
|
||||
if self.connectedControllers.count == 1
|
||||
if gameController.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: ControllersSettingsSection.externalControllers.rawValue), with: .fade)
|
||||
self.gameController = gameController
|
||||
break
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.insertRows(at: [IndexPath(row: index, section: ControllersSettingsSection.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic func externalControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? ExternalController else { return }
|
||||
|
||||
if let index = self.connectedControllers.index(of: controller)
|
||||
{
|
||||
self.connectedControllers.remove(at: index)
|
||||
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: ControllersSettingsSection.externalControllers.rawValue), with: .fade)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index, section: ControllersSettingsSection.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
}
|
||||
|
||||
if controller.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: ControllersSettingsSection.none.rawValue), with: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func numberOfSections(in tableView: UITableView) -> Int
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||
{
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
return 2
|
||||
}
|
||||
|
||||
return 3
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
{
|
||||
switch ControllersSettingsSection(rawValue: section)!
|
||||
{
|
||||
case .none: return 1
|
||||
case .localDevice: return 1
|
||||
case .externalControllers: return self.connectedControllers.count
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||
{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
cell.detailTextLabel?.text = nil
|
||||
cell.accessoryType = .none
|
||||
guard let identifier = segue.identifier else { return }
|
||||
|
||||
if (indexPath as NSIndexPath).section == ControllersSettingsSection.none.rawValue
|
||||
switch identifier
|
||||
{
|
||||
case "controllerInputsSegue":
|
||||
let controllerInputsViewController = (segue.destination as! UINavigationController).topViewController as! ControllerInputsViewController
|
||||
controllerInputsViewController.gameController = self.gameController
|
||||
controllerInputsViewController.system = .snes
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func unwindFromControllerControlsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
func configure(_ cell: UITableViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
cell.accessoryType = .none
|
||||
cell.detailTextLabel?.text = nil
|
||||
cell.textLabel?.textColor = .darkText
|
||||
|
||||
switch Section(rawValue: indexPath.section)!
|
||||
{
|
||||
case .none:
|
||||
cell.textLabel?.text = NSLocalizedString("None", comment: "")
|
||||
|
||||
if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex })
|
||||
{
|
||||
cell.accessoryType = .checkmark
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let controller: ExternalController
|
||||
|
||||
if (indexPath as NSIndexPath).section == ControllersSettingsSection.localDevice.rawValue
|
||||
case .localDevice, .externalControllers:
|
||||
let controller: GameController
|
||||
|
||||
if indexPath.section == Section.localDevice.rawValue
|
||||
{
|
||||
controller = self.localDeviceController
|
||||
}
|
||||
else if (indexPath as NSIndexPath).section == ControllersSettingsSection.externalControllers.rawValue
|
||||
else if indexPath.section == Section.externalControllers.rawValue
|
||||
{
|
||||
controller = self.connectedControllers[(indexPath as NSIndexPath).row]
|
||||
controller = self.connectedControllers[indexPath.row]
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -205,25 +158,216 @@ extension ControllersSettingsViewController
|
||||
cell.accessoryType = .checkmark
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
if let playerIndex = controller.playerIndex
|
||||
{
|
||||
cell.detailTextLabel?.text = NSLocalizedString("Player \(playerIndex + 1)", comment: "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case .customizeControls:
|
||||
cell.textLabel?.text = NSLocalizedString("Customize Controls…", comment: "")
|
||||
cell.textLabel?.textColor = self.view.tintColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ControllersSettingsViewController
|
||||
{
|
||||
@objc func externalGameControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? GameController else { return }
|
||||
|
||||
if let playerIndex = controller.playerIndex
|
||||
{
|
||||
// Keep connected controllers sorted.
|
||||
|
||||
self.connectedControllers.insert(controller, at: playerIndex)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.connectedControllers.append(controller)
|
||||
}
|
||||
|
||||
if let index = self.connectedControllers.index(where: { $0 == controller })
|
||||
{
|
||||
self.tableView.beginUpdates()
|
||||
|
||||
if self.connectedControllers.count == 1
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: Section.externalControllers.rawValue), with: .fade)
|
||||
|
||||
if self.connectedControllers.first?.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.insertRows(at: [IndexPath(row: index, section: Section.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
if controller.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.none.rawValue), with: .none)
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.localDevice.rawValue), with: .none)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func externalGameControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
guard let controller = notification.object as? GameController else { return }
|
||||
|
||||
if let index = self.connectedControllers.index(where: { $0 == controller })
|
||||
{
|
||||
self.connectedControllers.remove(at: index)
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: Section.externalControllers.rawValue), with: .fade)
|
||||
|
||||
if controller.playerIndex != nil
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.deleteRows(at: [IndexPath(row: index, section: Section.externalControllers.rawValue)], with: .automatic)
|
||||
}
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
if controller.playerIndex == self.playerIndex
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.none.rawValue), with: .none)
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.localDevice.rawValue), with: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func numberOfSections(in tableView: UITableView) -> Int
|
||||
{
|
||||
if self.connectedControllers.count == 0
|
||||
{
|
||||
return 2
|
||||
}
|
||||
|
||||
if self.gameController == nil || Settings.localControllerPlayerIndex == self.playerIndex
|
||||
{
|
||||
return 3
|
||||
}
|
||||
|
||||
return 4
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
{
|
||||
switch Section(rawValue: section)!
|
||||
{
|
||||
case .none: return 0
|
||||
case .localDevice: return 1
|
||||
case .externalControllers: return self.connectedControllers.count
|
||||
case .customizeControls: return 1
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||
{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: RSTCellContentGenericCellIdentifier, for: indexPath)
|
||||
|
||||
self.configure(cell, for: indexPath)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||
{
|
||||
switch ControllersSettingsSection(rawValue: section)!
|
||||
switch Section(rawValue: section)!
|
||||
{
|
||||
case .none: return nil
|
||||
case .localDevice: return NSLocalizedString("Local Device", comment: "")
|
||||
case .externalControllers: return self.connectedControllers.count > 0 ? NSLocalizedString("External Controllers", comment: "") : ""
|
||||
case .customizeControls: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
|
||||
{
|
||||
if section == Section.none.rawValue
|
||||
{
|
||||
return 1
|
||||
}
|
||||
|
||||
return UITableViewAutomaticDimension
|
||||
}
|
||||
}
|
||||
|
||||
extension ControllersSettingsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
let previousIndexPath: IndexPath?
|
||||
|
||||
if let gameController = self.gameController
|
||||
{
|
||||
if gameController == self.localDeviceController
|
||||
{
|
||||
previousIndexPath = IndexPath(row: 0, section: Section.localDevice.rawValue)
|
||||
}
|
||||
else if let row = self.connectedControllers.index(where: { $0 == gameController })
|
||||
{
|
||||
previousIndexPath = IndexPath(row: row, section: Section.externalControllers.rawValue)
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndexPath = nil
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndexPath = IndexPath(row: 0, section: Section.none.rawValue)
|
||||
}
|
||||
|
||||
switch Section(rawValue: indexPath.section)!
|
||||
{
|
||||
case .none: self.gameController = nil
|
||||
case .localDevice: self.gameController = self.localDeviceController
|
||||
case .externalControllers: self.gameController = self.connectedControllers[indexPath.row]
|
||||
case .customizeControls:
|
||||
guard let cell = tableView.cellForRow(at: indexPath) else { return }
|
||||
self.performSegue(withIdentifier: "controllerInputsSegue", sender: cell)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
|
||||
if let previousIndexPath = previousIndexPath, let cell = tableView.cellForRow(at: previousIndexPath)
|
||||
{
|
||||
// Must configure cell directly, or else a strange animation occurs when reloading row on iOS 11.
|
||||
self.configure(cell, for: previousIndexPath)
|
||||
}
|
||||
|
||||
self.tableView.reloadRows(at: [indexPath], with: .none)
|
||||
|
||||
if self.numberOfSections(in: self.tableView) > self.tableView.numberOfSections
|
||||
{
|
||||
self.tableView.insertSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
else if self.numberOfSections(in: self.tableView) < self.tableView.numberOfSections
|
||||
{
|
||||
self.tableView.deleteSections(IndexSet(integer: Section.customizeControls.rawValue), with: .fade)
|
||||
}
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
84
Delta/Settings/Controllers/InputCalloutView.swift
Normal file
84
Delta/Settings/Controllers/InputCalloutView.swift
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// InputCalloutView.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 7/9/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import SMCalloutView
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension InputCalloutView
|
||||
{
|
||||
enum State
|
||||
{
|
||||
case normal
|
||||
case listening
|
||||
}
|
||||
}
|
||||
|
||||
class InputCalloutView: SMCalloutView
|
||||
{
|
||||
var input: Input? {
|
||||
didSet {
|
||||
self.updateState()
|
||||
}
|
||||
}
|
||||
|
||||
var state: State = .normal {
|
||||
didSet {
|
||||
self.updateState()
|
||||
}
|
||||
}
|
||||
|
||||
private let textLabel: UILabel
|
||||
|
||||
init()
|
||||
{
|
||||
self.textLabel = UILabel()
|
||||
self.textLabel.font = UIFont.boldSystemFont(ofSize: 18.0)
|
||||
self.textLabel.textAlignment = .center
|
||||
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
self.titleView = self.textLabel
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func tintColorDidChange()
|
||||
{
|
||||
super.tintColorDidChange()
|
||||
|
||||
self.updateTintColor()
|
||||
}
|
||||
}
|
||||
|
||||
private extension InputCalloutView
|
||||
{
|
||||
func updateState()
|
||||
{
|
||||
switch self.state
|
||||
{
|
||||
case .normal: self.textLabel.text = self.input?.localizedName
|
||||
case .listening: self.textLabel.text = NSLocalizedString("Press Button", comment: "")
|
||||
}
|
||||
|
||||
self.updateTintColor()
|
||||
|
||||
self.textLabel.sizeToFit()
|
||||
}
|
||||
|
||||
func updateTintColor()
|
||||
{
|
||||
switch self.state
|
||||
{
|
||||
case .normal: self.textLabel.textColor = self.tintColor
|
||||
case .listening: self.textLabel.textColor = .red
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
import DeltaCore
|
||||
import SNESDeltaCore
|
||||
import GBADeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
@ -25,7 +23,7 @@ extension Settings
|
||||
{
|
||||
case name
|
||||
|
||||
case gameType
|
||||
case system
|
||||
case traits
|
||||
}
|
||||
|
||||
@ -42,12 +40,14 @@ struct Settings
|
||||
/// Controllers
|
||||
static var localControllerPlayerIndex: Int? = 0 {
|
||||
didSet {
|
||||
guard self.localControllerPlayerIndex != oldValue else { return }
|
||||
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.localControllerPlayerIndex])
|
||||
}
|
||||
}
|
||||
|
||||
static var translucentControllerSkinOpacity: CGFloat {
|
||||
set {
|
||||
guard newValue != self.translucentControllerSkinOpacity else { return }
|
||||
UserDefaults.standard.translucentControllerSkinOpacity = newValue
|
||||
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.translucentControllerSkinOpacity])
|
||||
}
|
||||
@ -72,9 +72,9 @@ struct Settings
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
}
|
||||
|
||||
static func preferredControllerSkin(for gameType: GameType, traits: DeltaCore.ControllerSkin.Traits) -> ControllerSkin?
|
||||
static func preferredControllerSkin(for system: System, traits: DeltaCore.ControllerSkin.Traits) -> ControllerSkin?
|
||||
{
|
||||
guard let userDefaultsKey = self.preferredControllerSkinKey(for: gameType, traits: traits) else { return nil }
|
||||
guard let userDefaultsKey = self.preferredControllerSkinKey(for: system, traits: traits) else { return nil }
|
||||
|
||||
let identifier = UserDefaults.standard.string(forKey: userDefaultsKey)
|
||||
|
||||
@ -86,7 +86,7 @@ struct Settings
|
||||
|
||||
if let identifier = identifier
|
||||
{
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %@", #keyPath(ControllerSkin.gameType), gameType.rawValue, #keyPath(ControllerSkin.identifier), identifier)
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %@", #keyPath(ControllerSkin.gameType), system.gameType.rawValue, #keyPath(ControllerSkin.identifier), identifier)
|
||||
|
||||
if let controllerSkin = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first
|
||||
{
|
||||
@ -96,11 +96,11 @@ struct Settings
|
||||
|
||||
// Controller skin doesn't exist, so fall back to standard controller skin
|
||||
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == YES", #keyPath(ControllerSkin.gameType), gameType.rawValue, #keyPath(ControllerSkin.isStandard))
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == YES", #keyPath(ControllerSkin.gameType), system.gameType.rawValue, #keyPath(ControllerSkin.isStandard))
|
||||
|
||||
if let controllerSkin = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first
|
||||
{
|
||||
Settings.setPreferredControllerSkin(controllerSkin, for: gameType, traits: traits)
|
||||
Settings.setPreferredControllerSkin(controllerSkin, for: system, traits: traits)
|
||||
return controllerSkin
|
||||
}
|
||||
}
|
||||
@ -112,26 +112,29 @@ struct Settings
|
||||
return nil
|
||||
}
|
||||
|
||||
static func setPreferredControllerSkin(_ controllerSkin: ControllerSkin, for gameType: GameType, traits: DeltaCore.ControllerSkin.Traits)
|
||||
static func setPreferredControllerSkin(_ controllerSkin: ControllerSkin, for system: System, traits: DeltaCore.ControllerSkin.Traits)
|
||||
{
|
||||
guard let userDefaultKey = self.preferredControllerSkinKey(for: gameType, traits: traits) else { return }
|
||||
guard let userDefaultKey = self.preferredControllerSkinKey(for: system, traits: traits) else { return }
|
||||
|
||||
guard UserDefaults.standard.string(forKey: userDefaultKey) != controllerSkin.identifier else { return }
|
||||
|
||||
UserDefaults.standard.set(controllerSkin.identifier, forKey: userDefaultKey)
|
||||
|
||||
NotificationCenter.default.post(name: .settingsDidChange, object: controllerSkin, userInfo: [NotificationUserInfoKey.name: Name.preferredControllerSkin, NotificationUserInfoKey.gameType: gameType, NotificationUserInfoKey.traits: traits])
|
||||
NotificationCenter.default.post(name: .settingsDidChange, object: controllerSkin, userInfo: [NotificationUserInfoKey.name: Name.preferredControllerSkin, NotificationUserInfoKey.system: system, NotificationUserInfoKey.traits: traits])
|
||||
}
|
||||
}
|
||||
|
||||
private extension Settings
|
||||
{
|
||||
static func preferredControllerSkinKey(for gameType: GameType, traits: DeltaCore.ControllerSkin.Traits) -> String?
|
||||
static func preferredControllerSkinKey(for system: System, traits: DeltaCore.ControllerSkin.Traits) -> String?
|
||||
{
|
||||
let systemName: String
|
||||
|
||||
switch gameType
|
||||
switch system
|
||||
{
|
||||
case GameType.snes: systemName = "snes"
|
||||
case GameType.gba: systemName = "gba"
|
||||
default: return nil
|
||||
case .snes: systemName = "snes"
|
||||
case .gba: systemName = "gba"
|
||||
case .gbc: systemName = "gbc"
|
||||
}
|
||||
|
||||
let orientation: String
|
||||
|
||||
@ -7,37 +7,49 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import DeltaCore
|
||||
|
||||
extension SettingsViewController
|
||||
import Roxas
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
fileprivate enum Section: Int
|
||||
enum Section: Int
|
||||
{
|
||||
case controllers
|
||||
case controllerSkins
|
||||
case controllerOpacity
|
||||
}
|
||||
|
||||
fileprivate enum Segue: String
|
||||
enum Segue: String
|
||||
{
|
||||
case controllers = "controllersSegue"
|
||||
case controllerSkins = "controllerSkinsSegue"
|
||||
}
|
||||
|
||||
enum ControllerSkinsRow: Int
|
||||
{
|
||||
case snes
|
||||
case gba
|
||||
case gbc
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsViewController: UITableViewController
|
||||
{
|
||||
@IBOutlet fileprivate var controllerOpacityLabel: UILabel!
|
||||
@IBOutlet fileprivate var controllerOpacitySlider: UISlider!
|
||||
@IBOutlet private var controllerOpacityLabel: UILabel!
|
||||
@IBOutlet private var controllerOpacitySlider: UISlider!
|
||||
|
||||
fileprivate var selectionFeedbackGenerator: UISelectionFeedbackGenerator?
|
||||
private var selectionFeedbackGenerator: UISelectionFeedbackGenerator?
|
||||
|
||||
private var previousSelectedRowIndexPath: IndexPath?
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
@ -52,8 +64,15 @@ class SettingsViewController: UITableViewController
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if let indexPath = self.tableView.indexPathForSelectedRow
|
||||
if let indexPath = self.previousSelectedRowIndexPath
|
||||
{
|
||||
if indexPath.section == Section.controllers.rawValue
|
||||
{
|
||||
// Update and temporarily re-select selected row.
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
|
||||
}
|
||||
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
@ -72,6 +91,8 @@ class SettingsViewController: UITableViewController
|
||||
let indexPath = self.tableView.indexPath(for: cell)
|
||||
else { return }
|
||||
|
||||
self.previousSelectedRowIndexPath = indexPath
|
||||
|
||||
switch segueType
|
||||
{
|
||||
case Segue.controllers:
|
||||
@ -79,13 +100,14 @@ class SettingsViewController: UITableViewController
|
||||
controllersSettingsViewController.playerIndex = indexPath.row
|
||||
|
||||
case Segue.controllerSkins:
|
||||
let gameTypeControllerSkinsViewController = segue.destination as! GameTypeControllerSkinsViewController
|
||||
let systemControllerSkinsViewController = segue.destination as! SystemControllerSkinsViewController
|
||||
|
||||
switch indexPath.row
|
||||
let row = ControllerSkinsRow(rawValue: indexPath.row)!
|
||||
switch row
|
||||
{
|
||||
case 0: gameTypeControllerSkinsViewController.gameType = .snes
|
||||
case 1: gameTypeControllerSkinsViewController.gameType = .gba
|
||||
default: break
|
||||
case .snes: systemControllerSkinsViewController.system = .snes
|
||||
case .gba: systemControllerSkinsViewController.system = .gba
|
||||
case .gbc: systemControllerSkinsViewController.system = .gbc
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,18 +122,6 @@ private extension SettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
@IBAction func unwindFromControllersSettingsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
let indexPath = self.tableView.indexPathForSelectedRow
|
||||
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
|
||||
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: UITableViewScrollPosition.none)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
@IBAction func beginChangingControllerOpacity(with sender: UISlider)
|
||||
@ -143,12 +153,12 @@ private extension SettingsViewController
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
dynamic func externalControllerDidConnect(_ notification: Notification)
|
||||
@objc func externalGameControllerDidConnect(_ notification: Notification)
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
}
|
||||
|
||||
dynamic func externalControllerDidDisconnect(_ notification: Notification)
|
||||
@objc func externalGameControllerDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
|
||||
}
|
||||
@ -162,6 +172,7 @@ extension SettingsViewController
|
||||
switch section
|
||||
{
|
||||
case .controllers: return 1 // Temporarily hide other controller indexes until controller logic is finalized
|
||||
case .controllerSkins: return System.supportedSystems.count
|
||||
default: return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
|
||||
}
|
||||
}
|
||||
@ -169,24 +180,29 @@ extension SettingsViewController
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
|
||||
{
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
|
||||
if indexPath.section == Section.controllers.rawValue
|
||||
|
||||
let section = Section(rawValue: indexPath.section)!
|
||||
switch section
|
||||
{
|
||||
case .controllers:
|
||||
if indexPath.row == Settings.localControllerPlayerIndex
|
||||
{
|
||||
cell.detailTextLabel?.text = UIDevice.current.name
|
||||
}
|
||||
else if let index = ExternalControllerManager.shared.connectedControllers.index(where: { $0.playerIndex == indexPath.row })
|
||||
else if let index = ExternalGameControllerManager.shared.connectedControllers.index(where: { $0.playerIndex == indexPath.row })
|
||||
{
|
||||
let controller = ExternalControllerManager.shared.connectedControllers[index]
|
||||
let controller = ExternalGameControllerManager.shared.connectedControllers[index]
|
||||
cell.detailTextLabel?.text = controller.name
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.detailTextLabel?.text = nil
|
||||
}
|
||||
|
||||
case .controllerSkins: cell.textLabel?.text = System.supportedSystems[indexPath.row].localizedName
|
||||
default: break
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
@ -194,7 +210,7 @@ extension SettingsViewController
|
||||
{
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
let section = Section(rawValue: indexPath.section)!
|
||||
|
||||
|
||||
switch section
|
||||
{
|
||||
case Section.controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)
|
||||
|
||||
@ -60,6 +60,20 @@
|
||||
<string>com.pkware.zip-archive</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>GBC Game</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.rileytestut.delta.game.gbc</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
@ -82,17 +96,17 @@
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>gamefaqs.net</key>
|
||||
<dict>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionMinimumTLSVersion</key>
|
||||
<string>TLSv1.1</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>gamefaqs.net</key>
|
||||
<dict>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionMinimumTLSVersion</key>
|
||||
<string>TLSv1.1</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
@ -112,6 +126,8 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Press "OK" to allow Delta to use images from your Photo Library as game artwork.</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -179,6 +195,38 @@
|
||||
<string>deltaskin</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>com.rileytestut.delta.game</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>GBC Game</string>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.rileytestut.delta.game.gbc</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>gbc</string>
|
||||
<string>gb</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>Fabric</key>
|
||||
<dict>
|
||||
<key>APIKey</key>
|
||||
<string>d542629b4f6625cfd5564d27318550321272076d</string>
|
||||
<key>Kits</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>KitInfo</key>
|
||||
<dict/>
|
||||
<key>KitName</key>
|
||||
<string>Crashlytics</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
103
Delta/Systems/System.swift
Normal file
103
Delta/Systems/System.swift
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// System.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 4/30/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import SNESDeltaCore
|
||||
import GBADeltaCore
|
||||
import GBCDeltaCore
|
||||
|
||||
extension GameType
|
||||
{
|
||||
init?(fileExtension: String)
|
||||
{
|
||||
switch fileExtension.lowercased()
|
||||
{
|
||||
case "smc", "sfc", "fig": self = .snes
|
||||
case "gba": self = .gba
|
||||
case "gbc", "gb": self = .gbc
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum System
|
||||
{
|
||||
case snes
|
||||
case gba
|
||||
case gbc
|
||||
|
||||
static var supportedSystems: [System] {
|
||||
return [.snes, .gba, .gbc]
|
||||
}
|
||||
}
|
||||
|
||||
extension System
|
||||
{
|
||||
var localizedName: String {
|
||||
switch self
|
||||
{
|
||||
case .snes: return NSLocalizedString("Super Nintendo", comment: "")
|
||||
case .gba: return NSLocalizedString("Game Boy Advance", comment: "")
|
||||
case .gbc: return NSLocalizedString("Game Boy Color", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
var localizedShortName: String {
|
||||
switch self
|
||||
{
|
||||
case .snes: return NSLocalizedString("SNES", comment: "")
|
||||
case .gba: return NSLocalizedString("GBA", comment: "")
|
||||
case .gbc: return NSLocalizedString("GBC", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
var year: Int {
|
||||
switch self
|
||||
{
|
||||
case .snes: return 1990
|
||||
case .gba: return 2001
|
||||
case .gbc: return 1998
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension System
|
||||
{
|
||||
var deltaCore: DeltaCoreProtocol {
|
||||
switch self
|
||||
{
|
||||
case .snes: return SNES.core
|
||||
case .gba: return GBA.core
|
||||
case .gbc: return GBC.core
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension System
|
||||
{
|
||||
var gameType: GameType {
|
||||
switch self
|
||||
{
|
||||
case .snes: return .snes
|
||||
case .gba: return .gba
|
||||
case .gbc: return .gbc
|
||||
}
|
||||
}
|
||||
|
||||
init?(gameType: GameType)
|
||||
{
|
||||
switch gameType
|
||||
{
|
||||
case GameType.snes: self = .snes
|
||||
case GameType.gba: self = .gba
|
||||
case GameType.gbc: self = .gbc
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit 9143d01afa25f07da8f1cdb5390b49f4efb1a75f
|
||||
Subproject commit 192682bb389830944d257946d870e1d50ae4c42b
|
||||
5
Podfile
5
Podfile
@ -7,4 +7,7 @@ target 'Delta' do
|
||||
pod 'FileMD5Hash', '~> 2.0.0'
|
||||
pod 'SQLite.swift', '~> 0.11.0'
|
||||
pod 'SDWebImage', '~> 3.8'
|
||||
end
|
||||
pod 'Fabric', '~> 1.6.0'
|
||||
pod 'Crashlytics', '~> 3.8.0'
|
||||
pod 'SMCalloutView'
|
||||
end
|
||||
22
Podfile.lock
22
Podfile.lock
@ -1,22 +1,32 @@
|
||||
PODS:
|
||||
- Crashlytics (3.8.6):
|
||||
- Fabric (~> 1.6.3)
|
||||
- Fabric (1.6.13)
|
||||
- FileMD5Hash (2.0.0)
|
||||
- SDWebImage (3.8.2):
|
||||
- SDWebImage/Core (= 3.8.2)
|
||||
- SDWebImage/Core (3.8.2)
|
||||
- SQLite.swift (0.11.3):
|
||||
- SQLite.swift/standard (= 0.11.3)
|
||||
- SQLite.swift/standard (0.11.3)
|
||||
- SMCalloutView (2.1.5)
|
||||
- SQLite.swift (0.11.4):
|
||||
- SQLite.swift/standard (= 0.11.4)
|
||||
- SQLite.swift/standard (0.11.4)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Crashlytics (~> 3.8.0)
|
||||
- Fabric (~> 1.6.0)
|
||||
- FileMD5Hash (~> 2.0.0)
|
||||
- SDWebImage (~> 3.8)
|
||||
- SMCalloutView
|
||||
- SQLite.swift (~> 0.11.0)
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Crashlytics: 95d05f4e4c19a771250c4bd9ce344d996de32bbf
|
||||
Fabric: 2fb5676bc811af011a04513451f463dac6803206
|
||||
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
|
||||
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
|
||||
SQLite.swift: 99b36c22084427f0abbeb957556ce1528cf10bb3
|
||||
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
|
||||
SQLite.swift: 3e3bee21da701b5b9f87c4a672cb54f233505692
|
||||
|
||||
PODFILE CHECKSUM: e583277236d810a5e080371e73349e52a4aaa25e
|
||||
PODFILE CHECKSUM: a1fb0ce1f1bb0e73380460cc4f946d297a4d5ff1
|
||||
|
||||
COCOAPODS: 1.2.0
|
||||
COCOAPODS: 1.2.1
|
||||
|
||||
1
Pods/Crashlytics/Crashlytics.framework/README
generated
vendored
Normal file
1
Pods/Crashlytics/Crashlytics.framework/README
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
We've now combined all our supported platforms into a single podspec. As a result, we moved our submit script to a new location for Cocoapods projects: ${PODS_ROOT}/Crashlytics/submit. To avoid breaking functionality that references the old location of the submit, we've placed this dummy script that calls to the correct location, while providing a helpful warning if it is invoked. This bridge for backwards compatibility will be removed in a future release, so please heed the warning!
|
||||
6
Pods/Crashlytics/Crashlytics.framework/submit
generated
vendored
Executable file
6
Pods/Crashlytics/Crashlytics.framework/submit
generated
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
if [[ -z $PODS_ROOT ]]; then
|
||||
echo "error: The submit binary delivered by cocoapods is in a new location, under '$"{"PODS_ROOT"}"/Crashlytics/submit'. This script was put in place for backwards compatibility, but it relies on PODS_ROOT, which does not have a value in your current setup. Please update the path to the submit binary to fix this issue."
|
||||
else
|
||||
echo "warning: The submit script is now located at '$"{"PODS_ROOT"}"/Crashlytics/submit'. To remove this warning, update your path to point to this new location."
|
||||
sh "${PODS_ROOT}/Crashlytics/submit" "$@"
|
||||
fi
|
||||
39
Pods/Crashlytics/README.md
generated
Normal file
39
Pods/Crashlytics/README.md
generated
Normal file
@ -0,0 +1,39 @@
|
||||

|
||||
|
||||
Part of [Google Fabric](https://get.fabric.io), [Crashlytics](http://try.crashlytics.com/) offers the most powerful, yet lightest weight crash reporting solution for iOS. Crashlytics also provides real-time analytics through [Answers](https://answers.io/) and app distributions to testers using [Beta](http://try.crashlytics.com/beta/).
|
||||
|
||||
## Setup
|
||||
|
||||
1. Visit [https://fabric.io/sign_up](https://fabric.io/sign_up) to create your Fabric account and to download Fabric.app.
|
||||
|
||||
1. Open Fabric.app, login and select the Crashlytics SDK.
|
||||
|
||||

|
||||
|
||||
1. The Fabric app automatically detects when a project uses CocoaPods and gives you the option to install via the Podfile or Xcode.
|
||||
|
||||

|
||||
|
||||
1. Select the Podfile option and follow the installation instructions to update your Podfile. **Note:** the Crashlytics Pod includes Answers. If you have Answers included as a separate Pod it should be removed from your Podfile to avoid duplicate symbol errors.
|
||||
|
||||
```
|
||||
pod 'Fabric'
|
||||
pod 'Crashlytics'
|
||||
```
|
||||
|
||||
1. Run `pod install`
|
||||
|
||||
1. Add a Run Script Build Phase and build your app.
|
||||
|
||||

|
||||
|
||||
1. Initialize the SDK by inserting code outlined in the Fabric.app.
|
||||
|
||||
1. Run your app to finish the installation.
|
||||
|
||||
## Resources
|
||||
|
||||
* [Documentation](https://docs.fabric.io/apple/crashlytics/overview.html)
|
||||
* [Forums](https://stackoverflow.com/questions/tagged/google-fabric)
|
||||
* [Website](http://try.crashlytics.com/)
|
||||
* Follow us on Twitter: [@fabric](https://twitter.com/fabric) and [@crashlytics](https://twitter.com/crashlytics)
|
||||
BIN
Pods/Crashlytics/iOS/Crashlytics.framework/Crashlytics
generated
vendored
Executable file
BIN
Pods/Crashlytics/iOS/Crashlytics.framework/Crashlytics
generated
vendored
Executable file
Binary file not shown.
31
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/ANSCompatibility.h
generated
vendored
Normal file
31
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/ANSCompatibility.h
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// ANSCompatibility.h
|
||||
// AnswersKit
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !__has_feature(nullability)
|
||||
#define nonnull
|
||||
#define nullable
|
||||
#define _Nullable
|
||||
#define _Nonnull
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_END
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_generics)
|
||||
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
|
||||
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
|
||||
#else
|
||||
#define ANS_GENERIC_NSARRAY(type) NSArray
|
||||
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
|
||||
#endif
|
||||
210
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/Answers.h
generated
vendored
Normal file
210
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/Answers.h
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
//
|
||||
// Answers.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ANSCompatibility.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* This class exposes the Answers Events API, allowing you to track key
|
||||
* user user actions and metrics in your app.
|
||||
*/
|
||||
@interface Answers : NSObject
|
||||
|
||||
/**
|
||||
* Log a Sign Up event to see users signing up for your app in real-time, understand how
|
||||
* many users are signing up with different methods and their success rate signing up.
|
||||
*
|
||||
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
|
||||
* @param signUpSucceededOrNil The ultimate success or failure of the login
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
|
||||
success:(nullable NSNumber *)signUpSucceededOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Log In event to see users logging into your app in real-time, understand how many
|
||||
* users are logging in with different methods and their success rate logging into your app.
|
||||
*
|
||||
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
|
||||
* @param loginSucceededOrNil The ultimate success or failure of the login
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
|
||||
success:(nullable NSNumber *)loginSucceededOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Share event to see users sharing from your app in real-time, letting you
|
||||
* understand what content they're sharing from the type or genre down to the specific id.
|
||||
*
|
||||
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
|
||||
contentName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Invite Event to track how users are inviting other users into
|
||||
* your application.
|
||||
*
|
||||
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
|
||||
* items are most popular, and track plenty of other important purchase-related metrics.
|
||||
*
|
||||
* @param itemPriceOrNil The purchased item's price.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param purchaseSucceededOrNil Was the purchase succesful or unsuccesful
|
||||
* @param itemNameOrNil The human-readable form of the item's name. Example:
|
||||
* @param itemTypeOrNil The type, or genre of the item. Example: Song
|
||||
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
|
||||
*/
|
||||
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
success:(nullable NSNumber *)purchaseSucceededOrNil
|
||||
itemName:(nullable NSString *)itemNameOrNil
|
||||
itemType:(nullable NSString *)itemTypeOrNil
|
||||
itemId:(nullable NSString *)itemIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Level Start Event to track where users are in your game.
|
||||
*
|
||||
* @param levelNameOrNil The level name
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
|
||||
*/
|
||||
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Level End event to track how users are completing levels in your game.
|
||||
*
|
||||
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
|
||||
* @param scoreOrNil The score the user completed the level with.
|
||||
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed succesfully.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
|
||||
score:(nullable NSNumber *)scoreOrNil
|
||||
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
|
||||
* many users start the purchase flow, see which items are most popular, and track plenty of other important
|
||||
* purchase-related metrics.
|
||||
*
|
||||
* @param itemPriceOrNil The purchased item's price.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param itemNameOrNil The human-readable form of the item's name. Example:
|
||||
* @param itemTypeOrNil The type, or genre of the item. Example: Song
|
||||
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
itemName:(nullable NSString *)itemNameOrNil
|
||||
itemType:(nullable NSString *)itemTypeOrNil
|
||||
itemId:(nullable NSString *)itemIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
|
||||
* users are doing this and how much they're spending per checkout, and see how it related to other important
|
||||
* purchase-related metrics.
|
||||
*
|
||||
* @param totalPriceOrNil The total price of the cart.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param itemCountOrNil The number of items in the cart.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
itemCount:(nullable NSNumber *)itemCountOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Rating event to see users rating content within your app in real-time and understand what
|
||||
* content is most engaging, from the type or genre down to the specific id.
|
||||
*
|
||||
* @param ratingOrNil The integer rating given by the user.
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logRating:(nullable NSNumber *)ratingOrNil
|
||||
contentName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Content View event to see users viewing content within your app in real-time and
|
||||
* understand what content is most engaging, from the type or genre down to the specific id.
|
||||
*
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Search event allows you to see users searching within your app in real-time and understand
|
||||
* exactly what they're searching for.
|
||||
*
|
||||
* @param queryOrNil The user's query.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
|
||||
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
|
||||
* the name of the event, since this is how the event will appear in Answers.
|
||||
*
|
||||
* @param eventName The human-readable name for the event.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
|
||||
* must be <code>NSString</code> and and values must be <code>NSNumber</code> or <code>NSString</code>.
|
||||
* @discussion How we treat <code>NSNumbers</code>:
|
||||
* We will provide information about the distribution of values over time.
|
||||
*
|
||||
* How we treat <code>NSStrings</code>:
|
||||
* NSStrings are used as categorical data, allowing comparison across different category values.
|
||||
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
|
||||
* truncated.
|
||||
*
|
||||
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
|
||||
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
|
||||
* engagement.
|
||||
*/
|
||||
+ (void)logCustomEventWithName:(NSString *)eventName
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
33
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSAttributes.h
generated
vendored
Normal file
33
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSAttributes.h
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// CLSAttributes.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
|
||||
|
||||
#if !__has_feature(nullability)
|
||||
#define nonnull
|
||||
#define nullable
|
||||
#define _Nullable
|
||||
#define _Nonnull
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_END
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_generics)
|
||||
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
|
||||
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
|
||||
#else
|
||||
#define CLS_GENERIC_NSARRAY(type) NSArray
|
||||
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
|
||||
#endif
|
||||
64
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSLogging.h
generated
vendored
Normal file
64
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSLogging.h
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// CLSLogging.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
#ifdef __OBJC__
|
||||
#import "CLSAttributes.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
|
||||
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
|
||||
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
|
||||
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
|
||||
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
|
||||
*
|
||||
* Example output:
|
||||
* -[AppDelegate login:] line 134 $ login start
|
||||
*
|
||||
* If you would like to change this macro, create a new header file, unset our define and then define
|
||||
* your own version. Make sure this new header file is imported after the Crashlytics header file.
|
||||
*
|
||||
* #undef CLS_LOG
|
||||
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
|
||||
*
|
||||
**/
|
||||
#ifdef __OBJC__
|
||||
#ifdef DEBUG
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#else
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
|
||||
* and will only be visible in your Crashlytics dashboard.
|
||||
*
|
||||
**/
|
||||
|
||||
#ifdef __OBJC__
|
||||
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will show up in the system.log
|
||||
* and your Crashlytics dashboard. It is not recommended for Release builds.
|
||||
*
|
||||
**/
|
||||
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
103
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSReport.h
generated
vendored
Normal file
103
Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSReport.h
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// CLSReport.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CLSAttributes.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details.
|
||||
**/
|
||||
@protocol CLSCrashReport <NSObject>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
|
||||
@property (nonatomic, copy, readonly) NSString *bundleVersion;
|
||||
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
|
||||
@property (nonatomic, readonly, nullable) NSDate *crashedOnDate;
|
||||
@property (nonatomic, copy, readonly) NSString *OSVersion;
|
||||
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can
|
||||
* use this class to get information about the event, and can also set some values after the
|
||||
* event has occured.
|
||||
**/
|
||||
@interface CLSReport : NSObject <CLSCrashReport>
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Returns the session identifier for the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
|
||||
/**
|
||||
* Returns the custom key value data for the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
|
||||
|
||||
/**
|
||||
* Returns the CFBundleVersion of the application that generated the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *bundleVersion;
|
||||
|
||||
/**
|
||||
* Returns the CFBundleShortVersionString of the application that generated the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
|
||||
|
||||
/**
|
||||
* Returns the date that the report was created.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSDate *dateCreated;
|
||||
|
||||
/**
|
||||
* Returns the os version that the application crashed on.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *OSVersion;
|
||||
|
||||
/**
|
||||
* Returns the os build version that the application crashed on.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
|
||||
|
||||
/**
|
||||
* Returns YES if the report contains any crash information, otherwise returns NO.
|
||||
**/
|
||||
@property (nonatomic, assign, readonly) BOOL isCrash;
|
||||
|
||||
/**
|
||||
* You can use this method to set, after the event, additional custom keys. The rules
|
||||
* and semantics for this method are the same as those documented in Crashlytics.h. Be aware
|
||||
* that the maximum size and count of custom keys is still enforced, and you can overwrite keys
|
||||
* and/or cause excess keys to be deleted by using this method.
|
||||
**/
|
||||
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Record an application-specific user identifier. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userIdentifier;
|
||||
|
||||
/**
|
||||
* Record a user name. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userName;
|
||||
|
||||
/**
|
||||
* Record a user email. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userEmail;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user