Adds basic implementation of GameViewController, replacement for EmulationViewController

This commit is contained in:
Riley Testut 2016-07-17 16:41:57 -05:00
parent e827faadad
commit 60bc9dcfbc
6 changed files with 185 additions and 849 deletions

@ -1 +1 @@
Subproject commit 9a587be47fc89154d7917267e8d423e8a5af0951
Subproject commit d819614e5fe422aa4975f70e3d4f1d2dc97b9c24

View File

@ -27,7 +27,6 @@
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FFD1C5DA3C500C1184C /* PausePresentationController.swift */; };
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
BF3540051C5DA70400C1184C /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */; };
BF4566E81BC090B6007BFA1A /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4566E61BC090B6007BFA1A /* Model.xcdatamodeld */; };
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
@ -46,12 +45,14 @@
BF99C6951D0A9AA600BA92BC /* SNESDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF9F4FCF1AAD7B87004C9500 /* DeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; };
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 */; };
BFA2315C1CED10BE0011E35A /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA2315B1CED10BE0011E35A /* Action.swift */; };
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; };
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 */; };
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 */; };
@ -61,7 +62,6 @@
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 */; };
BFFB709F1AF99B1700DE56FE /* EmulationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -115,6 +115,7 @@
BF4566E71BC090B6007BFA1A /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; 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>"; };
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
BF65E8621CEE5C6A00CD3247 /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cheat.swift; sourceTree = "<group>"; };
BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -142,7 +143,6 @@
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>"; };
BFFA71E61AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmulationViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -399,7 +399,7 @@
BFFB709D1AF99ACA00DE56FE /* Emulation */ = {
isa = PBXGroup;
children = (
BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */,
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */,
);
path = Emulation;
sourceTree = "<group>";
@ -541,9 +541,9 @@
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
BF7AE8241C2E984300B1B5BC /* GridCollectionViewLayout.swift in Sources */,
BF1FB1861C5EE643007E2494 /* SaveState.swift in Sources */,
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */,
BFA2315C1CED10BE0011E35A /* Action.swift in Sources */,
BFFB709F1AF99B1700DE56FE /* EmulationViewController.swift in Sources */,
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */,
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */,
@ -565,6 +565,7 @@
BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */,
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */,
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
BF65E8631CEE5C6A00CD3247 /* Cheat.swift in Sources */,
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
BF0CDDAD1C8155D200640168 /* LoadImageOperation.swift in Sources */,
@ -574,7 +575,6 @@
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */,
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */,
BF3540051C5DA70400C1184C /* SaveStatesViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="wKV-3d-NIY">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11163.2" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="wKV-3d-NIY">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11133"/>
<capability name="Segues with Peek and Pop" minToolsVersion="7.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Games-->
@ -14,17 +16,16 @@
<viewControllerLayoutGuide type="bottom" id="0om-QB-N5a"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="3Bk-k3-7J9">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tmn-gd-5UN">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<connections>
<segue destination="tpK-ou-yEA" kind="embed" id="cjU-nW-cHY"/>
</connections>
</containerView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="tmn-gd-5UN" secondAttribute="trailing" id="9Rq-HM-vqk"/>
<constraint firstItem="0om-QB-N5a" firstAttribute="top" secondItem="tmn-gd-5UN" secondAttribute="bottom" id="DV5-hh-1VN"/>
@ -54,9 +55,9 @@
<objects>
<collectionViewController storyboardIdentifier="gamesCollectionViewController" clearsSelectionOnViewWillAppear="NO" id="kqu-75-owz" customClass="GamesCollectionViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="OIq-Z8-kxO">
<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"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<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="NKN-dd-bTh" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
<size key="itemSize" width="100" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
@ -65,17 +66,16 @@
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="GameCell" id="ioT-sh-j8y" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="100" height="100"/>
<frame key="frameInset" minY="20" width="100" height="100"/>
<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="100" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<connections>
<segue destination="hx4-Ze-0Jw" kind="presentation" id="Ila-yL-N8w">
<segue key="commit" inheritsFrom="parent" id="0B7-vE-k6Z"/>
<segue key="preview" destination="hx4-Ze-0Jw" identifier="peekEmulationViewController" id="Jd4-q3-dNr"/>
<segue destination="yhz-fF-D91" kind="presentation" identifier="presentGameViewController" id="4ws-DY-L4D">
<segue key="commit" inheritsFrom="parent" id="sub-e4-fpl"/>
<segue key="preview" inheritsFrom="commit" id="WKY-hV-sn0"/>
</segue>
</connections>
</collectionViewCell>
@ -88,130 +88,25 @@
</collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1707" y="1002"/>
<point key="canvasLocation" x="1727" y="1002"/>
</scene>
<!--Emulation View Controller-->
<scene sceneID="g58-A4-ib1">
<!--Game View Controller-->
<scene sceneID="ASV-Uk-0aP">
<objects>
<viewController storyboardIdentifier="emulationViewController" id="hx4-Ze-0Jw" customClass="EmulationViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="yhz-fF-D91" customClass="GameViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Fo4-YI-5cN"/>
<viewControllerLayoutGuide type="bottom" id="UIe-oC-tUc"/>
<viewControllerLayoutGuide type="top" id="ItC-Bu-WRI"/>
<viewControllerLayoutGuide type="bottom" id="g58-HO-6L5"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="7ei-Ah-JvQ">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<view key="view" contentMode="scaleToFill" id="skW-1S-YD4">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GfI-Mx-CQT" customClass="GameView" customModule="DeltaCore">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<color key="backgroundColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="noi-yo-HIE">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pwD-5i-uQ2">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="S8Q-YY-Kog">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Eo-TR-4O4">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="8ur-lW-yiV">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JRb-Vg-JHA" customClass="RSTBackgroundView">
<rect key="frame" x="0.0" y="0.0" width="600" height="300"/>
</view>
</subviews>
<constraints>
<constraint firstItem="JRb-Vg-JHA" firstAttribute="top" secondItem="8ur-lW-yiV" secondAttribute="top" id="613-jP-t8K"/>
<constraint firstAttribute="trailing" secondItem="JRb-Vg-JHA" secondAttribute="trailing" id="Ele-t2-Kcg"/>
<constraint firstAttribute="bottom" secondItem="JRb-Vg-JHA" secondAttribute="bottom" id="aTp-7v-QFY"/>
<constraint firstItem="JRb-Vg-JHA" firstAttribute="leading" secondItem="8ur-lW-yiV" secondAttribute="leading" id="c05-Bl-4Q8"/>
</constraints>
</view>
<vibrancyEffect>
<blurEffect style="dark"/>
</vibrancyEffect>
</visualEffectView>
</subviews>
<constraints>
<constraint firstItem="9Eo-TR-4O4" firstAttribute="top" secondItem="S8Q-YY-Kog" secondAttribute="top" id="EgV-fR-NeD"/>
<constraint firstAttribute="bottom" secondItem="9Eo-TR-4O4" secondAttribute="bottom" id="TYE-at-xaY"/>
<constraint firstItem="9Eo-TR-4O4" firstAttribute="leading" secondItem="S8Q-YY-Kog" secondAttribute="leading" id="YRo-nB-Hsb"/>
<constraint firstAttribute="trailing" secondItem="9Eo-TR-4O4" secondAttribute="trailing" id="rWV-bD-7li"/>
</constraints>
</view>
<blurEffect style="dark"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="pwD-5i-uQ2" firstAttribute="leading" secondItem="noi-yo-HIE" secondAttribute="leading" id="ai3-VH-ZSe"/>
<constraint firstAttribute="trailing" secondItem="pwD-5i-uQ2" secondAttribute="trailing" id="fTZ-ah-pgi"/>
<constraint firstItem="pwD-5i-uQ2" firstAttribute="top" secondItem="noi-yo-HIE" secondAttribute="top" id="gYf-Oa-jve"/>
<constraint firstAttribute="bottom" secondItem="pwD-5i-uQ2" secondAttribute="bottom" id="u3P-PF-gpZ"/>
</constraints>
</view>
<view contentMode="scaleToFill" placeholderIntrinsicWidth="600" placeholderIntrinsicHeight="300" translatesAutoresizingMaskIntoConstraints="NO" id="2W1-IT-Y2l" customClass="ControllerView" customModule="DeltaCore">
<rect key="frame" x="0.0" y="300" width="600" height="300"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="300" id="PEV-zv-Buo"/>
</constraints>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="PEV-zv-Buo"/>
</mask>
</variation>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="noi-yo-HIE" firstAttribute="top" secondItem="GfI-Mx-CQT" secondAttribute="top" id="1wY-jJ-o0w"/>
<constraint firstItem="2W1-IT-Y2l" firstAttribute="top" secondItem="7ei-Ah-JvQ" secondAttribute="top" id="EF1-5h-FJs"/>
<constraint firstAttribute="trailing" secondItem="GfI-Mx-CQT" secondAttribute="trailing" id="EKN-e9-x37"/>
<constraint firstItem="noi-yo-HIE" firstAttribute="bottom" secondItem="GfI-Mx-CQT" secondAttribute="bottom" id="IEw-2f-nC4"/>
<constraint firstItem="noi-yo-HIE" firstAttribute="leading" secondItem="GfI-Mx-CQT" secondAttribute="leading" id="aE5-xg-A1u"/>
<constraint firstItem="GfI-Mx-CQT" firstAttribute="top" secondItem="7ei-Ah-JvQ" secondAttribute="top" id="ala-4P-wQR"/>
<constraint firstAttribute="bottom" secondItem="GfI-Mx-CQT" secondAttribute="bottom" id="gFo-XU-4PO">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstItem="GfI-Mx-CQT" firstAttribute="leading" secondItem="7ei-Ah-JvQ" secondAttribute="leading" id="hck-DY-vlt"/>
<constraint firstItem="noi-yo-HIE" firstAttribute="trailing" secondItem="GfI-Mx-CQT" secondAttribute="trailing" id="l7o-xb-Zxa"/>
<constraint firstItem="2W1-IT-Y2l" firstAttribute="top" secondItem="GfI-Mx-CQT" secondAttribute="bottom" id="uWb-Cv-NM1"/>
<constraint firstAttribute="bottom" secondItem="2W1-IT-Y2l" secondAttribute="bottom" id="vZg-yU-Kqm"/>
<constraint firstAttribute="trailing" secondItem="2W1-IT-Y2l" secondAttribute="trailing" id="wcY-Qm-RRC"/>
<constraint firstItem="2W1-IT-Y2l" firstAttribute="leading" secondItem="7ei-Ah-JvQ" secondAttribute="leading" id="ygW-0T-DPh"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="gFo-XU-4PO"/>
<exclude reference="EF1-5h-FJs"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="gFo-XU-4PO"/>
<include reference="EF1-5h-FJs"/>
<exclude reference="uWb-Cv-NM1"/>
</mask>
</variation>
</view>
<connections>
<outlet property="backgroundView" destination="JRb-Vg-JHA" id="85O-6W-WOe"/>
<outlet property="controllerView" destination="2W1-IT-Y2l" id="WCa-LM-fXF"/>
<outlet property="controllerViewHeightConstraint" destination="PEV-zv-Buo" id="1jo-Kg-HpO"/>
<outlet property="gameView" destination="GfI-Mx-CQT" id="HmP-OA-mci"/>
<outlet property="sustainButtonContentView" destination="noi-yo-HIE" id="7FG-2i-LC2"/>
<segue destination="Yrw-9v-Pcr" kind="presentation" identifier="pauseSegue" customClass="PauseStoryboardSegue" customModule="Delta" customModuleProvider="target" id="9cz-mr-lTk"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="ZYd-It-2t0" userLabel="First Responder" sceneMemberID="firstResponder"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="gxI-00-NlJ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2361" y="1002"/>
<point key="canvasLocation" x="2472" y="1002"/>
</scene>
<!--Page View Controller-->
<scene sceneID="35q-Io-64T">
@ -258,13 +153,13 @@
<viewControllerPlaceholder storyboardName="PauseMenu" id="Yrw-9v-Pcr" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="BDU-Ql-OgK" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2361" y="1440"/>
<point key="canvasLocation" x="2470" y="1338"/>
</scene>
</scenes>
<resources>
<image name="Settings_Button" width="22" height="22"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="Ila-yL-N8w"/>
<segue reference="4ws-DY-L4D"/>
</inferredMetricsTieBreakers>
</document>

View File

@ -1,681 +0,0 @@
//
// EmulationViewController.swift
// Delta
//
// Created by Riley Testut on 5/5/15.
// Copyright (c) 2015 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
import Roxas
// Temporary wrapper around dispatch_semaphore_t until Swift 3 + modernized libdispatch
private struct DispatchSemaphore: Hashable
{
let semaphore: Dispatch.DispatchSemaphore
var hashValue: Int {
return semaphore.hash
}
init(value: Int)
{
self.semaphore = Dispatch.DispatchSemaphore(value: value)
}
}
private func ==(lhs: DispatchSemaphore, rhs: DispatchSemaphore) -> Bool
{
return lhs.semaphore.isEqual(rhs.semaphore)
}
class EmulationViewController: UIViewController
{
//MARK: - Properties -
/** Properties **/
/// Should only be set when preparing for segue. Otherwise, should be considered immutable
var game: Game! {
didSet
{
guard oldValue != game else { return }
self.emulatorCore = EmulatorCore(game: game)
}
}
private(set) var emulatorCore: EmulatorCore! {
didSet
{
// Cannot set directly, or else we're left with a strong reference cycle
//self.emulatorCore.updateHandler = emulatorCoreDidUpdate
self.emulatorCore.updateHandler = { [weak self] core in
self?.emulatorCoreDidUpdate(core)
}
self.preferredContentSize = self.emulatorCore.preferredRenderingSize
}
}
// If non-nil, will override the default preview action items returned in previewActionItems()
var overridePreviewActionItems: [UIPreviewActionItem]?
// Annoying iOS gotcha: if the previewingContext(_:viewControllerForLocation:) callback takes too long, the peek/preview starts, but fails to actually present the view controller
// To workaround, we have this closure to defer work for Peeking/Popping until the view controller appears
// Hacky, but works
var deferredPreparationHandler: ((Void) -> Void)?
//MARK: - Private Properties
private var pauseViewController: PauseViewController?
private var pausingGameController: GameController?
private var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
private var updateSemaphores = Set<DispatchSemaphore>()
private var sustainedInputs = [ObjectIdentifier: [Input]]()
private var reactivateSustainInputsQueue: OperationQueue
private var choosingSustainedButtons = false
@IBOutlet private var controllerView: ControllerView!
@IBOutlet private var gameView: GameView!
@IBOutlet private var sustainButtonContentView: UIView!
@IBOutlet private var backgroundView: RSTBackgroundView!
@IBOutlet private var controllerViewHeightConstraint: NSLayoutConstraint!
//MARK: - Initializers -
/** Initializers **/
required init?(coder aDecoder: NSCoder)
{
self.reactivateSustainInputsQueue = OperationQueue()
self.reactivateSustainInputsQueue.maxConcurrentOperationCount = 1
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(EmulationViewController.updateControllers), name: .externalControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EmulationViewController.updateControllers), name: .externalControllerDidDisconnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(EmulationViewController.willResignActive(_:)), name: NSNotification.Name.UIApplicationWillResignActive, object: UIApplication.shared())
NotificationCenter.default.addObserver(self, selector: #selector(EmulationViewController.didBecomeActive(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: UIApplication.shared())
}
deinit
{
// To ensure the emulation stops when cancelling a peek/preview gesture
self.emulatorCore.stop()
}
//MARK: - Overrides
/** Overrides **/
//MARK: - UIViewController
/// UIViewController
override func viewDidLoad()
{
super.viewDidLoad()
// Set this to 0 now and update it in viewDidLayoutSubviews to ensure there are never conflicting constraints
// (such as when peeking and popping)
self.controllerViewHeightConstraint.constant = 0
self.gameView.backgroundColor = UIColor.clear()
self.emulatorCore.add(self.gameView)
self.backgroundView.textLabel.text = NSLocalizedString("Select Buttons to Sustain", comment: "")
self.backgroundView.detailTextLabel.text = NSLocalizedString("Press the Menu button when finished.", comment: "")
let controllerSkin = ControllerSkin.standardControllerSkin(for: self.game.type)
self.controllerView.controllerSkin = controllerSkin
self.updateControllers()
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
self.deferredPreparationHandler?()
self.deferredPreparationHandler = nil
// Yes, order DOES matter here, in order to prevent audio from being slightly delayed after peeking with 3D Touch (ugh so tired of that issue)
switch self.emulatorCore.state
{
case .stopped:
self.emulatorCore.start()
self.updateCheats()
case .running: break
case .paused:
self.updateCheats()
self.resumeEmulation()
}
// Toggle audioManager.enabled to reset the audio buffer and ensure the audio isn't delayed from the beginning
// This is especially noticeable when peeking a game
self.emulatorCore.audioManager.enabled = false
self.emulatorCore.audioManager.enabled = true
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
if Settings.localControllerPlayerIndex != nil && self.controllerView.intrinsicContentSize() != CGSize(width: UIViewNoIntrinsicMetric, height: UIViewNoIntrinsicMetric) && !self.isPreviewing
{
let scale = self.view.bounds.width / self.controllerView.intrinsicContentSize().width
self.controllerViewHeightConstraint.constant = self.controllerView.intrinsicContentSize().height * scale
}
else
{
self.controllerViewHeightConstraint.constant = 0
}
self.controllerView.isHidden = self.isPreviewing
}
override func prefersStatusBarHidden() -> Bool
{
return true
}
/// <UIContentContainer>
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
super.viewWillTransition(to: size, with: coordinator)
self.controllerView.beginAnimatingUpdateControllerSkin()
coordinator.animate(alongsideTransition: { _ in
if self.emulatorCore.state == .paused
{
// We need to manually "refresh" the game screen, otherwise the system tries to cache the rendered image, but skews it incorrectly when rotating b/c of UIVisualEffectView
self.gameView.inputImage = self.gameView.outputImage
}
}, completion: { _ in
self.controllerView.finishAnimatingUpdateControllerSkin()
})
}
// MARK: - Navigation -
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)
{
self.pauseEmulation()
if segue.identifier == "pauseSegue"
{
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.name
// Swift has a bug where using unowned references can lead to swift_abortRetainUnowned errors.
// Specifically, if you pause a game, open the save states menu, go back, return to menu, select a new game, then try to pause it, it will crash
// As a dirty workaround, we just use a weak reference, and force unwrap it if needed
let saveStateItem = PauseItem(image: UIImage(named: "SaveSaveState")!, text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in
pauseViewController.presentSaveStateViewControllerWithMode(.saving, delegate: self)
})
let loadStateItem = PauseItem(image: UIImage(named: "LoadSaveState")!, text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in
pauseViewController.presentSaveStateViewControllerWithMode(.loading, delegate: self)
})
let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in
pauseViewController.presentCheatsViewController(delegate: self)
})
var sustainButtonsItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Buttons", comment: ""), action: { [unowned self] item in
self.resetSustainedInputs(forGameController: gameController)
if item.selected
{
self.showSustainButtonView()
pauseViewController.dismiss()
}
})
sustainButtonsItem.selected = self.sustainedInputs[ObjectIdentifier(gameController)]?.count > 0
var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [unowned self] item in
self.emulatorCore.rate = item.selected ? self.emulatorCore.configuration.supportedRates.upperBound : self.emulatorCore.configuration.supportedRates.lowerBound
})
fastForwardItem.selected = self.emulatorCore.rate == self.emulatorCore.configuration.supportedRates.lowerBound ? false : true
pauseViewController.items = [saveStateItem, loadStateItem, cheatCodesItem, fastForwardItem, sustainButtonsItem]
self.pauseViewController = pauseViewController
}
}
@IBAction 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: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) {
self.emulatorCore.audioManager.enabled = true
}
}
}
//MARK: - 3D Touch -
/// 3D Touch
override func previewActionItems() -> [UIPreviewActionItem]
{
if let previewActionItems = self.overridePreviewActionItems
{
return previewActionItems
}
let presentingViewController = self.presentingViewController
let launchGameAction = UIPreviewAction(title: NSLocalizedString("Launch \(self.game.name)", comment: ""), style: .default) { (action, viewController) in
// Delaying until next run loop prevents self from being dismissed immediately
DispatchQueue.main.async {
presentingViewController?.present(viewController, animated: true, completion: nil)
}
}
return [launchGameAction]
}
}
//MARK: - Emulation -
/// Emulation
private extension EmulationViewController
{
func pause(sender: AnyObject?)
{
self.performSegue(withIdentifier: "pauseSegue", sender: sender)
}
@discardableResult func pauseEmulation() -> Bool
{
return self.emulatorCore.pause()
}
@discardableResult func resumeEmulation() -> Bool
{
guard !self.choosingSustainedButtons && self.pauseViewController == nil else { return false }
return self.emulatorCore.resume()
}
func emulatorCoreDidUpdate(_ emulatorCore: EmulatorCore)
{
for semaphore in self.updateSemaphores
{
semaphore.semaphore.signal()
}
}
}
//MARK: - Controllers -
/// Controllers
private extension EmulationViewController
{
@objc func updateControllers()
{
self.emulatorCore.removeAllGameControllers()
if let index = Settings.localControllerPlayerIndex
{
self.controllerView.playerIndex = index
}
var controllers = [GameController]()
controllers.append(self.controllerView)
// We need to map each item as a GameControllerProtocol due to a Swift bug
controllers.append(contentsOf: ExternalControllerManager.shared.connectedControllers.map { $0 as GameController })
for controller in controllers
{
if let index = controller.playerIndex
{
self.emulatorCore.setGameController(controller, at: index)
controller.addReceiver(self)
}
else
{
controller.removeReceiver(self)
}
}
self.view.setNeedsLayout()
}
}
//MARK: - Sustain Button -
private extension EmulationViewController
{
func showSustainButtonView()
{
self.choosingSustainedButtons = true
self.sustainButtonContentView.isHidden = false
}
func hideSustainButtonView()
{
self.choosingSustainedButtons = false
UIView.animate(withDuration: 0.4, animations: {
self.sustainButtonContentView.alpha = 0.0
}) { (finished) in
self.sustainButtonContentView.isHidden = true
self.sustainButtonContentView.alpha = 1.0
}
}
func resetSustainedInputs(forGameController gameController: GameController)
{
if let previousInputs = self.sustainedInputs[ObjectIdentifier(gameController)]
{
let receivers = gameController.receivers
receivers.forEach { gameController.removeReceiver($0) }
// Activate previousInputs without notifying anyone so we can then deactivate them
// We do this because deactivating an already deactivated input has no effect
previousInputs.forEach { gameController.activate($0) }
receivers.forEach { gameController.addReceiver($0) }
// Deactivate previously sustained inputs
previousInputs.forEach { gameController.deactivate($0) }
}
self.sustainedInputs[ObjectIdentifier(gameController)] = []
}
func addSustainedInput(_ input: Input, gameController: GameController)
{
var inputs = self.sustainedInputs[ObjectIdentifier(gameController)] ?? []
guard !inputs.contains({ $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, gameController: GameController)
{
// These MUST be performed serially, or else Bad Things Happen if multiple inputs are reactivated at once
self.reactivateSustainInputsQueue.addOperation {
// The manual activations/deactivations here are hidden implementation details, so we won't notify ourselves about them
gameController.removeReceiver(self)
// Must deactivate first so core recognizes a secondary activation
gameController.deactivate(input)
let dispatchQueue = DispatchQueue(label: "com.rileytestut.Delta.sustainButtonsQueue", attributes: DispatchQueueAttributes.serial)
dispatchQueue.async {
let semaphore = DispatchSemaphore(value: 0)
self.updateSemaphores.insert(semaphore)
// To ensure the emulator core recognizes us activating the input again, we need to wait at least two frames
// Unfortunately we cannot init DispatchSemaphore with value less than 0
// To compensate, we simply wait twice; once the first wait returns, we wait again
semaphore.semaphore.wait()
semaphore.semaphore.wait()
// These MUST be performed serially, or else Bad Things Happen if multiple inputs are reactivated at once
self.reactivateSustainInputsQueue.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.reactivateSustainInputsQueue.waitUntilAllOperationsAreFinished()
gameController.addReceiver(self)
}
}
}
}
//MARK: - Save States
/// Save States
extension EmulationViewController: SaveStatesViewControllerDelegate
{
func saveStatesViewControllerActiveEmulatorCore(_ saveStatesViewController: SaveStatesViewController) -> EmulatorCore
{
return self.emulatorCore
}
func saveStatesViewController(_ saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
{
guard let filepath = saveState.fileURL.path else { return }
var updatingExistingSaveState = true
self.emulatorCore.save { (temporarySaveState) in
do
{
if FileManager.default.fileExists(atPath: filepath)
{
try FileManager.default.replaceItem(at: saveState.fileURL, withItemAt: temporarySaveState.fileURL, backupItemName: nil, options: [], resultingItemURL: nil)
}
else
{
try FileManager.default.moveItem(at: temporarySaveState.fileURL, to: saveState.fileURL)
updatingExistingSaveState = false
}
}
catch let error as NSError
{
print(error)
}
}
if let outputImage = self.gameView.outputImage, let quartzImage = self.context.createCGImage(outputImage, from: outputImage.extent)
{
let image = UIImage(cgImage: quartzImage)
try! UIImagePNGRepresentation(image)?.write(to: saveState.imageFileURL, options: [.atomicWrite])
}
saveState.modifiedDate = Date()
// 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)
{
do
{
try self.emulatorCore.load(saveState)
}
catch EmulatorCore.SaveStateError.doesNotExist
{
print("Save State does not exist.")
}
catch let error as NSError
{
print(error)
}
self.updateCheats()
self.pauseViewController?.dismiss()
}
}
//MARK: - Cheats
/// Cheats
extension EmulationViewController: CheatsViewControllerDelegate
{
func cheatsViewControllerActiveEmulatorCore(_ saveStatesViewController: CheatsViewController) -> EmulatorCore
{
return self.emulatorCore
}
func cheatsViewController(_ cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
{
try self.emulatorCore.activate(cheat)
}
func cheatsViewController(_ cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat)
{
self.emulatorCore.deactivate(cheat)
}
private func updateCheats()
{
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
backgroundContext.performAndWait {
let running = (self.emulatorCore.state == .running)
if running
{
// Core MUST be paused when activating cheats, or else race conditions could crash the core
self.pauseEmulation()
}
let predicate = Predicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.emulatorCore.game as! Game)
let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: backgroundContext, type: Cheat.self)
for cheat in cheats
{
if cheat.enabled
{
do
{
try self.emulatorCore.activate(cheat)
}
catch EmulatorCore.CheatError.invalid
{
print("Invalid cheat:", cheat.name, cheat.code)
}
catch let error as NSError
{
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
}
}
else
{
self.emulatorCore.deactivate(cheat)
}
}
if running
{
self.resumeEmulation()
}
}
}
}
//MARK: - App Lifecycle -
private extension EmulationViewController
{
@objc func willResignActive(_ notification: Notification)
{
self.pauseEmulation()
}
@objc func didBecomeActive(_ notification: Notification)
{
self.resumeEmulation()
}
}
//MARK: - <GameControllerReceiver> -
/// <GameControllerReceiver>
extension EmulationViewController: GameControllerReceiver
{
func gameController(_ gameController: GameController, didActivate input: Input)
{
if gameController is ControllerView && UIDevice.current().isVibrationSupported
{
UIDevice.current().vibrate()
}
if let input = input as? ControllerInput
{
switch input
{
case ControllerInput.menu:
if self.choosingSustainedButtons { self.hideSustainButtonView() }
self.pause(sender: gameController)
// Return now, because Menu cannot be sustained
return
}
}
if self.choosingSustainedButtons
{
self.addSustainedInput(input, gameController: gameController)
return
}
if let sustainedInputs = self.sustainedInputs[ObjectIdentifier(gameController)] where sustainedInputs.contains({ $0.isEqual(input) })
{
// Perform on next run loop
DispatchQueue.main.async {
self.reactivateSustainedInput(input, gameController: gameController)
}
return
}
}
func gameController(_ gameController: GameController, didDeactivate input: Input)
{
guard let input = input as? ControllerInput else { return }
print("Deactivated \(input)")
}
}

View File

@ -0,0 +1,153 @@
//
// GameViewController.swift
// Delta
//
// Created by Riley Testut on 5/5/15.
// Copyright © 2016 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
class GameViewController: DeltaCore.GameViewController
{
override var game: GameProtocol? {
didSet {
guard let emulatorCore = self.emulatorCore else { return }
self.preferredContentSize = emulatorCore.preferredRenderingSize
}
}
// If non-nil, will override the default preview action items returned in previewActionItems()
var overridePreviewActionItems: [UIPreviewActionItem]?
required init()
{
super.init()
self.initialize()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.initialize()
}
private func initialize()
{
self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalControllerDidDisconnect, object: nil)
}
// MARK: GameControllerReceiver -
override func gameController(_ gameController: GameController, didActivate input: Input)
{
super.gameController(gameController, didActivate: input)
if gameController is ControllerView && UIDevice.current().isVibrationSupported
{
UIDevice.current().vibrate()
}
}
}
//MARK: UIViewController -
/// UIViewController
extension GameViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
self.updateControllers()
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
self.controllerView.isHidden = self.isPreviewing
}
override func previewActionItems() -> [UIPreviewActionItem]
{
if let previewActionItems = self.overridePreviewActionItems
{
return previewActionItems
}
guard let game = self.game as? Game else { return [] }
let presentingViewController = self.presentingViewController
let launchGameAction = UIPreviewAction(title: NSLocalizedString("Launch \(game.name)", comment: ""), style: .default) { (action, viewController) in
// Delaying until next run loop prevents self from being dismissed immediately
DispatchQueue.main.async {
presentingViewController?.present(viewController, animated: true, completion: nil)
}
}
return [launchGameAction]
}
}
//MARK: Controllers -
private extension GameViewController
{
@objc func updateControllers()
{
self.emulatorCore?.removeAllGameControllers()
if let index = Settings.localControllerPlayerIndex
{
self.controllerView.playerIndex = index
}
var controllers = [GameController]()
controllers.append(self.controllerView)
// We need to map each item as a GameControllerProtocol due to a Swift bug
controllers.append(contentsOf: ExternalControllerManager.shared.connectedControllers.map { $0 as GameController })
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()
}
}
//MARK: GameViewControllerDelegate -
/// GameViewControllerDelegate
extension GameViewController: GameViewControllerDelegate
{
func gameViewController(gameViewController: DeltaCore.GameViewController, handleMenuInputFrom gameController: GameController)
{
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)
}
}

