Merge branch 'release/beta5'

This commit is contained in:
Riley Testut 2017-10-16 13:47:49 -07:00
commit 93ffb83e94
183 changed files with 15651 additions and 2721 deletions

3
.gitmodules vendored
View File

@ -10,3 +10,6 @@
[submodule "Cores/GBADeltaCore"] [submodule "Cores/GBADeltaCore"]
path = Cores/GBADeltaCore path = Cores/GBADeltaCore
url = git@github.com:rileytestut/GBADeltaCore.git 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

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

@ -0,0 +1 @@
Subproject commit 9d714254ace7bd5d63805d434f4ccd1f2a947951

@ -1 +1 @@
Subproject commit e49d318e2e18034b2bccea34047800956298de4a Subproject commit a354c3700fc4576bbefb6a8324bcd7a121d9b934

View File

@ -27,12 +27,12 @@
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF0418131D01E93400E85BCF /* GBADeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */; };
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF107EC31BF413F000E0C32C /* GamesViewController.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 */; }; BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.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 */; }; 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 */; }; 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 */; }; BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; }; BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF31878A1D489AAA00BD020D /* CheatValidator.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 */; }; BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34FA101CF1899D006624C7 /* CheatTextView.swift */; };
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; }; BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; }; 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 */; }; BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */; };
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; }; BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; }; BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.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 */; }; BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */; };
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; }; BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; };
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; }; BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; };
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */; }; BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */; };
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426D1E09BC5D0051894B /* DatabaseManager.swift */; }; BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426D1E09BC5D0051894B /* DatabaseManager.swift */; };
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF59426E1E09BC5D0051894B /* GamesDatabase.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 */; }; BF59427C1E09BC830051894B /* Cheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942771E09BC830051894B /* Cheat.swift */; };
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942781E09BC830051894B /* ControllerSkin.swift */; }; BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942781E09BC830051894B /* ControllerSkin.swift */; };
BF59427E1E09BC830051894B /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942791E09BC830051894B /* Game.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 */; }; BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */; };
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; }; BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; }; 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 */; }; BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
BF696B801D9B2B02009639E0 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF696B7F1D9B2B02009639E0 /* Theme.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 */; }; BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; }; BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
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 */; }; 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 */; }; BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */; }; BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */; };
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.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, ); }; }; 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 */; }; BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63BDE91D389EEB00FCB040 /* GameViewController.swift */; };
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.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 */; }; BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.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 */; }; 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 */; }; 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 */; }; BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; }; 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, ); }; }; 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 */; }; BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; };
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; }; BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; };
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; };
@ -113,6 +134,7 @@
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */, BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */,
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */,
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */, BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */,
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */, BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */,
BF0418151D01E93400E85BCF /* GBADeltaCore.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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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; path = ControllersSettingsViewController.swift; sourceTree = "<group>"; };
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllersSettingsViewController.swift; path = Controllers/ControllersSettingsViewController.swift; sourceTree = "<group>"; };
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewGameViewController.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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; name = CheatValidator.swift; path = "Pause Menu/Cheats/CheatValidator.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; name = EditCheatViewController.swift; path = "Pause Menu/Cheats/EditCheatViewController.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; name = CheatTextView.swift; path = "Pause Menu/Cheats/CheatTextView.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; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.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>"; }; 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>"; }; 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; name = PausePresentationController.swift; path = "Pause Menu/Presentation Controller/PausePresentationController.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 = "Pause Menu/Presentation Controller/PausePresentationControllerContentView.xib"; 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; name = PauseStoryboardSegue.swift; path = "Pause Menu/Segues/PauseStoryboardSegue.swift"; sourceTree = "<group>"; }; 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; name = SaveStatesViewController.swift; path = "Pause Menu/Save States/SaveStatesViewController.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; name = PauseTransitionCoordinator.swift; path = "Pause Menu/Segues/PauseTransitionCoordinator.swift"; sourceTree = "<group>"; }; BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseTransitionCoordinator.swift; sourceTree = "<group>"; };
BF5942581E09BB810051894B /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Action.swift; path = Components/Action.swift; sourceTree = "<group>"; }; BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 2.xcdatamodel"; sourceTree = "<group>"; };
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadControllerSkinImageOperation.swift; path = Components/Loading/LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; }; BF4828831F9027B600028B97 /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Delta.xcdatamodel; sourceTree = "<group>"; };
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadImageURLOperation.swift; path = Components/Loading/LoadImageURLOperation.swift; sourceTree = "<group>"; }; BF4828851F9028F500028B97 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewCell.swift; path = "Components/Collection View/GridCollectionViewCell.swift"; sourceTree = "<group>"; }; BF4828871F90290F00028B97 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = "<group>"; };
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewLayout.swift; path = "Components/Collection View/GridCollectionViewLayout.swift"; sourceTree = "<group>"; }; BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; };
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseManager.swift; path = Database/DatabaseManager.swift; sourceTree = "<group>"; }; BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadImageURLOperation.swift; sourceTree = "<group>"; };
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabase.swift; path = Database/OpenVGDB/GamesDatabase.swift; sourceTree = "<group>"; }; BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewCell.swift; sourceTree = "<group>"; };
BF5942721E09BC700051894B /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; name = Model.xcdatamodel; path = Database/Model/Model.xcdatamodel; sourceTree = "<group>"; }; BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewLayout.swift; sourceTree = "<group>"; };
BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Cheat.swift; path = Database/Model/Human/Cheat.swift; sourceTree = "<group>"; }; BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkin.swift; path = Database/Model/Human/ControllerSkin.swift; sourceTree = "<group>"; }; BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesDatabase.swift; sourceTree = "<group>"; };
BF5942791E09BC830051894B /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Game.swift; path = Database/Model/Human/Game.swift; sourceTree = "<group>"; }; BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cheat.swift; sourceTree = "<group>"; };
BF59427A1E09BC830051894B /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollection.swift; path = Database/Model/Human/GameCollection.swift; sourceTree = "<group>"; }; BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkin.swift; sourceTree = "<group>"; };
BF59427B1E09BC830051894B /* SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveState.swift; path = Database/Model/Human/SaveState.swift; sourceTree = "<group>"; }; BF5942791E09BC830051894B /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = "<group>"; };
BF5942811E09BC8B0051894B /* _Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _Cheat.swift; path = Database/Model/Machine/_Cheat.swift; sourceTree = "<group>"; }; BF59427A1E09BC830051894B /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = "<group>"; };
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _ControllerSkin.swift; path = Database/Model/Machine/_ControllerSkin.swift; sourceTree = "<group>"; }; BF59427B1E09BC830051894B /* SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveState.swift; sourceTree = "<group>"; };
BF5942831E09BC8B0051894B /* _Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _Game.swift; path = Database/Model/Machine/_Game.swift; sourceTree = "<group>"; }; BF5942811E09BC8B0051894B /* _Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Cheat.swift; sourceTree = "<group>"; };
BF5942841E09BC8B0051894B /* _GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _GameCollection.swift; path = Database/Model/Machine/_GameCollection.swift; sourceTree = "<group>"; }; BF5942821E09BC8B0051894B /* _ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _ControllerSkin.swift; sourceTree = "<group>"; };
BF5942851E09BC8B0051894B /* _SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = _SaveState.swift; path = Database/Model/Machine/_SaveState.swift; sourceTree = "<group>"; }; BF5942831E09BC8B0051894B /* _Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Game.swift; sourceTree = "<group>"; };
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ControllerSkinConfigurations.h; path = Database/Model/Misc/ControllerSkinConfigurations.h; sourceTree = "<group>"; }; BF5942841E09BC8B0051894B /* _GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _GameCollection.swift; sourceTree = "<group>"; };
BF59428D1E09BCFB0051894B /* ImportController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImportController.swift; path = Components/Importing/ImportController.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; }; BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
BF7AE8041C2E858400B1B5BC /* 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>"; }; 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>"; }; BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuController.swift; sourceTree = "<group>"; };
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseBrowserViewController.swift; path = Database/OpenVGDB/GamesDatabaseBrowserViewController.swift; sourceTree = "<group>"; }; BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCalloutView.swift; sourceTree = "<group>"; };
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkinTableViewCell.swift; path = "Controller Skins/ControllerSkinTableViewCell.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; }; 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>"; }; 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; }; 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>"; }; 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; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.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>"; }; 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>"; }; 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; }; 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>"; }; BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadataTableViewCell.swift; path = Database/OpenVGDB/GameMetadataTableViewCell.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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; name = GamesStoryboardSegue.swift; path = Segues/GamesStoryboardSegue.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; name = InitialGamesStoryboardSegue.swift; path = Segues/InitialGamesStoryboardSegue.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; name = LaunchViewController.swift; path = Launch/LaunchViewController.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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
@ -224,6 +267,7 @@
BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */, BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */,
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */, BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */,
BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */, BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */,
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */,
4FE8465FD28810191C3E5212 /* Pods_Delta.framework in Frameworks */, 4FE8465FD28810191C3E5212 /* Pods_Delta.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -238,14 +282,15 @@
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */, BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */,
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */, BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */,
BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */, BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */,
BF11734C1DA32A5200047DF8 /* GameType+Localization.swift */,
BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */, BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */,
BF59428F1E09BD1A0051894B /* NSFetchedResultsController+Conveniences.h */, BF59428F1E09BD1A0051894B /* NSFetchedResultsController+Conveniences.h */,
BF5942901E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m */, BF5942901E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m */,
BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */, BF5942911E09BD1A0051894B /* NSManagedObject+Conveniences.swift */,
BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */, BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */,
BFC314761E0C8CFC0056E3A8 /* GameType+Delta.swift */, BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */,
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */, BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
BFC6F7B71F435BC500221B96 /* Input+Display.swift */,
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -254,18 +299,20 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */, BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */,
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */,
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */,
); );
name = Controllers; path = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF1DAD5B1D9F574900E752A7 /* Controller Skins */ = { BF1DAD5B1D9F574900E752A7 /* Controller Skins */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF1DAD5C1D9F576000E752A7 /* GameTypeControllerSkinsViewController.swift */, BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */,
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */, BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */,
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */, BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */,
); );
name = "Controller Skins"; path = "Controller Skins";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF353FFB1C5DA2F600C1184C /* Presentation Controller */ = { BF353FFB1C5DA2F600C1184C /* Presentation Controller */ = {
@ -274,7 +321,7 @@
BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */, BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */,
BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */, BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */,
); );
name = "Presentation Controller"; path = "Presentation Controller";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF3540031C5DA6D800C1184C /* Save States */ = { BF3540031C5DA6D800C1184C /* Save States */ = {
@ -283,7 +330,7 @@
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */, BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */,
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */, BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */,
); );
name = "Save States"; path = "Save States";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF46894D1AAC469800A2586D /* Game Selection */ = { BF46894D1AAC469800A2586D /* Game Selection */ = {
@ -299,12 +346,12 @@
BF5942571E09BB5D0051894B /* Components */ = { BF5942571E09BB5D0051894B /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF5942581E09BB810051894B /* Action.swift */, BF4828871F90290F00028B97 /* Action.swift */,
BF59428C1E09BCE50051894B /* Importing */, BFE0229C1F5B56840052D888 /* Popover Menu */,
BF5942671E09BBB70051894B /* Collection View */, BF5942671E09BBB70051894B /* Collection View */,
BF5942601E09BBA80051894B /* Loading */, BF5942601E09BBA80051894B /* Loading */,
); );
name = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942601E09BBA80051894B /* Loading */ = { BF5942601E09BBA80051894B /* Loading */ = {
@ -313,7 +360,7 @@
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */, BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */,
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */, BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */,
); );
name = Loading; path = Loading;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942671E09BBB70051894B /* Collection View */ = { BF5942671E09BBB70051894B /* Collection View */ = {
@ -322,28 +369,31 @@
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */, BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */,
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */, BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */,
); );
name = "Collection View"; path = "Collection View";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF59426C1E09BC450051894B /* Database */ = { BF59426C1E09BC450051894B /* Database */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */, BF59426D1E09BC5D0051894B /* DatabaseManager.swift */,
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */,
BF5942711E09BC690051894B /* Model */, BF5942711E09BC690051894B /* Model */,
BF95E2751E49763D0030E7AD /* OpenVGDB */, BF95E2751E49763D0030E7AD /* OpenVGDB */,
); );
name = Database; path = Database;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942711E09BC690051894B /* Model */ = { BF5942711E09BC690051894B /* Model */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF5942721E09BC700051894B /* Model.xcdatamodel */, BF4828811F9027B600028B97 /* Delta.xcdatamodeld */,
BF5942741E09BC740051894B /* Human */, BF5942741E09BC740051894B /* Human */,
BF5942751E09BC780051894B /* Machine */, BF5942751E09BC780051894B /* Machine */,
BF6B82A31F7CC29A00042BFB /* Transformers */,
BFEF24F01F7DD4B600454C62 /* Migrations */,
BF5942761E09BC7C0051894B /* Misc */, BF5942761E09BC7C0051894B /* Misc */,
); );
name = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942741E09BC740051894B /* Human */ = { BF5942741E09BC740051894B /* Human */ = {
@ -353,9 +403,10 @@
BF5942781E09BC830051894B /* ControllerSkin.swift */, BF5942781E09BC830051894B /* ControllerSkin.swift */,
BF5942791E09BC830051894B /* Game.swift */, BF5942791E09BC830051894B /* Game.swift */,
BF59427A1E09BC830051894B /* GameCollection.swift */, BF59427A1E09BC830051894B /* GameCollection.swift */,
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */,
BF59427B1E09BC830051894B /* SaveState.swift */, BF59427B1E09BC830051894B /* SaveState.swift */,
); );
name = Human; path = Human;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942751E09BC780051894B /* Machine */ = { BF5942751E09BC780051894B /* Machine */ = {
@ -365,9 +416,10 @@
BF5942821E09BC8B0051894B /* _ControllerSkin.swift */, BF5942821E09BC8B0051894B /* _ControllerSkin.swift */,
BF5942831E09BC8B0051894B /* _Game.swift */, BF5942831E09BC8B0051894B /* _Game.swift */,
BF5942841E09BC8B0051894B /* _GameCollection.swift */, BF5942841E09BC8B0051894B /* _GameCollection.swift */,
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */,
BF5942851E09BC8B0051894B /* _SaveState.swift */, BF5942851E09BC8B0051894B /* _SaveState.swift */,
); );
name = Machine; path = Machine;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF5942761E09BC7C0051894B /* Misc */ = { BF5942761E09BC7C0051894B /* Misc */ = {
@ -375,16 +427,17 @@
children = ( children = (
BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */, BF59428B1E09BC930051894B /* ControllerSkinConfigurations.h */,
); );
name = Misc; path = Misc;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF59428C1E09BCE50051894B /* Importing */ = { BF59428C1E09BCE50051894B /* Importing */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF59428D1E09BCFB0051894B /* ImportController.swift */, BF59428D1E09BCFB0051894B /* ImportController.swift */,
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */, BF6BF3121EB7E47F008E83CD /* ImportOption.swift */,
BF6BF3161EB820F4008E83CD /* Import Options */,
); );
name = Importing; path = Importing;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF696B7E1D9B2AE6009639E0 /* Theming */ = { BF696B7E1D9B2AE6009639E0 /* Theming */ = {
@ -392,7 +445,26 @@
children = ( children = (
BF696B7F1D9B2B02009639E0 /* Theme.swift */, 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>"; sourceTree = "<group>";
}; };
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = { BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = {
@ -400,14 +472,14 @@
children = ( children = (
BF353FF41C5D837600C1184C /* PauseMenu.storyboard */, BF353FF41C5D837600C1184C /* PauseMenu.storyboard */,
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */, BF353FF11C5D7FB000C1184C /* PauseViewController.swift */,
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */, BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */,
BF353FF81C5D870B00C1184C /* PauseItem.swift */, BF353FF81C5D870B00C1184C /* MenuItem.swift */,
BF3540031C5DA6D800C1184C /* Save States */, BF3540031C5DA6D800C1184C /* Save States */,
BFC9B7371CEFCD08008629BB /* Cheats */, BFC9B7371CEFCD08008629BB /* Cheats */,
BF353FFB1C5DA2F600C1184C /* Presentation Controller */, BF353FFB1C5DA2F600C1184C /* Presentation Controller */,
BF912E481C5CB5D50041527C /* Segues */, BF912E481C5CB5D50041527C /* Segues */,
); );
name = "Pause Menu"; path = "Pause Menu";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF912E481C5CB5D50041527C /* Segues */ = { BF912E481C5CB5D50041527C /* Segues */ = {
@ -416,23 +488,33 @@
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */, BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */,
BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */, BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */,
); );
name = Segues; path = Segues;
sourceTree = "<group>";
};
BF930FFB1EB6D6EC00E8DBA0 /* Systems */ = {
isa = PBXGroup;
children = (
BF4828851F9028F500028B97 /* System.swift */,
);
path = Systems;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF95E2751E49763D0030E7AD /* OpenVGDB */ = { BF95E2751E49763D0030E7AD /* OpenVGDB */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BF6BF31F1EB82362008E83CD /* GamesDatabase.storyboard */,
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */, BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */, BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */, BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */, BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */,
); );
name = OpenVGDB; path = OpenVGDB;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = { BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */,
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */, BF0418131D01E93400E85BCF /* GBADeltaCore.framework */,
BF27CC941BCB7B7A00A20D89 /* GameController.framework */, BF27CC941BCB7B7A00A20D89 /* GameController.framework */,
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */, BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */,
@ -467,7 +549,17 @@
BF34FA101CF1899D006624C7 /* CheatTextView.swift */, BF34FA101CF1899D006624C7 /* CheatTextView.swift */,
BF31878A1D489AAA00BD020D /* CheatValidator.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>"; sourceTree = "<group>";
}; };
BFEC732F1AAECCBD00650035 /* Resources */ = { BFEC732F1AAECCBD00650035 /* Resources */ = {
@ -479,12 +571,29 @@
path = Resources; path = Resources;
sourceTree = "<group>"; 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 = { BFFA71CE1AAC406100EE9DD1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BFFA71D91AAC406100EE9DD1 /* Delta */, BFFA71D91AAC406100EE9DD1 /* Delta */,
BFEC732F1AAECCBD00650035 /* Resources */,
BF9F4FCD1AAD7B25004C9500 /* Frameworks */, BF9F4FCD1AAD7B25004C9500 /* Frameworks */,
BFEC732F1AAECCBD00650035 /* Resources */,
BFFA71D81AAC406100EE9DD1 /* Products */, BFFA71D81AAC406100EE9DD1 /* Products */,
FD1E8AE87FA2DB8793F7B937 /* Pods */, FD1E8AE87FA2DB8793F7B937 /* Pods */,
); );
@ -509,6 +618,8 @@
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */, BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */,
BFAA1FEB1B8AA4E800495943 /* Settings */, BFAA1FEB1B8AA4E800495943 /* Settings */,
BF59426C1E09BC450051894B /* Database */, BF59426C1E09BC450051894B /* Database */,
BF59428C1E09BCE50051894B /* Importing */,
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
BF5942571E09BB5D0051894B /* Components */, BF5942571E09BB5D0051894B /* Components */,
BF696B7E1D9B2AE6009639E0 /* Theming */, BF696B7E1D9B2AE6009639E0 /* Theming */,
BF090CEE1B490C1A00DCAB45 /* Extensions */, BF090CEE1B490C1A00DCAB45 /* Extensions */,
@ -532,6 +643,7 @@
children = ( children = (
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */, BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */, BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */,
BF15AF831F54B43B009B6AAB /* ActionInput.swift */,
); );
path = Emulation; path = Emulation;
sourceTree = "<group>"; sourceTree = "<group>";
@ -544,7 +656,7 @@
BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */, BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */,
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */, BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */,
); );
name = Segues; path = Segues;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
BFFC46211D59848000AF2CC6 /* Launch */ = { BFFC46211D59848000AF2CC6 /* Launch */ = {
@ -553,7 +665,7 @@
BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */, BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */,
BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */, BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */,
); );
name = Launch; path = Launch;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FD1E8AE87FA2DB8793F7B937 /* Pods */ = { FD1E8AE87FA2DB8793F7B937 /* Pods */ = {
@ -579,6 +691,7 @@
BF9F4FCC1AAD7AEE004C9500 /* Embed Frameworks */, BF9F4FCC1AAD7AEE004C9500 /* Embed Frameworks */,
B444B2BB31CBCEE7D86E943D /* [CP] Embed Pods Frameworks */, B444B2BB31CBCEE7D86E943D /* [CP] Embed Pods Frameworks */,
3521C8FA1BB6004A6E9FE324 /* [CP] Copy Pods Resources */, 3521C8FA1BB6004A6E9FE324 /* [CP] Copy Pods Resources */,
BF6BF3281EB897F6008E83CD /* Fabric */,
); );
buildRules = ( buildRules = (
); );
@ -596,7 +709,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0700; LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0820; LastUpgradeCheck = 0910;
ORGANIZATIONNAME = "Riley Testut"; ORGANIZATIONNAME = "Riley Testut";
TargetAttributes = { TargetAttributes = {
BF14D8941DE7A512002CA1BE = { BF14D8941DE7A512002CA1BE = {
@ -607,7 +720,8 @@
BFFA71D61AAC406100EE9DD1 = { BFFA71D61AAC406100EE9DD1 = {
CreatedOnToolsVersion = 6.3; CreatedOnToolsVersion = 6.3;
DevelopmentTeam = 6XVY5G3U44; DevelopmentTeam = 6XVY5G3U44;
LastSwiftMigration = 0800; LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
SystemCapabilities = { SystemCapabilities = {
com.apple.iCloud = { com.apple.iCloud = {
enabled = 1; enabled = 1;
@ -641,6 +755,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */, BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */,
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */,
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */, BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */, BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */, BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
@ -697,6 +812,20 @@
shellPath = /bin/sh; 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\""; 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 */ = { DBD91E7D7EC2729786B4C5B1 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -709,7 +838,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
@ -719,8 +848,12 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */,
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */, BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */,
BF6BF3181EB82111008E83CD /* iTunesImportOption.swift in Sources */,
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */,
BF59427C1E09BC830051894B /* Cheat.swift in Sources */, BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */, BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */, BF59427E1E09BC830051894B /* Game.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */, BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
@ -729,27 +862,34 @@
BF5942801E09BC830051894B /* SaveState.swift in Sources */, BF5942801E09BC830051894B /* SaveState.swift in Sources */,
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */, BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */, BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */, BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */,
BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.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 */, BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */, BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */, BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */, BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */, BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */, BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
BFDC7B9F1F7DE0CF0052A7C5 /* Delta.xcmappingmodel in Sources */,
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */, BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */, BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.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 */, BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */, BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */, BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */, BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
BF5942881E09BC8B0051894B /* _Game.swift in Sources */, BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */, BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */,
BF11734D1DA32A5200047DF8 /* GameType+Localization.swift in Sources */,
BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */, BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */,
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */, BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */, BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */,
@ -758,14 +898,17 @@
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */, BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */, BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */, BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */, BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */, BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */, BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */, BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
BFC314771E0C8CFC0056E3A8 /* GameType+Delta.swift in Sources */,
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */, BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */, BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */,
BF4828861F9028F500028B97 /* System.swift in Sources */,
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */, BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */,
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */, BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */,
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */, BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */,
@ -775,12 +918,16 @@
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */, BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */,
BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */, BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */,
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */, BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */,
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */,
BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */, BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */,
BF4828881F90290F00028B97 /* Action.swift in Sources */,
BF6BF3271EB87EB8008E83CD /* PhotoLibraryImportOption.swift in Sources */,
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */, BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */,
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */, BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
BF59425C1E09BB810051894B /* Action.swift in Sources */,
BF696B801D9B2B02009639E0 /* Theme.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 */, BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */,
BF59427F1E09BC830051894B /* GameCollection.swift in Sources */, BF59427F1E09BC830051894B /* GameCollection.swift in Sources */,
); );
@ -795,7 +942,17 @@
BF353FF51C5D837600C1184C /* Base */, BF353FF51C5D837600C1184C /* Base */,
); );
name = PauseMenu.storyboard; 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 */ = { BFFA71E01AAC406100EE9DD1 /* Main.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -803,8 +960,8 @@
BFFA71E11AAC406100EE9DD1 /* Base */, BFFA71E11AAC406100EE9DD1 /* Base */,
); );
name = Main.storyboard; name = Main.storyboard;
path = .; path = Delta;
sourceTree = "<group>"; sourceTree = SOURCE_ROOT;
}; };
BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */ = { BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -812,7 +969,8 @@
BFFC46451D59861000AF2CC6 /* Base */, BFFC46451D59861000AF2CC6 /* Base */,
); );
name = LaunchScreen.storyboard; name = LaunchScreen.storyboard;
sourceTree = "<group>"; path = Delta;
sourceTree = SOURCE_ROOT;
}; };
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
@ -841,20 +999,26 @@
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
@ -891,14 +1055,20 @@
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@ -934,12 +1104,17 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements"; 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"; INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 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_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0; SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
}; };
name = Debug; name = Debug;
}; };
@ -950,12 +1125,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements"; 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"; INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 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_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0; SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
}; };
name = Release; name = Release;
}; };
@ -990,6 +1171,20 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* 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 */; rootObject = BFFA71CF1AAC406100EE9DD1 /* Project object */;
} }

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0820" LastUpgradeVersion = "0910"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "NO" parallelizeBuildables = "NO"
@ -62,6 +62,20 @@
ReferencedContainer = "container:Cores/GBADeltaCore/GBADeltaCore.xcodeproj"> ReferencedContainer = "container:Cores/GBADeltaCore/GBADeltaCore.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </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 <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
@ -82,6 +96,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
</Testables> </Testables>
@ -101,6 +116,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"

