diff --git a/Common/Components/BoxArtImageView.swift b/Common/Components/BoxArtImageView.swift deleted file mode 100644 index f12ae9f..0000000 --- a/Common/Components/BoxArtImageView.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// BoxArtImageView.swift -// Delta -// -// Created by Riley Testut on 10/27/15. -// Copyright © 2015 Riley Testut. All rights reserved. -// - -import UIKit - -class BoxArtImageView: UIImageView -{ - override var image: UIImage? { - didSet - { - if image == nil - { - image = UIImage(named: "BoxArt") - } - - } - } - - init() - { - super.init(image: nil) - - self.initialize() - } - - override init(frame: CGRect) - { - super.init(frame: frame) - - self.image = nil - - self.initialize() - } - - required init?(coder aDecoder: NSCoder) - { - super.init(coder: aDecoder) - - self.initialize() - } - - private func initialize() - { - #if os(tvOS) - self.adjustsImageWhenAncestorFocused = true - #endif - - self.contentMode = .scaleAspectFit - } -} diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index cf8083f..df6e0bd 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -53,16 +53,20 @@ BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */; }; BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC273171BE6152200D22B05 /* GameCollection.swift */; }; BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; }; + BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; }; BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; }; BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */; }; BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE39391BC0CEDF003F72E8 /* Game.swift */; }; BFE704F51CEA426E0058BAC8 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.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, ); }; }; - BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E01AAC406100EE9DD1 /* Main.storyboard */; }; - BFFA71E71AAC406100EE9DD1 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E51AAC406100EE9DD1 /* LaunchScreen.xib */; }; + BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */; }; + BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */; }; + BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */; }; + BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */; }; + BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */; }; BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC464B1D5998D600AF2CC6 /* CheatTableViewCell.swift */; }; /* End PBXBuildFile section */ @@ -137,15 +141,19 @@ BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BFC273171BE6152200D22B05 /* GameCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollection.swift; sourceTree = ""; }; BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatsViewController.swift; path = "Pause Menu/Cheats/CheatsViewController.swift"; sourceTree = ""; }; + BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = ""; }; BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamePickerController.swift; sourceTree = ""; }; BFDE39391BC0CEDF003F72E8 /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = ""; }; BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoxArtImageView.swift; path = Components/BoxArtImageView.swift; sourceTree = ""; }; 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 = ""; }; BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BFFA71E11AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - BFFA71E61AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesPresentationController.swift; path = Segues/GamesPresentationController.swift; sourceTree = ""; }; + BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesStoryboardSegue.swift; path = Segues/GamesStoryboardSegue.swift; sourceTree = ""; }; + BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InitialGamesStoryboardSegue.swift; path = Segues/InitialGamesStoryboardSegue.swift; sourceTree = ""; }; + BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LaunchViewController.swift; path = Launch/LaunchViewController.swift; sourceTree = ""; }; + BFFC46451D59861000AF2CC6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; BFFC464B1D5998D600AF2CC6 /* CheatTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheatTableViewCell.swift; path = "Pause Menu/Cheats/CheatTableViewCell.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -183,6 +191,7 @@ BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */, BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */, BF04A5A21CF8E61C00B4A267 /* UIViewController+PeekPop.swift */, + BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */, ); path = Extensions; sourceTree = ""; @@ -243,6 +252,7 @@ children = ( BF107EC31BF413F000E0C32C /* GamesViewController.swift */, BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */, + BFFC461A1D59820F00AF2CC6 /* Segues */, ); path = "Game Selection"; sourceTree = ""; @@ -350,7 +360,6 @@ isa = PBXGroup; children = ( BFA2315B1CED10BE0011E35A /* Action.swift */, - BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */, BF0CDDAC1C8155D200640168 /* LoadImageOperation.swift */, ); name = Components; @@ -381,6 +390,7 @@ children = ( BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */, BFFA71E01AAC406100EE9DD1 /* Main.storyboard */, + BFFC46211D59848000AF2CC6 /* Launch */, BF46894D1AAC469800A2586D /* Game Selection */, BFFB709D1AF99ACA00DE56FE /* Emulation */, BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */, @@ -397,7 +407,6 @@ BF090CF11B490D8300DCAB45 /* Delta-Bridging-Header.h */, BF27CC861BC9E3C600A20D89 /* Delta.entitlements */, BFFA71DB1AAC406100EE9DD1 /* Info.plist */, - BFFA71E51AAC406100EE9DD1 /* LaunchScreen.xib */, ); path = "Supporting Files"; sourceTree = ""; @@ -410,6 +419,25 @@ path = Emulation; sourceTree = ""; }; + BFFC461A1D59820F00AF2CC6 /* Segues */ = { + isa = PBXGroup; + children = ( + BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */, + BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */, + BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */, + ); + name = Segues; + sourceTree = ""; + }; + BFFC46211D59848000AF2CC6 /* Launch */ = { + isa = PBXGroup; + children = ( + BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */, + BFFC46221D5984A000AF2CC6 /* LaunchViewController.swift */, + ); + name = Launch; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -481,8 +509,8 @@ files = ( BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */, BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */, - BFFA71E71AAC406100EE9DD1 /* LaunchScreen.xib in Resources */, BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */, + BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */, BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */, BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */, ); @@ -550,6 +578,7 @@ BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */, BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */, BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */, + BFFC46201D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift in Sources */, BFA2315C1CED10BE0011E35A /* Action.swift in Sources */, BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */, BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */, @@ -557,12 +586,13 @@ BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */, BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */, + BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */, BF04A5A31CF8E61C00B4A267 /* UIViewController+PeekPop.swift in Sources */, BF4566E81BC090B6007BFA1A /* Model.xcdatamodeld in Sources */, BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */, BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */, BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */, - BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */, + BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */, BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */, BF762EAB1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */, BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */, @@ -579,6 +609,8 @@ BF0CDDAD1C8155D200640168 /* LoadImageOperation.swift in Sources */, BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */, BF172AEB1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */, + BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */, + BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */, BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */, BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */, BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */, @@ -606,13 +638,12 @@ path = .; sourceTree = ""; }; - BFFA71E51AAC406100EE9DD1 /* LaunchScreen.xib */ = { + BFFC46441D59861000AF2CC6 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - BFFA71E61AAC406100EE9DD1 /* Base */, + BFFC46451D59861000AF2CC6 /* Base */, ); - name = LaunchScreen.xib; - path = .; + name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ diff --git a/Delta/Base.lproj/LaunchScreen.storyboard b/Delta/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..61c9dc3 --- /dev/null +++ b/Delta/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delta/Base.lproj/Main.storyboard b/Delta/Base.lproj/Main.storyboard index 604d526..c56c871 100644 --- a/Delta/Base.lproj/Main.storyboard +++ b/Delta/Base.lproj/Main.storyboard @@ -1,9 +1,8 @@ - + - - + @@ -48,7 +47,7 @@ - + @@ -73,10 +72,7 @@ - - - - + @@ -87,8 +83,9 @@ + - + @@ -105,11 +102,13 @@ + + - + @@ -117,7 +116,7 @@ - + @@ -125,7 +124,7 @@ - + @@ -148,7 +147,7 @@ - + @@ -156,13 +155,13 @@ - + - + diff --git a/Delta/Base.lproj/PauseMenu.storyboard b/Delta/Base.lproj/PauseMenu.storyboard index a552ae8..93db1ba 100644 --- a/Delta/Base.lproj/PauseMenu.storyboard +++ b/Delta/Base.lproj/PauseMenu.storyboard @@ -108,12 +108,12 @@ - + - + diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index 915c87b..6f68305 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -185,59 +185,106 @@ extension GameViewController override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { - guard let identifier = segue.identifier, identifier == "pause" else { return } + guard let identifier = segue.identifier else { return } - guard let gameController = sender as? GameController else { - fatalError("sender for pauseSegue must be the game controller that pressed the Menu button") - } - - self.pausingGameController = gameController - - let pauseViewController = segue.destination as! PauseViewController - pauseViewController.pauseText = (self.game as? Game)?.name ?? NSLocalizedString("Delta", comment: "") - pauseViewController.emulatorCore = self.emulatorCore - pauseViewController.saveStatesViewControllerDelegate = self - pauseViewController.cheatsViewControllerDelegate = self - - pauseViewController.fastForwardItem?.selected = (self.emulatorCore?.rate != self.emulatorCore?.configuration.supportedRates.lowerBound) - pauseViewController.fastForwardItem?.action = { [unowned self] item in - guard let emulatorCore = self.emulatorCore else { return } - emulatorCore.rate = item.selected ? emulatorCore.configuration.supportedRates.upperBound : emulatorCore.configuration.supportedRates.lowerBound - } - - pauseViewController.sustainButtonsItem?.selected = (self.sustainedInputs[ObjectIdentifier(gameController)]?.count ?? 0) > 0 - pauseViewController.sustainButtonsItem?.action = { [unowned self] item in + switch identifier + { + case "showGamesViewController": + let gamesViewController = (segue.destination as! UINavigationController).topViewController as! GamesViewController + gamesViewController.theme = .dark - self.resetSustainedInputs(for: gameController) - - if item.selected - { - self.showSustainButtonView() - pauseViewController.dismiss() + case "pause": + guard let gameController = sender as? GameController else { + fatalError("sender for pauseSegue must be the game controller that pressed the Menu button") } + + self.pausingGameController = gameController + + let pauseViewController = segue.destination as! PauseViewController + pauseViewController.pauseText = (self.game as? Game)?.name ?? NSLocalizedString("Delta", comment: "") + pauseViewController.emulatorCore = self.emulatorCore + pauseViewController.saveStatesViewControllerDelegate = self + pauseViewController.cheatsViewControllerDelegate = self + + pauseViewController.fastForwardItem?.selected = (self.emulatorCore?.rate != self.emulatorCore?.configuration.supportedRates.lowerBound) + pauseViewController.fastForwardItem?.action = { [unowned self] item in + guard let emulatorCore = self.emulatorCore else { return } + emulatorCore.rate = item.selected ? emulatorCore.configuration.supportedRates.upperBound : emulatorCore.configuration.supportedRates.lowerBound + } + + pauseViewController.sustainButtonsItem?.selected = (self.sustainedInputs[ObjectIdentifier(gameController)]?.count ?? 0) > 0 + pauseViewController.sustainButtonsItem?.action = { [unowned self] item in + + self.resetSustainedInputs(for: gameController) + + if item.selected + { + self.showSustainButtonView() + pauseViewController.dismiss() + } + } + + self.pauseViewController = pauseViewController + + default: break } - - self.pauseViewController = pauseViewController } @IBAction private func unwindFromPauseViewController(_ segue: UIStoryboardSegue) - { + { self.pauseViewController = nil self.pausingGameController = nil - if self.resumeEmulation() + guard let identifier = segue.identifier else { return } + + switch identifier { - // Temporarily disable audioManager to prevent delayed audio bug when using 3D Touch Peek & Pop - self.emulatorCore?.audioManager.enabled = false - - // Re-enable after delay + case "unwindFromPauseMenu": + DispatchQueue.main.async { + if + let transitionCoordinator = self.transitionCoordinator, + let navigationController = segue.source.navigationController, + navigationController.viewControllers.count == 1 + { + // If user pressed "Resume" from Pause Menu, we wait for the transition to complete before resuming emulation + transitionCoordinator.animate(alongsideTransition: nil, completion: { (context) in + self.resumeEmulation() + }) + } + else + { + // Otherwise, we resume emulation immediately (such as when loading save states and the game view needs to be updated ASAP) + + if self.resumeEmulation() + { + // Temporarily disable audioManager to prevent delayed audio bug when using 3D Touch Peek & Pop + self.emulatorCore?.audioManager.enabled = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.emulatorCore?.audioManager.enabled = true + // Re-enable after delay + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.emulatorCore?.audioManager.enabled = true + } + } + } } + + case "unwindToGames": + DispatchQueue.main.async { + self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in + self.performSegue(withIdentifier: "showGamesViewController", sender: nil) + }) + } + + default: break } } + @IBAction private func unwindFromGamesViewController(with segue: UIStoryboardSegue) + { + self.emulatorCore?.resume() + } + // MARK: - KVO /// KVO override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer?) @@ -552,7 +599,6 @@ private extension GameViewController self.reactivateSustainedInputsQueue.waitUntilAllOperationsAreFinished() gameController.addReceiver(self) - } } } @@ -575,7 +621,7 @@ extension GameViewController: GameViewControllerDelegate func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool { - return self.pauseViewController == nil && !self.selectingSustainedButtons + return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.selectingSustainedButtons } func gameViewControllerDidUpdate(_ gameViewController: DeltaCore.GameViewController) diff --git a/Delta/Extensions/UIViewControllerContextTransitioning+Conveniences.swift b/Delta/Extensions/UIViewControllerContextTransitioning+Conveniences.swift new file mode 100644 index 0000000..1914d5c --- /dev/null +++ b/Delta/Extensions/UIViewControllerContextTransitioning+Conveniences.swift @@ -0,0 +1,52 @@ +// +// UIViewControllerContextTransitioning+Conveniences.swift +// Delta +// +// Created by Riley Testut on 7/28/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +extension UIViewControllerContextTransitioning +{ + /// UIViewControllers + var sourceViewController: UIViewController { + return self.viewController(forKey: UITransitionContextFromViewControllerKey)! + } + + var destinationViewController: UIViewController { + return self.viewController(forKey: UITransitionContextToViewControllerKey)! + } + + /// UIViews + var sourceView: UIView { + return self.sourceViewController.view + } + + var destinationView: UIView { + return self.destinationViewController.view + } + + + /// Frames + var sourceViewInitialFrame: CGRect? { + let frame = self.initialFrame(for: self.sourceViewController) + return frame.isEmpty ? nil : frame + } + + var sourceViewFinalFrame: CGRect? { + let frame = self.finalFrame(for: self.sourceViewController) + return frame.isEmpty ? nil : frame + } + + var destinationViewInitialFrame: CGRect? { + let frame = self.initialFrame(for: self.destinationViewController) + return frame.isEmpty ? nil : frame + } + + var destinationViewFinalFrame: CGRect? { + let frame = self.finalFrame(for: self.destinationViewController) + return frame.isEmpty ? nil : frame + } +} diff --git a/Delta/Game Selection/GamesCollectionViewController.swift b/Delta/Game Selection/GamesCollectionViewController.swift index 05963a2..dcf82f7 100644 --- a/Delta/Game Selection/GamesCollectionViewController.swift +++ b/Delta/Game Selection/GamesCollectionViewController.swift @@ -13,6 +13,12 @@ import DeltaCore class GamesCollectionViewController: UICollectionViewController { + var theme: GamesViewController.Theme = .light { + didSet { + self.collectionView?.reloadData() + } + } + weak var segueHandler: UIViewController? var gameCollection: GameCollection! { @@ -74,6 +80,19 @@ class GamesCollectionViewController: UICollectionViewController cell.maximumImageSize = CGSize(width: 90, height: 90) cell.textLabel.text = game.name cell.imageView.image = UIImage(named: "BoxArt") + + switch self.theme + { + case .light: + cell.textLabel.textColor = UIColor.darkText + cell.isTextLabelVibrancyEnabled = false + cell.isImageViewVibrancyEnabled = false + + case .dark: + cell.textLabel.textColor = UIColor.white + cell.isTextLabelVibrancyEnabled = true + cell.isImageViewVibrancyEnabled = true + } } } diff --git a/Delta/Game Selection/GamesViewController.swift b/Delta/Game Selection/GamesViewController.swift index 0ab83be..7c11ce2 100644 --- a/Delta/Game Selection/GamesViewController.swift +++ b/Delta/Game Selection/GamesViewController.swift @@ -13,8 +13,23 @@ import DeltaCore import Roxas +extension GamesViewController +{ + enum Theme + { + case light + case dark + } +} + class GamesViewController: UIViewController { + var theme: Theme = .light { + didSet { + self.updateTheme() + } + } + private var pageViewController: UIPageViewController! private var backgroundView: RSTBackgroundView! private var pageControl: UIPageControl! @@ -133,6 +148,32 @@ class GamesViewController: UIViewController } } +// MARK: - UI - +/// UI +private extension GamesViewController +{ + func updateTheme() + { + switch self.theme + { + case .light: + self.view.backgroundColor = UIColor.white + self.navigationController?.navigationBar.barStyle = .default + self.navigationController?.toolbar.barStyle = .default + + case .dark: + self.view.backgroundColor = nil + self.navigationController?.navigationBar.barStyle = .blackTranslucent + self.navigationController?.toolbar.barStyle = .blackTranslucent + } + + if let collectionViewController = self.pageViewController.viewControllers?.first as? UICollectionViewController + { + collectionViewController.collectionView?.reloadData() + } + } +} + private extension GamesViewController { func viewControllerForIndex(_ index: Int) -> GamesCollectionViewController? @@ -154,6 +195,7 @@ private extension GamesViewController viewController.gameCollection = self.fetchedResultsController.object(at: indexPath) as! GameCollection viewController.collectionView?.contentInset.top = self.topLayoutGuide.length viewController.segueHandler = self + viewController.theme = self.theme return viewController } diff --git a/Delta/Game Selection/Segues/GamesPresentationController.swift b/Delta/Game Selection/Segues/GamesPresentationController.swift new file mode 100644 index 0000000..9167fde --- /dev/null +++ b/Delta/Game Selection/Segues/GamesPresentationController.swift @@ -0,0 +1,46 @@ +// +// GamesPresentationController.swift +// Delta +// +// Created by Riley Testut on 8/7/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +class GamesPresentationController: UIPresentationController +{ + private let blurView: UIVisualEffectView + + override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) + { + self.blurView = UIVisualEffectView(effect: nil) + self.blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + super.init(presentedViewController: presentedViewController, presenting: presentingViewController) + } + + override func presentationTransitionWillBegin() + { + guard let containerView = self.containerView else { return } + + self.blurView.frame = CGRect(x: 0, y: 0, width: containerView.bounds.width, height: containerView.bounds.height) + containerView.addSubview(self.blurView) + + self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in + self.blurView.effect = UIBlurEffect(style: .dark) + }) + } + + override func dismissalTransitionWillBegin() + { + self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in + self.blurView.effect = nil + }) + } + + override func dismissalTransitionDidEnd(_ completed: Bool) + { + self.blurView.removeFromSuperview() + } +} diff --git a/Delta/Game Selection/Segues/GamesStoryboardSegue.swift b/Delta/Game Selection/Segues/GamesStoryboardSegue.swift new file mode 100644 index 0000000..9888780 --- /dev/null +++ b/Delta/Game Selection/Segues/GamesStoryboardSegue.swift @@ -0,0 +1,158 @@ +// +// GamesStoryboardSegue.swift +// Delta +// +// Created by Riley Testut on 8/7/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +class GamesStoryboardSegue: UIStoryboardSegue +{ + private let animator: UIViewPropertyAnimator + + private var isPresenting: Bool = true + + override init(identifier: String?, source: UIViewController, destination: UIViewController) + { + let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0)) + self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters) + + super.init(identifier: identifier, source: source, destination: destination) + } + + override func perform() + { + self.destination.transitioningDelegate = self + self.destination.modalPresentationStyle = .custom + self.destination.modalPresentationCapturesStatusBarAppearance = true + + super.perform() + } +} + +extension GamesStoryboardSegue: UIViewControllerTransitioningDelegate +{ + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + self.isPresenting = true + return self + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + self.isPresenting = false + return self + } + + func presentationController(forPresented presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, source: UIViewController) -> UIPresentationController? + { + let presentationController = GamesPresentationController(presentedViewController: presentedViewController, presenting: presentingViewController) + return presentationController + } +} + +extension GamesStoryboardSegue: UIViewControllerAnimatedTransitioning +{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval + { + return self.animator.duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) + { + if self.isPresenting + { + self.animatePresentationTransition(using: transitionContext) + } + else + { + self.animateDismissalTransition(using: transitionContext) + } + } + + func animatePresentationTransition(using transitionContext: UIViewControllerContextTransitioning) + { + transitionContext.destinationView.frame = transitionContext.destinationViewFinalFrame! + transitionContext.destinationView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0) + transitionContext.containerView.addSubview(transitionContext.destinationView) + + let snapshotView = transitionContext.sourceView.snapshotView(afterScreenUpdates: false)! + snapshotView.frame = transitionContext.sourceViewInitialFrame! + snapshotView.alpha = 1.0 + transitionContext.containerView.addSubview(snapshotView) + + // Ensures navigation controller toolbar (if visible) has been added to view heirachy, allowing us to add constraints + transitionContext.containerView.layoutIfNeeded() + + // We add extra padding around the existing navigation bar and toolbar so they never appear to be detached from the edges of the screen during the overshooting of the spring animation + var topPaddingToolbar: UIToolbar? = nil + var bottomPaddingToolbar: UIToolbar? = nil + + if let navigationController = transitionContext.destinationViewController as? UINavigationController + { + let padding: CGFloat = 44 + + if !navigationController.isNavigationBarHidden + { + let topToolbar = UIToolbar(frame: CGRect.zero) + topToolbar.translatesAutoresizingMaskIntoConstraints = false + topToolbar.barStyle = navigationController.toolbar.barStyle + transitionContext.destinationView.insertSubview(topToolbar, belowSubview: navigationController.navigationBar) + + topToolbar.bottomAnchor.constraint(equalTo: navigationController.navigationBar.bottomAnchor).isActive = true + topToolbar.centerXAnchor.constraint(equalTo: navigationController.navigationBar.centerXAnchor).isActive = true + topToolbar.widthAnchor.constraint(equalTo: navigationController.navigationBar.widthAnchor, constant: padding * 2).isActive = true + topToolbar.heightAnchor.constraint(equalTo: navigationController.navigationBar.heightAnchor, constant: padding).isActive = true + + topPaddingToolbar = topToolbar + } + + if !navigationController.isToolbarHidden + { + let bottomToolbar = UIToolbar(frame: CGRect.zero) + bottomToolbar.translatesAutoresizingMaskIntoConstraints = false + bottomToolbar.barStyle = navigationController.toolbar.barStyle + transitionContext.destinationView.insertSubview(bottomToolbar, belowSubview: navigationController.navigationBar) + + bottomToolbar.topAnchor.constraint(equalTo: navigationController.toolbar.topAnchor).isActive = true + bottomToolbar.centerXAnchor.constraint(equalTo: navigationController.toolbar.centerXAnchor).isActive = true + bottomToolbar.widthAnchor.constraint(equalTo: navigationController.toolbar.widthAnchor, constant: padding * 2).isActive = true + bottomToolbar.heightAnchor.constraint(equalTo: navigationController.toolbar.heightAnchor, constant: padding).isActive = true + + bottomPaddingToolbar = bottomToolbar + } + } + + self.animator.addAnimations { + snapshotView.alpha = 0.0 + transitionContext.destinationView.transform = CGAffineTransform.identity + } + + self.animator.addCompletion { (position) in + transitionContext.completeTransition(position == .end) + + snapshotView.removeFromSuperview() + + topPaddingToolbar?.removeFromSuperview() + bottomPaddingToolbar?.removeFromSuperview() + } + + self.animator.startAnimation() + } + + func animateDismissalTransition(using transitionContext: UIViewControllerContextTransitioning) + { + self.animator.addAnimations { + transitionContext.sourceView.alpha = 0.0 + transitionContext.sourceView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0) + } + + self.animator.addCompletion { (position) in + transitionContext.completeTransition(position == .end) + } + + self.animator.startAnimation() + } +} diff --git a/Delta/Game Selection/Segues/InitialGamesStoryboardSegue.swift b/Delta/Game Selection/Segues/InitialGamesStoryboardSegue.swift new file mode 100644 index 0000000..f2ec7a9 --- /dev/null +++ b/Delta/Game Selection/Segues/InitialGamesStoryboardSegue.swift @@ -0,0 +1,96 @@ +// +// InitialGamesStoryboardSegue.swift +// Delta +// +// Created by Riley Testut on 8/7/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +class InitialGamesStoryboardSegue: UIStoryboardSegue +{ + private let animator: UIViewPropertyAnimator + + private var isPresenting: Bool = true + + override init(identifier: String?, source: UIViewController, destination: UIViewController) + { + let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0)) + self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters) + + super.init(identifier: identifier, source: source, destination: destination) + } + + override func perform() + { + self.destination.transitioningDelegate = self + self.destination.modalPresentationStyle = .custom + self.destination.modalPresentationCapturesStatusBarAppearance = true + + super.perform() + } +} + +extension InitialGamesStoryboardSegue: UIViewControllerTransitioningDelegate +{ + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + self.isPresenting = true + return self + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? + { + self.isPresenting = false + return self + } +} + +extension InitialGamesStoryboardSegue: UIViewControllerAnimatedTransitioning +{ + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval + { + return self.isPresenting ? 0.0 : self.animator.duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) + { + if self.isPresenting + { + self.animatePresentationTransition(using: transitionContext) + } + else + { + self.animateDismissalTransition(using: transitionContext) + } + } + + func animatePresentationTransition(using transitionContext: UIViewControllerContextTransitioning) + { + // No animation + transitionContext.destinationView.frame = transitionContext.destinationViewFinalFrame! + transitionContext.containerView.addSubview(transitionContext.destinationView) + transitionContext.completeTransition(true) + } + + func animateDismissalTransition(using transitionContext: UIViewControllerContextTransitioning) + { + transitionContext.destinationView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + transitionContext.destinationView.alpha = 0.0 + + self.animator.addAnimations { + transitionContext.sourceView.alpha = 0.0 + transitionContext.sourceView.transform = CGAffineTransform(scaleX: 2.0, y: 2.0) + + transitionContext.destinationView.alpha = 1.0 + transitionContext.destinationView.transform = CGAffineTransform.identity + } + + self.animator.addCompletion { (position) in + transitionContext.completeTransition(position == .end) + } + + self.animator.startAnimation() + } +} diff --git a/Delta/Launch/LaunchViewController.swift b/Delta/Launch/LaunchViewController.swift new file mode 100644 index 0000000..1f88f54 --- /dev/null +++ b/Delta/Launch/LaunchViewController.swift @@ -0,0 +1,47 @@ +// +// LaunchViewController.swift +// Delta +// +// Created by Riley Testut on 8/8/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +class LaunchViewController: UIViewController +{ + @IBOutlet private var containerView: UIView! + private var gameViewController: GameViewController! + + private var presentedGameViewController: Bool = false + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.gameViewController?.preferredStatusBarStyle ?? .default + } + + override var prefersStatusBarHidden: Bool { + return self.gameViewController?.prefersStatusBarHidden ?? false + } + + override func viewDidAppear(_ animated: Bool) + { + super.viewDidAppear(animated) + + if !self.presentedGameViewController + { + self.presentedGameViewController = true + + self.gameViewController.performSegue(withIdentifier: "showInitialGamesViewController", sender: nil) + self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in + self.containerView.isHidden = false + }) + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) + { + guard segue.identifier == "embedGameViewController" else { return } + + self.gameViewController = segue.destination as! GameViewController + } +} diff --git a/Delta/Supporting Files/Base.lproj/LaunchScreen.xib b/Delta/Supporting Files/Base.lproj/LaunchScreen.xib deleted file mode 100644 index 9960752..0000000 --- a/Delta/Supporting Files/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Delta/Supporting Files/Info.plist b/Delta/Supporting Files/Info.plist index 95684da..b12b0c9 100644 --- a/Delta/Supporting Files/Info.plist +++ b/Delta/Supporting Files/Info.plist @@ -42,7 +42,7 @@ UILaunchStoryboardName LaunchScreen UIMainStoryboardFile - Main + LaunchScreen UIRequiredDeviceCapabilities armv7