View File

@ -118,50 +118,19 @@ class GamesViewController: UIViewController
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)
{
guard let sourceViewController = segue.sourceViewController as? GamesCollectionViewController else { return }
guard let destinationViewController = segue.destinationViewController as? EmulationViewController else { return }
guard let destinationViewController = segue.destinationViewController as? GameViewController else { return }
guard let cell = sender as? UICollectionViewCell else { return }
let indexPath = sourceViewController.collectionView?.indexPath(for: cell)
let game = sourceViewController.dataSource.fetchedResultsController.object(at: indexPath!) as! Game
destinationViewController.game = game
if segue.identifier == "peekEmulationViewController"
{
destinationViewController.deferredPreparationHandler = { [unowned destinationViewController] in
if let saveState = game.previewSaveState
{
destinationViewController.emulatorCore.start()
destinationViewController.emulatorCore.pause()
do
{
try destinationViewController.emulatorCore.load(saveState)
}
catch EmulatorCore.SaveStateError.doesNotExist
{
print("Save State \(saveState.name) does not exist.")
}
catch let error as NSError
{
print(error)
}
}
}
}
}
@IBAction func unwindFromSettingsViewController(_ segue: UIStoryboardSegue)
{
}
@IBAction func unwindFromEmulationViewController(_ segue: UIStoryboardSegue)
{
}
}
private extension GamesViewController