View File

@ -13,6 +13,9 @@
<FileRef <FileRef
location = "group:Cores/GBADeltaCore/GBADeltaCore.xcodeproj"> location = "group:Cores/GBADeltaCore/GBADeltaCore.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Cores/GBCDeltaCore/GBCDeltaCore.xcodeproj">
</FileRef>
<FileRef <FileRef
location = "group:External/Roxas/Roxas.xcodeproj"> location = "group:External/Roxas/Roxas.xcodeproj">
</FileRef> </FileRef>

View File

@ -9,8 +9,9 @@
import UIKit import UIKit
import DeltaCore import DeltaCore
import SNESDeltaCore
import GBADeltaCore import Fabric
import Crashlytics
@UIApplicationMain @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate class AppDelegate: UIResponder, UIApplicationDelegate
@ -19,10 +20,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{ {
Fabric.with([Crashlytics.self])
Settings.registerDefaults() Settings.registerDefaults()
Delta.register(SNES.core) System.supportedSystems.forEach { Delta.register($0.deltaCore) }
Delta.register(GBA.core)
self.configureAppearance() self.configureAppearance()
@ -38,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate
} }
// Controllers // Controllers
ExternalControllerManager.shared.startMonitoringExternalControllers() ExternalGameControllerManager.shared.startMonitoring()
return true return true
} }
@ -77,7 +79,7 @@ extension AppDelegate
{ {
self.window?.tintColor = UIColor.deltaPurple 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) return self.openURL(url)
} }
@discardableResult fileprivate func openURL(_ url: URL) -> Bool @discardableResult private func openURL(_ url: URL) -> Bool
{ {
guard url.isFileURL else { return false } guard url.isFileURL else { return false }
let gameType = GameType.gameType(forFileExtension: url.pathExtension) if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
if gameType != .unknown || url.pathExtension.lowercased() == "zip"
{ {
return self.importGame(at: url) return self.importGame(at: url)
} }

View 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>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <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="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints with non-1.0 multipliers" 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"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -47,6 +47,6 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="Delta" width="1280" height="1140"/> <image name="Delta" width="1342" height="1196"/>
</resources> </resources>
</document> </document>

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/> <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"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -32,13 +31,13 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstAttribute="trailing" secondItem="tmn-gd-5UN" secondAttribute="trailing" id="9Rq-HM-vqk"/> <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 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"/> <constraint firstItem="tmn-gd-5UN" firstAttribute="top" secondItem="3Bk-k3-7J9" secondAttribute="top" id="nhS-aC-rUR"/>
</constraints> </constraints>
</view> </view>
<navigationItem key="navigationItem" title="Games" id="pFk-as-3k4"> <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> <connections>
<segue destination="xMK-Cs-fAS" kind="presentation" id="uN5-PN-7FK"/> <segue destination="xMK-Cs-fAS" kind="presentation" id="uN5-PN-7FK"/>
</connections> </connections>
@ -49,91 +48,16 @@
</connections> </connections>
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>
<connections>
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="7TT-mP-bjt"/>
</connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1036" y="1002"/> <point key="canvasLocation" x="1036" y="1002"/>
</scene> </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--> <!--Game Collection View Controller-->
<scene sceneID="qNA-NP-TiF"> <scene sceneID="qNA-NP-TiF">
<objects> <objects>
<collectionViewController storyboardIdentifier="gameCollectionViewController" clearsSelectionOnViewWillAppear="NO" id="kqu-75-owz" customClass="GameCollectionViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController"> <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"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <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"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@ -161,7 +85,6 @@
<connections> <connections>
<segue destination="X2o-q6-XD5" kind="unwind" identifier="unwindFromGames" unwindAction="unwindFromGamesViewControllerWith:" id="k8C-Xn-maU"/> <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="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> </connections>
</collectionViewController> </collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -260,12 +183,11 @@
<toolbarItems/> <toolbarItems/>
<nil key="simulatedBottomBarMetrics"/> <nil key="simulatedBottomBarMetrics"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wj9-1e-eev"> <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"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="M4r-sO-G4H"> <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"/> <autoresizingMask key="autoresizingMask"/>
</toolbar> </toolbar>
<connections> <connections>
@ -290,7 +212,7 @@
<navigationController storyboardIdentifier="saveStatesNavigationController" automaticallyAdjustsScrollViewInsets="NO" id="MPk-bF-nkj" sceneMemberID="viewController"> <navigationController storyboardIdentifier="saveStatesNavigationController" automaticallyAdjustsScrollViewInsets="NO" id="MPk-bF-nkj" sceneMemberID="viewController">
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="twH-3X-6DV"> <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"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@ -314,31 +236,11 @@
</objects> </objects>
<point key="canvasLocation" x="3409" y="1716"/> <point key="canvasLocation" x="3409" y="1716"/>
</scene> </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> </scenes>
<resources> <resources>
<image name="BoxArt" width="100" height="100"/> <image name="SettingsButton" width="16" height="16"/>
<image name="Settings_Button" width="22" height="22"/>
</resources> </resources>
<inferredMetricsTieBreakers> <inferredMetricsTieBreakers>
<segue reference="mzX-Bb-MaX"/>
<segue reference="Tey-6Z-UHp"/> <segue reference="Tey-6Z-UHp"/>
</inferredMetricsTieBreakers> </inferredMetricsTieBreakers>
</document> </document>

View File

@ -1,11 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<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"> <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> <dependencies>
<deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.16"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11151.4"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<customFonts key="customFonts">
<array key="Menlo.ttc">
<string>Menlo-Regular</string>
</array>
</customFonts>
<scenes> <scenes>
<!--Pause View Controller--> <!--Pause View Controller-->
<scene sceneID="Wst-Dv-TjM"> <scene sceneID="Wst-Dv-TjM">
@ -16,15 +20,17 @@
<viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/> <viewControllerLayoutGuide type="bottom" id="gF0-0U-kR7"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="oOH-ea-jcb"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p2M-dE-BJs" userLabel="Blur View"> <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"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rqN-NB-jbb"> <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> <connections>
<segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/> <segue destination="sWv-Ky-VGs" kind="embed" identifier="embedNavigationController" id="1Ja-XW-uoT"/>
</connections> </connections>
@ -63,7 +69,7 @@
<objects> <objects>
<navigationController id="sWv-Ky-VGs" sceneMemberID="viewController"> <navigationController id="sWv-Ky-VGs" sceneMemberID="viewController">
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/> <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"/> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
@ -78,9 +84,9 @@
<!--Paused--> <!--Paused-->
<scene sceneID="1md-hu-g0J"> <scene sceneID="1md-hu-g0J">
<objects> <objects>
<collectionViewController id="0jA-NY-mvB" customClass="PauseMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController"> <collectionViewController id="0jA-NY-mvB" customClass="GridMenuViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" delaysContentTouches="NO" dataMode="prototypes" id="scc-uc-vaJ"> <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="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <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"/> <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"> <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="10" id="yXv-zl-idO" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
@ -91,7 +97,7 @@
</collectionViewFlowLayout> </collectionViewFlowLayout>
<cells> <cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center"> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="60" height="80"/> <rect key="frame" x="0.0" y="0.0" width="60" height="80"/>
@ -128,7 +134,7 @@
<objects> <objects>
<collectionViewController storyboardIdentifier="saveStatesViewController" id="OOk-k7-INg" customClass="SaveStatesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController"> <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"> <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"/> <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"/> <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"> <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
@ -139,7 +145,7 @@
</collectionViewFlowLayout> </collectionViewFlowLayout>
<cells> <cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="c3N-1A-ryV" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center"> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/> <rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
@ -148,7 +154,7 @@
</collectionViewCell> </collectionViewCell>
</cells> </cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Header" id="YeY-W9-CC6" customClass="SaveStatesCollectionHeaderView" customModule="Delta" customModuleProvider="target"> <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"/> <autoresizingMask key="autoresizingMask"/>
</collectionReusableView> </collectionReusableView>
<connections> <connections>
@ -174,22 +180,22 @@
<objects> <objects>
<tableViewController id="wb8-5o-1jE" customClass="CheatsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController"> <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"> <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"/> <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"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="e8g-ZW-5lQ" customClass="CheatTableViewCell" customModule="Delta" customModuleProvider="target"> <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"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="emc-gw-KkJ" userLabel="Selected Background View"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" id="9bA-Tg-Bko"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view> </view>
@ -198,8 +204,9 @@
</vibrancyEffect> </vibrancyEffect>
</visualEffectView> </visualEffectView>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R4A-9d-DGb"> <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"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view> </view>
<vibrancyEffect> <vibrancyEffect>
@ -245,20 +252,21 @@
<objects> <objects>
<tableViewController storyboardIdentifier="editCheatViewController" id="jTR-Oe-YUJ" customClass="EditCheatViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController"> <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"> <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"/> <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"/> <color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<sections> <sections>
<tableViewSection headerTitle="Name" id="QT6-DZ-g70"> <tableViewSection headerTitle="Name" id="QT6-DZ-g70">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="ZeC-rg-QFa"> <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"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="ZeC-rg-QFa" id="UIF-fK-ApW"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <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"> <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"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/> <textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" returnKeyType="done"/>
<connections> <connections>
@ -280,13 +288,14 @@
<tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH"> <tableViewSection headerTitle="Type" footerTitle="Description" id="rvn-VK-2uH">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Tst-zn-e04"> <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"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Tst-zn-e04" id="gwV-zS-RWQ"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="xrD-ue-96Q"> <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> <segments>
<segment title="First"/> <segment title="First"/>
<segment title="Second"/> <segment title="Second"/>
@ -308,13 +317,14 @@
<tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0"> <tableViewSection headerTitle="Code" footerTitle="Description" id="rHC-nA-ga0">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="210" id="xxc-cz-sb7"> <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"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="xxc-cz-sb7" id="agU-SE-fNa"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" text="XXXXXXXX YYYYYYYY" translatesAutoresizingMaskIntoConstraints="NO" id="a17-LB-QXD" customClass="CheatTextView" customModule="Delta" customModuleProvider="target"> <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"/> <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"/> <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="26"/>
<textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/> <textInputTraits key="textInputTraits" autocapitalizationType="allCharacters" autocorrectionType="no" spellCheckingType="no" returnKeyType="done"/>

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES"> <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> <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> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PausePresentationController" customModule="Delta" customModuleProvider="target"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PausePresentationController" customModule="Delta" customModuleProvider="target">
@ -13,14 +18,14 @@
</placeholder> </placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="qFz-hB-77X"> <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> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="500" image="Pause" translatesAutoresizingMaskIntoConstraints="NO" id="cWa-Ht-i5m"> <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> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="900" constant="44" id="xki-9P-sHi"> <constraint firstAttribute="height" relation="greaterThanOrEqual" priority="900" constant="44" id="xki-9P-sHi">
<variation key="heightClass=compact" constant="25"/> <variation key="heightClass=compact" constant="25"/>
@ -28,9 +33,9 @@
</constraints> </constraints>
</imageView> </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"> <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"/> <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"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <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="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -21,15 +21,15 @@
<sections> <sections>
<tableViewSection headerTitle="Inputs" id="c6K-sJ-0vW"> <tableViewSection headerTitle="Inputs" id="c6K-sJ-0vW">
<cells> <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"> <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="56" width="375" height="44"/> <rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="jvV-ZB-Rq1" id="AVi-6C-eIS"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tls-Hv-Rx2"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -45,15 +45,15 @@
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
</tableViewCell> </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"> <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="100" width="375" height="44"/> <rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1Fv-H5-0oH" id="kFJ-zK-MLZ"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e3u-x9-IEC"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -69,15 +69,15 @@
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
</tableViewCell> </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"> <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="144" width="375" height="44"/> <rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="EcC-Be-jV5" id="9ZS-um-scR"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Cdn-11-xZe"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -93,15 +93,15 @@
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
</tableViewCell> </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"> <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="188" width="375" height="44"/> <rect key="frame" x="0.0" y="187.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hO9-Ov-vsA" id="MRi-re-XI7"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 4" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hls-3b-EaS"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -121,15 +121,15 @@
</tableViewSection> </tableViewSection>
<tableViewSection headerTitle="Controller Skins" id="Nch-k1-6pR"> <tableViewSection headerTitle="Controller Skins" id="Nch-k1-6pR">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="mBC-YU-BVK" style="IBUITableViewCellStyleDefault" id="ICf-ug-NwS"> <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="289" width="375" height="44"/> <rect key="frame" x="0.0" y="287.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ICf-ug-NwS" id="7se-sE-x9e"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Super Nintendo" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mBC-YU-BVK"> <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="15" y="0.0" width="325" height="43.5"/> <rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -138,15 +138,32 @@
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="MFD-lC-Vfk" style="IBUITableViewCellStyleDefault" id="4Sh-Mb-vqu"> <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="333" width="375" height="44"/> <rect key="frame" x="0.0" y="331.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4Sh-Mb-vqu" id="cJG-Gr-n6q"> <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="342" height="43.5"/> <rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Game Boy Advance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MFD-lC-Vfk"> <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="15" y="0.0" width="325" height="43.5"/> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <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"/> <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"> <tableViewSection headerTitle="Controller Opacity" footerTitle="Determines how translucent the controller appears, if supported by the controller skin." id="SwK-m9-8gt">
<cells> <cells>
<tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Xxk-vo-eu4"> <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"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="Xxk-vo-eu4" id="vxt-Ex-b4b"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="whi-If-wFf"> <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> <connections>
<action selector="beginChangingControllerOpacityWith:" destination="eHi-aO-uGS" eventType="touchDown" id="NG9-FX-62d"/> <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"/> <action selector="changeControllerOpacityWith:" destination="eHi-aO-uGS" eventType="valueChanged" id="Zci-tN-4uU"/>
@ -177,7 +194,7 @@
</connections> </connections>
</slider> </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"> <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> <constraints>
<constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/> <constraint firstAttribute="height" constant="21" id="ACD-qY-k0J"/>
<constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/> <constraint firstAttribute="width" constant="46" id="ZVd-ie-qRm"/>
@ -223,6 +240,78 @@
</objects> </objects>
<point key="canvasLocation" x="1555" y="471"/> <point key="canvasLocation" x="1555" y="471"/>
</scene> </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--> <!--Controllers-->
<scene sceneID="swa-DT-VKS"> <scene sceneID="swa-DT-VKS">
<objects> <objects>
@ -240,14 +329,14 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Controller Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VBO-V1-Wfu"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Player 1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="tqn-1q-p53"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <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"/> <color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -255,9 +344,6 @@
</label> </label>
</subviews> </subviews>
</tableViewCellContentView> </tableViewCellContentView>
<connections>
<segue destination="HzL-SB-qry" kind="unwind" identifier="unwindControllersSegue" unwindAction="unwindFromControllersSettingsViewController:" id="WJe-ZI-clo"/>
</connections>
</tableViewCell> </tableViewCell>
</prototypes> </prototypes>
<connections> <connections>
@ -267,16 +353,18 @@
</tableView> </tableView>
<navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/> <navigationItem key="navigationItem" title="Controllers" id="QK7-oi-2jJ"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/> <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections>
<segue destination="0QR-U9-gtx" kind="presentation" identifier="controllerInputsSegue" id="E3Y-yV-zT5"/>
</connections>
</tableViewController> </tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="HzL-SB-qry" userLabel="Exit" sceneMemberID="exit"/>
</objects> </objects>
<point key="canvasLocation" x="2221" y="471"/> <point key="canvasLocation" x="2221" y="471"/>
</scene> </scene>
<!--Game Boy Advance--> <!--Game Boy Advance-->
<scene sceneID="pkL-Te-puh"> <scene sceneID="pkL-Te-puh">
<objects> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -399,7 +487,7 @@
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController"> <navigationController automaticallyAdjustsScrollViewInsets="NO" id="ssH-mM-uG6" sceneMemberID="viewController">
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Ckw-ES-lkE"> <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"/> <autoresizingMask key="autoresizingMask"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
@ -411,5 +499,76 @@
</objects> </objects>
<point key="canvasLocation" x="889" y="471"/> <point key="canvasLocation" x="889" y="471"/>
</scene> </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> </scenes>
</document> </document>

View File

@ -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! private var imageViewWidthConstraint: NSLayoutConstraint!
fileprivate var imageViewHeightConstraint: NSLayoutConstraint! private var imageViewHeightConstraint: NSLayoutConstraint!
fileprivate var textLabelBottomAnchorConstraint: NSLayoutConstraint! private var textLabelBottomAnchorConstraint: NSLayoutConstraint!
fileprivate var textLabelVerticalSpacingConstraint: NSLayoutConstraint! private var textLabelVerticalSpacingConstraint: NSLayoutConstraint!
fileprivate var textLabelFocusedVerticalSpacingConstraint: NSLayoutConstraint? private var textLabelFocusedVerticalSpacingConstraint: NSLayoutConstraint?
override init(frame: CGRect) override init(frame: CGRect)
{ {

View File

@ -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)
}
}

View File

@ -45,6 +45,11 @@ class LoadImageURLOperation: RSTLoadOperation<UIImage, NSURL>
super.cancel() super.cancel()
self.downloadOperation?.cancel() self.downloadOperation?.cancel()
if self.isAsynchronous
{
self.finish()
}
} }
override func loadResult(completion: @escaping (UIImage?, Swift.Error?) -> Void) 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) private func loadLocalImage(completion: @escaping (UIImage?, Error?) -> Void)
{ {
let options: NSDictionary = [kCGImageSourceShouldCache as NSString: true] guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, nil) else {
guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, options) else {
completion(nil, .doesNotExist) completion(nil, .doesNotExist)
return return
} }
guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else { guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
completion(nil, .invalid) completion(nil, .invalid)
return return
} }

