diff --git a/Delta/Base.lproj/Main.storyboard b/Delta/Base.lproj/Main.storyboard index 8512ccb..604d526 100644 --- a/Delta/Base.lproj/Main.storyboard +++ b/Delta/Base.lproj/Main.storyboard @@ -103,6 +103,9 @@ + + + @@ -153,7 +156,7 @@ - + diff --git a/Delta/Base.lproj/PauseMenu.storyboard b/Delta/Base.lproj/PauseMenu.storyboard index 7795544..0ab5e34 100644 --- a/Delta/Base.lproj/PauseMenu.storyboard +++ b/Delta/Base.lproj/PauseMenu.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -14,23 +16,20 @@ - + - - + - - + - @@ -98,9 +97,9 @@ - + - + @@ -109,12 +108,11 @@ - + - @@ -125,7 +123,7 @@ - + @@ -147,9 +145,9 @@ - + - + @@ -158,17 +156,16 @@ - + - - + @@ -194,19 +191,19 @@ - + - + - + - + - + - + @@ -232,21 +229,20 @@ - + - + - + - + - @@ -268,14 +264,13 @@ - + - + - @@ -297,15 +292,14 @@ - + - + - - + diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index 32b5ca4..1c3b332 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -12,6 +12,7 @@ import DeltaCore class GameViewController: DeltaCore.GameViewController { + /// Assumed to be Delta.Game instance override var game: GameProtocol? { didSet { guard let emulatorCore = self.emulatorCore else { return } @@ -22,6 +23,10 @@ class GameViewController: DeltaCore.GameViewController // If non-nil, will override the default preview action items returned in previewActionItems() var overridePreviewActionItems: [UIPreviewActionItem]? + //MARK: - Private Properties - + private var pauseViewController: PauseViewController? + private var pausingGameController: GameController? + required init() { super.init() @@ -94,6 +99,39 @@ extension GameViewController } return [launchGameAction] } + + override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) + { + guard let identifier = segue.identifier where identifier == "pause" 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.destinationViewController as! PauseViewController + pauseViewController.pauseText = (self.game as? Game)?.name ?? NSLocalizedString("Delta", comment: "") + pauseViewController.emulatorCore = self.emulatorCore + self.pauseViewController = pauseViewController + } + + @IBAction private func unwindFromPauseViewController(_ segue: UIStoryboardSegue) + { + self.pauseViewController = nil + self.pausingGameController = nil + + if self.resumeEmulation() + { + // Temporarily disable audioManager to prevent delayed audio bug when using 3D Touch Peek & Pop + self.emulatorCore?.audioManager.enabled = false + + // Re-enable after delay + DispatchQueue.main.after(when: .now() + 0.1) { + self.emulatorCore?.audioManager.enabled = true + } + } + } } //MARK: Controllers - @@ -141,13 +179,11 @@ extension GameViewController: GameViewControllerDelegate { self.pauseEmulation() - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: { (action) in - self.resumeEmulation() - })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Stop Emulation", comment: ""), style: .destructive, handler: { (action) in - self.dismiss(animated: true) - })) - self.present(alertController, animated: true) + self.performSegue(withIdentifier: "pause", sender: gameController) + } + + func gameViewControllerShouldResumeEmulation(gameViewController: DeltaCore.GameViewController) -> Bool + { + return self.pauseViewController == nil } } diff --git a/Delta/Pause Menu/PauseViewController.swift b/Delta/Pause Menu/PauseViewController.swift index a12a25e..3685d7c 100644 --- a/Delta/Pause Menu/PauseViewController.swift +++ b/Delta/Pause Menu/PauseViewController.swift @@ -8,18 +8,31 @@ import UIKit -class PauseViewController: UIViewController, PauseInfoProvidable +import DeltaCore + +class PauseViewController: UIViewController, PauseInfoProviding { + var emulatorCore: EmulatorCore? { + didSet { + self.updatePauseItems() + } + } + + var pauseItems: [PauseItem] { + return [self.saveStateItem, self.loadStateItem, self.cheatCodesItem, self.sustainButtonsItem, self.fastForwardItem].flatMap { $0 } + } + /// Pause Items - var items = [PauseItem]() + private(set) var saveStateItem: PauseItem? + private(set) var loadStateItem: PauseItem? + private(set) var cheatCodesItem: PauseItem? + private(set) var sustainButtonsItem: PauseItem? + private(set) var fastForwardItem: PauseItem? - /// - var pauseText: String? = nil + /// PauseInfoProviding + var pauseText: String? - private weak var saveStatesViewControllerDelegate: SaveStatesViewControllerDelegate? - private var saveStatesViewControllerMode = SaveStatesViewController.Mode.saving - - private weak var cheatsViewControllerDelegate: CheatsViewControllerDelegate? + private var pauseNavigationController: UINavigationController! /// UIViewController override var preferredContentSize: CGSize { @@ -39,22 +52,10 @@ class PauseViewController: UIViewController, PauseInfoProvidable override var navigationController: UINavigationController? { return self.pauseNavigationController } - - private var pauseNavigationController: UINavigationController! } extension PauseViewController { - override func viewDidLoad() - { - super.viewDidLoad() - } - - override func didReceiveMemoryWarning() - { - super.didReceiveMemoryWarning() - } - override func preferredStatusBarStyle() -> UIStatusBarStyle { return .lightContent @@ -68,69 +69,49 @@ extension PauseViewController self.pauseNavigationController.navigationBar.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.pauseNavigationController.navigationBar.bounds.height) } - override func targetViewController(forAction action: Selector, sender: AnyObject?) -> UIViewController? { + override func targetViewController(forAction action: Selector, sender: AnyObject?) -> UIViewController? + { return self.pauseNavigationController } override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { - switch segue.identifier ?? "" + guard let identifier = segue.identifier else { return } + + switch identifier { case "embedNavigationController": self.pauseNavigationController = segue.destinationViewController as! UINavigationController - self.pauseNavigationController.delegate = self self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaLightPurpleColor() self.pauseNavigationController.view.backgroundColor = UIColor.clear() let pauseMenuViewController = self.pauseNavigationController.topViewController as! PauseMenuViewController - pauseMenuViewController.items = self.items + pauseMenuViewController.items = self.pauseItems // Keep navigation bar outside the UIVisualEffectView's self.view.addSubview(self.pauseNavigationController.navigationBar) - - case "saveStates": - let saveStatesViewController = segue.destinationViewController as! SaveStatesViewController - saveStatesViewController.delegate = self.saveStatesViewControllerDelegate - saveStatesViewController.mode = self.saveStatesViewControllerMode - - case "cheats": - let cheatsViewController = segue.destinationViewController as! CheatsViewController - cheatsViewController.delegate = self.cheatsViewControllerDelegate - + default: break } } } -extension PauseViewController +private extension PauseViewController { - func dismiss() + func updatePauseItems() { - self.performSegue(withIdentifier: "unwindFromPauseMenu", sender: self) - } - - func presentSaveStateViewControllerWithMode(_ mode: SaveStatesViewController.Mode, delegate: SaveStatesViewControllerDelegate) - { - self.saveStatesViewControllerMode = mode - self.saveStatesViewControllerDelegate = delegate + self.saveStateItem = nil + self.loadStateItem = nil + self.cheatCodesItem = nil + self.sustainButtonsItem = nil + self.fastForwardItem = nil - self.performSegue(withIdentifier: "saveStates", sender: self) - } - - func presentCheatsViewController(delegate: CheatsViewControllerDelegate) - { - self.cheatsViewControllerDelegate = delegate + guard self.emulatorCore != nil else { return } - self.performSegue(withIdentifier: "cheats", sender: self) - } -} - -extension PauseViewController: UINavigationControllerDelegate -{ - func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? - { - let transitionCoordinator = PauseTransitionCoordinator(presentationController: self.presentationController!) - transitionCoordinator.presenting = (operation == .push) - return transitionCoordinator + self.saveStateItem = PauseItem(image: UIImage(named: "SaveSaveState")!, text: NSLocalizedString("Save State", comment: ""), action: { _ in }) + self.loadStateItem = PauseItem(image: UIImage(named: "LoadSaveState")!, text: NSLocalizedString("Load State", comment: ""), action: { _ in }) + self.cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { _ in }) + self.sustainButtonsItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Buttons", comment: ""), action: { _ in }) + self.fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { _ in }) } } diff --git a/Delta/Pause Menu/Presentation Controller/PausePresentationController.swift b/Delta/Pause Menu/Presentation Controller/PausePresentationController.swift index 5ebb2bd..cf20162 100644 --- a/Delta/Pause Menu/Presentation Controller/PausePresentationController.swift +++ b/Delta/Pause Menu/Presentation Controller/PausePresentationController.swift @@ -10,7 +10,7 @@ import UIKit import Roxas -protocol PauseInfoProvidable +protocol PauseInfoProviding { var pauseText: String? { get } } @@ -57,11 +57,11 @@ class PausePresentationController: UIPresentationController override func presentationTransitionWillBegin() { - if let provider = self.presentedViewController as? PauseInfoProvidable + if let provider = self.presentedViewController as? PauseInfoProviding { self.pauseLabel.text = provider.pauseText } - else if let navigationController = self.presentedViewController as? UINavigationController, provider = navigationController.topViewController as? PauseInfoProvidable + else if let navigationController = self.presentedViewController as? UINavigationController, provider = navigationController.topViewController as? PauseInfoProviding { self.pauseLabel.text = provider.pauseText } @@ -160,7 +160,10 @@ class PausePresentationController: UIPresentationController self.presentedView()?.setNeedsLayout() self.presentedView()?.layoutIfNeeded() - self.presentedView()?.frame = self.frameOfPresentedViewInContainerView() + if self.presentingViewController.transitionCoordinator() == nil + { + self.presentedView()?.frame = self.frameOfPresentedViewInContainerView() + } // Unhide pauseIconImageView so its height is involved with layout calculations self.pauseIconImageView.isHidden = false diff --git a/Delta/Pause Menu/Segues/PauseStoryboardSegue.swift b/Delta/Pause Menu/Segues/PauseStoryboardSegue.swift index ecc64c0..ffc2bf3 100644 --- a/Delta/Pause Menu/Segues/PauseStoryboardSegue.swift +++ b/Delta/Pause Menu/Segues/PauseStoryboardSegue.swift @@ -10,10 +10,14 @@ import UIKit class PauseStoryboardSegue: UIStoryboardSegue { + private let animator: UIViewPropertyAnimator private let presentationController: PausePresentationController override init(identifier: String?, source: UIViewController, destination: UIViewController) { + let timingParameters = UISpringTimingParameters(mass: 3.0, stiffness: 750, damping: 65, initialVelocity: CGVector(dx: 0, dy: 0)) + self.animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParameters) + self.presentationController = PausePresentationController(presentedViewController: destination, presenting: source) super.init(identifier: identifier, source: source, destination: destination) @@ -25,6 +29,11 @@ class PauseStoryboardSegue: UIStoryboardSegue self.destinationViewController.modalPresentationStyle = .custom self.destinationViewController.modalPresentationCapturesStatusBarAppearance = true + // We need to force layout of destinationViewController.view _before_ animateTransition(using:) + // Otherwise, we'll get "Unable to simultaneously satisfy constraints" errors + self.destinationViewController.view.frame = self.sourceViewController.view.frame + self.destinationViewController.view.layoutIfNeeded() + super.perform() } } @@ -51,29 +60,26 @@ extension PauseStoryboardSegue: UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.65 + return self.animator.duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - let destinationViewController = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey)! + let presentedView = transitionContext.view(forKey: UITransitionContextToViewKey)! + let presentedViewController = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey)! - destinationViewController.view.frame = transitionContext.finalFrame(for: destinationViewController) - destinationViewController.view.frame.origin.y = transitionContext.containerView().bounds.height - transitionContext.containerView().addSubview(destinationViewController.view) + presentedView.frame = transitionContext.finalFrame(for: presentedViewController) + presentedView.frame.origin.y = transitionContext.containerView().bounds.height + transitionContext.containerView().addSubview(presentedView) - UIView.animate(withDuration: self.transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: [], animations: { - - // Calling layoutIfNeeded before the animation block for some reason prevents the blur from fading in - // Additionally, if it's animated, it looks weird - // So we need to wrap it in a no-animation block, inside an animation block. Blech. - UIView.performWithoutAnimation({ - destinationViewController.view.layoutIfNeeded() - }) - - destinationViewController.view.frame = self.presentationController.frameOfPresentedViewInContainerView() - }, completion: { finished in - transitionContext.completeTransition(finished) - }) + self.animator.addAnimations { [unowned self] in + presentedView.frame = self.presentationController.frameOfPresentedViewInContainerView() + } + + self.animator.addCompletion { position in + transitionContext.completeTransition(position == .end) + } + + self.animator.startAnimation() } }