View 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()
}
}

View 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)
}
}
}

View 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
}
}

View File

@ -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)
}
}

View File

@ -22,6 +22,7 @@ extension DatabaseManager
{ {
case doesNotExist(URL) case doesNotExist(URL)
case invalid(URL) case invalid(URL)
case unsupported(URL)
case unknown(URL, NSError) case unknown(URL, NSError)
case saveFailed(Set<URL>, NSError) case saveFailed(Set<URL>, NSError)
@ -30,8 +31,9 @@ extension DatabaseManager
{ {
case .doesNotExist: return 0 case .doesNotExist: return 0
case .invalid: return 1 case .invalid: return 1
case .unknown: return 2 case .unsupported: return 2
case .saveFailed: return 3 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 .doesNotExist(url1), let .doesNotExist(url2)) where url1 == url2: return true
case (let .invalid(url1), let .invalid(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 .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 (let .saveFailed(urls1, error1), let .saveFailed(urls2, error2)) where urls1 == urls2 && error1 == error2: return true
case (.doesNotExist, _): return false case (.doesNotExist, _): return false
case (.invalid, _): return false case (.invalid, _): return false
case (.unsupported, _): return false
case (.unknown, _): return false case (.unknown, _): return false
case (.saveFailed, _): return false case (.saveFailed, _): return false
} }
} }
} }
} }
@ -57,16 +60,15 @@ final class DatabaseManager: NSPersistentContainer
{ {
static let shared = DatabaseManager() static let shared = DatabaseManager()
fileprivate var gamesDatabase: GamesDatabase? = nil private var gamesDatabase: GamesDatabase? = nil
private init() private init()
{ {
guard 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) let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
else { fatalError("Core Data model cannot be found. Aborting.") } else { fatalError("Core Data model cannot be found. Aborting.") }
super.init(name: "Delta", managedObjectModel: managedObjectModel) super.init(name: "Delta", managedObjectModel: managedObjectModel)
self.viewContext.automaticallyMergesChangesFromParent = true self.viewContext.automaticallyMergesChangesFromParent = true
@ -95,13 +97,13 @@ extension DatabaseManager
//MARK: - Preparation - //MARK: - Preparation -
private extension DatabaseManager private extension DatabaseManager
{ {
func prepareDatabase(completion: @escaping (Void) -> Void) func prepareDatabase(completion: @escaping () -> Void)
{ {
self.performBackgroundTask { (context) in 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) let controllerSkin = ControllerSkin(context: context)
controllerSkin.isStandard = true controllerSkin.isStandard = true
@ -173,21 +175,27 @@ extension DatabaseManager
continue 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 filename = identifier + "." + url.pathExtension
let game = Game.insertIntoManagedObjectContext(context) let game = Game(context: context)
game.identifier = identifier game.identifier = identifier
game.type = gameType
game.filename = filename game.filename = filename
let databaseMetadata = self.gamesDatabase?.metadata(for: game) let databaseMetadata = self.gamesDatabase?.metadata(for: game)
game.name = databaseMetadata?.name ?? url.deletingPathExtension().lastPathComponent game.name = databaseMetadata?.name ?? url.deletingPathExtension().lastPathComponent
game.artworkURL = databaseMetadata?.artworkURL game.artworkURL = databaseMetadata?.artworkURL
let gameCollection = GameCollection.gameSystemCollectionForPathExtension(url.pathExtension, inManagedObjectContext: context) let gameCollection = GameCollection(context: context)
game.type = GameType(rawValue: gameCollection.identifier) gameCollection.identifier = gameType.rawValue
game.gameCollections.insert(gameCollection) gameCollection.index = Int16(system.year)
gameCollection.games.insert(game)
do do
{ {
@ -212,7 +220,6 @@ extension DatabaseManager
errors.insert(.unknown(url, error)) errors.insert(.unknown(url, error))
} }
} }
do do
@ -328,9 +335,8 @@ extension DatabaseManager
guard !entry.fileName.contains("/") else { continue } guard !entry.fileName.contains("/") else { continue }
let fileExtension = (entry.fileName as NSString).pathExtension let fileExtension = (entry.fileName as NSString).pathExtension
let gameType = GameType.gameType(forFileExtension: fileExtension)
guard GameType(fileExtension: fileExtension) != nil else { continue }
guard gameType != .unknown else { continue }
// At least one entry is a valid game file, so we set archiveContainsValidGameFile to true // 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 // 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 return gameTypeDirectoryURL
} }
class func artworkURL(for game: Game) -> URL
{
let gameURL = game.fileURL
let artworkURL = gameURL.deletingPathExtension().appendingPathExtension("jpg")
return artworkURL
}
} }
//MARK: - Private - //MARK: - Private -

View File

@ -15,9 +15,9 @@ class InputStreamOutputWriter: NSObject
let inputStream: InputStream let inputStream: InputStream
let outputStream: OutputStream 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) init(inputStream: InputStream, outputStream: OutputStream)
{ {

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -61,7 +61,7 @@ public class ControllerSkin: _ControllerSkin
return self.controllerSkin?.isDebugModeEnabled ?? false 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) let controllerSkin = self.isStandard ? DeltaCore.ControllerSkin.standardControllerSkin(for: self.gameType) : DeltaCore.ControllerSkin(fileURL: self.fileURL)
return controllerSkin return controllerSkin
}() }()
@ -88,9 +88,9 @@ extension ControllerSkin: ControllerSkinProtocol
return self.controllerSkin?.image(for: traits, preferredSize: preferredSize) 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]? public func items(for traits: DeltaCore.ControllerSkin.Traits) -> [DeltaCore.ControllerSkin.Item]?

View File

@ -22,6 +22,37 @@ public class Game: _Game, GameProtocol
return fileURL 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 extension Game

View File

@ -9,48 +9,22 @@
import CoreData import CoreData
import DeltaCore import DeltaCore
import SNESDeltaCore
import GBADeltaCore
@objc(GameCollection) @objc(GameCollection)
public class GameCollection: _GameCollection public class GameCollection: _GameCollection
{ {
var name: String var name: String {
{ return self.system?.localizedName ?? NSLocalizedString("Unknown", comment: "")
let gameType = GameType(rawValue: self.identifier)
return gameType.localizedName
} }
var shortName: String var shortName: String {
{ return self.system?.localizedShortName ?? NSLocalizedString("Unknown", comment: "")
let gameType = GameType(rawValue: self.identifier)
return gameType.localizedShortName
} }
class func gameSystemCollectionForPathExtension(_ pathExtension: String?, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> GameCollection var system: System? {
{ let gameType = GameType(rawValue: self.identifier)
let gameType = GameType.gameType(forFileExtension: pathExtension ?? "")
let identifier = gameType.rawValue
let index: Int16 let system = System(gameType: gameType)
return system
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!
} }
} }

View File

@ -0,0 +1,77 @@
//
// GameControllerInputMapping.swift
// Delta
//
// Created by Riley Testut on 8/30/16.
// Copyright (c) 2016 Riley Testut. All rights reserved.
//
import Foundation
import DeltaCore
@objc(GameControllerInputMapping)
public class GameControllerInputMapping: _GameControllerInputMapping
{
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)
}
}

View File

@ -13,6 +13,7 @@ import DeltaCore
@objc public enum SaveStateType: Int16 @objc public enum SaveStateType: Int16
{ {
case auto case auto
case quick
case general case general
case locked case locked
} }
@ -78,4 +79,14 @@ public class SaveState: _SaveState, SaveStateProtocol
print(error) 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
}
} }

View File

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

File diff suppressed because one or more lines are too long

View File

@ -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
}
}
}

View File

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

View File

@ -8,9 +8,32 @@
import Foundation import Foundation
// Must be a class (not struct) so it can be used with Objective-C generics // Must be an NSObject subclass so it can be used with RSTCellContentDataSource.
class GameMetadata class GameMetadata: NSObject
{ {
var name: String? let identifier: Int
var artworkURL: URL?
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
}
} }

View File

@ -9,6 +9,11 @@
import Foundation import Foundation
import SQLite import SQLite
private extension UserDefaults
{
@NSManaged var previousGamesDatabaseVersion: Int
}
extension ExpressionType extension ExpressionType
{ {
static var name: SQLite.Expression<String?> { static var name: SQLite.Expression<String?> {
@ -19,13 +24,17 @@ extension ExpressionType
return SQLite.Expression<String?>("releaseCoverFront") return SQLite.Expression<String?>("releaseCoverFront")
} }
static var hash: SQLite.Expression<String> { static var sha1Hash: SQLite.Expression<String> {
return SQLite.Expression<String>("romHashSHA1") return SQLite.Expression<String>("romHashSHA1")
} }
static var romID: SQLite.Expression<Int> { static var romID: SQLite.Expression<Int> {
return SQLite.Expression<Int>("romID") return SQLite.Expression<Int>("romID")
} }
static var releaseID: SQLite.Expression<Int> {
return SQLite.Expression<Int>("releaseID")
}
} }
extension Table extension Table
@ -57,7 +66,9 @@ extension GamesDatabase
class GamesDatabase class GamesDatabase
{ {
fileprivate let connection: Connection static let version = -1
private let connection: Connection
init() throws init() throws
{ {
@ -71,28 +82,36 @@ class GamesDatabase
{ {
throw Error.connection(error) throw Error.connection(error)
} }
self.invalidateVirtualTableIfNeeded()
} }
func metadataResults(forGameName gameName: String) -> [GameMetadata] func metadataResults(forGameName gameName: String) -> [GameMetadata]
{ {
let releaseID = Expression<Any>.releaseID
let name = Expression<Any>.name let name = Expression<Any>.name
let artworkAddress = Expression<Any>.artworkAddress 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 do
{ {
let rows = try self.connection.prepare(query) let rows = try self.connection.prepare(query)
let results = rows.map { row -> GameMetadata in let results = rows.map { (row) -> GameMetadata in
let metadata = GameMetadata()
metadata.name = row[name] let artworkURL: URL?
if let address = row[artworkAddress] 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 return metadata
} }
@ -118,26 +137,31 @@ class GamesDatabase
func metadata(for game: Game) -> GameMetadata? func metadata(for game: Game) -> GameMetadata?
{ {
let releaseID = Expression<Any>.releaseID
let name = Expression<Any>.name let name = Expression<Any>.name
let artworkAddress = Expression<Any>.artworkAddress let artworkAddress = Expression<Any>.artworkAddress
let hash = Expression<Any>.hash
let sha1Hash = Expression<Any>.sha1Hash
let romID = Expression<Any>.romID let romID = Expression<Any>.romID
let gameHash = game.identifier.uppercased() 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 do
{ {
if let row = try self.connection.pluck(query) if let row = try self.connection.pluck(query)
{ {
let metadata = GameMetadata() let artworkURL: URL?
metadata.name = row[name]
if let address = row[artworkAddress] 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 return metadata
} }
} }
@ -152,16 +176,33 @@ class GamesDatabase
private extension 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 func prepareFTS() -> Bool
{ {
let name = Expression<Any>.name let name = Expression<Any>.name
let artworkAddress = Expression<Any>.artworkAddress let artworkAddress = Expression<Any>.artworkAddress
let releaseID = Expression<Any>.releaseID
do 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) _ = try self.connection.run(update)
} }
catch catch

View File

@ -15,11 +15,9 @@ class GamesDatabaseBrowserViewController: UITableViewController
{ {
var selectionHandler: ((GameMetadata) -> Void)? var selectionHandler: ((GameMetadata) -> Void)?
fileprivate let database: GamesDatabase? private let database: GamesDatabase?
fileprivate let dataSource: RSTArrayTableViewDataSource<GameMetadata>
fileprivate let operationQueue = RSTOperationQueue() private let dataSource: RSTArrayTableViewPrefetchingDataSource<GameMetadata, UIImage>
fileprivate let imageCache = NSCache<NSURL, UIImage>()
override init(style: UITableViewStyle) { override init(style: UITableViewStyle) {
fatalError() fatalError()
@ -37,39 +35,13 @@ class GamesDatabaseBrowserViewController: UITableViewController
print(error) print(error)
} }
self.dataSource = RSTArrayTableViewDataSource<GameMetadata>(items: []) self.dataSource = RSTArrayTableViewPrefetchingDataSource<GameMetadata, UIImage>(items: [])
let placeholderView = RSTPlaceholderView()
placeholderView.textLabel.textColor = UIColor.lightText
placeholderView.detailTextLabel.textColor = UIColor.lightText
self.dataSource.placeholderView = placeholderView
super.init(coder: aDecoder) 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.definesPresentationContext = true
self.prepareDataSource()
} }
override var preferredStatusBarStyle: UIStatusBarStyle { override var preferredStatusBarStyle: UIStatusBarStyle {
@ -83,6 +55,8 @@ class GamesDatabaseBrowserViewController: UITableViewController
self.view.backgroundColor = UIColor.deltaDarkGray self.view.backgroundColor = UIColor.deltaDarkGray
self.tableView.dataSource = self.dataSource self.tableView.dataSource = self.dataSource
self.tableView.prefetchDataSource = self.dataSource
self.tableView.indicatorStyle = .white self.tableView.indicatorStyle = .white
self.tableView.separatorColor = UIColor.gray 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) func configure(cell: GameMetadataTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
{ {
@ -112,30 +152,6 @@ extension GamesDatabaseBrowserViewController
cell.artworkImageViewTrailingConstraint.constant = 15 cell.artworkImageViewTrailingConstraint.constant = 15
cell.separatorInset.left = cell.nameLabel.frame.minX 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() 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: "") 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 extension GamesDatabaseBrowserViewController
@ -167,12 +189,6 @@ extension GamesDatabaseBrowserViewController
let metadata = self.dataSource.item(at: indexPath) let metadata = self.dataSource.item(at: indexPath)
self.selectionHandler?(metadata) 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 extension GamesDatabaseBrowserViewController: UISearchControllerDelegate
@ -193,7 +209,6 @@ extension GamesDatabaseBrowserViewController: UISearchControllerDelegate
func didDismissSearchController(_ searchController: UISearchController) func didDismissSearchController(_ searchController: UISearchController)
{ {
// Fix potentially incorrect offset if user dismisses searchController while scrolling // Fix potentially incorrect offset if user dismisses searchController while scrolling
self.tableView.setContentOffset(CGPoint.zero, animated: false) self.resetTableViewContentOffset()
self.tableView.setContentOffset(CGPoint(x: 0, y: -self.topLayoutGuide.length), animated: false)
} }
} }

View 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)
}
}

View File

@ -29,6 +29,25 @@ private extension GameViewController
self.gameType = gameType 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 class GameViewController: DeltaCore.GameViewController
@ -53,11 +72,11 @@ class GameViewController: DeltaCore.GameViewController
} }
//MARK: - Private Properties - //MARK: - Private Properties -
fileprivate var pauseViewController: PauseViewController? private var pauseViewController: PauseViewController?
fileprivate var pausingGameController: GameController? private var pausingGameController: GameController?
// Prevents the same save state from being saved multiple times // Prevents the same save state from being saved multiple times
fileprivate var pausedSaveState: PausedSaveState? { private var pausedSaveState: PausedSaveState? {
didSet didSet
{ {
if let saveState = oldValue, self.pausedSaveState == nil 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 // Sustain Buttons
fileprivate var updateSemaphores = Set<DispatchSemaphore>() private var isSelectingSustainedButtons = false
fileprivate var sustainedInputs = [ObjectIdentifier: [Input]]() private var sustainInputsMapping: SustainInputsMapping?
fileprivate var reactivateSustainedInputsQueue: OperationQueue
fileprivate var selectingSustainedButtons = false
fileprivate var sustainButtonsContentView: UIView! private var sustainButtonsContentView: UIView!
fileprivate var sustainButtonsBlurView: UIVisualEffectView! private var sustainButtonsBlurView: UIVisualEffectView!
fileprivate var sustainButtonsBackgroundView: RSTPlaceholderView! private var sustainButtonsBackgroundView: RSTPlaceholderView!
required init() required init()
{ {
self.reactivateSustainedInputsQueue = OperationQueue()
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
super.init() super.init()
self.initialize() self.initialize()
@ -100,9 +114,6 @@ class GameViewController: DeltaCore.GameViewController
required init?(coder aDecoder: NSCoder) required init?(coder aDecoder: NSCoder)
{ {
self.reactivateSustainedInputsQueue = OperationQueue()
self.reactivateSustainedInputsQueue.maxConcurrentOperationCount = 1
super.init(coder: aDecoder) super.init(coder: aDecoder)
self.initialize() self.initialize()
@ -112,8 +123,8 @@ class GameViewController: DeltaCore.GameViewController
{ {
self.delegate = self self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidConnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, 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.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil) 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) super.gameController(gameController, didActivate: input)
guard (input as? ControllerInput) != .menu else { return } if self.isSelectingSustainedButtons
if self.selectingSustainedButtons
{ {
self.addSustainedInput(input, for: gameController) guard let pausingGameController = self.pausingGameController, gameController == pausingGameController else { return }
}
else if let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)], sustainedInputs.contains(where: { $0.isEqual(input) }) if input != StandardGameControllerInput.menu
{ {
// Perform on next run loop gameController.sustain(input)
DispatchQueue.main.async {
self.reactivateSustainedInput(input, for: gameController)
} }
} }
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() 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 = UIView(frame: CGRect(x: 0, y: 0, width: self.gameView.bounds.width, height: self.gameView.bounds.height))
self.sustainButtonsContentView.translatesAutoresizingMaskIntoConstraints = false self.sustainButtonsContentView.translatesAutoresizingMaskIntoConstraints = false
self.sustainButtonsContentView.isHidden = true 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 blurEffect = UIBlurEffect(style: .dark)
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
@ -182,10 +221,10 @@ extension GameViewController
vibrancyView.contentView.addSubview(self.sustainButtonsBackgroundView) vibrancyView.contentView.addSubview(self.sustainButtonsBackgroundView)
// Auto Layout // Auto Layout
self.sustainButtonsContentView.leadingAnchor.constraint(equalTo: self.gameView.leadingAnchor).isActive = true self.sustainButtonsContentView.leadingAnchor.constraint(equalTo: gameViewContainerView.leadingAnchor).isActive = true
self.sustainButtonsContentView.trailingAnchor.constraint(equalTo: self.gameView.trailingAnchor).isActive = true self.sustainButtonsContentView.trailingAnchor.constraint(equalTo: gameViewContainerView.trailingAnchor).isActive = true
self.sustainButtonsContentView.topAnchor.constraint(equalTo: self.gameView.topAnchor).isActive = true self.sustainButtonsContentView.topAnchor.constraint(equalTo: gameViewContainerView.topAnchor).isActive = true
self.sustainButtonsContentView.bottomAnchor.constraint(equalTo: self.gameView.bottomAnchor).isActive = true self.sustainButtonsContentView.bottomAnchor.constraint(equalTo: gameViewContainerView.bottomAnchor).isActive = true
self.updateControllerSkin() self.updateControllerSkin()
self.updateControllers() self.updateControllers()
@ -220,7 +259,7 @@ extension GameViewController
if let game = self.game if let game = self.game
{ {
let fileURL = FileManager.uniqueTemporaryURL() let fileURL = FileManager.default.uniqueTemporaryURL()
self.pausedSaveState = PausedSaveState(fileURL: fileURL, gameType: game.type) self.pausedSaveState = PausedSaveState(fileURL: fileURL, gameType: game.type)
self.emulatorCore?.saveSaveState(to: fileURL) self.emulatorCore?.saveSaveState(to: fileURL)
@ -238,22 +277,27 @@ extension GameViewController
pauseViewController.saveStatesViewControllerDelegate = self pauseViewController.saveStatesViewControllerDelegate = self
pauseViewController.cheatsViewControllerDelegate = 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 pauseViewController.fastForwardItem?.action = { [unowned self] item in
guard let emulatorCore = self.emulatorCore else { return } self.performFastForwardAction(activate: item.isSelected)
emulatorCore.rate = item.selected ? emulatorCore.configuration.supportedRates.upperBound : emulatorCore.configuration.supportedRates.lowerBound
} }
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 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() self.showSustainButtonView()
pauseViewController.dismiss() pauseViewController.dismiss()
} }
// Re-set gameController as pausingGameController.
self.pausingGameController = gameController
} }
self.pauseViewController = pauseViewController self.pauseViewController = pauseViewController
@ -341,13 +385,14 @@ private extension GameViewController
{ {
@objc func updateControllers() @objc func updateControllers()
{ {
var controllers = [GameController]() let isExternalGameControllerConnected = ExternalGameControllerManager.shared.connectedControllers.contains(where: { $0.playerIndex != nil })
controllers.append(self.controllerView) if !isExternalGameControllerConnected && Settings.localControllerPlayerIndex == nil
{
Settings.localControllerPlayerIndex = 0
}
// We need to map each item as a GameControllerProtocol due to a Swift bug // If Settings.localControllerPlayerIndex is non-nil, and there isn't a connected controller with same playerIndex, show controller view.
controllers.append(contentsOf: ExternalControllerManager.shared.connectedControllers.map { $0 as GameController }) if let index = Settings.localControllerPlayerIndex, !ExternalGameControllerManager.shared.connectedControllers.contains { $0.playerIndex == index }
if let index = Settings.localControllerPlayerIndex
{ {
self.controllerView.playerIndex = index self.controllerView.playerIndex = index
self.controllerView.isHidden = false self.controllerView.isHidden = false
@ -356,44 +401,48 @@ private extension GameViewController
{ {
self.controllerView.playerIndex = nil self.controllerView.playerIndex = nil
self.controllerView.isHidden = true self.controllerView.isHidden = true
}
Settings.localControllerPlayerIndex = nil
// 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)
}
} }
self.view.setNeedsLayout() self.view.setNeedsLayout()
self.view.layoutIfNeeded() 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() 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 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 self.controllerView.controllerSkin = controllerSkin
if controllerSkin?.isTranslucent(for: traits) ?? false if controllerSkin?.isTranslucent(for: traits) ?? false
@ -411,7 +460,7 @@ private extension GameViewController
/// Save States /// Save States
extension GameViewController: SaveStatesViewControllerDelegate extension GameViewController: SaveStatesViewControllerDelegate
{ {
fileprivate func updateAutoSaveState() private func updateAutoSaveState()
{ {
// Ensures game is non-nil and also a Game subclass // Ensures game is non-nil and also a Game subclass
guard let game = self.game as? Game else { return } 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 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 = SaveState.fetchRequest(for: game, type: .auto)
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)] fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)]
var saveStates: [SaveState]? = nil
do 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 catch
{ {
print(error) 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() 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) let isRunning = (self.emulatorCore?.state == .running)
@ -523,25 +567,14 @@ extension GameViewController: SaveStatesViewControllerDelegate
} }
} }
//MARK: - SaveStatesViewControllerDelegate private func load(_ saveState: SaveStateProtocol)
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
{ {
let updatingExistingSaveState = FileManager.default.fileExists(atPath: saveState.fileURL.path) let isRunning = (self.emulatorCore?.state == .running)
self.update(saveState) if isRunning
// Dismiss if updating an existing save state.
// If creating a new one, don't dismiss.
if updatingExistingSaveState
{ {
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. // 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. // 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 if let autoSaveState = saveState as? SaveState, autoSaveState.type == .auto
{ {
let temporaryURL = FileManager.uniqueTemporaryURL() let temporaryURL = FileManager.default.uniqueTemporaryURL()
do do
{ {
@ -585,16 +618,33 @@ extension GameViewController: SaveStatesViewControllerDelegate
print(error) print(error)
} }
// Reactivate sustained inputs if isRunning
for gameController in self.emulatorCore?.gameControllers ?? []
{ {
guard let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)] else { continue } self.resumeEmulation()
for input in sustainedInputs
{
self.reactivateSustainedInput(input, for: gameController)
}
} }
}
//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() self.pauseViewController?.dismiss()
} }
@ -620,7 +670,12 @@ private extension GameViewController
{ {
func showSustainButtonView() 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 let blurEffect = self.sustainButtonsBlurView.effect
self.sustainButtonsBlurView.effect = nil self.sustainButtonsBlurView.effect = nil
@ -635,7 +690,18 @@ private extension GameViewController
func hideSustainButtonView() 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 let blurEffect = self.sustainButtonsBlurView.effect
@ -647,94 +713,76 @@ private extension GameViewController
self.sustainButtonsBlurView.effect = blurEffect 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)] guard let game = self.game as? Game else { return }
{
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) }
}
self.sustainedInputs[ObjectIdentifier(gameController)] = [] let backgroundContext = DatabaseManager.shared.newBackgroundContext()
} backgroundContext.performAndWait {
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 {
// The manual activations/deactivations here are hidden implementation details, so we won't notify ourselves about them let game = backgroundContext.object(with: game.objectID) as! Game
gameController.removeReceiver(self) let fetchRequest = SaveState.fetchRequest(for: game, type: .quick)
// Must deactivate first so core recognizes a secondary activation do
gameController.deactivate(input) {
if let quickSaveState = try fetchRequest.execute().first
let dispatchQueue = DispatchQueue(label: "com.rileytestut.Delta.sustainButtonsQueue") {
dispatchQueue.async { self.update(quickSaveState)
}
let semaphore = DispatchSemaphore(value: 0) else
self.updateSemaphores.insert(semaphore) {
let saveState = SaveState(context: backgroundContext)
// To ensure the emulator core recognizes us activating the input again, we need to wait at least two frames saveState.type = .quick
// Unfortunately we cannot init DispatchSemaphore with value less than 0 saveState.game = game
// To compensate, we simply wait twice; once the first wait returns, we wait again
semaphore.wait() self.update(saveState)
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) }
} }
// 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) 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() self.hideSustainButtonView()
} }
if let pauseViewController = self.pauseViewController, !self.selectingSustainedButtons if let pauseViewController = self.pauseViewController, !self.isSelectingSustainedButtons
{ {
pauseViewController.dismiss() pauseViewController.dismiss()
} }
@ -763,15 +816,7 @@ extension GameViewController: GameViewControllerDelegate
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
{ {
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.selectingSustainedButtons && self.view.window != nil return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
}
func gameViewControllerDidUpdate(_ gameViewController: DeltaCore.GameViewController)
{
for semaphore in self.updateSemaphores
{
semaphore.signal()
}
} }
} }
@ -805,12 +850,12 @@ private extension GameViewController
case .preferredControllerSkin: case .preferredControllerSkin:
guard 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 let traits = notification.userInfo?[Settings.NotificationUserInfoKey.traits] as? DeltaCore.ControllerSkin.Traits
else { return } else { return }
let currentTraits = DeltaCore.ControllerSkin.Traits.defaults(for: self.view) 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() self.updateControllerSkin()
} }

View File

@ -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? { override var game: GameProtocol? {
willSet { willSet {

View 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
}
}
}

View File

@ -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
}
}

View File

@ -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: "")
}
}
}

View 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 ""
}
}

View File

@ -38,6 +38,7 @@ extension UIAlertController
{ {
case .doesNotExist(let url): urls.insert(url) case .doesNotExist(let url): urls.insert(url)
case .invalid(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 .unknown(let url, _): urls.insert(url)
case .saveFailed(let errorURLs, _): urls.formUnion(errorURLs) case .saveFailed(let errorURLs, _): urls.formUnion(errorURLs)
} }

View File

@ -12,7 +12,7 @@ extension UIColor
{ {
class var deltaPurple: 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 class var deltaDarkGray: UIColor

View File

@ -18,7 +18,7 @@ internal extension UILabel
context.minimumScaleFactor = self.minimumScaleFactor context.minimumScaleFactor = self.minimumScaleFactor
// Using self.attributedString returns incorrect calculations, so we create our own attributed string // 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) attributedString.boundingRect(with: self.bounds.size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: context)
let scaleFactor = context.actualScaleFactor let scaleFactor = context.actualScaleFactor

View 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
}
}

View File

@ -21,11 +21,11 @@ extension UIViewControllerContextTransitioning
/// UIViews /// UIViews
var sourceView: UIView { var sourceView: UIView {
return self.sourceViewController.view return self.view(forKey: .from) ?? self.sourceViewController.view
} }
var destinationView: UIView { var destinationView: UIView {
return self.destinationViewController.view return self.view(forKey: .to) ?? self.destinationViewController.view
} }

View File

@ -7,6 +7,7 @@
// //
import UIKit import UIKit
import MobileCoreServices
import DeltaCore import DeltaCore
@ -16,9 +17,9 @@ import SDWebImage
class GameCollectionViewController: UICollectionViewController class GameCollectionViewController: UICollectionViewController
{ {
var gameCollection: GameCollection! { var gameCollection: GameCollection? {
didSet { didSet {
self.title = self.gameCollection.shortName self.title = self.gameCollection?.shortName
self.updateDataSource() self.updateDataSource()
} }
} }
@ -40,17 +41,28 @@ class GameCollectionViewController: UICollectionViewController
} }
} }
internal let dataSource: RSTFetchedResultsCollectionViewPrefetchingDataSource<Game, UIImage>
weak var activeEmulatorCore: EmulatorCore? weak var activeEmulatorCore: EmulatorCore?
fileprivate var activeSaveState: SaveStateProtocol? private var activeSaveState: SaveStateProtocol?
fileprivate let dataSource = RSTFetchedResultsCollectionViewDataSource<Game>(fetchedResultsController: NSFetchedResultsController()) private let prototypeCell = GridCollectionViewCell()
fileprivate let prototypeCell = GridCollectionViewCell()
fileprivate var _performing3DTouchTransition = false private var _performing3DTouchTransition = false
fileprivate weak var _destination3DTouchTransitionViewController: UIViewController? 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 - //MARK: - UIViewController -
@ -61,11 +73,8 @@ extension GameCollectionViewController
{ {
super.viewDidLoad() 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?.dataSource = self.dataSource
self.collectionView?.prefetchDataSource = self.dataSource
self.collectionView?.delegate = self self.collectionView?.delegate = self
let layout = self.collectionViewLayout as! GridCollectionViewLayout let layout = self.collectionViewLayout as! GridCollectionViewLayout
@ -122,25 +131,7 @@ extension GameCollectionViewController
saveStatesViewController.game = game saveStatesViewController.game = game
saveStatesViewController.mode = .loading saveStatesViewController.mode = .loading
saveStatesViewController.theme = self.theme 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": case "unwindFromGames":
let destinationViewController = segue.destination as! GameViewController let destinationViewController = segue.destination as! GameViewController
let cell = sender as! UICollectionViewCell let cell = sender as! UICollectionViewCell
@ -195,11 +186,42 @@ extension GameCollectionViewController
//MARK: - Private Methods - //MARK: - Private Methods -
private extension GameCollectionViewController 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() func updateDataSource()
{ {
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest() 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.sortDescriptors = [NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
fetchRequest.returnsObjectsAsFaults = false fetchRequest.returnsObjectsAsFaults = false
@ -207,7 +229,7 @@ private extension GameCollectionViewController
} }
//MARK: - Configure Cells //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) let game = self.dataSource.item(at: indexPath)
@ -222,29 +244,11 @@ private extension GameCollectionViewController
cell.isImageViewVibrancyEnabled = true cell.isImageViewVibrancyEnabled = true
} }
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
cell.maximumImageSize = CGSize(width: 90, height: 90) cell.maximumImageSize = CGSize(width: 90, height: 90)
cell.textLabel.text = game.name cell.textLabel.text = game.name
cell.textLabel.textColor = UIColor.gray 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 //MARK: - Emulation
@ -342,7 +346,7 @@ private extension GameCollectionViewController
func rename(_ game: Game, with name: String) 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 DatabaseManager.shared.performBackgroundTask { (context) in
let game = context.object(with: game.objectID) as! Game let game = context.object(with: game.objectID) as! Game
@ -356,7 +360,16 @@ private extension GameCollectionViewController
func changeArtwork(for game: Game) 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) func share(_ game: Game)
@ -394,7 +407,7 @@ private extension GameCollectionViewController
@objc func textFieldTextDidChange(_ textField: UITextField) @objc func textFieldTextDidChange(_ textField: UITextField)
{ {
let text = textField.text ?? "" let text = textField.text ?? ""
self._renameAction?.isEnabled = text.characters.count > 0 self._renameAction?.isEnabled = text.count > 0
} }
@objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) @objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer)
@ -417,7 +430,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
{ {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? 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 guard
let collectionView = self.collectionView, let collectionView = self.collectionView,
@ -452,7 +465,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: game)! let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: game)!
let cell = self.collectionView?.cellForItem(at: indexPath) let cell = self.collectionView?.cellForItem(at: indexPath)
let fileURL = FileManager.uniqueTemporaryURL() let fileURL = FileManager.default.uniqueTemporaryURL()
self.activeSaveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL) self.activeSaveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL)
gameViewController.emulatorCore?.stop() 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 - //MARK: - UICollectionViewDelegate -
/// UICollectionViewDelegate /// UICollectionViewDelegate
extension GameCollectionViewController extension GameCollectionViewController
{ {
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 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 cell = collectionView.cellForItem(at: indexPath)
let game = self.dataSource.item(at: indexPath) let game = self.dataSource.item(at: indexPath)
@ -542,12 +647,6 @@ extension GameCollectionViewController
self.launchGame(withSender: cell, clearScreen: true) 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 - //MARK: - UICollectionViewDelegateFlowLayout -
@ -562,7 +661,7 @@ extension GameCollectionViewController: UICollectionViewDelegateFlowLayout
widthConstraint.isActive = true widthConstraint.isActive = true
defer { widthConstraint.isActive = false } 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) let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return size return size

View File

@ -8,6 +8,7 @@
import UIKit import UIKit
import CoreData import CoreData
import MobileCoreServices
import DeltaCore import DeltaCore
@ -34,11 +35,17 @@ class GamesViewController: UIViewController
} }
} }
fileprivate var pageViewController: UIPageViewController! override var preferredStatusBarStyle: UIStatusBarStyle {
fileprivate var placeholderView: RSTPlaceholderView! return .lightContent
fileprivate var pageControl: UIPageControl! }
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?) { override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
fatalError("initWithNibName: not implemented") fatalError("initWithNibName: not implemented")
@ -87,6 +94,11 @@ extension GamesViewController
self.navigationController?.navigationBar.barStyle = .blackTranslucent self.navigationController?.navigationBar.barStyle = .blackTranslucent
self.navigationController?.toolbar.barStyle = .blackTranslucent self.navigationController?.toolbar.barStyle = .blackTranslucent
if #available(iOS 11.0, *)
{
self.prepareSearchController()
}
self.updateTheme() self.updateTheme()
} }
@ -108,11 +120,15 @@ extension GamesViewController
{ {
super.viewDidLayoutSubviews() 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 /// UI
private extension GamesViewController 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() func updateTheme()
{ {
switch self.theme switch self.theme
@ -185,12 +243,16 @@ private extension GamesViewController
let indexPath = IndexPath(row: safeIndex, section: 0) let indexPath = IndexPath(row: safeIndex, section: 0)
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "gameCollectionViewController") as! GameCollectionViewController 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.theme = self.theme
viewController.activeEmulatorCore = self.activeEmulatorCore viewController.activeEmulatorCore = self.activeEmulatorCore
// Need to set content inset here AND willTransitionTo callback to ensure its correct for all edge cases if #available(iOS 11.0, *) {}
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length 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 return viewController
} }
@ -264,39 +326,57 @@ private extension GamesViewController
/// Importing /// Importing
extension GamesViewController: ImportControllerDelegate 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.delegate = self
importController.importOptions = [itunesImportOption]
self.present(importController, animated: true, completion: nil) self.present(importController, animated: true, completion: nil)
} }
//MARK: - ImportControllerDelegate func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
@nonobjc func importController(_ importController: ImportController, didImport games: Set<Game>, with errors: Set<DatabaseManager.ImportError>)
{ {
if errors.count > 0 for error in errors
{ {
let alertController = UIAlertController.alertController(for: .games, with: errors) print(error)
self.present(alertController, animated: true, completion: nil)
} }
if games.count > 0 let gameURLs = urls.filter { $0.pathExtension.lowercased() != "deltaskin" }
{ DatabaseManager.shared.importGames(at: Set(gameURLs)) { (games, errors) in
print("Imported Games:", games.map { $0.name }) if errors.count > 0
} {
} let alertController = UIAlertController.alertController(for: .games, with: errors)
self.present(alertController, animated: true, completion: nil)
@nonobjc func importController(_ importController: ImportController, didImport controllerSkins: Set<ControllerSkin>, with errors: Set<DatabaseManager.ImportError>) }
{
if errors.count > 0 if games.count > 0
{ {
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors) print("Imported Games:", games.map { $0.name })
self.present(alertController, animated: true, completion: nil) }
} }
if controllerSkins.count > 0 let controllerSkinURLs = urls.filter { $0.pathExtension.lowercased() == "deltaskin" }
{ DatabaseManager.shared.importControllerSkins(at: Set(controllerSkinURLs)) { (controllerSkins, errors) in
print("Imported Controller Skins:", controllerSkins.map { $0.name }) 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 } 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 - //MARK: - NSFetchedResultsControllerDelegate -
/// NSFetchedResultsControllerDelegate /// NSFetchedResultsControllerDelegate
extension GamesViewController: NSFetchedResultsControllerDelegate extension GamesViewController: NSFetchedResultsControllerDelegate

View File

@ -12,8 +12,12 @@ class GamesPresentationController: UIPresentationController
{ {
private let blurView: UIVisualEffectView 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 = UIVisualEffectView(effect: nil)
self.blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 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) self.blurView.frame = CGRect(x: 0, y: 0, width: containerView.bounds.width, height: containerView.bounds.height)
containerView.addSubview(self.blurView) containerView.addSubview(self.blurView)
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in self.animator.addAnimations {
self.blurView.effect = UIBlurEffect(style: .dark) self.blurView.effect = UIBlurEffect(style: .dark)
}) }
} }
override func dismissalTransitionWillBegin() override func dismissalTransitionWillBegin()
{ {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in self.animator.addAnimations {
self.blurView.effect = nil self.blurView.effect = nil
}) }
} }
override func dismissalTransitionDidEnd(_ completed: Bool) override func dismissalTransitionDidEnd(_ completed: Bool)

View File

@ -10,9 +10,9 @@ import UIKit
class GamesStoryboardSegue: UIStoryboardSegue 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) 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? 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 return presentationController
} }
} }
@ -85,48 +85,51 @@ extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning
snapshotView.alpha = 1.0 snapshotView.alpha = 1.0
transitionContext.containerView.addSubview(snapshotView) 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 // 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 topPaddingToolbar: UIToolbar? = nil
var bottomPaddingToolbar: UIToolbar? = nil var bottomPaddingToolbar: UIToolbar? = nil
if let navigationController = transitionContext.destinationViewController as? UINavigationController // Must be wrapped in no-animation block to prevent iOS 11 search bar from not appearing.
{ UIView.performWithoutAnimation {
let padding: CGFloat = 44 // 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) let padding: CGFloat = 44
topToolbar.translatesAutoresizingMaskIntoConstraints = false
topToolbar.barStyle = navigationController.toolbar.barStyle
transitionContext.destinationView.insertSubview(topToolbar, belowSubview: navigationController.navigationBar)
topToolbar.bottomAnchor.constraint(equalTo: navigationController.navigationBar.bottomAnchor).isActive = true if !navigationController.isNavigationBarHidden
topToolbar.centerXAnchor.constraint(equalTo: navigationController.navigationBar.centerXAnchor).isActive = true {
topToolbar.widthAnchor.constraint(equalTo: navigationController.navigationBar.widthAnchor, constant: padding * 2).isActive = true let topToolbar = UIToolbar(frame: CGRect.zero)
topToolbar.heightAnchor.constraint(equalTo: navigationController.navigationBar.heightAnchor, constant: padding).isActive = true 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)
if !navigationController.isToolbarHidden bottomToolbar.translatesAutoresizingMaskIntoConstraints = false
{ bottomToolbar.barStyle = navigationController.toolbar.barStyle
let bottomToolbar = UIToolbar(frame: CGRect.zero) transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar)
bottomToolbar.translatesAutoresizingMaskIntoConstraints = false
bottomToolbar.barStyle = navigationController.toolbar.barStyle bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true
transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar) bottomToolbar.bottomAnchor.constraint(equalTo: navigationController.toolbar.bottomAnchor, constant: padding).isActive = true
bottomToolbar.leftAnchor.constraint(equalTo: navigationController.toolbar.leftAnchor, constant: -padding).isActive = true
bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true bottomToolbar.rightAnchor.constraint(equalTo: navigationController.toolbar.rightAnchor, constant: padding).isActive = true
bottomToolbar.centerXAnchor.constraint(equalTo: navigationController.toolbar.centerXAnchor).isActive = true
bottomToolbar.widthAnchor.constraint(equalTo: navigationController.toolbar.widthAnchor, constant: padding * 2).isActive = true bottomPaddingToolbar = bottomToolbar
bottomToolbar.heightAnchor.constraint(equalTo: navigationController.toolbar.heightAnchor, constant: padding).isActive = true }
bottomPaddingToolbar = bottomToolbar
} }
} }
self.animator.addAnimations { self.animator.addAnimations {
snapshotView.alpha = 0.0 snapshotView.alpha = 0.0
transitionContext.destinationView.transform = CGAffineTransform.identity transitionContext.destinationView.transform = CGAffineTransform.identity
@ -163,3 +166,4 @@ extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning
self.animator.startAnimation() self.animator.startAnimation()
} }
} }

View File

@ -10,9 +10,9 @@ import UIKit
class InitialGamesStoryboardSegue: UIStoryboardSegue 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) override init(identifier: String?, source: UIViewController, destination: UIViewController)
{ {

View 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([])
}
}
}

View File

@ -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)
}
}

View File

@ -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?([])
}
}
}

View 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)
}
}

View 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)
}
}

View 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)
}

View File

@ -10,10 +10,10 @@ import UIKit
class LaunchViewController: UIViewController class LaunchViewController: UIViewController
{ {
@IBOutlet fileprivate var gameViewContainerView: UIView! @IBOutlet private var gameViewContainerView: UIView!
fileprivate var gameViewController: GameViewController! private var gameViewController: GameViewController!
fileprivate var presentedGameViewController: Bool = false private var presentedGameViewController: Bool = false
override var preferredStatusBarStyle: UIStatusBarStyle { override var preferredStatusBarStyle: UIStatusBarStyle {
return self.gameViewController?.preferredStatusBarStyle ?? .lightContent return self.gameViewController?.preferredStatusBarStyle ?? .lightContent

View File

@ -13,7 +13,10 @@ import DeltaCore
import Roxas import Roxas
private let CheatPrefixAttribute = "prefix" private extension NSAttributedStringKey
{
static let cheatPrefix = NSAttributedStringKey("CheatPrefix")
}
class CheatTextView: UITextView 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) required init?(coder aDecoder: NSCoder)
{ {
@ -44,9 +47,9 @@ extension CheatTextView
if let format = self.cheatFormat, let font = self.font 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) self.textContainer.size = CGSize(width: width, height: 0)
} }
} }
@ -79,7 +82,7 @@ private extension CheatTextView
if let prefixString = prefixString, prefixString.length > 0 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) attributedFormat.append(attributedString)
@ -105,7 +108,7 @@ private extension CheatTextView
extension CheatTextView: NSLayoutManagerDelegate 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 // Returning 0 = let the layoutManager do the normal logic
guard let attributedFormat = self.attributedFormat else { return 0 } guard let attributedFormat = self.attributedFormat else { return 0 }
@ -118,7 +121,7 @@ extension CheatTextView: NSLayoutManagerDelegate
// Allocate our replacement buffers // Allocate our replacement buffers
let glyphBuffer = UnsafeMutablePointer<CGGlyph>.allocate(capacity: bufferSize) 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) let characterBuffer = UnsafeMutablePointer<Int>.allocate(capacity: bufferSize)
var offset = 0 var offset = 0
@ -128,10 +131,10 @@ extension CheatTextView: NSLayoutManagerDelegate
// The index the actual character maps to in the cheat format // The index the actual character maps to in the cheat format
let characterIndex = charIndexes[i] % attributedFormat.length 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 // 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 for j in 0 ..< prefixCount
{ {

View File

@ -51,7 +51,7 @@ struct CheatValidator
// Remove newline characters (code should already be formatted) // Remove newline characters (code should already be formatted)
let sanitizedCode = (cheat.code as NSString).replacingOccurrences(of: "\n", with: "") 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 throw Error.invalidCode
} }

View File

@ -29,7 +29,7 @@ class CheatsViewController: UITableViewController
weak var delegate: CheatsViewControllerDelegate? weak var delegate: CheatsViewControllerDelegate?
fileprivate let dataSource = RSTFetchedResultsTableViewDataSource<Cheat>(fetchedResultsController: NSFetchedResultsController()) private let dataSource = RSTFetchedResultsTableViewDataSource<Cheat>(fetchedResultsController: NSFetchedResultsController())
} }
extension CheatsViewController extension CheatsViewController

View File

@ -33,7 +33,7 @@ class EditCheatViewController: UITableViewController
var game: Game! { var game: Game! {
didSet { didSet {
let deltaCore = Delta.core(for: self.game.type)! 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 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] let cheatFormat = self.supportedCheatFormats[self.typeSegmentedControl.selectedSegmentIndex]
return cheatFormat return cheatFormat
} }
fileprivate var mutableCheat: Cheat! private var mutableCheat: Cheat!
fileprivate var managedObjectContext = DatabaseManager.shared.newBackgroundContext() private var managedObjectContext = DatabaseManager.shared.newBackgroundContext()
@IBOutlet fileprivate var nameTextField: UITextField! @IBOutlet private var nameTextField: UITextField!
@IBOutlet fileprivate var typeSegmentedControl: UISegmentedControl! @IBOutlet private var typeSegmentedControl: UISegmentedControl!
@IBOutlet fileprivate var codeTextView: CheatTextView! @IBOutlet private var codeTextView: CheatTextView!
override var previewActionItems: [UIPreviewActionItem] override var previewActionItems: [UIPreviewActionItem]
{ {
@ -132,7 +132,7 @@ extension EditCheatViewController
// Update UI // Update UI
if name.characters.count == 0 if name.count == 0
{ {
self.title = NSLocalizedString("Cheat", comment: "") self.title = NSLocalizedString("Cheat", comment: "")
} }
@ -224,7 +224,7 @@ private extension EditCheatViewController
@IBAction func updateCheatName(_ sender: UITextField) @IBAction func updateCheatName(_ sender: UITextField)
{ {
var title = sender.text ?? "" var title = sender.text ?? ""
if title.characters.count == 0 if title.count == 0
{ {
title = NSLocalizedString("Cheat", comment: "") title = NSLocalizedString("Cheat", comment: "")
} }
@ -325,7 +325,7 @@ private extension EditCheatViewController
sender.resignFirstResponder() sender.resignFirstResponder()
} }
func presentErrorAlert(title: String, message: String, handler: ((Void) -> Void)?) func presentErrorAlert(title: String, message: String, handler: (() -> Void)?)
{ {
DispatchQueue.main.async { DispatchQueue.main.async {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 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 // 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 // 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) textView.textStorage.replaceCharacters(in: range, with: attributedString)
// We must add attributedString.length, not range.length, in case the attributed string's length differs // We must add attributedString.length, not range.length, in case the attributed string's length differs

View 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)
}
}

View 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
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 } return [self.saveStateItem, self.loadStateItem, self.cheatCodesItem, self.fastForwardItem, self.sustainButtonsItem].flatMap { $0 }
} }
/// Pause Items /// Pause Items
var saveStateItem: PauseItem? var saveStateItem: MenuItem?
var loadStateItem: PauseItem? var loadStateItem: MenuItem?
var cheatCodesItem: PauseItem? var cheatCodesItem: MenuItem?
var fastForwardItem: PauseItem? var fastForwardItem: MenuItem?
var sustainButtonsItem: PauseItem? var sustainButtonsItem: MenuItem?
/// PauseInfoProviding /// PauseInfoProviding
var pauseText: String? var pauseText: String?
@ -38,9 +38,9 @@ class PauseViewController: UIViewController, PauseInfoProviding
/// Save States /// Save States
weak var saveStatesViewControllerDelegate: SaveStatesViewControllerDelegate? 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 /// UIViewController
override var preferredContentSize: CGSize { override var preferredContentSize: CGSize {
@ -85,8 +85,8 @@ extension PauseViewController
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaPurple
self.pauseNavigationController.view.backgroundColor = UIColor.clear self.pauseNavigationController.view.backgroundColor = UIColor.clear
let pauseMenuViewController = self.pauseNavigationController.topViewController as! PauseMenuViewController let gridMenuViewController = self.pauseNavigationController.topViewController as! GridMenuViewController
pauseMenuViewController.items = self.pauseItems gridMenuViewController.items = self.pauseItems
case "saveStates": case "saveStates":
let saveStatesViewController = segue.destination as! SaveStatesViewController let saveStatesViewController = segue.destination as! SaveStatesViewController
@ -135,21 +135,21 @@ private extension PauseViewController
guard self.emulatorCore != nil else { return } 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.saveStatesViewControllerMode = .saving
self.performSegue(withIdentifier: "saveStates", sender: self) 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.saveStatesViewControllerMode = .loading
self.performSegue(withIdentifier: "saveStates", sender: self) 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.performSegue(withIdentifier: "cheats", sender: self)
}) })
self.fastForwardItem = PauseItem(image: #imageLiteral(resourceName: "FastForward"), text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in }) self.fastForwardItem = MenuItem(text: NSLocalizedString("Fast Forward", comment: ""), image: #imageLiteral(resourceName: "FastForward"), action: { _ in })
self.sustainButtonsItem = PauseItem(image: #imageLiteral(resourceName: "SustainButtons"), text: NSLocalizedString("Sustain Buttons", comment: ""), action: { _ in }) self.sustainButtonsItem = MenuItem(text: NSLocalizedString("Sustain Buttons", comment: ""), image: #imageLiteral(resourceName: "SustainButtons"), action: { _ in })
} }
} }

View File

@ -17,13 +17,17 @@ protocol PauseInfoProviding
class PausePresentationController: UIPresentationController class PausePresentationController: UIPresentationController
{ {
let presentationAnimator: UIViewPropertyAnimator
private let blurringView: UIVisualEffectView private let blurringView: UIVisualEffectView
private let vibrancyView: UIVisualEffectView private let vibrancyView: UIVisualEffectView
private var contentView: UIView! private var contentView: UIView!
@IBOutlet private weak var pauseLabel: UILabel!
@IBOutlet private weak var pauseIconImageView: UIImageView! // Must not be weak, or else may result in crash when deallocating.
@IBOutlet private weak var stackView: UIStackView! @IBOutlet private var pauseLabel: UILabel!
@IBOutlet private var pauseIconImageView: UIImageView!
@IBOutlet private var stackView: UIStackView!
override var frameOfPresentedViewInContainerView: CGRect override var frameOfPresentedViewInContainerView: CGRect
{ {
@ -45,8 +49,10 @@ class PausePresentationController: UIPresentationController
return frame 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.blurringView = UIVisualEffectView(effect: nil)
self.vibrancyView = UIVisualEffectView(effect: nil) self.vibrancyView = UIVisualEffectView(effect: nil)
@ -83,16 +89,18 @@ class PausePresentationController: UIPresentationController
self.contentView.alpha = 0.0 self.contentView.alpha = 0.0
self.vibrancyView.contentView.addSubview(self.contentView) self.vibrancyView.contentView.addSubview(self.contentView)
self.presentingViewController.transitionCoordinator?.animate(alongsideTransition: { context in self.presentationAnimator.addAnimations {
let blurEffect = UIBlurEffect(style: .dark) let blurEffect = UIBlurEffect(style: .dark)
self.blurringView.effect = blurEffect self.blurringView.effect = blurEffect
self.vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect) self.vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect)
self.contentView.alpha = 1.0 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() override func dismissalTransitionWillBegin()

View File

@ -12,7 +12,7 @@ class SaveStatesCollectionHeaderView: UICollectionReusableView
{ {
let textLabel = UILabel() 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 { var isTextLabelVibrancyEnabled = true {
didSet { didSet {

View File

@ -29,6 +29,7 @@ extension SaveStatesViewController
enum Section: Int enum Section: Int
{ {
case auto case auto
case quick
case general case general
case locked case locked
} }
@ -58,29 +59,30 @@ class SaveStatesViewController: UICollectionViewController
} }
} }
fileprivate var vibrancyView: UIVisualEffectView! private var vibrancyView: UIVisualEffectView!
fileprivate var placeholderView: RSTPlaceholderView! private var placeholderView: RSTPlaceholderView!
fileprivate var prototypeCell = GridCollectionViewCell() private var prototypeCell = GridCollectionViewCell()
fileprivate var prototypeCellWidthConstraint: NSLayoutConstraint! private var prototypeCellWidthConstraint: NSLayoutConstraint!
fileprivate var prototypeHeader = SaveStatesCollectionHeaderView() private var prototypeHeader = SaveStatesCollectionHeaderView()
fileprivate let dataSource = RSTFetchedResultsCollectionViewDataSource<SaveState>(fetchedResultsController: NSFetchedResultsController()) private let dataSource: RSTFetchedResultsCollectionViewPrefetchingDataSource<SaveState, UIImage>
fileprivate let imageOperationQueue = RSTOperationQueue() private var emulatorCoreSaveState: SaveStateProtocol?
fileprivate let imageCache = NSCache<NSURL, UIImage>()
fileprivate var emulatorCoreSaveState: SaveStateProtocol? private let dateFormatter: DateFormatter
fileprivate let dateFormatter: DateFormatter
required init?(coder aDecoder: NSCoder) required init?(coder aDecoder: NSCoder)
{ {
self.dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<SaveState, UIImage>(fetchedResultsController: NSFetchedResultsController())
self.dateFormatter = DateFormatter() self.dateFormatter = DateFormatter()
self.dateFormatter.timeStyle = .short self.dateFormatter.timeStyle = .short
self.dateFormatter.dateStyle = .short self.dateFormatter.dateStyle = .short
super.init(coder: aDecoder) super.init(coder: aDecoder)
self.prepareDataSource()
} }
} }
@ -90,21 +92,8 @@ extension SaveStatesViewController
{ {
super.viewDidLoad() 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?.dataSource = self.dataSource
self.collectionView?.prefetchDataSource = self.dataSource
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2 let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
@ -118,11 +107,11 @@ extension SaveStatesViewController
{ {
case .saving: case .saving:
self.title = NSLocalizedString("Save State", comment: "") 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: case .loading:
self.title = NSLocalizedString("Load State", comment: "") 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 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 private extension SaveStatesViewController
{ {
//MARK: - Update - //MARK: - Update -
func updateDataSource() func updateDataSource()
{ {
let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest() let fetchRequest: NSFetchRequest<SaveState> = SaveState.fetchRequest()
@ -194,7 +230,7 @@ private extension SaveStatesViewController
//MARK: - Configure Views - //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) let saveState = self.dataSource.item(at: indexPath)
@ -213,35 +249,9 @@ private extension SaveStatesViewController
cell.isImageViewVibrancyEnabled = true 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 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.maximumImageSize = CGSize(width: self.prototypeCellWidthConstraint.constant, height: (self.prototypeCellWidthConstraint.constant / dimensions.width) * dimensions.height)
cell.textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) cell.textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
@ -259,6 +269,7 @@ private extension SaveStatesViewController
switch section switch section
{ {
case .auto: title = NSLocalizedString("Auto Save", comment: "") case .auto: title = NSLocalizedString("Auto Save", comment: "")
case .quick: title = NSLocalizedString("Quick Save", comment: "")
case .general: title = NSLocalizedString("General", comment: "") case .general: title = NSLocalizedString("General", comment: "")
case .locked: title = NSLocalizedString("Locked", comment: "") case .locked: title = NSLocalizedString("Locked", comment: "")
} }
@ -301,6 +312,7 @@ private extension SaveStatesViewController
let game = backgroundContext.object(with: self.game.objectID) as! Game let game = backgroundContext.object(with: self.game.objectID) as! Game
saveState = SaveState.insertIntoManagedObjectContext(backgroundContext) saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
saveState.type = .general
saveState.game = game saveState.game = game
} }
@ -365,7 +377,7 @@ private extension SaveStatesViewController
func rename(_ saveState: SaveState, with name: String?) func rename(_ saveState: SaveState, with name: String?)
{ {
var name = name var name = name
if (name ?? "").characters.count == 0 if (name ?? "").count == 0
{ {
// When text is nil, we know to show the timestamp instead // When text is nil, we know to show the timestamp instead
name = nil name = nil
@ -471,6 +483,7 @@ private extension SaveStatesViewController
switch saveState.type switch saveState.type
{ {
case .auto: break case .auto: break
case .quick: break
case .general: case .general:
let lockAction = Action(title: NSLocalizedString("Lock", comment: ""), style: .default, action: { [unowned self] action in let lockAction = Action(title: NSLocalizedString("Lock", comment: ""), style: .default, action: { [unowned self] action in
self.lockSaveState(saveState) self.lockSaveState(saveState)
@ -549,13 +562,13 @@ private extension SaveStatesViewController
//MARK: - 3D Touch - //MARK: - 3D Touch -
extension SaveStatesViewController: UIViewControllerPreviewingDelegate extension SaveStatesViewController: UIViewControllerPreviewingDelegate
{ {
fileprivate func prepareEmulatorCoreSaveState() private func prepareEmulatorCoreSaveState()
{ {
guard let emulatorCore = self.emulatorCore else { return } 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 // 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) self.emulatorCoreSaveState = emulatorCore.saveSaveState(to: fileURL)
if self.emulatorCoreSaveState != nil if self.emulatorCoreSaveState != nil
@ -577,7 +590,7 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
let saveState = self.dataSource.item(at: indexPath) let saveState = self.dataSource.item(at: indexPath)
let actions = self.actionsForSaveState(saveState)?.previewActions ?? [] 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() let previewGameViewController = PreviewGameViewController()
previewGameViewController.game = self.game previewGameViewController.game = self.game
@ -593,7 +606,7 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
let gameViewController = viewControllerToCommit as! PreviewGameViewController let gameViewController = viewControllerToCommit as! PreviewGameViewController
gameViewController.emulatorCore?.pause() gameViewController.emulatorCore?.pause()
let fileURL = FileManager.uniqueTemporaryURL() let fileURL = FileManager.default.uniqueTemporaryURL()
if let saveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL) if let saveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL)
{ {
gameViewController.emulatorCore?.stop() gameViewController.emulatorCore?.stop()
@ -634,11 +647,11 @@ extension SaveStatesViewController
{ {
case .saving: case .saving:
let section = self.correctedSectionForSectionIndex((indexPath as NSIndexPath).section) let section = self.correctedSectionForSectionIndex(indexPath.section)
switch section switch section
{ {
case .auto: break case .auto: break
case .general: case .quick, .general:
let backgroundContext = DatabaseManager.shared.newBackgroundContext() let backgroundContext = DatabaseManager.shared.newBackgroundContext()
backgroundContext.performAndWait() { backgroundContext.performAndWait() {
let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState let temporarySaveState = backgroundContext.object(with: saveState.objectID) as! SaveState
@ -655,12 +668,6 @@ extension SaveStatesViewController
case .loading: self.loadSaveState(saveState) 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> - //MARK: - <UICollectionViewDelegateFlowLayout> -
@ -668,8 +675,7 @@ extension SaveStatesViewController: UICollectionViewDelegateFlowLayout
{ {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 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)
self.configure(self.prototypeCell, for: indexPath, ignoreExpensiveOperations: true)
let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize) let size = self.prototypeCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return size return size

View File

@ -10,15 +10,15 @@ import UIKit
class PauseStoryboardSegue: UIStoryboardSegue class PauseStoryboardSegue: UIStoryboardSegue
{ {
fileprivate let animator: UIViewPropertyAnimator private let animator: UIViewPropertyAnimator
fileprivate let presentationController: PausePresentationController private let presentationController: PausePresentationController
override init(identifier: String?, source: UIViewController, destination: UIViewController) override init(identifier: String?, source: UIViewController, destination: UIViewController)
{ {
let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0)) let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0))
self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters) 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) super.init(identifier: identifier, source: source, destination: destination)
} }
@ -29,6 +29,9 @@ class PauseStoryboardSegue: UIStoryboardSegue
self.destination.modalPresentationStyle = .custom self.destination.modalPresentationStyle = .custom
self.destination.modalPresentationCapturesStatusBarAppearance = true 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:) // We need to force layout of destinationViewController.view _before_ animateTransition(using:)
// Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors // Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors
self.destination.view.frame = self.source.view.frame self.destination.view.frame = self.source.view.frame

View File

@ -37,6 +37,13 @@ class PauseTransitionCoordinator: NSObject, UIViewControllerAnimatedTransitionin
destinationViewController.view.layoutIfNeeded() 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: { 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 sourceViewController.view.frame.origin.y = self.presenting ? -sourceViewController.view.bounds.height : transitionContext.containerView.bounds.height

View File

@ -12,18 +12,9 @@ import DeltaCore
import Roxas import Roxas
extension ControllerSkinsViewController
{
enum Section: Int
{
case standard
case custom
}
}
class ControllerSkinsViewController: UITableViewController class ControllerSkinsViewController: UITableViewController
{ {
var gameType: GameType! { var system: System! {
didSet { didSet {
self.updateDataSource() 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() required init?(coder aDecoder: NSCoder)
{
fileprivate let imageCache = NSCache<ControllerSkinImageCacheKey, UIImage>() self.dataSource = RSTFetchedResultsTableViewPrefetchingDataSource<ControllerSkin, UIImage>(fetchedResultsController: NSFetchedResultsController())
super.init(coder: aDecoder)
self.prepareDataSource()
}
} }
extension ControllerSkinsViewController extension ControllerSkinsViewController
@ -48,11 +44,8 @@ extension ControllerSkinsViewController
{ {
super.viewDidLoad() 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.dataSource = self.dataSource
self.tableView.prefetchDataSource = self.dataSource
} }
override func didReceiveMemoryWarning() override func didReceiveMemoryWarning()
@ -65,57 +58,53 @@ extension ControllerSkinsViewController
private extension ControllerSkinsViewController private extension ControllerSkinsViewController
{ {
//MARK: - Update //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() 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 configuration = ControllerSkinConfigurations(traits: traits)
let fetchRequest: NSFetchRequest<ControllerSkin> = ControllerSkin.fetchRequest() 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)] 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) 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 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 extension ControllerSkinsViewController
{ {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{ {
let controllerSkin = self.dataSource.item(at: 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) _ = self.navigationController?.popViewController(animated: true)
} }
@ -176,10 +138,4 @@ extension ControllerSkinsViewController
return height return height
} }
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let operation = self.imageOperationQueue[indexPath as NSCopying]
operation?.cancel()
}
} }

View File

@ -1,5 +1,5 @@
// //
// GameTypeControllerSkinsViewController.swift // SystemControllerSkinsViewController.swift
// Delta // Delta
// //
// Created by Riley Testut on 9/30/16. // Created by Riley Testut on 9/30/16.
@ -10,30 +10,30 @@ import UIKit
import DeltaCore import DeltaCore
extension GameTypeControllerSkinsViewController extension SystemControllerSkinsViewController
{ {
fileprivate enum Section: Int private enum Section: Int
{ {
case portrait case portrait
case landscape case landscape
} }
} }
class GameTypeControllerSkinsViewController: UITableViewController class SystemControllerSkinsViewController: UITableViewController
{ {
var gameType: GameType! var system: System!
@IBOutlet fileprivate var portraitImageView: UIImageView! @IBOutlet private var portraitImageView: UIImageView!
@IBOutlet fileprivate var landscapeImageView: UIImageView! @IBOutlet private var landscapeImageView: UIImageView!
} }
extension GameTypeControllerSkinsViewController extension SystemControllerSkinsViewController
{ {
override func viewDidLoad() override func viewDidLoad()
{ {
super.viewDidLoad() super.viewDidLoad()
self.title = self.gameType.localizedShortName self.title = self.system.localizedShortName
} }
override func viewWillAppear(_ animated: Bool) 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 } guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return }
let controllerSkinsViewController = segue.destination as! ControllerSkinsViewController let controllerSkinsViewController = segue.destination as! ControllerSkinsViewController
controllerSkinsViewController.gameType = self.gameType controllerSkinsViewController.system = self.system
var traits = DeltaCore.ControllerSkin.Traits.defaults(for: self.view) 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 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{ {
@ -98,15 +98,15 @@ extension GameTypeControllerSkinsViewController
} }
} }
private extension GameTypeControllerSkinsViewController private extension SystemControllerSkinsViewController
{ {
func updateControllerSkins() func updateControllerSkins()
{ {
let portraitTraits = DeltaCore.ControllerSkin.Traits(deviceType: .iphone, displayMode: DeltaCore.ControllerSkin.DisplayMode.fullScreen, orientation: .portrait) 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 landscapeTraits = DeltaCore.ControllerSkin.Traits(deviceType: .iphone, displayMode: DeltaCore.ControllerSkin.DisplayMode.fullScreen, orientation: .landscape)
let portraitControllerSkin = Settings.preferredControllerSkin(for: self.gameType, traits: portraitTraits) let portraitControllerSkin = Settings.preferredControllerSkin(for: self.system, traits: portraitTraits)
let landscapeControllerSkin = Settings.preferredControllerSkin(for: self.gameType, traits: landscapeTraits) let landscapeControllerSkin = Settings.preferredControllerSkin(for: self.system, traits: landscapeTraits)
self.portraitImageView.image = portraitControllerSkin?.image(for: portraitTraits, preferredSize: UIScreen.main.defaultControllerSkinSize) self.portraitImageView.image = portraitControllerSkin?.image(for: portraitTraits, preferredSize: UIScreen.main.defaultControllerSkinSize)
self.landscapeImageView.image = landscapeControllerSkin?.image(for: landscapeTraits, preferredSize: UIScreen.main.defaultControllerSkinSize) self.landscapeImageView.image = landscapeControllerSkin?.image(for: landscapeTraits, preferredSize: UIScreen.main.defaultControllerSkinSize)

View 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)
}
}

View File

@ -9,39 +9,62 @@
import UIKit import UIKit
import DeltaCore import DeltaCore
private enum ControllersSettingsSection: Int import Roxas
extension ControllersSettingsViewController
{ {
case none private enum Section: Int
case localDevice {
case externalControllers 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 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 class ControllersSettingsViewController: UITableViewController
{ {
var playerIndex: Int? { var playerIndex: Int! {
didSet didSet {
{ self.title = NSLocalizedString("Player \(self.playerIndex + 1)", comment: "")
if let playerIndex = self.playerIndex }
}
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 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() let device = LocalDeviceController()
device.playerIndex = Settings.localControllerPlayerIndex device.playerIndex = Settings.localControllerPlayerIndex
@ -52,146 +75,76 @@ class ControllersSettingsViewController: UITableViewController
{ {
super.init(coder: aDecoder) super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(ControllersSettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
} }
override func viewDidLoad() override func viewDidLoad()
{ {
super.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 let gameControllers = [self.localDeviceController as GameController] + self.connectedControllers
controllers.append(self.localDeviceController) for gameController in gameControllers
// Reset previous controller
if let playerIndex = self.playerIndex, let index = controllers.index(where: { $0.playerIndex == playerIndex })
{ {
let controller = controllers[index] if gameController.playerIndex == self.playerIndex
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
{ {
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 extension ControllersSettingsViewController
{ {
override func numberOfSections(in tableView: UITableView) -> Int override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{ {
if self.connectedControllers.count == 0 guard let identifier = segue.identifier else { return }
{
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
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: "") cell.textLabel?.text = NSLocalizedString("None", comment: "")
if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex }) if Settings.localControllerPlayerIndex != self.playerIndex && !self.connectedControllers.contains(where: { $0.playerIndex == self.playerIndex })
{ {
cell.accessoryType = .checkmark 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 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 else
{ {
@ -205,25 +158,216 @@ extension ControllersSettingsViewController
cell.accessoryType = .checkmark cell.accessoryType = .checkmark
} }
else else
{ {
if let playerIndex = controller.playerIndex if let playerIndex = controller.playerIndex
{ {
cell.detailTextLabel?.text = NSLocalizedString("Player \(playerIndex + 1)", comment: "") 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 return cell
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{ {
switch ControllersSettingsSection(rawValue: section)! switch Section(rawValue: section)!
{ {
case .none: return nil case .none: return nil
case .localDevice: return NSLocalizedString("Local Device", comment: "") case .localDevice: return NSLocalizedString("Local Device", comment: "")
case .externalControllers: return self.connectedControllers.count > 0 ? NSLocalizedString("External Controllers", 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()
}
} }

View 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
}
}
}

View File

@ -9,8 +9,6 @@
import Foundation import Foundation
import DeltaCore import DeltaCore
import SNESDeltaCore
import GBADeltaCore
import Roxas import Roxas
@ -25,7 +23,7 @@ extension Settings
{ {
case name case name
case gameType case system
case traits case traits
} }
@ -42,12 +40,14 @@ struct Settings
/// Controllers /// Controllers
static var localControllerPlayerIndex: Int? = 0 { static var localControllerPlayerIndex: Int? = 0 {
didSet { didSet {
guard self.localControllerPlayerIndex != oldValue else { return }
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.localControllerPlayerIndex]) NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.localControllerPlayerIndex])
} }
} }
static var translucentControllerSkinOpacity: CGFloat { static var translucentControllerSkinOpacity: CGFloat {
set { set {
guard newValue != self.translucentControllerSkinOpacity else { return }
UserDefaults.standard.translucentControllerSkinOpacity = newValue UserDefaults.standard.translucentControllerSkinOpacity = newValue
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.translucentControllerSkinOpacity]) NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.translucentControllerSkinOpacity])
} }
@ -72,9 +72,9 @@ struct Settings
UserDefaults.standard.register(defaults: defaults) 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) let identifier = UserDefaults.standard.string(forKey: userDefaultsKey)
@ -86,7 +86,7 @@ struct Settings
if let identifier = identifier 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 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 // 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 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 return controllerSkin
} }
} }
@ -112,26 +112,29 @@ struct Settings
return nil 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) 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 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 let systemName: String
switch gameType switch system
{ {
case GameType.snes: systemName = "snes" case .snes: systemName = "snes"
case GameType.gba: systemName = "gba" case .gba: systemName = "gba"
default: return nil case .gbc: systemName = "gbc"
} }
let orientation: String let orientation: String

View File

@ -7,37 +7,49 @@
// //
import UIKit import UIKit
import DeltaCore import DeltaCore
extension SettingsViewController import Roxas
private extension SettingsViewController
{ {
fileprivate enum Section: Int enum Section: Int
{ {
case controllers case controllers
case controllerSkins case controllerSkins
case controllerOpacity case controllerOpacity
} }
fileprivate enum Segue: String enum Segue: String
{ {
case controllers = "controllersSegue" case controllers = "controllersSegue"
case controllerSkins = "controllerSkinsSegue" case controllerSkins = "controllerSkinsSegue"
} }
enum ControllerSkinsRow: Int
{
case snes
case gba
case gbc
}
} }
class SettingsViewController: UITableViewController class SettingsViewController: UITableViewController
{ {
@IBOutlet fileprivate var controllerOpacityLabel: UILabel! @IBOutlet private var controllerOpacityLabel: UILabel!
@IBOutlet fileprivate var controllerOpacitySlider: UISlider! @IBOutlet private var controllerOpacitySlider: UISlider!
fileprivate var selectionFeedbackGenerator: UISelectionFeedbackGenerator? private var selectionFeedbackGenerator: UISelectionFeedbackGenerator?
private var previousSelectedRowIndexPath: IndexPath?
required init?(coder aDecoder: NSCoder) required init?(coder aDecoder: NSCoder)
{ {
super.init(coder: aDecoder) super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidConnect(_:)), name: .externalControllerDidConnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalControllerDidDisconnect(_:)), name: .externalControllerDidDisconnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
} }
override func viewDidLoad() override func viewDidLoad()
@ -52,8 +64,15 @@ class SettingsViewController: UITableViewController
{ {
super.viewWillAppear(animated) 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) self.tableView.deselectRow(at: indexPath, animated: true)
} }
} }
@ -72,6 +91,8 @@ class SettingsViewController: UITableViewController
let indexPath = self.tableView.indexPath(for: cell) let indexPath = self.tableView.indexPath(for: cell)
else { return } else { return }
self.previousSelectedRowIndexPath = indexPath
switch segueType switch segueType
{ {
case Segue.controllers: case Segue.controllers:
@ -79,13 +100,14 @@ class SettingsViewController: UITableViewController
controllersSettingsViewController.playerIndex = indexPath.row controllersSettingsViewController.playerIndex = indexPath.row
case Segue.controllerSkins: 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 .snes: systemControllerSkinsViewController.system = .snes
case 1: gameTypeControllerSkinsViewController.gameType = .gba case .gba: systemControllerSkinsViewController.system = .gba
default: break 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 private extension SettingsViewController
{ {
@IBAction func beginChangingControllerOpacity(with sender: UISlider) @IBAction func beginChangingControllerOpacity(with sender: UISlider)
@ -143,12 +153,12 @@ private extension SettingsViewController
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) 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) self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
} }
@ -162,6 +172,7 @@ extension SettingsViewController
switch section switch section
{ {
case .controllers: return 1 // Temporarily hide other controller indexes until controller logic is finalized 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) default: return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
} }
} }
@ -169,24 +180,29 @@ extension SettingsViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{ {
let cell = super.tableView(tableView, cellForRowAt: indexPath) 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 if indexPath.row == Settings.localControllerPlayerIndex
{ {
cell.detailTextLabel?.text = UIDevice.current.name 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 cell.detailTextLabel?.text = controller.name
} }
else else
{ {
cell.detailTextLabel?.text = nil cell.detailTextLabel?.text = nil
} }
case .controllerSkins: cell.textLabel?.text = System.supportedSystems[indexPath.row].localizedName
default: break
} }
return cell return cell
} }
@ -194,7 +210,7 @@ extension SettingsViewController
{ {
let cell = tableView.cellForRow(at: indexPath) let cell = tableView.cellForRow(at: indexPath)
let section = Section(rawValue: indexPath.section)! let section = Section(rawValue: indexPath.section)!
switch section switch section
{ {
case Section.controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell) case Section.controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)

View File

@ -60,6 +60,20 @@
<string>com.pkware.zip-archive</string> <string>com.pkware.zip-archive</string>
</array> </array>
</dict> </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> </array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
@ -82,17 +96,17 @@
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSExceptionDomains</key> <key>NSExceptionDomains</key>
<dict> <dict>
<key>gamefaqs.net</key> <key>gamefaqs.net</key>
<dict> <dict>
<key>NSIncludesSubdomains</key> <key>NSIncludesSubdomains</key>
<true/> <true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/> <true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key> <key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string> <string>TLSv1.1</string>
</dict> </dict>
</dict> </dict>
</dict> </dict>
<key>UIFileSharingEnabled</key> <key>UIFileSharingEnabled</key>
<true/> <true/>
@ -112,6 +126,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>NSPhotoLibraryUsageDescription</key>
<string>Press &quot;OK&quot; to allow Delta to use images from your Photo Library as game artwork.</string>
<key>UTExportedTypeDeclarations</key> <key>UTExportedTypeDeclarations</key>
<array> <array>
<dict> <dict>
@ -179,6 +195,38 @@
<string>deltaskin</string> <string>deltaskin</string>
</dict> </dict>
</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> </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> </dict>
</plist> </plist>

103
Delta/Systems/System.swift Normal file
View 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

@ -1 +1 @@
Subproject commit 9143d01afa25f07da8f1cdb5390b49f4efb1a75f Subproject commit 192682bb389830944d257946d870e1d50ae4c42b

View File

@ -7,4 +7,7 @@ target 'Delta' do
pod 'FileMD5Hash', '~> 2.0.0' pod 'FileMD5Hash', '~> 2.0.0'
pod 'SQLite.swift', '~> 0.11.0' pod 'SQLite.swift', '~> 0.11.0'
pod 'SDWebImage', '~> 3.8' pod 'SDWebImage', '~> 3.8'
end pod 'Fabric', '~> 1.6.0'
pod 'Crashlytics', '~> 3.8.0'
pod 'SMCalloutView'
end

View File

@ -1,22 +1,32 @@
PODS: PODS:
- Crashlytics (3.8.6):
- Fabric (~> 1.6.3)
- Fabric (1.6.13)
- FileMD5Hash (2.0.0) - FileMD5Hash (2.0.0)
- SDWebImage (3.8.2): - SDWebImage (3.8.2):
- SDWebImage/Core (= 3.8.2) - SDWebImage/Core (= 3.8.2)
- SDWebImage/Core (3.8.2) - SDWebImage/Core (3.8.2)
- SQLite.swift (0.11.3): - SMCalloutView (2.1.5)
- SQLite.swift/standard (= 0.11.3) - SQLite.swift (0.11.4):
- SQLite.swift/standard (0.11.3) - SQLite.swift/standard (= 0.11.4)
- SQLite.swift/standard (0.11.4)
DEPENDENCIES: DEPENDENCIES:
- Crashlytics (~> 3.8.0)
- Fabric (~> 1.6.0)
- FileMD5Hash (~> 2.0.0) - FileMD5Hash (~> 2.0.0)
- SDWebImage (~> 3.8) - SDWebImage (~> 3.8)
- SMCalloutView
- SQLite.swift (~> 0.11.0) - SQLite.swift (~> 0.11.0)
SPEC CHECKSUMS: SPEC CHECKSUMS:
Crashlytics: 95d05f4e4c19a771250c4bd9ce344d996de32bbf
Fabric: 2fb5676bc811af011a04513451f463dac6803206
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0 FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c' 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
View 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
View 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
View File

@ -0,0 +1,39 @@
![Crashlytics Header](https://docs.fabric.io/ios/cocoapod-readmes/cocoapods-crashlytics-header.png)
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.
![Fabric Plugin](https://docs.fabric.io/ios/cocoapod-readmes/cocoapods-fabric-plugin.png)
1. The Fabric app automatically detects when a project uses CocoaPods and gives you the option to install via the Podfile or Xcode.
![Fabric Installation Options](https://docs.fabric.io/ios/cocoapod-readmes/cocoapods-pod-installation-option.png)
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.
![Fabric Run Script Build Phase](https://docs.fabric.io/ios/cocoapod-readmes/cocoapods-rsbp.png)
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

Binary file not shown.

View 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

View 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

View 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

View 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

View 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