- feat: global controller navigation
- shorten initial host detection
This commit is contained in:
@@ -39,6 +39,8 @@
|
||||
2A263D4F2FF3BA2800154DEA /* PublicUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A263D4E2FF3BA2800154DEA /* PublicUtils.swift */; };
|
||||
2A263D502FF3BA2800154DEA /* PublicUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A263D4E2FF3BA2800154DEA /* PublicUtils.swift */; };
|
||||
2A7113FD2F5A6C65001D852D /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7113FC2F5A6C65001D852D /* ThemeManager.swift */; };
|
||||
2A7113FF2F5A6C65001D852D /* UIAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7113FE2F5A6C65001D852D /* UIAppView.swift */; };
|
||||
2A7114012F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7114002F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift */; };
|
||||
2AAA4E822F7666C5001E20DC /* ControllerUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A765BC2ECD51FB004784AA /* ControllerUtil.swift */; };
|
||||
2AAA4E842F7666EE001E20DC /* LocalizationHelperExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E1CDF92C4FE83400547904 /* LocalizationHelperExtension.swift */; };
|
||||
2AAA4E872F985F12001E20DC /* LocalizationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 84FD0B502C31338D006B5BE5 /* LocalizationHelper.m */; };
|
||||
@@ -156,7 +158,6 @@
|
||||
FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3494F19FF2174002D2A60 /* SettingsViewController.m */; };
|
||||
FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD3495219FF36FB002D2A60 /* SWRevealViewController.m */; };
|
||||
FBD349621A0089F6002D2A60 /* DataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD349611A0089F6002D2A60 /* DataManager.m */; };
|
||||
FBDE86E619F82297001C18A8 /* UIAppView.m in Sources */ = {isa = PBXBuildFile; fileRef = FBDE86E519F82297001C18A8 /* UIAppView.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -214,6 +215,8 @@
|
||||
2A263D492FF3B3F800154DEA /* PublicExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicExtensions.swift; sourceTree = "<group>"; };
|
||||
2A263D4E2FF3BA2800154DEA /* PublicUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUtils.swift; sourceTree = "<group>"; };
|
||||
2A7113FC2F5A6C65001D852D /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
|
||||
2A7113FE2F5A6C65001D852D /* UIAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAppView.swift; sourceTree = "<group>"; };
|
||||
2A7114002F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamepadNavigationIllustrationHud.swift; sourceTree = "<group>"; };
|
||||
2AC1E3822F507D80004F76C0 /* GenericUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericUtils.swift; sourceTree = "<group>"; };
|
||||
2AC1EAFA2F922602004EDA80 /* UIStackExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackExtension.swift; sourceTree = "<group>"; };
|
||||
3A4A3A102F0C000100000001 /* OSCProfilesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSCProfilesTableViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -422,8 +425,6 @@
|
||||
FBD3495219FF36FB002D2A60 /* SWRevealViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SWRevealViewController.m; sourceTree = "<group>"; };
|
||||
FBD349601A0089F6002D2A60 /* DataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DataManager.h; path = Database/DataManager.h; sourceTree = "<group>"; };
|
||||
FBD349611A0089F6002D2A60 /* DataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DataManager.m; path = Database/DataManager.m; sourceTree = "<group>"; };
|
||||
FBDE86E419F82297001C18A8 /* UIAppView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAppView.h; sourceTree = "<group>"; };
|
||||
FBDE86E519F82297001C18A8 /* UIAppView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAppView.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
@@ -699,8 +700,8 @@
|
||||
848F45692DDCD12500581E36 /* HostCardView.swift */,
|
||||
2A7113FC2F5A6C65001D852D /* ThemeManager.swift */,
|
||||
847D2FDA2DDA015F00937A62 /* MenuSectionView.swift */,
|
||||
FBDE86E419F82297001C18A8 /* UIAppView.h */,
|
||||
FBDE86E519F82297001C18A8 /* UIAppView.m */,
|
||||
2A7113FE2F5A6C65001D852D /* UIAppView.swift */,
|
||||
2A7114002F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift */,
|
||||
180E6E362E93BFDB0067C73A /* AlertControllerUtil.swift */,
|
||||
1888C1902E24C2C800108730 /* AboutView.swift */,
|
||||
1888C1912E24C33100108730 /* AboutViewController.swift */,
|
||||
@@ -1154,7 +1155,8 @@
|
||||
FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */,
|
||||
FB9AFD401A7E127D00872C98 /* AppListResponse.m in Sources */,
|
||||
FB89463419F646E200339C8A /* Utils.m in Sources */,
|
||||
FBDE86E619F82297001C18A8 /* UIAppView.m in Sources */,
|
||||
2A7113FF2F5A6C65001D852D /* UIAppView.swift in Sources */,
|
||||
2A7114012F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift in Sources */,
|
||||
FB89462F19F646E200339C8A /* Connection.m in Sources */,
|
||||
FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */,
|
||||
843E93BE2C41528700B1651F /* OrientationHelper.swift in Sources */,
|
||||
|
||||
@@ -10,7 +10,26 @@
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@objc class AboutViewController: UIViewController {
|
||||
@objc class AboutViewController: UIViewController, ControllerUINavigationDelegate {
|
||||
func getNavigationElements() -> [ControllerNavigationElement] {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .x, action: "ok"))
|
||||
return elements
|
||||
}
|
||||
|
||||
func navigateByController(forward: Bool) {}
|
||||
func navigateByController(downward: Bool) {}
|
||||
func persistControllerNavigationHighlight() {}
|
||||
func restoreControllerNavigationHighlight() {}
|
||||
func restoreControllerNavigationHighlightAfterSettingsModeSwitch() {}
|
||||
func uiWidgetActionForControllerNavigator(forward: Bool, from navigation: ControllerNavigationElement) {}
|
||||
|
||||
func uiButtonActionForControllerNavigator(pressed: Bool, from navigation: ControllerNavigationElement) {
|
||||
if pressed, navigation.action == "ok" {
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
@@ -32,6 +51,14 @@ import SwiftUI
|
||||
|
||||
modalPresentationStyle = .formSheet
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
ControllerNavigator.setUINavigationDelegate(self)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
ControllerNavigator.restorePreviousUINavigationDelegate(ifCurrentDelegateIs: self)
|
||||
}
|
||||
}
|
||||
|
||||
func isIPhone() -> Bool {
|
||||
|
||||
@@ -76,6 +76,7 @@ import UIKit
|
||||
@objc public static var actionCancelled:Bool = false
|
||||
@objc public static var alertController:UIAlertController = UIAlertController()
|
||||
@objc public static var cancelButtonString:String = "Cancel"
|
||||
@objc static var completion: (() -> Void)? = nil
|
||||
@objc public static var autoCompletion:Bool = false
|
||||
@objc public static var isCountingDown:Bool = false
|
||||
|
||||
@@ -93,6 +94,8 @@ import UIKit
|
||||
let originalMessage = message
|
||||
var isAlertDismissed = false
|
||||
|
||||
AlertControllerUtil.completion = completion
|
||||
|
||||
guard let viewController = viewController else {return}
|
||||
|
||||
alertController = UIAlertController(
|
||||
@@ -148,16 +151,19 @@ import UIKit
|
||||
isCountingDown = false
|
||||
isAlertDismissed = true
|
||||
timer.cancel()
|
||||
|
||||
if autoCompletion {
|
||||
alertController.message = originalMessage
|
||||
applyLeftAlignment(title: title, message: originalMessage)
|
||||
autoCompletion = false
|
||||
completion?()
|
||||
AlertControllerUtil.completion = nil
|
||||
alertController.dismiss(animated: false)
|
||||
return
|
||||
}
|
||||
|
||||
if PublicUtils.isRunningOnMacAsiPadApp && buttonTitle != "" {
|
||||
AlertControllerUtil.completion = nil
|
||||
alertController.dismiss(animated: false) {
|
||||
showAlert(
|
||||
in: viewController,
|
||||
@@ -176,6 +182,10 @@ import UIKit
|
||||
confirmAction.isEnabled = true
|
||||
alertController.message = originalMessage
|
||||
applyLeftAlignment(title: title, message: originalMessage)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
ControllerNavigator.setUINavigationDelegate(alertController)
|
||||
}
|
||||
} else {
|
||||
if !autoCompletion {
|
||||
let countdownMessage = makeCountdownMessage(baseMessage: originalMessage, remainingSeconds: remainingSeconds)
|
||||
|
||||
@@ -71,27 +71,32 @@
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="zpX-lo-MLA" userLabel="Resolutionstak">
|
||||
<rect key="frame" x="16" y="57" width="380" height="92.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" clearsContextBeforeDrawing="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYZ-GR-EwO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="0.0"/>
|
||||
</label>
|
||||
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="5" translatesAutoresizingMaskIntoConstraints="NO" id="ckc-Dm-8ex">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="35"/>
|
||||
<segments>
|
||||
<segment title="360p" enabled="NO"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="FullScr/Window"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
|
||||
</segmentedControl>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Iv4-iA-dkv" userLabel="ResolutionSelectorStack">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="59.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" clearsContextBeforeDrawing="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYZ-GR-EwO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="0.0"/>
|
||||
</label>
|
||||
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="5" translatesAutoresizingMaskIntoConstraints="NO" id="ckc-Dm-8ex">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="35"/>
|
||||
<segments>
|
||||
<segment title="360p" enabled="NO"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="FullScr/Window"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="djj-8b-2pB" userLabel="ResolutionInnerStack2">
|
||||
<rect key="frame" x="0.0" y="64.5" width="380" height="28"/>
|
||||
<subviews>
|
||||
@@ -972,16 +977,16 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="0n9-vQ-WC2">
|
||||
<rect key="frame" x="852" y="850" width="300" height="31"/>
|
||||
<rect key="frame" x="852" y="850" width="310.5" height="31"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Controller → Mouse" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P1g-rW-Mgp" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="150" height="31"/>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Controller Navigation" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P1g-rW-Mgp" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="160.5" height="31"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ect-CM-odK">
|
||||
<rect key="frame" x="170" y="0.0" width="132" height="31"/>
|
||||
<rect key="frame" x="180.5" y="0.0" width="132" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="130" id="G04-14-Q74"/>
|
||||
</constraints>
|
||||
@@ -991,44 +996,24 @@
|
||||
<constraint firstAttribute="height" constant="31" id="7kp-OR-e5f"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="lBq-CW-xFb">
|
||||
<rect key="frame" x="852" y="899" width="380" height="55.5"/>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="xXr-dB-QWZ">
|
||||
<rect key="frame" x="852" y="911" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Pointer Velocity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xnd-C4-8N9" userLabel="uiLabel">
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Streaming Radial Menu Delay" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9d1-RN-lqW" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="60" translatesAutoresizingMaskIntoConstraints="NO" id="w6S-nD-AXU">
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="3" translatesAutoresizingMaskIntoConstraints="NO" id="qZX-tG-Pwf">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="4aR-eF-inq"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="ScL-Y2-bDH"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-F1-wWR">
|
||||
<rect key="frame" x="852" y="974" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Curve Exponent" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Cy-aH-DXp" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="1" maxValue="5" translatesAutoresizingMaskIntoConstraints="NO" id="ciD-6k-n6o">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="h9D-G7-Nuh"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="kLV-xF-5SV"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="3OW-fn-a4x"/>
|
||||
<constraint firstAttribute="width" constant="380" id="Vyh-Uu-Qdl"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="v6i-HA-XRx">
|
||||
@@ -1056,46 +1041,6 @@
|
||||
<constraint firstAttribute="width" constant="380" id="qOk-ee-a8A"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="B5K-lN-ZSc">
|
||||
<rect key="frame" x="852" y="1055" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Left Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3XV-jL-NYF" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="KtJ-qH-KJX">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="EJU-Jy-UYb"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="z8Y-An-DwI"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="yl0-WY-ZqV">
|
||||
<rect key="frame" x="852" y="1129" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Right Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f3L-bA-RW6" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="qt5-Hq-mIa">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="Skp-ya-Odl"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="A31-WC-As7">
|
||||
<rect key="frame" x="441" y="1363" width="280" height="31"/>
|
||||
<subviews>
|
||||
@@ -2198,6 +2143,86 @@
|
||||
<constraint firstAttribute="height" constant="31" id="EpQ-lj-XBq"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="lBq-CW-xFb">
|
||||
<rect key="frame" x="852" y="987" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Controller Mouse Velocity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xnd-C4-8N9" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="60" translatesAutoresizingMaskIntoConstraints="NO" id="w6S-nD-AXU">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="4aR-eF-inq"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="ScL-Y2-bDH"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="5gN-F1-wWR">
|
||||
<rect key="frame" x="852" y="1062" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Curve Exponent" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Cy-aH-DXp" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="1" maxValue="5" translatesAutoresizingMaskIntoConstraints="NO" id="ciD-6k-n6o">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="h9D-G7-Nuh"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="kLV-xF-5SV"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="B5K-lN-ZSc">
|
||||
<rect key="frame" x="852" y="1143" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Left Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3XV-jL-NYF" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="KtJ-qH-KJX">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="EJU-Jy-UYb"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="z8Y-An-DwI"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="yl0-WY-ZqV">
|
||||
<rect key="frame" x="852" y="1217" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Right Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="f3L-bA-RW6" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="qt5-Hq-mIa">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="380" id="Skp-ya-Odl"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
@@ -2230,10 +2255,11 @@
|
||||
<outlet property="controllerMouseExpoStack" destination="5gN-F1-wWR" id="hgt-xH-dts"/>
|
||||
<outlet property="controllerMouseVelocitySlider" destination="w6S-nD-AXU" id="rws-D6-zMT"/>
|
||||
<outlet property="controllerMouseVelocityStack" destination="lBq-CW-xFb" id="zOW-iC-wBn"/>
|
||||
<outlet property="controllerToMouseStack" destination="0n9-vQ-WC2" id="DhB-o2-VWn"/>
|
||||
<outlet property="controllerToMouseSwitch" destination="Ect-CM-odK" id="SCB-GH-0hd"/>
|
||||
<outlet property="controllerNavigationStack" destination="0n9-vQ-WC2" id="DhB-o2-VWn"/>
|
||||
<outlet property="controllerNavigationSwitch" destination="Ect-CM-odK" id="SCB-GH-0hd"/>
|
||||
<outlet property="ctrlDownForPinchStack" destination="4Jb-ey-ca7" id="Ou2-Qz-VO6"/>
|
||||
<outlet property="ctrlDownForPinchSwitch" destination="zfr-Wn-dB4" id="GXR-zn-daI"/>
|
||||
<outlet property="customResolutionStack" destination="djj-8b-2pB" id="MQH-Fc-cH9"/>
|
||||
<outlet property="customResolutionSwitch" destination="E5w-kK-dOo" id="hrt-v2-8dY"/>
|
||||
<outlet property="delayLeftClickStack" destination="Xp7-7O-Ndt" id="CwB-Wy-beh"/>
|
||||
<outlet property="delayLeftClickSwitch" destination="Haf-rg-zLB" id="FaA-3p-JDw"/>
|
||||
@@ -2340,6 +2366,7 @@
|
||||
<outlet property="renderingBackendSelector" destination="53H-ST-OtY" id="jhu-Pw-Phg"/>
|
||||
<outlet property="renderingBackendStack" destination="6hG-dw-YSr" id="qqz-lA-EOE"/>
|
||||
<outlet property="resolutionSelector" destination="ckc-Dm-8ex" id="rl6-rx-wd3"/>
|
||||
<outlet property="resolutionSelectorStack" destination="Iv4-iA-dkv" id="vlg-5X-iMR"/>
|
||||
<outlet property="resolutionStack" destination="zpX-lo-MLA" id="9Hu-I9-hjH"/>
|
||||
<outlet property="reverseHoldButtonStack" destination="mB8-aJ-7qO" id="3bo-br-Tcb"/>
|
||||
<outlet property="reverseHoldButtonSwitch" destination="00w-mO-Ehj" id="Ptz-f8-nTx"/>
|
||||
@@ -2378,6 +2405,8 @@
|
||||
<outlet property="squeezeShortcutSwitch" destination="VCg-Es-PQV" id="kgj-FT-fcZ"/>
|
||||
<outlet property="statsOverlaySelector" destination="NLH-qN-qCo" id="EF8-JF-O3S"/>
|
||||
<outlet property="statsOverlayStack" destination="UcP-0b-nJD" id="UdI-NS-O6k"/>
|
||||
<outlet property="streamingRadialMenuDelaySlider" destination="qZX-tG-Pwf" id="V5Z-kn-irq"/>
|
||||
<outlet property="streamingRadialMenuDelayStack" destination="xXr-dB-QWZ" id="I9M-3Y-HGg"/>
|
||||
<outlet property="swapAbxyStack" destination="2rP-Ui-wVU" id="gUR-Vz-ORP"/>
|
||||
<outlet property="swapAbxySwitch" destination="hv7-6b-rez" id="iKX-qk-Dep"/>
|
||||
<outlet property="swapYawAndRollStack" destination="EsN-yO-7OW" id="Ncq-Y1-lku"/>
|
||||
|
||||
@@ -1600,27 +1600,32 @@
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="sbK-BT-6rK" userLabel="Resolutionstak">
|
||||
<rect key="frame" x="16" y="57" width="380" height="92.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" clearsContextBeforeDrawing="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T0w-xM-ze7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="0.0"/>
|
||||
</label>
|
||||
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="5" translatesAutoresizingMaskIntoConstraints="NO" id="7Sl-WI-zyo">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="35"/>
|
||||
<segments>
|
||||
<segment title="360p" enabled="NO"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="FullScr/Window"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
|
||||
</segmentedControl>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="xpB-c8-tbW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="59.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" clearsContextBeforeDrawing="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="T0w-xM-ze7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<size key="shadowOffset" width="0.0" height="0.0"/>
|
||||
</label>
|
||||
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="5" translatesAutoresizingMaskIntoConstraints="NO" id="7Sl-WI-zyo">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="35"/>
|
||||
<segments>
|
||||
<segment title="360p" enabled="NO"/>
|
||||
<segment title="720p"/>
|
||||
<segment title="1080p"/>
|
||||
<segment title="4K"/>
|
||||
<segment title="Safe Area"/>
|
||||
<segment title="FullScr/Window"/>
|
||||
<segment title="Custom"/>
|
||||
</segments>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="GWt-Kz-lZ9" userLabel="ResolutionInnerStack2">
|
||||
<rect key="frame" x="0.0" y="64.5" width="380" height="28"/>
|
||||
<subviews>
|
||||
@@ -3087,16 +3092,16 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="xyO-r6-abA">
|
||||
<rect key="frame" x="456" y="3007" width="300" height="31"/>
|
||||
<rect key="frame" x="456" y="3007" width="310.5" height="31"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Controller → Mouse" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F0O-Ht-fW5" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="150" height="31"/>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Controller Navigation" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F0O-Ht-fW5" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="160.5" height="31"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OAq-o7-H0d">
|
||||
<rect key="frame" x="170" y="0.0" width="132" height="31"/>
|
||||
<rect key="frame" x="180.5" y="0.0" width="132" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="130" id="LDl-c7-M09"/>
|
||||
</constraints>
|
||||
@@ -3106,44 +3111,24 @@
|
||||
<constraint firstAttribute="height" constant="31" id="svn-9D-BlC"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="QuI-NM-qCU">
|
||||
<rect key="frame" x="459" y="3137" width="380" height="55.5"/>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="ySL-A5-fcn">
|
||||
<rect key="frame" x="456" y="3068" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Curve Exponent" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AEN-EU-8LZ" userLabel="uiLabel">
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Streaming Radial Menu Delay" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bPX-Kf-kdP" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="1" maxValue="5" translatesAutoresizingMaskIntoConstraints="NO" id="scG-Rx-agb">
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="3" translatesAutoresizingMaskIntoConstraints="NO" id="Tgh-nL-w5K">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="28R-1J-Wwa"/>
|
||||
<constraint firstAttribute="width" constant="380" id="auC-uE-Gzq"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="jxy-UY-jFk">
|
||||
<rect key="frame" x="459" y="3062" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Pointer Velocity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H3s-ud-yqh" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="60" translatesAutoresizingMaskIntoConstraints="NO" id="CkB-cG-N8r">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="YOw-2j-9UH"/>
|
||||
<constraint firstAttribute="width" constant="380" id="mG1-Vg-QwM"/>
|
||||
<constraint firstAttribute="width" constant="380" id="Tb8-iE-aB1"/>
|
||||
<constraint firstAttribute="height" constant="55.5" id="sbd-1K-4IW"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="5fm-3b-ixg">
|
||||
@@ -3171,46 +3156,6 @@
|
||||
<constraint firstAttribute="height" constant="56.5" id="MNC-Cn-lnb"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Twt-JJ-ihg">
|
||||
<rect key="frame" x="459" y="3218" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Left Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="whI-oR-V8o" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="vOQ-zv-JOQ">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="Gid-x3-eh7"/>
|
||||
<constraint firstAttribute="width" constant="380" id="qs5-21-iSz"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="aQs-Ww-sPZ">
|
||||
<rect key="frame" x="459" y="3292" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Right Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="q6s-4H-y9n" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="nOn-5b-blZ">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="sam-oW-HVg"/>
|
||||
<constraint firstAttribute="width" constant="380" id="tSR-LD-qKK"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="HCc-SK-NBk">
|
||||
<rect key="frame" x="459" y="2252" width="280" height="31"/>
|
||||
<subviews>
|
||||
@@ -3555,6 +3500,86 @@
|
||||
<constraint firstAttribute="height" constant="31" id="rXC-R1-f3Z"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="QuI-NM-qCU">
|
||||
<rect key="frame" x="456" y="3213" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Curve Exponent" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AEN-EU-8LZ" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="1" maxValue="5" translatesAutoresizingMaskIntoConstraints="NO" id="scG-Rx-agb">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="28R-1J-Wwa"/>
|
||||
<constraint firstAttribute="width" constant="380" id="auC-uE-Gzq"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="jxy-UY-jFk">
|
||||
<rect key="frame" x="456" y="3138" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Controller Mouse Velocity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H3s-ud-yqh" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="60" translatesAutoresizingMaskIntoConstraints="NO" id="CkB-cG-N8r">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="YOw-2j-9UH"/>
|
||||
<constraint firstAttribute="width" constant="380" id="mG1-Vg-QwM"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Twt-JJ-ihg">
|
||||
<rect key="frame" x="456" y="3294" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Left Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="whI-oR-V8o" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="vOQ-zv-JOQ">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="Gid-x3-eh7"/>
|
||||
<constraint firstAttribute="width" constant="380" id="qs5-21-iSz"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="aQs-Ww-sPZ">
|
||||
<rect key="frame" x="456" y="3368" width="380" height="55.5"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Right Stick Minimum Offset" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="q6s-4H-y9n" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
|
||||
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="16383" translatesAutoresizingMaskIntoConstraints="NO" id="nOn-5b-blZ">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55.5" id="sam-oW-HVg"/>
|
||||
<constraint firstAttribute="width" constant="380" id="tSR-LD-qKK"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="0sf-mG-PCk"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -3588,8 +3613,8 @@
|
||||
<outlet property="controllerMouseExpoStack" destination="QuI-NM-qCU" id="Kmp-Cc-bWI"/>
|
||||
<outlet property="controllerMouseVelocitySlider" destination="CkB-cG-N8r" id="AMa-cl-2Wz"/>
|
||||
<outlet property="controllerMouseVelocityStack" destination="jxy-UY-jFk" id="os6-z6-A7h"/>
|
||||
<outlet property="controllerToMouseStack" destination="xyO-r6-abA" id="vWd-Hf-o4C"/>
|
||||
<outlet property="controllerToMouseSwitch" destination="OAq-o7-H0d" id="xk7-q8-bjc"/>
|
||||
<outlet property="controllerNavigationStack" destination="xyO-r6-abA" id="vWd-Hf-o4C"/>
|
||||
<outlet property="controllerNavigationSwitch" destination="OAq-o7-H0d" id="xk7-q8-bjc"/>
|
||||
<outlet property="ctrlDownForPinchStack" destination="eYW-eD-LTg" id="eQS-WT-74l"/>
|
||||
<outlet property="ctrlDownForPinchSwitch" destination="q46-JG-pX4" id="jjp-BU-9RP"/>
|
||||
<outlet property="customResolutionSwitch" destination="kRO-HO-zKO" id="ZUP-Yd-QDY"/>
|
||||
@@ -3683,6 +3708,7 @@
|
||||
<outlet property="renderingBackendSelector" destination="53H-ST-OtY" id="jhu-Pw-Ph2"/>
|
||||
<outlet property="renderingBackendStack" destination="6hG-dw-YSr" id="qqz-lA-EO2"/>
|
||||
<outlet property="resolutionSelector" destination="7Sl-WI-zyo" id="YMv-Xy-dF9"/>
|
||||
<outlet property="resolutionSelectorStack" destination="xpB-c8-tbW" id="esq-e9-gSA"/>
|
||||
<outlet property="resolutionStack" destination="sbK-BT-6rK" id="hEi-h6-te9"/>
|
||||
<outlet property="reverseHoldButtonStack" destination="97k-tp-tqF" id="C7z-79-7jb"/>
|
||||
<outlet property="reverseHoldButtonSwitch" destination="Aey-h4-DoE" id="rFw-PX-RCC"/>
|
||||
@@ -3719,6 +3745,8 @@
|
||||
<outlet property="softKeyboardToolbarSwitch" destination="c4W-2p-R3A" id="gf6-pq-L8o"/>
|
||||
<outlet property="statsOverlaySelector" destination="Bda-3L-Wtg" id="9kj-wY-VjF"/>
|
||||
<outlet property="statsOverlayStack" destination="dby-k3-aiH" id="1Ha-Y3-su4"/>
|
||||
<outlet property="streamingRadialMenuDelaySlider" destination="Tgh-nL-w5K" id="91f-Q5-IIl"/>
|
||||
<outlet property="streamingRadialMenuDelayStack" destination="ySL-A5-fcn" id="HrE-C7-y0g"/>
|
||||
<outlet property="swapAbxyStack" destination="reE-vr-Mdk" id="y3v-ac-LHy"/>
|
||||
<outlet property="swapAbxySwitch" destination="T5q-1k-zB3" id="153-6O-0Pv"/>
|
||||
<outlet property="swapYawAndRollStack" destination="bOa-JN-z7e" id="Xrg-wN-OIu"/>
|
||||
@@ -3768,19 +3796,19 @@
|
||||
<image name="trash.fill" catalog="system" width="117" height="128"/>
|
||||
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
|
||||
<systemColor name="linkColor">
|
||||
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBrownColor">
|
||||
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.67450980392156867" green="0.49803921568627452" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemGray2Color">
|
||||
<color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemGray3Color">
|
||||
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemTealColor">
|
||||
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.0" green="0.76470588235294112" blue="0.81568627450980391" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -146,7 +146,7 @@ typedef NS_ENUM(NSInteger, PencilTickMode) {
|
||||
leftClickDelayMs:(CGFloat)leftClickDelayMs
|
||||
settingsMenuOffset:(CGFloat)settingsMenuOffset
|
||||
passthroughGestures:(BOOL)passthroughGestures
|
||||
mapControllerToMouse:(BOOL)mapControllerToMouse
|
||||
enableControllerNavigation:(BOOL)mapControllerToMouse
|
||||
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
|
||||
controllerMouseExpo:(CGFloat)controllerMouseExpo
|
||||
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
|
||||
@@ -155,6 +155,7 @@ typedef NS_ENUM(NSInteger, PencilTickMode) {
|
||||
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
|
||||
softKeyboardHeight:(CGFloat)softKeyboardHeight
|
||||
globeAsEscape:(BOOL)globeAsEscape
|
||||
streamingRadialMenuDelay:(CGFloat)streamingRadialMenuDelay
|
||||
backgroundSessionTimer:(NSInteger)backgroundSessionTimer;
|
||||
|
||||
- (NSArray*) getHosts;
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
leftClickDelayMs:(CGFloat)leftClickDelayMs
|
||||
settingsMenuOffset:(CGFloat)settingsMenuOffset
|
||||
passthroughGestures:(BOOL)passthroughGestures
|
||||
mapControllerToMouse:(BOOL)mapControllerToMouse
|
||||
enableControllerNavigation:(BOOL)enableControllerNavigation
|
||||
controllerMousePointerVelocity:(CGFloat)controllerMousePointerVelocity
|
||||
controllerMouseExpo:(CGFloat)controllerMouseExpo
|
||||
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
|
||||
@@ -132,6 +132,7 @@
|
||||
sdrPerformanceWorkaround:(BOOL)sdrPerformanceWorkaround
|
||||
softKeyboardHeight:(CGFloat)softKeyboardHeight
|
||||
globeAsEscape:(BOOL)globeAsEscape
|
||||
streamingRadialMenuDelay:(CGFloat)streamingRadialMenuDelay
|
||||
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
|
||||
|
||||
__block Settings* settingsToSave = settings;
|
||||
@@ -208,11 +209,12 @@
|
||||
settingsToSave.leftClickDelayMs = [NSNumber numberWithFloat:leftClickDelayMs];
|
||||
settingsToSave.settingsMenuOffset = [NSNumber numberWithFloat:settingsMenuOffset];
|
||||
settingsToSave.passthroughGestures = passthroughGestures;
|
||||
settingsToSave.mapControllerToMouse = mapControllerToMouse;
|
||||
settingsToSave.enableControllerNavigation = enableControllerNavigation;
|
||||
settingsToSave.controllerMousePointerVelocity = [NSNumber numberWithFloat:controllerMousePointerVelocity];
|
||||
settingsToSave.controllerMouseExpo = [NSNumber numberWithFloat:controllerMouseExpo];
|
||||
settingsToSave.softKeyboardHeight = softKeyboardHeight;
|
||||
settingsToSave.globeAsEscape = globeAsEscape;
|
||||
settingsToSave.streamingRadialMenuDelay = @(streamingRadialMenuDelay);
|
||||
settingsToSave.rememberFoldState = rememberFoldState;
|
||||
[self saveData];
|
||||
}];
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
@property (atomic, nullable, retain) NSString *localAddress;
|
||||
@property (atomic, nullable, retain) NSString *ipv6Address;
|
||||
@property (atomic, nullable, retain) NSString *mac;
|
||||
@property (atomic, nullable, retain) NSString *controllerNavigationHighlightedAppID;
|
||||
@property (atomic) int serverCodecModeSupport;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
if(!(host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"])) self.mac = host.mac; // try to fix invalid mac happens in some cases
|
||||
self.name = host.name;
|
||||
self.uuid = host.uuid;
|
||||
self.controllerNavigationHighlightedAppID = host.controllerNavigationHighlightedAppID;
|
||||
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
||||
self.serverCert = host.serverCert;
|
||||
|
||||
@@ -101,6 +102,7 @@
|
||||
}
|
||||
parentHost.name = self.name;
|
||||
parentHost.uuid = self.uuid;
|
||||
parentHost.controllerNavigationHighlightedAppID = self.controllerNavigationHighlightedAppID;
|
||||
parentHost.serverCodecModeSupport = self.serverCodecModeSupport;
|
||||
parentHost.pairState = [NSNumber numberWithInt:self.pairState];
|
||||
}
|
||||
|
||||
@@ -107,10 +107,12 @@
|
||||
@property (nonatomic, retain) NSNumber * controllerMouseLeftButton;
|
||||
@property (nonatomic, retain) NSNumber * controllerMouseRightButton;
|
||||
@property (nonatomic, retain) NSNumber * controllerMouseStick;
|
||||
@property (nonatomic, retain) NSNumber * controllerMouseSwitch;
|
||||
@property (nonatomic) BOOL mapControllerToMouse;
|
||||
@property (nonatomic, retain) NSNumber * localRadialMenuButton;
|
||||
@property (nonatomic, retain) NSNumber * streamingRadialMenuButton;
|
||||
@property (nonatomic) BOOL enableControllerNavigation;
|
||||
@property (nonatomic, retain) NSNumber * controllerMousePointerVelocity;
|
||||
@property (nonatomic, retain) NSNumber * controllerMouseExpo;
|
||||
@property (nonatomic, retain) NSNumber * streamingRadialMenuDelay;
|
||||
@property (nonatomic) BOOL globeAsEscape;
|
||||
|
||||
// Pencil settings:
|
||||
|
||||
@@ -161,13 +161,15 @@
|
||||
self.ctrlDownForPinch = settings.ctrlDownForPinch;
|
||||
self.settingsMenuOffset = settings.settingsMenuOffset;
|
||||
self.passthroughGestures = settings.passthroughGestures;
|
||||
self.mapControllerToMouse = settings.mapControllerToMouse;
|
||||
self.enableControllerNavigation = settings.enableControllerNavigation;
|
||||
self.controllerMouseLeftButton = settings.controllerMouseLeftButton;
|
||||
self.controllerMouseRightButton = settings.controllerMouseRightButton;
|
||||
self.controllerMouseSwitch = settings.controllerMouseSwitch;
|
||||
self.localRadialMenuButton = settings.localRadialMenuButton;
|
||||
self.streamingRadialMenuButton = settings.streamingRadialMenuButton;
|
||||
self.controllerMouseStick = settings.controllerMouseStick;
|
||||
self.controllerMousePointerVelocity = settings.controllerMousePointerVelocity;
|
||||
self.controllerMouseExpo = settings.controllerMouseExpo;
|
||||
self.streamingRadialMenuDelay = settings.streamingRadialMenuDelay;
|
||||
self.globeAsEscape = settings.globeAsEscape;
|
||||
|
||||
// Pencil settings:
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
//
|
||||
// GamepadNavigationIllustrationHud.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True Zhuanjia on 2026/7/14.
|
||||
// Copyright 2026 True Zhuanjia @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@objcMembers
|
||||
final class GamepadNavigationIllustrationHud: UIView {
|
||||
private enum KeyContent {
|
||||
case text(String)
|
||||
case symbol(String)
|
||||
}
|
||||
|
||||
private struct Hint {
|
||||
let control: ControllerElement
|
||||
let keyContent: KeyContent
|
||||
let title: String
|
||||
let isInAction: Bool
|
||||
}
|
||||
|
||||
private let containerView = UIView()
|
||||
private let contentStackView = UIStackView()
|
||||
private var themeObserver: NSObjectProtocol?
|
||||
private var windowConstraints: [NSLayoutConstraint] = []
|
||||
private var trailingConstraint: NSLayoutConstraint?
|
||||
private static let keyBorderViewTag = 4101
|
||||
private static let keyContentViewTag = 4102
|
||||
private static var currentHud: GamepadNavigationIllustrationHud?
|
||||
private static var activeControls = Set<ControllerElement>()
|
||||
private static var actionStateMinimumEndTimes: [ControllerElement: CFTimeInterval] = [:]
|
||||
private static var pendingActionStateWorkItems: [ControllerElement: DispatchWorkItem] = [:]
|
||||
private static var pendingClearHudWorkItem: DispatchWorkItem?
|
||||
private static let minimumActionStateDuration: CFTimeInterval = 0.09
|
||||
private static let hudScale: CGFloat = PublicUtils.isIPhone ? 0.74 : 1
|
||||
private static let hudWidth: CGFloat = 235
|
||||
private static let edgeMargin: CGFloat = PublicUtils.isIPhone ? 5 : 24
|
||||
private var hints: [Hint] = [
|
||||
// Hint(control: .leftStick, keyContent: .text("L"), title: "浏览", isInAction: false),
|
||||
// Hint(control: .a, keyContent: .text("A"), title: "选择", isInAction: false)
|
||||
]
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupView()
|
||||
observeThemeChanges()
|
||||
updateTheme()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
observeThemeChanges()
|
||||
updateTheme()
|
||||
}
|
||||
|
||||
deinit {
|
||||
NSLayoutConstraint.deactivate(windowConstraints)
|
||||
containerView.removeFromSuperview()
|
||||
if let themeObserver {
|
||||
NotificationCenter.default.removeObserver(themeObserver)
|
||||
}
|
||||
}
|
||||
|
||||
override func safeAreaInsetsDidChange() {
|
||||
super.safeAreaInsetsDidChange()
|
||||
if let window {
|
||||
updateTrailingOffset(for: window)
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func showInKeyWindow() -> GamepadNavigationIllustrationHud? {
|
||||
guard let window = keyWindow(), ControllerUtil.primaryGCController != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hud = currentHud ?? GamepadNavigationIllustrationHud()
|
||||
currentHud = hud
|
||||
hud.attach(to: window)
|
||||
|
||||
if let mainFrameVC = ControllerNavigator.radialMenuDelegate as? MainFrameViewController {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
|
||||
let streamFrameVC = StreamFrameViewController.sharedInstance()
|
||||
if ControllerNavigator.radialMenuView == nil, mainFrameVC.isStreaming(), !mainFrameVC.settingsExpandedInStreamView, streamFrameVC?.hasNoPresentedVC == true {
|
||||
requestHudDetachKeepingMinimumActionDuration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hud
|
||||
}
|
||||
|
||||
static func updateCurrentTheme() {
|
||||
currentHud?.updateTheme()
|
||||
}
|
||||
|
||||
|
||||
@objc static func updateNavigationElements(_ elements: [ControllerNavigationElement], forceDisplay: Bool = false) {
|
||||
if !forceDisplay {
|
||||
guard ControllerNavigator.enabled, ControllerUtil.primaryGCController != nil else {return}
|
||||
guard !ControllerNavigator.stickReleasedInRadialMenu else {return}
|
||||
}
|
||||
PublicUtils.runOnMain {
|
||||
let hud = showInKeyWindow()
|
||||
hud?.updateHints(with: elements)
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func updateHud() {
|
||||
let elements = ControllerNavigator.uiNavigationDelegate?.getNavigationElements() ?? []
|
||||
/*
|
||||
for element in elements {
|
||||
print("element \(element.action)")
|
||||
} */
|
||||
updateNavigationElements(elements)
|
||||
}
|
||||
|
||||
static func updateActionState(for control: ControllerElement, isInAction: Bool) {
|
||||
PublicUtils.runOnMain {
|
||||
if isInAction {
|
||||
pendingClearHudWorkItem?.cancel()
|
||||
pendingClearHudWorkItem = nil
|
||||
pendingActionStateWorkItems[control]?.cancel()
|
||||
pendingActionStateWorkItems[control] = nil
|
||||
actionStateMinimumEndTimes[control] = CACurrentMediaTime() + minimumActionStateDuration
|
||||
|
||||
let didChange = activeControls.insert(control).inserted
|
||||
guard didChange else { return }
|
||||
currentHud?.refreshActionStates()
|
||||
} else {
|
||||
requestActionStateEnd(for: control)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func resetActionStates() {
|
||||
PublicUtils.runOnMain {
|
||||
guard !activeControls.isEmpty else { return }
|
||||
Array(activeControls).forEach { requestActionStateEnd(for: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
static func clearHud() {
|
||||
PublicUtils.runOnMain {
|
||||
if ControllerNavigator.controllerMouseEnabled {
|
||||
finishHudDetach()
|
||||
return
|
||||
}
|
||||
requestHudDetachKeepingMinimumActionDuration()
|
||||
}
|
||||
}
|
||||
|
||||
func updateTheme() {
|
||||
let isDark = ThemeManager.userInterfaceStyle() == .dark
|
||||
|
||||
backgroundColor = isDark
|
||||
? UIColor(red: 28.0 / 255.0, green: 28.0 / 255.0, blue: 30.0 / 255.0, alpha: 0.62)
|
||||
: UIColor.white.withAlphaComponent(0.6)
|
||||
layer.borderColor = (isDark ? UIColor.white.withAlphaComponent(0.10) : UIColor.black.withAlphaComponent(0.06)).cgColor
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
layer.shadowOpacity = isDark ? 0.34 : 0.12
|
||||
layer.shadowRadius = isDark ? 18 : 16
|
||||
layer.shadowOffset = CGSize(width: 0, height: 8)
|
||||
|
||||
for (index, row) in contentStackView.arrangedSubviews.enumerated() {
|
||||
guard hints.indices.contains(index),
|
||||
let rowStack = row as? UIStackView,
|
||||
let keyView = rowStack.arrangedSubviews.first,
|
||||
let titleLabel = rowStack.arrangedSubviews.last as? UILabel else {
|
||||
continue
|
||||
}
|
||||
|
||||
let isInAction = hints[index].isInAction
|
||||
let keyBorderView = keyView.viewWithTag(Self.keyBorderViewTag) ?? keyView
|
||||
let keyContentView = keyView.viewWithTag(Self.keyContentViewTag) ?? keyView
|
||||
let keyBackgroundColor = isInAction
|
||||
? ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.2 : 0.13)
|
||||
: ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.09 : 0.05)
|
||||
keyView.backgroundColor = .clear
|
||||
keyBorderView.layer.borderColor = ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.69 : 0.6).cgColor
|
||||
keyContentView.backgroundColor = keyBackgroundColor
|
||||
keyView.transform = .identity
|
||||
for subview in keyContentView.subviews {
|
||||
if let keyLabel = subview as? UILabel {
|
||||
keyLabel.textColor = isInAction ? ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 1 : 1) : ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.83 : 0.83)
|
||||
} else if let imageView = subview as? UIImageView {
|
||||
imageView.tintColor = isInAction ? ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 1 : 1) : ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.83 : 0.83)
|
||||
}
|
||||
}
|
||||
titleLabel.textColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.69 : 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
isUserInteractionEnabled = false
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.isUserInteractionEnabled = false
|
||||
transform = CGAffineTransform(scaleX: Self.hudScale, y: Self.hudScale)
|
||||
layer.cornerRadius = 18
|
||||
layer.cornerCurve = .continuous
|
||||
layer.borderWidth = 1
|
||||
|
||||
contentStackView.axis = .vertical
|
||||
contentStackView.alignment = .leading
|
||||
contentStackView.spacing = 6
|
||||
contentStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(contentStackView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
|
||||
contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
|
||||
contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: 10),
|
||||
contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10)
|
||||
])
|
||||
|
||||
reloadHintRows()
|
||||
}
|
||||
|
||||
private func updateHints(with elements: [ControllerNavigationElement]) {
|
||||
let nextHints = elements.compactMap { hint(for: $0) }
|
||||
hints = nextHints.isEmpty ? [] : nextHints
|
||||
guard !hints.isEmpty else {return}
|
||||
reloadHintRows()
|
||||
updateTheme()
|
||||
isHidden = hints.isEmpty
|
||||
}
|
||||
|
||||
private func refreshActionStates() {
|
||||
hints = hints.map { hint in
|
||||
Hint(
|
||||
control: hint.control,
|
||||
keyContent: hint.keyContent,
|
||||
title: hint.title,
|
||||
isInAction: Self.isControlActive(hint.control)
|
||||
)
|
||||
}
|
||||
updateTheme()
|
||||
}
|
||||
|
||||
private func reloadHintRows() {
|
||||
contentStackView.arrangedSubviews.forEach { row in
|
||||
contentStackView.removeArrangedSubview(row)
|
||||
row.removeFromSuperview()
|
||||
}
|
||||
|
||||
hints.forEach { contentStackView.addArrangedSubview(makeRow(for: $0)) }
|
||||
}
|
||||
|
||||
private func hint(for navigationElement: ControllerNavigationElement) -> Hint? {
|
||||
return Hint(
|
||||
control: navigationElement.control,
|
||||
keyContent: keyContent(for: navigationElement.control),
|
||||
title: navigationElement.action.localized,
|
||||
isInAction: navigationElement.isInAction || Self.isControlActive(navigationElement.control)
|
||||
)
|
||||
}
|
||||
|
||||
private func keyContent(for control: ControllerElement) -> KeyContent {
|
||||
let symbol = control.symbol
|
||||
return symbol.isEmpty ? .text(control.displayName) : .symbol(symbol)
|
||||
}
|
||||
|
||||
private func attach(to window: UIWindow) {
|
||||
if containerView.superview !== window {
|
||||
NSLayoutConstraint.deactivate(windowConstraints)
|
||||
windowConstraints.removeAll()
|
||||
removeFromSuperview()
|
||||
containerView.removeFromSuperview()
|
||||
window.addSubview(containerView)
|
||||
containerView.addSubview(self)
|
||||
|
||||
let trailingConstraint = containerView.trailingAnchor.constraint(equalTo: window.safeAreaLayoutGuide.trailingAnchor)
|
||||
self.trailingConstraint = trailingConstraint
|
||||
windowConstraints = [
|
||||
containerView.widthAnchor.constraint(equalToConstant: Self.hudWidth * Self.hudScale),
|
||||
containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: Self.hudScale),
|
||||
trailingConstraint,
|
||||
containerView.bottomAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: -Self.edgeMargin),
|
||||
|
||||
widthAnchor.constraint(equalToConstant: Self.hudWidth),
|
||||
centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
|
||||
centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
|
||||
]
|
||||
NSLayoutConstraint.activate(windowConstraints)
|
||||
}
|
||||
|
||||
updateTrailingOffset(for: window)
|
||||
window.bringSubviewToFront(containerView)
|
||||
}
|
||||
|
||||
private func detachFromWindow() {
|
||||
NSLayoutConstraint.deactivate(windowConstraints)
|
||||
windowConstraints.removeAll()
|
||||
trailingConstraint = nil
|
||||
removeFromSuperview()
|
||||
containerView.removeFromSuperview()
|
||||
}
|
||||
|
||||
private func updateTrailingOffset(for window: UIWindow) {
|
||||
let horizontalMargin = Self.edgeMargin + (PublicUtils.isIPhone ? 0 : window.safeAreaInsets.bottom)
|
||||
trailingConstraint?.constant = -horizontalMargin
|
||||
}
|
||||
|
||||
private func makeRow(for hint: Hint) -> UIStackView {
|
||||
let keyView = makeKeyView(for: hint.keyContent)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = hint.title
|
||||
titleLabel.font = UIFont.roundedSystemFont(ofSize: 14, weight: .medium)
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
titleLabel.minimumScaleFactor = 0.2
|
||||
titleLabel.numberOfLines = 1
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
|
||||
let rowStackView = UIStackView(arrangedSubviews: [keyView, titleLabel])
|
||||
rowStackView.axis = .horizontal
|
||||
rowStackView.alignment = .center
|
||||
rowStackView.spacing = 8
|
||||
return rowStackView
|
||||
}
|
||||
|
||||
private func makeKeyView(for content: KeyContent) -> UIView {
|
||||
let keyContainerView = UIView()
|
||||
keyContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
keyContainerView.backgroundColor = .clear
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
keyContainerView.widthAnchor.constraint(equalToConstant: 50),
|
||||
keyContainerView.heightAnchor.constraint(equalToConstant: PublicUtils.isIPhone ? 38 : 42)
|
||||
])
|
||||
|
||||
let borderView = UIView()
|
||||
borderView.tag = Self.keyBorderViewTag
|
||||
borderView.translatesAutoresizingMaskIntoConstraints = false
|
||||
borderView.isUserInteractionEnabled = false
|
||||
borderView.backgroundColor = .clear
|
||||
borderView.layer.cornerRadius = 13
|
||||
borderView.layer.cornerCurve = .continuous
|
||||
borderView.layer.borderWidth = 1
|
||||
borderView.layer.masksToBounds = true
|
||||
keyContainerView.addSubview(borderView)
|
||||
|
||||
let keyView = UIView()
|
||||
keyView.tag = Self.keyContentViewTag
|
||||
keyView.translatesAutoresizingMaskIntoConstraints = false
|
||||
keyView.layer.cornerRadius = 12
|
||||
keyView.layer.cornerCurve = .continuous
|
||||
keyView.layer.masksToBounds = true
|
||||
keyContainerView.addSubview(keyView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
borderView.leadingAnchor.constraint(equalTo: keyContainerView.leadingAnchor),
|
||||
borderView.trailingAnchor.constraint(equalTo: keyContainerView.trailingAnchor),
|
||||
borderView.topAnchor.constraint(equalTo: keyContainerView.topAnchor),
|
||||
borderView.bottomAnchor.constraint(equalTo: keyContainerView.bottomAnchor),
|
||||
|
||||
keyView.leadingAnchor.constraint(equalTo: keyContainerView.leadingAnchor, constant: 1),
|
||||
keyView.trailingAnchor.constraint(equalTo: keyContainerView.trailingAnchor, constant: -1),
|
||||
keyView.topAnchor.constraint(equalTo: keyContainerView.topAnchor, constant: 1),
|
||||
keyView.bottomAnchor.constraint(equalTo: keyContainerView.bottomAnchor, constant: -1)
|
||||
])
|
||||
|
||||
switch content {
|
||||
case .text(let text):
|
||||
let keyLabel = InsetLabel()
|
||||
keyLabel.contentInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 6)
|
||||
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
keyLabel.text = text
|
||||
keyLabel.textAlignment = .center
|
||||
keyLabel.font = UIFont.roundedSystemFont(ofSize: 12, weight: .medium)
|
||||
keyLabel.adjustsFontSizeToFitWidth = true
|
||||
keyLabel.minimumScaleFactor = 0.2
|
||||
keyLabel.numberOfLines = 1
|
||||
keyLabel.lineBreakMode = .byClipping
|
||||
keyView.addSubview(keyLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
keyLabel.leadingAnchor.constraint(equalTo: keyView.leadingAnchor),
|
||||
keyLabel.trailingAnchor.constraint(equalTo: keyView.trailingAnchor),
|
||||
keyLabel.topAnchor.constraint(equalTo: keyView.topAnchor),
|
||||
keyLabel.bottomAnchor.constraint(equalTo: keyView.bottomAnchor)
|
||||
])
|
||||
case .symbol(let symbolName):
|
||||
let imageView = UIImageView(image: UIImage(systemName: symbolName))
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)
|
||||
keyView.addSubview(imageView)
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.centerXAnchor.constraint(equalTo: keyView.centerXAnchor),
|
||||
imageView.centerYAnchor.constraint(equalTo: keyView.centerYAnchor),
|
||||
imageView.widthAnchor.constraint(lessThanOrEqualTo: keyView.widthAnchor, multiplier: 0.58),
|
||||
imageView.heightAnchor.constraint(lessThanOrEqualTo: keyView.heightAnchor, multiplier: 0.58)
|
||||
])
|
||||
}
|
||||
|
||||
return keyContainerView
|
||||
}
|
||||
|
||||
private func observeThemeChanges() {
|
||||
themeObserver = NotificationCenter.default.addObserver(
|
||||
forName: Notification.Name(ThemeManager.ThemeDidChangeNotification),
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.updateTheme()
|
||||
}
|
||||
}
|
||||
|
||||
private static func keyWindow() -> UIWindow? {
|
||||
let sceneWindows = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.flatMap { $0.windows }
|
||||
return sceneWindows.first(where: { $0.isKeyWindow })
|
||||
?? sceneWindows.first
|
||||
?? UIApplication.shared.windows.first(where: { $0.isKeyWindow })
|
||||
?? UIApplication.shared.windows.first
|
||||
}
|
||||
|
||||
private static func isControlActive(_ control: ControllerElement) -> Bool {
|
||||
!relatedControls(for: control).isDisjoint(with: activeControls)
|
||||
}
|
||||
|
||||
private static func requestActionStateEnd(for control: ControllerElement) {
|
||||
pendingActionStateWorkItems[control]?.cancel()
|
||||
|
||||
let now = CACurrentMediaTime()
|
||||
let remainingDuration = max((actionStateMinimumEndTimes[control] ?? now) - now, 0)
|
||||
guard remainingDuration > 0 else {
|
||||
finishActionStateEnd(for: control)
|
||||
return
|
||||
}
|
||||
|
||||
let workItem = DispatchWorkItem {
|
||||
finishActionStateEnd(for: control)
|
||||
}
|
||||
pendingActionStateWorkItems[control] = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + remainingDuration, execute: workItem)
|
||||
}
|
||||
|
||||
private static func finishActionStateEnd(for control: ControllerElement) {
|
||||
pendingActionStateWorkItems[control]?.cancel()
|
||||
pendingActionStateWorkItems[control] = nil
|
||||
actionStateMinimumEndTimes[control] = nil
|
||||
|
||||
guard activeControls.remove(control) != nil else { return }
|
||||
currentHud?.refreshActionStates()
|
||||
}
|
||||
|
||||
private static func requestHudDetachKeepingMinimumActionDuration() {
|
||||
pendingClearHudWorkItem?.cancel()
|
||||
|
||||
let remainingDuration = maximumRemainingActionStateDuration()
|
||||
guard remainingDuration > 0 else {
|
||||
finishHudDetach()
|
||||
return
|
||||
}
|
||||
|
||||
let workItem = DispatchWorkItem {
|
||||
finishHudDetach()
|
||||
}
|
||||
pendingClearHudWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + remainingDuration, execute: workItem)
|
||||
}
|
||||
|
||||
private static func finishHudDetach() {
|
||||
pendingClearHudWorkItem?.cancel()
|
||||
pendingClearHudWorkItem = nil
|
||||
pendingActionStateWorkItems.values.forEach { $0.cancel() }
|
||||
pendingActionStateWorkItems.removeAll()
|
||||
actionStateMinimumEndTimes.removeAll()
|
||||
activeControls.removeAll()
|
||||
currentHud?.detachFromWindow()
|
||||
currentHud = nil
|
||||
}
|
||||
|
||||
private static func maximumRemainingActionStateDuration() -> CFTimeInterval {
|
||||
let now = CACurrentMediaTime()
|
||||
return activeControls.reduce(0) { partialResult, control in
|
||||
max(partialResult, max((actionStateMinimumEndTimes[control] ?? now) - now, 0))
|
||||
}
|
||||
}
|
||||
|
||||
private static func relatedControls(for control: ControllerElement) -> Set<ControllerElement> {
|
||||
switch control {
|
||||
case .leftStick, .leftStickX, .leftStickY:
|
||||
return [.leftStick, .leftStickX, .leftStickY]
|
||||
case .rightStick, .rightStickX, .rightStickY:
|
||||
return [.rightStick, .rightStickX, .rightStickY]
|
||||
default:
|
||||
return [control]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class InsetLabel: UILabel {
|
||||
var contentInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)
|
||||
|
||||
override func drawText(in rect: CGRect) {
|
||||
super.drawText(in: rect.inset(by: contentInsets))
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
let size = super.intrinsicContentSize
|
||||
return CGSize(
|
||||
width: size.width + contentInsets.left + contentInsets.right,
|
||||
height: size.height + contentInsets.top + contentInsets.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,7 @@ class HostCardView: UIView {
|
||||
}
|
||||
|
||||
@objc func appButtonTapped() {
|
||||
guard host?.pairState == pairStatePaired, host?.state == .online, self.parentViewController?.hasNoPresentedVC == true else { return }
|
||||
if longPressFired {
|
||||
longPressFired = false
|
||||
return
|
||||
@@ -120,6 +121,7 @@ class HostCardView: UIView {
|
||||
}
|
||||
|
||||
@objc func launchButtonTapped() {
|
||||
guard host?.pairState == pairStatePaired, host?.state == .online, self.parentViewController?.hasNoPresentedVC == true else { return }
|
||||
if longPressFired {
|
||||
longPressFired = false
|
||||
return
|
||||
@@ -133,6 +135,7 @@ class HostCardView: UIView {
|
||||
}
|
||||
|
||||
@objc func wakeupButtonTapped() {
|
||||
guard host?.state != .online, self.parentViewController?.hasNoPresentedVC == true else { return }
|
||||
if longPressFired {
|
||||
longPressFired = false
|
||||
return
|
||||
@@ -146,6 +149,7 @@ class HostCardView: UIView {
|
||||
}
|
||||
|
||||
@objc func pairButtonTapped() {
|
||||
guard !pairButton.isHidden, self.parentViewController?.hasNoPresentedVC == true else { return }
|
||||
if longPressFired {
|
||||
longPressFired = false
|
||||
return
|
||||
@@ -442,7 +446,6 @@ class HostCardView: UIView {
|
||||
}
|
||||
|
||||
func tintAdjustmentModeDidChange() {
|
||||
NSLog("tintChanged........")
|
||||
tintAdjustmentMode = .normal
|
||||
}
|
||||
|
||||
@@ -476,7 +479,10 @@ class HostCardView: UIView {
|
||||
return
|
||||
}
|
||||
|
||||
updateContents(for: host)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+0.36) { [weak self] in
|
||||
self?.updateContents(for: self?.host)
|
||||
}
|
||||
|
||||
perform(#selector(updateLoop), with: self, afterDelay: Self.refreshCycle)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "circle.lefthalf.filled.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 26.5852 26.2402">
|
||||
<g>
|
||||
<rect height="26.2402" opacity="0" width="26.5852" x="0" y="0"/>
|
||||
<path d="M13.1137 24.8338L13.1137 1.38328C6.44134 1.38328 1.38556 6.44134 1.38556 13.1137C1.38556 19.7861 6.44134 24.8338 13.1137 24.8338ZM13.1137 26.2275C20.3549 26.2275 26.2298 20.3526 26.2298 13.1137C26.2298 5.87488 20.3549 0 13.1137 0C5.87488 0 0 5.87488 0 13.1137C0 20.3526 5.87488 26.2275 13.1137 26.2275ZM13.1137 23.6443C7.29091 23.6443 2.58322 18.9366 2.58322 13.1137C2.58322 7.29091 7.29091 2.58322 13.1137 2.58322C18.9366 2.58322 23.6465 7.29091 23.6465 13.1137C23.6465 18.9366 18.9366 23.6443 13.1137 23.6443Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 940 B |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "controllerMouse.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 17.225 22.7356">
|
||||
<g>
|
||||
<rect height="22.7356" opacity="0" width="17.225" x="0" y="0"/>
|
||||
<path d="M7.98991 16.743L14.7319 16.743C16.6897 16.743 17.5002 14.9583 16.3368 13.7776L3.19844 0.64154C1.94161-0.624432 0 0.0799009 0 1.90751L0 20.5374C0 22.1976 1.83421 22.8801 3.21901 21.5058Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 612 B |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "disableControllerMouse.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 30.0974 26.0826">
|
||||
<g>
|
||||
<rect height="26.0826" opacity="0" width="30.0974" x="0" y="0"/>
|
||||
<path d="M14.0091 18.5296L9.61512 22.9119C8.37098 24.156 6.72059 23.5467 6.72059 22.0486L6.72059 11.264ZM9.62782 2.61209L22.5136 15.5105C23.5673 16.5769 22.8436 18.1765 21.079 18.1765L20.638 18.1765L6.72059 4.29423L6.72059 3.75467C6.72059 2.09158 8.47254 1.46951 9.62782 2.61209Z" fill="white"/>
|
||||
<path d="M1.75672 4.2117L21.9169 24.3084C22.2977 24.7019 22.9325 24.7019 23.3007 24.3084C23.6688 23.9275 23.6815 23.3181 23.3007 22.9246L3.15321 2.82791C2.77235 2.44705 2.15028 2.44705 1.75672 2.82791C1.38856 3.20877 1.38856 3.84354 1.75672 4.2117Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 966 B |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macwindow.and.pointer.arrow.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36.2556 34.0064">
|
||||
<g>
|
||||
<rect height="34.0064" opacity="0" width="36.2556" x="0" y="0"/>
|
||||
<path d="M32.9778 10.6393L32.9778 21.2524L30.4905 18.7118L30.4905 10.7627C30.4905 8.79238 29.3426 7.64447 27.3723 7.64447L8.51517 7.64447C6.54486 7.64447 5.39694 8.79238 5.39694 10.7627L5.39694 23.2564C5.39694 25.229 6.54486 26.3665 8.51517 26.3665L24.569 26.3665L24.5407 28.4391C24.5386 28.5831 24.5481 28.722 24.5715 28.8538L8.38137 28.8538C4.91606 28.8538 2.9097 26.8578 2.9097 23.3798L2.9097 10.6393C2.9097 7.1613 4.91606 5.15722 8.38137 5.15722L27.7709 5.15722C31.057 5.15722 32.9778 7.1613 32.9778 10.6393ZM10.0795 11.0857C10.0795 11.7166 9.55111 12.2658 8.90161 12.2658C8.25212 12.2658 7.72146 11.7166 7.72146 11.0857C7.72146 10.4489 8.25212 9.89739 8.90161 9.89739C9.55111 9.89739 10.0795 10.4489 10.0795 11.0857ZM13.8213 11.0857C13.8213 11.7166 13.2929 12.2658 12.6434 12.2658C11.9917 12.2658 11.4633 11.7166 11.4633 11.0857C11.4633 10.4489 11.9917 9.89739 12.6434 9.89739C13.2929 9.89739 13.8213 10.4489 13.8213 11.0857ZM17.5631 11.0857C17.5631 11.7166 17.0324 12.2658 16.3852 12.2658C15.7335 12.2658 15.2051 11.7166 15.2051 11.0857C15.2051 10.4489 15.7335 9.89739 16.3852 9.89739C17.0324 9.89739 17.5631 10.4489 17.5631 11.0857Z" fill="white"/>
|
||||
<path d="M32.5208 32.1175C33.1251 31.8661 33.4145 31.1857 33.1527 30.5836L31.2545 26.174L33.8561 26.0963C34.3956 26.0847 34.6538 25.5545 34.2699 25.1498L27.3886 18.1123C27.0219 17.7456 26.4847 17.9589 26.472 18.4685L26.3463 28.4749C26.3313 29.0318 26.9747 29.2161 27.3355 28.8462L29.1464 26.9975L30.9661 31.4878C31.2002 32.0852 31.9083 32.3747 32.5208 32.1175Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rectangle.portrait.and.arrow.right.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 34.2565 27.6836">
|
||||
<g>
|
||||
<rect height="27.6836" opacity="0" width="34.2565" x="0" y="0"/>
|
||||
<path d="M23.7582 5.55752L23.7582 10.1349L20.9311 10.1349L20.9311 5.6619C20.9311 3.85035 19.9125 2.82713 18.0963 2.82713L7.33593 2.82713C5.52438 2.82713 4.49712 3.85035 4.49712 5.6619L4.49712 22.0171C4.49712 23.8373 5.52438 24.8518 7.33593 24.8518L18.0963 24.8518C19.9125 24.8518 20.9311 23.8373 20.9311 22.0171L20.9311 17.5359L23.7582 17.5359L23.7582 22.1301C23.7582 25.6945 21.7778 27.679 18.2134 27.679L7.22751 27.679C3.6631 27.679 1.66999 25.6945 1.66999 22.1301L1.66999 5.55752C1.66999 1.99311 3.6631 0 7.22751 0L18.2134 0C21.7778 0 23.7582 1.99311 23.7582 5.55752Z" fill="white"/>
|
||||
<path d="M13.1283 13.8354C13.1283 14.5549 13.714 15.1331 14.4006 15.1331L27.0257 15.1331L30.8363 14.8838C31.4191 14.8423 31.8888 14.4142 31.8888 13.8354C31.8888 13.2527 31.4191 12.8245 30.8363 12.783L27.0257 12.5292L14.4006 12.5292C13.714 12.5292 13.1283 13.116 13.1283 13.8354ZM25.8536 9.43889C25.8536 9.7568 25.9875 10.0845 26.231 10.3067L28.0471 12.0034L30.4369 13.8354L28.0471 15.6588L26.231 17.3642C25.9875 17.5864 25.8536 17.9014 25.8536 18.2193C25.8536 18.8327 26.3152 19.3681 26.9649 19.3681C27.2961 19.3681 27.5396 19.2337 27.7704 19.0029L31.7792 14.8423C32.1139 14.5036 32.2351 14.1782 32.2351 13.8354C32.2351 13.4841 32.1139 13.1633 31.7792 12.8286L27.7704 8.65533C27.5396 8.42453 27.2961 8.30279 26.9649 8.30279C26.3152 8.30279 25.8536 8.81687 25.8536 9.43889Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import "StreamView.h"
|
||||
#import "TemporarySettings.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,8 @@
|
||||
|
||||
@property (readonly) bool shallDisableGyroHotSwitch;
|
||||
|
||||
+(ControllerSupport*) sharedInstance;
|
||||
|
||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
|
||||
-(void) connectionEstablished;
|
||||
|
||||
@@ -60,4 +62,7 @@
|
||||
|
||||
-(void)updateControllerSupport:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
|
||||
|
||||
-(void) reinitiatePrimaryController;
|
||||
-(void)sendNavigationButtonPress;
|
||||
|
||||
@end
|
||||
|
||||
+229
-364
@@ -24,6 +24,7 @@
|
||||
@import AudioToolbox;
|
||||
|
||||
static const double MOUSE_SPEED_DIVISOR = 1.25;
|
||||
static __weak ControllerSupport *VLSharedControllerSupport = nil;
|
||||
|
||||
@interface ControllerSupport()
|
||||
|
||||
@@ -50,22 +51,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
||||
float accumulatedScrollX;
|
||||
float accumulatedScrollY;
|
||||
|
||||
int _controllerMouseSwitch;
|
||||
bool _mouseSwitchButtonPressed;
|
||||
bool _mouseSwitchButtonBeingClicked;
|
||||
NSTimeInterval mouseSwitchDownTimestamp;
|
||||
int _controllerMouseLeftButton;
|
||||
int _controllerMouseRightButton;
|
||||
ControllerMouseStick _controllerMouseStick;
|
||||
bool _mapControllerToMouse;
|
||||
bool _controllerMouseEnabledFlag;
|
||||
float stickToMouseInputX;
|
||||
float stickToMouseInputY;
|
||||
float stickToWheelInputX;
|
||||
float stickToWheelInputY;
|
||||
float _stickToMouseExpo;
|
||||
float _stickToMouseVelocity;
|
||||
CADisplayLink *_displayLink;
|
||||
bool _controllerNavigationEnabled;
|
||||
|
||||
float stickMaxOffset;
|
||||
float _leftStickMinOffset;
|
||||
@@ -100,6 +86,11 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
||||
__weak MotionHandler* motionHandler;
|
||||
}
|
||||
|
||||
+(ControllerSupport*) sharedInstance
|
||||
{
|
||||
return VLSharedControllerSupport;
|
||||
}
|
||||
|
||||
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
|
||||
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
||||
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
||||
@@ -1020,39 +1011,6 @@ double rc_expo(double x, double expo) {
|
||||
return x > 0 ? y : -y;
|
||||
}
|
||||
|
||||
- (void)sendStickToMouseMoveEventWithStickX:(float)stickX stickY:(float)stickY expo:(float)expo {
|
||||
CGFloat mouseDeltaX = _stickToMouseVelocity*rc_expo(stickX, expo);
|
||||
CGFloat mouseDeltaY = _stickToMouseVelocity*rc_expo(stickY, expo);
|
||||
LiSendMouseMoveEvent(mouseDeltaX, -mouseDeltaY);
|
||||
}
|
||||
|
||||
- (void)stopDisplayLink {
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
|
||||
- (void)displayLinkCallBack {
|
||||
if(!_controllerMouseEnabledFlag){
|
||||
return;
|
||||
}
|
||||
NSTimeInterval delay = 0.5/_displayLink.preferredFramesPerSecond*NSEC_PER_SEC;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self sendStickToMouseMoveEventWithStickX:self->stickToMouseInputX stickY:self->stickToMouseInputY expo:self->_stickToMouseExpo];
|
||||
LiSendHighResScrollEvent(15*self->stickToWheelInputY);
|
||||
LiSendHighResHScrollEvent(15*self->stickToWheelInputX);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)sendControllerMouseSwitchClick:(VoidController*) voidController{
|
||||
[self setButtonFlag:voidController flags:self->_controllerMouseSwitch];
|
||||
_mouseSwitchButtonBeingClicked = true;
|
||||
[self updateFinished:voidController];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.03*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self clearButtonFlag:voidController flags:self->_controllerMouseSwitch];
|
||||
self->_mouseSwitchButtonBeingClicked = false;
|
||||
[self updateFinished:voidController];
|
||||
});
|
||||
}
|
||||
|
||||
- (bool)useMotionHandler{
|
||||
return self->tempSettings.emulatedControllerType.intValue!=LI_CTYPE_PS
|
||||
@@ -1068,6 +1026,199 @@ double rc_expo(double x, double expo) {
|
||||
else self->_gyroEnabledFlag = self->_gyroEnabledFlag || !self->_controllerGyroSwitchEnabled;
|
||||
}
|
||||
|
||||
- (void)sendNavigationButtonPress {
|
||||
if(!ControllerUtil.primaryGCController) return;
|
||||
if(@available(iOS 13.0, *)) if(ControllerNavigator.controllerMouseEnabled) return;
|
||||
VoidController* voidController = [self->_voidControllers objectForKey:@(ControllerUtil.primaryGCController.playerIndex)];
|
||||
int radialMenuButton = ControllerElementNull;
|
||||
if (@available(iOS 13.0, *)) radialMenuButton = ControllerNavigator.radialMenuButton;
|
||||
[self setButtonFlag:voidController flags:(int)radialMenuButton];
|
||||
[self updateFinished:voidController];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.03*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self clearButtonFlag:voidController flags:radialMenuButton];
|
||||
[self updateFinished:voidController];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
- (void) listenToGCController:(GCController*) controller {
|
||||
/*
|
||||
if(self->_swapABXYButtons){
|
||||
switch (self->_controllerMouseLeftButton) {
|
||||
case ControllerElementA:
|
||||
self->_controllerMouseLeftButton = ControllerElementB;
|
||||
break;
|
||||
case ControllerElementB:
|
||||
self->_controllerMouseLeftButton = ControllerElementA;
|
||||
break;
|
||||
case ControllerElementX:
|
||||
self->_controllerMouseLeftButton = ControllerElementY;
|
||||
break;
|
||||
case ControllerElementY:
|
||||
self->_controllerMouseLeftButton = ControllerElementX;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (self->_controllerMouseRightButton) {
|
||||
case ControllerElementA:
|
||||
self->_controllerMouseRightButton = ControllerElementB;
|
||||
break;
|
||||
case ControllerElementB:
|
||||
self->_controllerMouseRightButton = ControllerElementA;
|
||||
break;
|
||||
case ControllerElementX:
|
||||
self->_controllerMouseRightButton = ControllerElementY;
|
||||
break;
|
||||
case ControllerElementY:
|
||||
self->_controllerMouseRightButton = ControllerElementX;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
__block ControllerElement radialMenuButton = ControllerElementNull;
|
||||
__block bool controllerNavigatorEnabled = false;
|
||||
|
||||
[ControllerUtil listenWithController:controller swapABXY:self->_swapABXYButtons handler:^(NSDictionary * elementDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
|
||||
VoidController* voidController = [self->_voidControllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||
short leftStickX, leftStickY;
|
||||
short rightStickX, rightStickY;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
|
||||
if (@available(iOS 13.0, *)){
|
||||
radialMenuButton = ControllerNavigator.radialMenuButton;
|
||||
controllerNavigatorEnabled = ControllerNavigator.enabled;
|
||||
}
|
||||
|
||||
for(NSNumber* elementFlagId in elementDict){
|
||||
GCControllerElement * element = (GCControllerElement *)elementDict[elementFlagId];
|
||||
if ([element isKindOfClass:[GCControllerButtonInput class]]) {
|
||||
GCControllerButtonInput* button = (GCControllerButtonInput *)element;
|
||||
if(controllerNavigatorEnabled && voidController.playerIndex==0 && elementFlagId.intValue == radialMenuButton){
|
||||
if(!button.pressed){
|
||||
// [self sendNavigationButtonPress:voidController];
|
||||
}
|
||||
}
|
||||
|
||||
// controller switch buttons
|
||||
if(true){
|
||||
if(button.pressed){
|
||||
if (elementFlagId.intValue == self->_controllerGyroSwitchToggle
|
||||
&& !self->_controllerGyroSwitchTogglePressed) {
|
||||
self->_controllerGyroSwitchTogglePressed = true;
|
||||
|
||||
self->_gyroEnabledFlag = !self->_gyroEnabledFlag;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
if (elementFlagId.intValue == self->_controllerGyroSwitchHold
|
||||
&& !self->_controllerGyroSwitchHoldPressed) {
|
||||
self->_controllerGyroSwitchHoldPressed = true;
|
||||
|
||||
self->_gyroEnabledFlag = !self->_reverseHoldButton;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (elementFlagId.intValue == self->_controllerGyroSwitchToggle
|
||||
&& self->_controllerGyroSwitchTogglePressed) {
|
||||
self->_controllerGyroSwitchTogglePressed = false;
|
||||
}
|
||||
if (elementFlagId.intValue == self->_controllerGyroSwitchHold
|
||||
&& self->_controllerGyroSwitchHoldPressed) {
|
||||
self->_controllerGyroSwitchHoldPressed = false;
|
||||
|
||||
self->_gyroEnabledFlag = self->_reverseHoldButton;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(controller != ControllerUtil.primaryGCController || elementFlagId.intValue!=radialMenuButton || !controllerNavigatorEnabled) UPDATE_BUTTON_FLAG(voidController, elementFlagId.intValue, button.pressed);
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat leftStickXRaw = gamepad.leftThumbstick.xAxis.value * self->stickMaxOffset;
|
||||
CGFloat leftStickYRaw = gamepad.leftThumbstick.yAxis.value * self->stickMaxOffset;
|
||||
|
||||
CGFloat rightStickXRaw = gamepad.rightThumbstick.xAxis.value * self->stickMaxOffset;
|
||||
CGFloat rightStickYRaw = gamepad.rightThumbstick.yAxis.value * self->stickMaxOffset;
|
||||
|
||||
CGVector leftStickOffset = [ControllerUtil compensatedWithOffsetVector:CGVectorMake(leftStickXRaw, leftStickYRaw) minOffset:self->_leftStickMinOffset circulate:false];
|
||||
|
||||
CGVector rightStickOffset = [ControllerUtil compensatedWithOffsetVector:CGVectorMake(rightStickXRaw, rightStickYRaw) minOffset:self->_rightStickMinOffset circulate:false];
|
||||
|
||||
leftStickX = leftStickOffset.dx;
|
||||
leftStickY = leftStickOffset.dy;
|
||||
|
||||
rightStickX = rightStickOffset.dx;
|
||||
rightStickY = rightStickOffset.dy;
|
||||
|
||||
/*
|
||||
if(self->oscProfile.mapGyroTo!=mapGyroToControllerStick
|
||||
||!self->oscProfile.rollToLeftStick) [self updateLeftStick:voidController x:leftStickX y:leftStickY];
|
||||
*/
|
||||
|
||||
if([self useMotionHandler]
|
||||
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
|
||||
&& self->oscProfile.yawPitchToRightStick
|
||||
&& self->_gyroEnabledFlag
|
||||
) {
|
||||
[self->motionHandler mixPhysicalRightStickAndGyroInputWithX:rightStickX y:rightStickY];
|
||||
}
|
||||
else [self updateRightStick: voidController.playerIndex==0?self->_oscController:voidController x:rightStickX y:rightStickY];
|
||||
|
||||
if([self useMotionHandler]
|
||||
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
|
||||
&& self->oscProfile.rollToLeftStick
|
||||
&& self->_gyroEnabledFlag
|
||||
) [self->motionHandler mixPhysicalLeftStickAndGyroInputWithX:leftStickX y:leftStickY];
|
||||
else [self updateLeftStick: voidController.playerIndex==0?self->_oscController:voidController x:leftStickX y:leftStickY];
|
||||
|
||||
leftTrigger = gamepad.leftTrigger.value * 0xFF;
|
||||
rightTrigger = gamepad.rightTrigger.value * 0xFF;
|
||||
[self updateTriggers:voidController left:leftTrigger right:rightTrigger];
|
||||
|
||||
[self updateFinished:voidController];
|
||||
|
||||
if (@available(iOS 14.0, *)) {
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]
|
||||
index:0];
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]
|
||||
index:1];
|
||||
}
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
-(void) reinitiatePrimaryController {
|
||||
if(@available(iOS 13.0, *)){
|
||||
if(ControllerNavigator.controllerMouseEnabled){
|
||||
[ControllerNavigator startControllerMouse];
|
||||
return;
|
||||
}
|
||||
if(ControllerNavigator.radialMenuView) return;
|
||||
}
|
||||
if(![StreamFrameViewController sharedInstance]) return;
|
||||
MainFrameViewController* mainFrameVC =(MainFrameViewController* )ControllerUtil.delegate;
|
||||
if(mainFrameVC.settingsExpandedInStreamView) return;
|
||||
[ControllerUtil stopListeningPrimaryControllerWithStopListenToRadialMenuButton:false];
|
||||
[self listenToGCController: ControllerUtil.primaryGCController];
|
||||
if (@available(iOS 13.0, *)){
|
||||
NSLog(@"ControllerUtil setUINavigationDelegate %f",CACurrentMediaTime());
|
||||
[ControllerNavigator setStreamFrameVCAsUINavigationDelegate];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) registerControllerCallbacks:(GCController*) controller
|
||||
{
|
||||
if (controller != NULL) {
|
||||
@@ -1106,299 +1257,19 @@ double rc_expo(double x, double expo) {
|
||||
if (controller.extendedGamepad != NULL) {
|
||||
// Disable system gestures on the gamepad to avoid interfering
|
||||
// with in-game controller actions
|
||||
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
for (GCControllerElement* element in controller.physicalInputProfile.allElements) {
|
||||
element.preferredSystemGestureState = GCSystemGestureStateDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
if(self->_swapABXYButtons){
|
||||
switch (self->_controllerMouseLeftButton) {
|
||||
case ControllerButtonA:
|
||||
self->_controllerMouseLeftButton = ControllerButtonB;
|
||||
break;
|
||||
case ControllerButtonB:
|
||||
self->_controllerMouseLeftButton = ControllerButtonA;
|
||||
break;
|
||||
case ControllerButtonX:
|
||||
self->_controllerMouseLeftButton = ControllerButtonY;
|
||||
break;
|
||||
case ControllerButtonY:
|
||||
self->_controllerMouseLeftButton = ControllerButtonX;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (self->_controllerMouseRightButton) {
|
||||
case ControllerButtonA:
|
||||
self->_controllerMouseRightButton = ControllerButtonB;
|
||||
break;
|
||||
case ControllerButtonB:
|
||||
self->_controllerMouseRightButton = ControllerButtonA;
|
||||
break;
|
||||
case ControllerButtonX:
|
||||
self->_controllerMouseRightButton = ControllerButtonY;
|
||||
break;
|
||||
case ControllerButtonY:
|
||||
self->_controllerMouseRightButton = ControllerButtonX;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ControllerUtil listenWithController:controller swapABXY:self->_swapABXYButtons handler:^(NSDictionary * buttonDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
|
||||
VoidController* voidController = [self->_voidControllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||
short leftStickX, leftStickY;
|
||||
short rightStickX, rightStickY;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
|
||||
for(NSNumber* buttonFlagId in buttonDict){
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId];
|
||||
if(self->_mapControllerToMouse && voidController.playerIndex==0){
|
||||
if(button.pressed){
|
||||
if(buttonFlagId.intValue == self->_controllerMouseSwitch){
|
||||
self->_mouseSwitchButtonPressed = true;
|
||||
if(self->mouseSwitchDownTimestamp == 0) self->mouseSwitchDownTimestamp = CACurrentMediaTime();
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(buttonFlagId.intValue == self->_controllerMouseSwitch && self->_mouseSwitchButtonPressed){
|
||||
if(CACurrentMediaTime()-self->mouseSwitchDownTimestamp>1){
|
||||
self->_controllerMouseEnabledFlag = !self->_controllerMouseEnabledFlag;
|
||||
[self updateLeftStick:voidController x:0 y:0];
|
||||
[self updateRightStick:voidController x:0 y:0];
|
||||
}
|
||||
else [self sendControllerMouseSwitchClick:voidController];
|
||||
self->_mouseSwitchButtonPressed = false;
|
||||
self->mouseSwitchDownTimestamp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else self->_controllerMouseEnabledFlag = false;
|
||||
|
||||
// controller switch buttons
|
||||
if(true){
|
||||
if(button.pressed){
|
||||
if (buttonFlagId.intValue == self->_controllerGyroSwitchToggle
|
||||
&& !self->_controllerGyroSwitchTogglePressed) {
|
||||
self->_controllerGyroSwitchTogglePressed = true;
|
||||
|
||||
self->_gyroEnabledFlag = !self->_gyroEnabledFlag;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
if (buttonFlagId.intValue == self->_controllerGyroSwitchHold
|
||||
&& !self->_controllerGyroSwitchHoldPressed) {
|
||||
self->_controllerGyroSwitchHoldPressed = true;
|
||||
|
||||
self->_gyroEnabledFlag = !self->_reverseHoldButton;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (buttonFlagId.intValue == self->_controllerGyroSwitchToggle
|
||||
&& self->_controllerGyroSwitchTogglePressed) {
|
||||
self->_controllerGyroSwitchTogglePressed = false;
|
||||
}
|
||||
if (buttonFlagId.intValue == self->_controllerGyroSwitchHold
|
||||
&& self->_controllerGyroSwitchHoldPressed) {
|
||||
self->_controllerGyroSwitchHoldPressed = false;
|
||||
|
||||
self->_gyroEnabledFlag = self->_reverseHoldButton;
|
||||
[self switchMotionControlOnOffByControllerButton];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(self->_controllerMouseEnabledFlag){
|
||||
if(buttonFlagId.intValue == self->_controllerMouseLeftButton || buttonFlagId.intValue == self->_controllerMouseRightButton){
|
||||
if(buttonFlagId.intValue == self->_controllerMouseLeftButton) LiSendMouseButtonEvent(button.pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
if(buttonFlagId.intValue == self->_controllerMouseRightButton) LiSendMouseButtonEvent(button.pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
||||
UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, NO);
|
||||
}
|
||||
else if(buttonFlagId.intValue!=self->_controllerMouseSwitch || !self->_mapControllerToMouse) UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, button.pressed);
|
||||
}
|
||||
else if(buttonFlagId.intValue!=self->_controllerMouseSwitch || !self->_mapControllerToMouse) UPDATE_BUTTON_FLAG(voidController, buttonFlagId.intValue, button.pressed);
|
||||
}
|
||||
|
||||
CGFloat leftStickXRaw = gamepad.leftThumbstick.xAxis.value * self->stickMaxOffset;
|
||||
CGFloat leftStickYRaw = gamepad.leftThumbstick.yAxis.value * self->stickMaxOffset;
|
||||
|
||||
CGFloat rightStickXRaw = gamepad.rightThumbstick.xAxis.value * self->stickMaxOffset;
|
||||
CGFloat rightStickYRaw = gamepad.rightThumbstick.yAxis.value * self->stickMaxOffset;
|
||||
|
||||
CGVector leftStickOffset = [ControllerUtil compensatedWithOffsetVector:CGVectorMake(leftStickXRaw, leftStickYRaw) minOffset:self->_leftStickMinOffset circulate:false];
|
||||
|
||||
CGVector rightStickOffset = [ControllerUtil compensatedWithOffsetVector:CGVectorMake(rightStickXRaw, rightStickYRaw) minOffset:self->_rightStickMinOffset circulate:false];
|
||||
|
||||
leftStickX = self->_controllerMouseEnabledFlag ? 0 : leftStickOffset.dx;
|
||||
leftStickY = self->_controllerMouseEnabledFlag ? 0 : leftStickOffset.dy;
|
||||
|
||||
rightStickX = self->_controllerMouseEnabledFlag ? 0 : rightStickOffset.dx;
|
||||
rightStickY = self->_controllerMouseEnabledFlag ? 0 : rightStickOffset.dy;
|
||||
|
||||
if(self->_controllerMouseEnabledFlag){
|
||||
self->stickToMouseInputX = self->_controllerMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value : gamepad.rightThumbstick.xAxis.value;
|
||||
self->stickToMouseInputY = self->_controllerMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value : gamepad.rightThumbstick.yAxis.value;
|
||||
|
||||
self->stickToWheelInputX = self->_controllerMouseStick == LeftStickToMouse ? gamepad.rightThumbstick.xAxis.value: gamepad.leftThumbstick.xAxis.value;
|
||||
self->stickToWheelInputY = self->_controllerMouseStick == LeftStickToMouse ? gamepad.rightThumbstick.yAxis.value: gamepad.leftThumbstick.yAxis.value;
|
||||
}
|
||||
else{
|
||||
self->stickToMouseInputX = 0;
|
||||
self->stickToMouseInputY = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
if(self->oscProfile.mapGyroTo!=mapGyroToControllerStick
|
||||
||!self->oscProfile.rollToLeftStick) [self updateLeftStick:voidController x:leftStickX y:leftStickY];
|
||||
*/
|
||||
|
||||
if([self useMotionHandler]
|
||||
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
|
||||
&& self->oscProfile.yawPitchToRightStick
|
||||
&& self->_gyroEnabledFlag
|
||||
) [self->motionHandler mixPhysicalRightStickAndGyroInputWithX:rightStickX y:rightStickY];
|
||||
else [self updateRightStick: voidController.playerIndex==0?self->_oscController:voidController x:rightStickX y:rightStickY];
|
||||
|
||||
if([self useMotionHandler]
|
||||
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
|
||||
&& self->oscProfile.rollToLeftStick
|
||||
&& self->_gyroEnabledFlag
|
||||
) [self->motionHandler mixPhysicalLeftStickAndGyroInputWithX:leftStickX y:leftStickY];
|
||||
else [self updateLeftStick: voidController.playerIndex==0?self->_oscController:voidController x:leftStickX y:leftStickY];
|
||||
|
||||
leftTrigger = gamepad.leftTrigger.value * 0xFF;
|
||||
rightTrigger = gamepad.rightTrigger.value * 0xFF;
|
||||
[self updateTriggers:voidController left:leftTrigger right:rightTrigger];
|
||||
|
||||
[self updateFinished:voidController];
|
||||
|
||||
if (@available(iOS 14.0, *)) {
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]
|
||||
index:0];
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]
|
||||
index:1];
|
||||
}
|
||||
}
|
||||
if(controller == ControllerUtil.primaryGCController){
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self reinitiatePrimaryController];
|
||||
});
|
||||
}];
|
||||
|
||||
/*
|
||||
controller.extendedGamepad.valueChangedHandler =
|
||||
^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
||||
VoidController* voidController = [self->_voidControllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
||||
short leftStickX, leftStickY;
|
||||
short rightStickX, rightStickY;
|
||||
unsigned char leftTrigger, rightTrigger;
|
||||
if (self->_swapABXYButtons) {
|
||||
UPDATE_BUTTON_FLAG(voidController, B_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, A_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, Y_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, X_FLAG, gamepad.buttonY.pressed);
|
||||
}
|
||||
else {
|
||||
UPDATE_BUTTON_FLAG(voidController, A_FLAG, gamepad.buttonA.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, B_FLAG, gamepad.buttonB.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, X_FLAG, gamepad.buttonX.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, Y_FLAG, gamepad.buttonY.pressed);
|
||||
}
|
||||
|
||||
UPDATE_BUTTON_FLAG(voidController, UP_FLAG, gamepad.dpad.up.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, DOWN_FLAG, gamepad.dpad.down.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, LEFT_FLAG, gamepad.dpad.left.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, RIGHT_FLAG, gamepad.dpad.right.pressed);
|
||||
|
||||
UPDATE_BUTTON_FLAG(voidController, LB_FLAG, gamepad.leftShoulder.pressed);
|
||||
UPDATE_BUTTON_FLAG(voidController, RB_FLAG, gamepad.rightShoulder.pressed);
|
||||
|
||||
// Yay, iOS 12.1 now supports analog stick buttons
|
||||
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
||||
if (gamepad.leftThumbstickButton != nil) {
|
||||
UPDATE_BUTTON_FLAG(voidController, LS_CLK_FLAG, gamepad.leftThumbstickButton.pressed);
|
||||
}
|
||||
if (gamepad.rightThumbstickButton != nil) {
|
||||
UPDATE_BUTTON_FLAG(voidController, RS_CLK_FLAG, gamepad.rightThumbstickButton.pressed);
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
||||
// Options button is optional (only present on Xbox One S and PS4 gamepads)
|
||||
if (gamepad.buttonOptions != nil) {
|
||||
UPDATE_BUTTON_FLAG(voidController, BACK_FLAG, gamepad.buttonOptions.pressed);
|
||||
|
||||
// For older MFi gamepads, the menu button will already be handled by
|
||||
// the controllerPausedHandler.
|
||||
UPDATE_BUTTON_FLAG(voidController, PLAY_FLAG, gamepad.buttonMenu.pressed);
|
||||
}
|
||||
}
|
||||
|
||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||
// Home/Guide button is optional (only present on Xbox One S and PS4 gamepads)
|
||||
if (gamepad.buttonHome != nil) {
|
||||
UPDATE_BUTTON_FLAG(voidController, SPECIAL_FLAG, gamepad.buttonHome.pressed);
|
||||
}
|
||||
|
||||
// Xbox One/Series controllers
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleOne]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, PADDLE1_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, PADDLE2_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleThree]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, PADDLE3_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleFour]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, PADDLE4_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].pressed);
|
||||
}
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputButtonShare]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, MISC_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputButtonShare].pressed);
|
||||
}
|
||||
}
|
||||
|
||||
// DualShock/DualSense controllers
|
||||
if (gamepad.controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton]) {
|
||||
UPDATE_BUTTON_FLAG(voidController, TOUCHPAD_FLAG, gamepad.controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].pressed);
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]
|
||||
index:0];
|
||||
}
|
||||
if (gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]) {
|
||||
[self handleControllerTouchpad:voidController
|
||||
touch:gamepad.controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]
|
||||
index:1];
|
||||
}
|
||||
}
|
||||
|
||||
leftStickX = self->controllerToMouseStick != LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value * 0x7FFE : 0;
|
||||
leftStickY = self->controllerToMouseStick != LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value * 0x7FFE : 0;;
|
||||
|
||||
rightStickX = self->controllerToMouseStick != RightStickToMouse ? gamepad.rightThumbstick.xAxis.value * 0x7FFE : 0;
|
||||
rightStickY = self->controllerToMouseStick != RightStickToMouse ? gamepad.rightThumbstick.yAxis.value * 0x7FFE : 0;
|
||||
|
||||
self->stickToMouseInputX = self->controllerToMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.xAxis.value : gamepad.rightThumbstick.xAxis.value;
|
||||
self->stickToMouseInputY = self->controllerToMouseStick == LeftStickToMouse ? gamepad.leftThumbstick.yAxis.value : gamepad.rightThumbstick.yAxis.value;
|
||||
|
||||
leftTrigger = gamepad.leftTrigger.value * 0xFF;
|
||||
rightTrigger = gamepad.rightTrigger.value * 0xFF;
|
||||
|
||||
[self updateLeftStick:voidController x:leftStickX y:leftStickY];
|
||||
[self updateRightStick:voidController x:rightStickX y:rightStickY];
|
||||
[self updateTriggers:voidController left:leftTrigger right:rightTrigger];
|
||||
[self updateFinished:voidController];
|
||||
};
|
||||
*/
|
||||
}
|
||||
else [self listenToGCController:controller];
|
||||
}
|
||||
} else {
|
||||
Log(LOG_W, @"Tried to register controller callbacks on NULL controller");
|
||||
@@ -1749,29 +1620,9 @@ double rc_expo(double x, double expo) {
|
||||
_oscEnabled = _oscEnabled || (OnScreenControlsLevel)[tempSettings.onscreenControls integerValue] != OnScreenControlsLevelOff || streamConfig.gyroMode != GyroModeOff;
|
||||
_gyroSensitivity = tempSettings.gyroSensitivity.floatValue;
|
||||
|
||||
_mapControllerToMouse = tempSettings.mapControllerToMouse;
|
||||
_controllerMouseSwitch = tempSettings.controllerMouseSwitch.intValue;
|
||||
mouseSwitchDownTimestamp = 0;
|
||||
_mouseSwitchButtonPressed = false;
|
||||
_mouseSwitchButtonBeingClicked = false;
|
||||
_controllerMouseStick = tempSettings.controllerMouseStick.intValue;
|
||||
_controllerMouseLeftButton = tempSettings.controllerMouseLeftButton.intValue;
|
||||
_controllerMouseRightButton = tempSettings.controllerMouseRightButton.intValue;
|
||||
_stickToMouseExpo = tempSettings.controllerMouseExpo.floatValue;
|
||||
_stickToMouseVelocity = tempSettings.controllerMousePointerVelocity.floatValue*60/tempSettings.framerate.intValue;
|
||||
stickToMouseInputX = 0;
|
||||
stickToMouseInputY = 0;
|
||||
stickToWheelInputY = 0;
|
||||
[self stopDisplayLink];
|
||||
if(_mapControllerToMouse){
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallBack)];
|
||||
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
||||
[_displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(tempSettings.framerate.intValue,tempSettings.framerate.intValue, tempSettings.framerate.intValue)];
|
||||
}
|
||||
else {
|
||||
_displayLink.preferredFramesPerSecond = tempSettings.framerate.intValue;
|
||||
}
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_controllerNavigationEnabled = ControllerNavigator.enabled;
|
||||
[ControllerNavigator configureControllerMouseWith:tempSettings];
|
||||
}
|
||||
|
||||
_controllerGyroSwitchEnabled = oscProfile.controllerGyroSwitchMode != ControllerGyroSwitchDisabled;
|
||||
@@ -1787,9 +1638,11 @@ double rc_expo(double x, double expo) {
|
||||
_leftStickMinOffset = oscProfile.physicalLeftStickMinOffset;
|
||||
_rightStickMinOffset = oscProfile.physicalRightStickMinOffset;
|
||||
|
||||
if(oscProfile.controllerGyroSwitchMode == ControllerGyroSwitchDisabled && ![self useMotionHandler]) _gyroEnabledFlag = true;
|
||||
|
||||
if(![self useMotionHandler]) [self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false];
|
||||
// if(oscProfile.controllerGyroSwitchMode == ControllerGyroSwitchDisabled && ![self useMotionHandler]) _gyroEnabledFlag = false;
|
||||
_gyroEnabledFlag = false;
|
||||
|
||||
// if(![self useMotionHandler]) [self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false];
|
||||
[self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false];
|
||||
}
|
||||
|
||||
- (void)resetGyroInputForController:(VoidController* )voidController{
|
||||
@@ -1833,6 +1686,9 @@ double rc_expo(double x, double expo) {
|
||||
-(id)initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
VLSharedControllerSupport = self;
|
||||
}
|
||||
|
||||
NSLog(@"controller support init");
|
||||
|
||||
@@ -1985,7 +1841,7 @@ double rc_expo(double x, double expo) {
|
||||
[self initializeControllerHaptics:_oscController];
|
||||
_gyroMode = AlwaysDevice;
|
||||
|
||||
_controllerMouseEnabledFlag = false;
|
||||
// _controllerMouseEnabledFlag = false;
|
||||
|
||||
_gyroEnabledFlag = false;
|
||||
oscProfileMan = [OSCProfilesManager sharedManager:CGRectZero];
|
||||
@@ -2096,6 +1952,10 @@ double rc_expo(double x, double expo) {
|
||||
|
||||
-(void) cleanup
|
||||
{
|
||||
if (VLSharedControllerSupport == self) {
|
||||
VLSharedControllerSupport = nil;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_controllerDisconnectObserver];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:_mouseConnectObserver];
|
||||
@@ -2137,8 +1997,13 @@ double rc_expo(double x, double expo) {
|
||||
[self unregisterMouseCallbacks:mouse];
|
||||
}
|
||||
}
|
||||
|
||||
[self stopDisplayLink];
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
if (VLSharedControllerSupport == self) {
|
||||
VLSharedControllerSupport = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,22 @@ import Combine
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
@objc enum ControllerButton: Int {
|
||||
@objc enum ControllerElementType: Int {
|
||||
case button
|
||||
case stick
|
||||
case stickAxis
|
||||
case touchpad
|
||||
case undefined
|
||||
}
|
||||
|
||||
@objc enum ControllerElementPosition: Int {
|
||||
case left
|
||||
case right
|
||||
case middle
|
||||
case undefined
|
||||
}
|
||||
|
||||
@objc enum ControllerElement: Int32 {
|
||||
// Face buttons
|
||||
case a = 0x1000
|
||||
case b = 0x2000
|
||||
@@ -34,8 +49,8 @@ import Foundation
|
||||
case rightStickButton = 0x0080
|
||||
|
||||
// Menu/Back/Special
|
||||
case menu = 0x0010 // PLAY_FLAG
|
||||
case back = 0x0020 // BACK_FLAG
|
||||
case start = 0x0010 // PLAY_FLAG
|
||||
case select = 0x0020 // BACK_FLAG
|
||||
case special = 0x0400
|
||||
|
||||
// Extended buttons (Sunshine only)
|
||||
@@ -49,37 +64,209 @@ import Foundation
|
||||
case leftTrigger = 0x400000
|
||||
case rightTrigger = 0x800000
|
||||
|
||||
case leftStick = 0x800010
|
||||
case leftStickX = 0x80011
|
||||
case leftStickY = 0x800012
|
||||
case rightStick = 0x800020
|
||||
case rightStickX = 0x800021
|
||||
case rightStickY = 0x800022
|
||||
|
||||
case null = 0xFFFFFF
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .a: return "A"
|
||||
case .b: return "B"
|
||||
case .x: return "X"
|
||||
case .y: return "Y"
|
||||
|
||||
case .dpadUp: return "DPadU".localized
|
||||
case .dpadDown: return "DPadD".localized
|
||||
case .dpadLeft: return "DPadL".localized
|
||||
case .dpadRight: return "DPadR".localized
|
||||
|
||||
case .leftShoulder: return "LB"
|
||||
case .rightShoulder: return "RB"
|
||||
|
||||
case .leftStickButton: return "LS"
|
||||
case .rightStickButton: return "RS"
|
||||
|
||||
case .start: return "Start"
|
||||
case .select: return "Select"
|
||||
case .special: return "Home"
|
||||
|
||||
case .paddle1: return "Paddle1".localized
|
||||
case .paddle2: return "Paddle2".localized
|
||||
case .paddle3: return "Paddle3".localized
|
||||
case .paddle4: return "Paddle4".localized
|
||||
case .touchpadButton: return "TouchBtn".localized
|
||||
case .misc: return "Misc"
|
||||
|
||||
case .leftTrigger: return "LT"
|
||||
case .rightTrigger: return "RT"
|
||||
|
||||
case .leftStick: return "LStick".localized
|
||||
case .leftStickX: return "LStickX".localized
|
||||
case .leftStickY: return "LStickY".localized
|
||||
case .rightStick: return "RStick".localized
|
||||
case .rightStickX: return "RStickX".localized
|
||||
case .rightStickY: return "RStickY".localized
|
||||
|
||||
case .null: return "Null".localized
|
||||
}
|
||||
}
|
||||
|
||||
var symbol: String {
|
||||
switch self {
|
||||
case .dpadUp:
|
||||
if #available(iOS 15.0, *) {return "dpad.up.filled"}
|
||||
else {return ""}
|
||||
case .dpadDown:
|
||||
if #available(iOS 15.0, *) {return "dpad.down.filled"}
|
||||
else {return ""}
|
||||
case .dpadLeft:
|
||||
if #available(iOS 15.0, *) {return "dpad.left.filled"}
|
||||
else {return ""}
|
||||
case .dpadRight:
|
||||
if #available(iOS 15.0, *) {return "dpad.right.filled"}
|
||||
else {return ""}
|
||||
case .a:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.down.filled"}
|
||||
else {return ""}
|
||||
case .b:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.right.filled"}
|
||||
else {return ""}
|
||||
case .x:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.left.filled"}
|
||||
else {return ""}
|
||||
case .y:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.up.filled"}
|
||||
else {return ""}
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
var type: ControllerElementType {
|
||||
switch self {
|
||||
case .a: return .button
|
||||
case .b: return .button
|
||||
case .x: return .button
|
||||
case .y: return .button
|
||||
|
||||
case .dpadUp: return .button
|
||||
case .dpadDown: return .button
|
||||
case .dpadLeft: return .button
|
||||
case .dpadRight: return .button
|
||||
|
||||
case .leftShoulder: return .button
|
||||
case .rightShoulder: return .button
|
||||
|
||||
case .leftStickButton: return .button
|
||||
case .rightStickButton: return .button
|
||||
|
||||
case .start: return .button
|
||||
case .select: return .button
|
||||
case .special: return .button
|
||||
|
||||
case .paddle1: return .button
|
||||
case .paddle2: return .button
|
||||
case .paddle3: return .button
|
||||
case .paddle4: return .button
|
||||
case .touchpadButton: return .button
|
||||
case .misc: return .button
|
||||
|
||||
case .leftTrigger: return .button
|
||||
case .rightTrigger: return .button
|
||||
|
||||
case .leftStick: return .stick
|
||||
case .leftStickX: return .stickAxis
|
||||
case .leftStickY: return .stickAxis
|
||||
case .rightStick: return .stick
|
||||
case .rightStickX: return .stickAxis
|
||||
case .rightStickY: return .stickAxis
|
||||
|
||||
case .null: return .undefined
|
||||
}
|
||||
}
|
||||
|
||||
var position: ControllerElementPosition {
|
||||
switch self {
|
||||
case .a: return .right
|
||||
case .b: return .right
|
||||
case .x: return .right
|
||||
case .y: return .right
|
||||
|
||||
case .dpadUp: return .left
|
||||
case .dpadDown: return .left
|
||||
case .dpadLeft: return .left
|
||||
case .dpadRight: return .left
|
||||
|
||||
case .leftShoulder: return .left
|
||||
case .rightShoulder: return .right
|
||||
|
||||
case .leftStickButton: return .left
|
||||
case .rightStickButton: return .right
|
||||
|
||||
case .start: return .right
|
||||
case .select: return .left
|
||||
case .special: return .undefined
|
||||
|
||||
case .paddle1: return .right
|
||||
case .paddle2: return .right
|
||||
case .paddle3: return .left
|
||||
case .paddle4: return .left
|
||||
case .touchpadButton: return .middle
|
||||
case .misc: return .undefined
|
||||
|
||||
case .leftTrigger: return .left
|
||||
case .rightTrigger: return .right
|
||||
|
||||
case .leftStick: return .left
|
||||
case .leftStickX: return .left
|
||||
case .leftStickY: return .left
|
||||
case .rightStick: return .right
|
||||
case .rightStickX: return .right
|
||||
case .rightStickY: return .right
|
||||
|
||||
case .null: return .undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol ControllerUtilDelegate: AnyObject {
|
||||
func isStreaming() -> Bool
|
||||
func isInAppView() -> Bool
|
||||
}
|
||||
|
||||
@objc class ControllerUtil: NSObject {
|
||||
@objc static weak var delegate: ControllerUtilDelegate?
|
||||
|
||||
static private let stickMaxOffset:CGFloat = 0x7FFE
|
||||
@objc static var navigationActionTriggered:Bool = false
|
||||
@objc static private(set) var navigationActionTriggeredPrivate:Bool = false
|
||||
|
||||
|
||||
@objc static func listen(
|
||||
controller: GCController,
|
||||
swapABXY: Bool,
|
||||
handler: @escaping (_ buttonDict: NSDictionary,
|
||||
handler: @escaping (_ elementDict: NSDictionary,
|
||||
_ gamepad: GCExtendedGamepad,
|
||||
_ element: GCControllerElement) -> Void
|
||||
) {
|
||||
guard let gamepad = controller.extendedGamepad else { return }
|
||||
|
||||
// 内部生成 map
|
||||
var tempMap: [NSNumber: GCControllerButtonInput] = [:]
|
||||
var tempMap: [NSNumber: GCControllerElement] = [:]
|
||||
let swiftMap = buildMapping(for: controller, swapABXY: swapABXY)
|
||||
for (button, input) in swiftMap {
|
||||
tempMap[NSNumber(value: button.rawValue)] = input
|
||||
}
|
||||
let buttonDict = tempMap as NSDictionary
|
||||
let elementDict = tempMap as NSDictionary
|
||||
|
||||
// 单一 gamepad.valueChangedHandler
|
||||
gamepad.valueChangedHandler = { gamepad, element in
|
||||
handler(buttonDict, gamepad, element)
|
||||
handler(elementDict, gamepad, element)
|
||||
if #available(iOS 13.0, *) {
|
||||
// print("controller.playerIndex \(controller.playerIndex)")
|
||||
if controller.playerIndex == .index1 {
|
||||
GamepadOverlayStateCenter.shared.publish(snapshot: GamepadOverlaySnapshot(gamepad: gamepad))
|
||||
}
|
||||
@@ -87,12 +274,12 @@ import Foundation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - 构建按钮映射
|
||||
private static func buildMapping(for controller: GCController, swapABXY:Bool)
|
||||
-> [ControllerButton : GCControllerButtonInput]
|
||||
static func buildMapping(for controller: GCController, swapABXY:Bool)
|
||||
-> [ControllerElement : GCControllerElement]
|
||||
{
|
||||
var result: [ControllerButton : GCControllerButtonInput] = [:]
|
||||
var result: [ControllerElement : GCControllerElement] = [:]
|
||||
ControllerUtil.swapABXY = swapABXY
|
||||
|
||||
if let pad = controller.extendedGamepad {
|
||||
// Face buttons
|
||||
@@ -114,7 +301,7 @@ import Foundation
|
||||
result[.rightShoulder] = pad.rightShoulder
|
||||
result[.leftTrigger] = pad.leftTrigger
|
||||
result[.rightTrigger] = pad.rightTrigger
|
||||
|
||||
|
||||
// Stick buttons
|
||||
if #available(iOS 12.1, *) {
|
||||
result[.leftStickButton] = pad.leftThumbstickButton
|
||||
@@ -127,10 +314,19 @@ import Foundation
|
||||
result[.dpadLeft] = pad.dpad.left
|
||||
result[.dpadRight] = pad.dpad.right
|
||||
|
||||
// Sticks
|
||||
result[.leftStick] = pad.leftThumbstick
|
||||
result[.leftStickX] = pad.leftThumbstick.xAxis
|
||||
result[.leftStickY] = pad.leftThumbstick.yAxis
|
||||
result[.rightStick] = pad.rightThumbstick
|
||||
result[.rightStickX] = pad.rightThumbstick.xAxis
|
||||
result[.rightStickY] = pad.rightThumbstick.yAxis
|
||||
|
||||
|
||||
// Menu / Options / Share
|
||||
if #available(iOS 13.0, *) {
|
||||
result[.menu] = pad.buttonMenu
|
||||
if let options = pad.buttonOptions { result[.back] = options }
|
||||
result[.start] = pad.buttonMenu
|
||||
if let options = pad.buttonOptions { result[.select] = options }
|
||||
}
|
||||
|
||||
if #available(iOS 14.0, tvOS 14.0, *) {
|
||||
@@ -163,49 +359,185 @@ import Foundation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if controller === primaryGCController {ControllerUtil.primaryControllerElementMap = result}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@objc static var activeStreamingGCControllers:NSMutableSet = NSMutableSet()
|
||||
|
||||
@objc static func string(for button: ControllerButton) -> String {
|
||||
switch button {
|
||||
case .a: return "A"
|
||||
case .b: return "B"
|
||||
case .x: return "X"
|
||||
case .y: return "Y"
|
||||
|
||||
case .dpadUp: return LocalizationHelper.localizedString(forKey: "Up")
|
||||
case .dpadDown: return LocalizationHelper.localizedString(forKey: "Down")
|
||||
case .dpadLeft: return LocalizationHelper.localizedString(forKey: "Left")
|
||||
case .dpadRight: return LocalizationHelper.localizedString(forKey: "Right")
|
||||
|
||||
case .leftShoulder: return "LB"
|
||||
case .rightShoulder: return "RB"
|
||||
|
||||
case .leftStickButton: return "LS"
|
||||
case .rightStickButton: return "RS"
|
||||
|
||||
case .menu: return "Menu"
|
||||
case .back: return "Back"
|
||||
case .special: return "Home"
|
||||
|
||||
case .paddle1: return LocalizationHelper.localizedString(forKey: "Paddle1")
|
||||
case .paddle2: return LocalizationHelper.localizedString(forKey: "Paddle2")
|
||||
case .paddle3: return LocalizationHelper.localizedString(forKey: "Paddle3")
|
||||
case .paddle4: return LocalizationHelper.localizedString(forKey: "Paddle4")
|
||||
case .touchpadButton: return LocalizationHelper.localizedString(forKey: "Touch button")
|
||||
case .misc: return "Misc"
|
||||
|
||||
case .leftTrigger: return "LT"
|
||||
case .rightTrigger: return "RT"
|
||||
|
||||
case .null: return LocalizationHelper.localizedString(forKey: "Null")
|
||||
|
||||
default: return "UNKNOWN"
|
||||
@objc static var swapABXY:Bool = false
|
||||
|
||||
@objc static func string(for element: ControllerElement) -> String {
|
||||
return element.displayName
|
||||
}
|
||||
|
||||
@objc static func position(for element: ControllerElement) -> ControllerElementPosition {
|
||||
return element.position
|
||||
}
|
||||
|
||||
|
||||
// MARK: - primary controller section
|
||||
|
||||
private static var connectObserver: NSObjectProtocol?
|
||||
private static var disconnectObserver: NSObjectProtocol?
|
||||
|
||||
@objc static weak var primaryGCController: GCController?
|
||||
static var primaryControllerElementMap: [ControllerElement : GCControllerElement]?
|
||||
private static var primaryControllerAxisStates: [ControllerElement : ComparisonResult] = [:]
|
||||
|
||||
@objc static func installControllerObserversIfNeeded() {
|
||||
guard connectObserver == nil, disconnectObserver == nil else { return }
|
||||
|
||||
if GCController.controllers().first(where: { $0.extendedGamepad != nil }) != nil {
|
||||
preparePrimaryController()
|
||||
}
|
||||
|
||||
connectObserver = NotificationCenter.default.addObserver(
|
||||
forName: .GCControllerDidConnect,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { notification in
|
||||
guard (notification.object as? GCController)?.extendedGamepad != nil else { return }
|
||||
preparePrimaryController()
|
||||
}
|
||||
|
||||
disconnectObserver = NotificationCenter.default.addObserver(
|
||||
forName: .GCControllerDidDisconnect,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { notification in
|
||||
preparePrimaryController()
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func removeControllerObservers() {
|
||||
if let connectObserver {
|
||||
NotificationCenter.default.removeObserver(connectObserver)
|
||||
self.connectObserver = nil
|
||||
}
|
||||
|
||||
if let disconnectObserver {
|
||||
NotificationCenter.default.removeObserver(disconnectObserver)
|
||||
self.disconnectObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func disableSysGestures(_ controller: GCController?){
|
||||
if let controller = controller {
|
||||
if #available(iOS 14.0, *) {
|
||||
for element in controller.physicalInputProfile.allElements {
|
||||
element.preferredSystemGestureState = .disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func preparePrimaryController() {
|
||||
guard let controller = GCController.controllers().first(where: { $0.extendedGamepad != nil }) else {
|
||||
stopListeningPrimaryController()
|
||||
ControllerUtil.primaryGCController = nil
|
||||
if #available(iOS 13.0, *) {
|
||||
ControllerNavigator.stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard GCController.controllers().count == 1 else { return }
|
||||
|
||||
primaryGCController = controller
|
||||
_ = buildMapping(for: controller, swapABXY: false)
|
||||
disableSysGestures(controller)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
if ControllerNavigator.enabled {
|
||||
ControllerNavigator.start()
|
||||
GamepadNavigationIllustrationHud.updateNavigationElements(ControllerNavigator.uiNavigationDelegate?.getNavigationElements() ?? [])
|
||||
ControllerNavigator.restoreUINavigationHighlight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func stopListeningPrimaryController(stopListenToRadialMenuButton: Bool = false) {
|
||||
guard let primaryControllerButtonMap = primaryControllerElementMap else { return }
|
||||
for gcElement in primaryControllerButtonMap.values {
|
||||
if let gcButton = gcElement as? GCControllerButtonInput {
|
||||
if #available(iOS 13.0, *) {
|
||||
if !stopListenToRadialMenuButton, gcButton === primaryControllerButtonMap[ControllerNavigator.radialMenuButton] as? GCControllerButtonInput {continue}
|
||||
}
|
||||
gcButton.pressedChangedHandler = nil
|
||||
}
|
||||
if let gcStick = gcElement as? GCControllerDirectionPad {
|
||||
gcStick.valueChangedHandler = nil
|
||||
}
|
||||
if let gcAxis = gcElement as? GCControllerAxisInput {
|
||||
gcAxis.valueChangedHandler = nil
|
||||
}
|
||||
}
|
||||
primaryControllerAxisStates.removeAll()
|
||||
primaryGCController?.extendedGamepad?.valueChangedHandler = nil
|
||||
}
|
||||
|
||||
@objc static func listenPrimaryControllerButton(_ button: ControllerElement, ignoreSwapABXY: Bool = true, handler: @escaping (_ pressed: Bool) -> Void){
|
||||
if var elementMap = ControllerUtil.primaryControllerElementMap {
|
||||
if ignoreSwapABXY {
|
||||
elementMap[.a] = ControllerUtil.primaryGCController?.extendedGamepad?.buttonA
|
||||
elementMap[.b] = ControllerUtil.primaryGCController?.extendedGamepad?.buttonB
|
||||
elementMap[.x] = ControllerUtil.primaryGCController?.extendedGamepad?.buttonX
|
||||
elementMap[.y] = ControllerUtil.primaryGCController?.extendedGamepad?.buttonY
|
||||
}
|
||||
if let gcButton = elementMap[button] as? GCControllerButtonInput {
|
||||
gcButton.pressedChangedHandler = { _, _, pressed in
|
||||
handler(pressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func listenPrimaryControllerStick(_ stick: ControllerElement, handler: @escaping (_ offsetVector: CGVector) -> Void){
|
||||
if let elementMap = ControllerUtil.primaryControllerElementMap {
|
||||
if let gcStick = elementMap[stick] as? GCControllerDirectionPad {
|
||||
gcStick.valueChangedHandler = { _, xValue, yValue in
|
||||
handler(CGVector(dx: CGFloat(xValue), dy: CGFloat(yValue)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func listenPrimaryControllerStickAxis(_ stickAxis: ControllerElement, threshold: CGFloat, handler: @escaping (_ state: ComparisonResult) -> Void) {
|
||||
guard threshold > 0 else { return }
|
||||
guard let elementMap = ControllerUtil.primaryControllerElementMap,
|
||||
let gcAxis = elementMap[stickAxis] as? GCControllerAxisInput else { return }
|
||||
|
||||
primaryControllerAxisStates[stickAxis] = stickAxisState(for: CGFloat(gcAxis.value), threshold: threshold)
|
||||
gcAxis.valueChangedHandler = { _, value in
|
||||
let newState = stickAxisState(for: CGFloat(value), threshold: threshold)
|
||||
let previousState = primaryControllerAxisStates[stickAxis] ?? .orderedSame
|
||||
guard newState != previousState else { return }
|
||||
|
||||
primaryControllerAxisStates[stickAxis] = newState
|
||||
handler(newState)
|
||||
}
|
||||
}
|
||||
|
||||
private static func stickAxisState(for value: CGFloat, threshold: CGFloat) -> ComparisonResult {
|
||||
if value > threshold {
|
||||
return .orderedDescending
|
||||
}
|
||||
|
||||
if value < -threshold {
|
||||
return .orderedAscending
|
||||
}
|
||||
|
||||
return .orderedSame
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: - input processing
|
||||
|
||||
@objc static func compensated(offsetVector: CGVector, minOffset: CGFloat, circulate:Bool=false) -> CGVector{
|
||||
let vectorHypot = hypot(offsetVector.dx, offsetVector.dy)
|
||||
guard vectorHypot > 0 else {return CGVector(dx: 0, dy: 0)}
|
||||
@@ -233,9 +565,11 @@ import Foundation
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - realtime overlay
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct GamepadOverlaySnapshot: Equatable {
|
||||
var pressedButtons: Set<ControllerButton> = []
|
||||
var pressedButtons: Set<ControllerElement> = []
|
||||
var dpadHighlight: Int = 0
|
||||
var leftStick: CGPoint = .zero
|
||||
var rightStick: CGPoint = .zero
|
||||
@@ -247,7 +581,7 @@ struct GamepadOverlaySnapshot: Equatable {
|
||||
init() {}
|
||||
|
||||
init(gamepad: GCExtendedGamepad) {
|
||||
var pressedButtons = Set<ControllerButton>()
|
||||
var pressedButtons = Set<ControllerElement>()
|
||||
|
||||
if gamepad.buttonA.isPressed { pressedButtons.insert(.a) }
|
||||
if gamepad.buttonB.isPressed { pressedButtons.insert(.b) }
|
||||
@@ -255,8 +589,8 @@ struct GamepadOverlaySnapshot: Equatable {
|
||||
if gamepad.buttonY.isPressed { pressedButtons.insert(.y) }
|
||||
if gamepad.leftShoulder.isPressed { pressedButtons.insert(.leftShoulder) }
|
||||
if gamepad.rightShoulder.isPressed { pressedButtons.insert(.rightShoulder) }
|
||||
if gamepad.buttonMenu.isPressed { pressedButtons.insert(.menu) }
|
||||
if gamepad.buttonOptions?.isPressed == true { pressedButtons.insert(.back) }
|
||||
if gamepad.buttonMenu.isPressed { pressedButtons.insert(.start) }
|
||||
if gamepad.buttonOptions?.isPressed == true { pressedButtons.insert(.select) }
|
||||
if #available(iOS 14.0, *), gamepad.buttonHome?.isPressed == true { pressedButtons.insert(.special) }
|
||||
if gamepad.leftThumbstickButton?.isPressed == true { pressedButtons.insert(.leftStickButton) }
|
||||
if gamepad.rightThumbstickButton?.isPressed == true { pressedButtons.insert(.rightStickButton) }
|
||||
|
||||
@@ -121,8 +121,8 @@
|
||||
self.physicalRightStickMinOffset = [decoder containsValueForKey:@"physicalRightStickMinOffset"] ? [decoder decodeDoubleForKey:@"physicalRightStickMinOffset"] : 0;
|
||||
self.controllerGyroSwitchMode = [decoder containsValueForKey:@"controllerGyroSwitchMode"] ? [decoder decodeIntForKey:@"controllerGyroSwitchMode"] : ControllerGyroSwitchDisabled;
|
||||
self.reverseGyroHoldButton = [decoder containsValueForKey:@"reverseGyroHoldButton"] ? [decoder decodeBoolForKey:@"reverseGyroHoldButton"] : false;
|
||||
self.controllerGyroSwitchHold = [decoder containsValueForKey:@"controllerGyroSwitchHold"] ? [decoder decodeIntForKey:@"controllerGyroSwitchHold"] : ControllerButtonNull;
|
||||
self.controllerGyroSwitchToggle = [decoder containsValueForKey:@"controllerGyroSwitchToggle"] ? [decoder decodeIntForKey:@"controllerGyroSwitchToggle"] : ControllerButtonNull;
|
||||
self.controllerGyroSwitchHold = [decoder containsValueForKey:@"controllerGyroSwitchHold"] ? [decoder decodeIntForKey:@"controllerGyroSwitchHold"] : ControllerElementNull;
|
||||
self.controllerGyroSwitchToggle = [decoder containsValueForKey:@"controllerGyroSwitchToggle"] ? [decoder decodeIntForKey:@"controllerGyroSwitchToggle"] : ControllerElementNull;
|
||||
|
||||
self.touchMode = [decoder containsValueForKey:@"touchMode"] ? [decoder decodeIntForKey:@"touchMode"] : NativeTouch;
|
||||
if(self.touchMode == NativeTouchOnly) self.touchMode = NativeTouch;
|
||||
|
||||
@@ -329,7 +329,7 @@ class OSCProfilesManager: NSObject {
|
||||
|
||||
return profiles[Int(selectedIndex)] as! OSCProfile
|
||||
|
||||
return (profiles.firstObject as? OSCProfile) ?? OSCProfile(name: "", buttonStates: NSMutableArray(), isSelected: false)
|
||||
// return (profiles.firstObject as? OSCProfile) ?? OSCProfile(name: "", buttonStates: NSMutableArray(), isSelected: false)
|
||||
}
|
||||
|
||||
func getIndexOfSelectedProfile() -> Int {
|
||||
@@ -401,8 +401,8 @@ class OSCProfilesManager: NSObject {
|
||||
normalizedPosition.x = position.x / Self.layoutViewBounds.size.width
|
||||
normalizedPosition.y = position.y / Self.layoutViewBounds.size.height
|
||||
}
|
||||
NSLog("layoutToolView bounds: %f, %f", Self.layoutViewBounds.size.width, Self.layoutViewBounds.size.height)
|
||||
NSLog("position: %f, %f, denormalized position: %f, %f", normalizedPosition.x, normalizedPosition.y, originalPosition.x, originalPosition.y)
|
||||
// NSLog("layoutToolView bounds: %f, %f", Self.layoutViewBounds.size.width, Self.layoutViewBounds.size.height)
|
||||
// NSLog("position: %f, %f, denormalized position: %f, %f", normalizedPosition.x, normalizedPosition.y, originalPosition.x, originalPosition.y)
|
||||
return normalizedPosition
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,6 @@ import CoreMotion
|
||||
self.widgetYawFactor = yawFactor
|
||||
self.widgetPitchFactor = pitchFactor
|
||||
self.widgetRollFactor = rollFactor
|
||||
print("self.widgetRollFactor \(self.widgetRollFactor)")
|
||||
if self.motionStarter == nil {
|
||||
self.motionStarter = sender
|
||||
if sender.motionControlButtonString != "GYROPAUSE" {self.startMotionUpdate()}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "NativeTouchPointer.h"
|
||||
#import "OnScreenControls.h"
|
||||
#import "StreamView.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
@@ -59,13 +59,11 @@ import ObjectiveC.runtime
|
||||
func openWidgetProfileTable(pickProfile: Bool)
|
||||
func bringUpSoftKeyboard()
|
||||
func alterAbsTouchDragWith(mouseButton:Int32)
|
||||
func switchPencilHover()
|
||||
func enablePencilHover()
|
||||
func disablePencilHover()
|
||||
func setAllowSingleTouchEnabled(_ enabled:Bool)
|
||||
func replaceBrush(shortcut:String)
|
||||
func replaceEraser(shortcut:String)
|
||||
func setPencilTilt(disabled:Bool)
|
||||
func presentPressureCurveVC()
|
||||
func toggleTouch(disabled:Bool)
|
||||
func toggleGamepadOverlay(overlayEnabled:Bool)
|
||||
@@ -1550,7 +1548,7 @@ import ObjectiveC.runtime
|
||||
private func sendLongMouseLeftButtonClickEvent() {
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
// Logging the press event
|
||||
NSLog("Sending left mouse button press")
|
||||
// NSLog("Sending left mouse button press")
|
||||
LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), BUTTON_LEFT)
|
||||
|
||||
// Wait 200 ms to simulate a real button press
|
||||
@@ -1560,7 +1558,9 @@ import ObjectiveC.runtime
|
||||
LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_LEFT)
|
||||
// NSLog("double click: first long click release")
|
||||
}
|
||||
else{NSLog("Left mouse button release cancelled, keep pressing down, turning into dragging...")}
|
||||
else{
|
||||
// NSLog("Left mouse button release cancelled, keep pressing down, turning into dragging...")
|
||||
}
|
||||
// Don't release the button if we're still dragging, this will prevent the dragging from being interrupted.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "NativeTouchPointer.h"
|
||||
#import "OnScreenControls.h"
|
||||
#import "StreamView.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
@@ -337,4 +338,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// RadialMenuOverlayView.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by Codex on 2026/7/1.
|
||||
// Created by True砖家 on 2026/7/1.
|
||||
// Copyright © 2026 True砖家 on Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
@@ -11,60 +11,91 @@ import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
final class RadialMenuOverlayView: UIView {
|
||||
func getNavigationElements() -> [ControllerNavigationElement] {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .right ? .leftStick : .rightStick, action: "focusNavigation"))
|
||||
return elements
|
||||
}
|
||||
|
||||
private enum Metrics {
|
||||
static let diameter: CGFloat = 260
|
||||
static let bottomInset: CGFloat = 92
|
||||
static let diameter: CGFloat = PublicUtils.isIPhone ? 200 : 260
|
||||
static let releaseDistanceEpsilon: CGFloat = 0.015
|
||||
}
|
||||
|
||||
private let selectionState = RadialMenuSelectionState()
|
||||
private var hostingController: UIHostingController<RadialMenuView>?
|
||||
private var themeObserver: NSObjectProtocol?
|
||||
private var previousJoystickDistance: CGFloat?
|
||||
private var isJoystickReturningToCenter = false
|
||||
private var releaseLatchedSelectedIndex: Int?
|
||||
|
||||
static var menuSectors: [RadialMenuSector] = []
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
setupView()
|
||||
observeThemeChanges()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupView()
|
||||
observeThemeChanges()
|
||||
}
|
||||
|
||||
deinit {
|
||||
removeThemeObserver()
|
||||
}
|
||||
|
||||
static func presentInKeyWindow() -> RadialMenuOverlayView? {
|
||||
guard let window = keyWindow() else { return nil }
|
||||
|
||||
let overlayView = RadialMenuOverlayView()
|
||||
overlayView.translatesAutoresizingMaskIntoConstraints = false
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
window.addSubview(overlayView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
overlayView.widthAnchor.constraint(equalToConstant: Metrics.diameter),
|
||||
overlayView.heightAnchor.constraint(equalToConstant: Metrics.diameter),
|
||||
overlayView.centerXAnchor.constraint(equalTo: window.centerXAnchor),
|
||||
overlayView.bottomAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: -Metrics.bottomInset)
|
||||
overlayView.centerYAnchor.constraint(equalTo: window.centerYAnchor)
|
||||
])
|
||||
|
||||
CATransaction.commit()
|
||||
return overlayView
|
||||
}
|
||||
|
||||
func updateSelection(xOffset: Float, yOffset: Float) -> RadialMenuItem? {
|
||||
func updateSelection(xOffset: CGFloat, yOffset: CGFloat) -> RadialMenuItem? {
|
||||
guard Thread.isMainThread else {
|
||||
return DispatchQueue.main.sync {
|
||||
self.updateSelection(xOffset: xOffset, yOffset: yOffset)
|
||||
}
|
||||
}
|
||||
|
||||
let previousSelectedIndex = selectionState.selectedIndex
|
||||
latchSelectionIfNeeded(
|
||||
distance: Self.normalizedDistance(xOffset: xOffset, yOffset: yOffset),
|
||||
selectedIndex: previousSelectedIndex
|
||||
)
|
||||
|
||||
selectionState.updateSelection(xOffset: xOffset, yOffset: yOffset)
|
||||
|
||||
guard previousSelectedIndex != nil, selectionState.selectedIndex == nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let previousSelectedIndex,
|
||||
Self.menuSectors.indices.contains(previousSelectedIndex) else {
|
||||
let actionSelectedIndex = releaseLatchedSelectedIndex ?? previousSelectedIndex
|
||||
resetReleaseLatch()
|
||||
|
||||
guard let actionSelectedIndex,
|
||||
Self.menuSectors.indices.contains(actionSelectedIndex) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Self.menuSectors[previousSelectedIndex].item
|
||||
return Self.menuSectors[actionSelectedIndex].item
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
removeThemeObserver()
|
||||
hostingController?.willMove(toParent: nil)
|
||||
hostingController?.view.removeFromSuperview()
|
||||
hostingController?.removeFromParent()
|
||||
@@ -77,11 +108,7 @@ final class RadialMenuOverlayView: UIView {
|
||||
isUserInteractionEnabled = false
|
||||
clipsToBounds = false
|
||||
|
||||
let radialMenuView = RadialMenuView(
|
||||
sectors: Self.menuSectors,
|
||||
isTouchSelectionEnabled: false,
|
||||
selectionState: selectionState
|
||||
)
|
||||
let radialMenuView = makeRadialMenuView()
|
||||
|
||||
let hostingController = UIHostingController(rootView: radialMenuView)
|
||||
hostingController.view.backgroundColor = .clear
|
||||
@@ -97,9 +124,80 @@ final class RadialMenuOverlayView: UIView {
|
||||
hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
])
|
||||
|
||||
GamepadNavigationIllustrationHud.updateNavigationElements(self.getNavigationElements())
|
||||
}
|
||||
|
||||
private func observeThemeChanges() {
|
||||
themeObserver = NotificationCenter.default.addObserver(
|
||||
forName: Notification.Name(ThemeManager.ThemeDidChangeNotification),
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.refreshTheme()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeThemeObserver() {
|
||||
if let themeObserver {
|
||||
NotificationCenter.default.removeObserver(themeObserver)
|
||||
self.themeObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshTheme() {
|
||||
hostingController?.rootView = makeRadialMenuView()
|
||||
}
|
||||
|
||||
private func latchSelectionIfNeeded(distance: CGFloat, selectedIndex: Int?) {
|
||||
defer { previousJoystickDistance = distance }
|
||||
|
||||
guard let previousJoystickDistance else {
|
||||
return
|
||||
}
|
||||
|
||||
if distance + Metrics.releaseDistanceEpsilon < previousJoystickDistance {
|
||||
if !isJoystickReturningToCenter {
|
||||
releaseLatchedSelectedIndex = selectedIndex
|
||||
isJoystickReturningToCenter = true
|
||||
}
|
||||
} else if distance > previousJoystickDistance + Metrics.releaseDistanceEpsilon {
|
||||
resetReleaseLatch(keepingPreviousDistance: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func resetReleaseLatch(keepingPreviousDistance: Bool = false) {
|
||||
if !keepingPreviousDistance {
|
||||
previousJoystickDistance = nil
|
||||
}
|
||||
isJoystickReturningToCenter = false
|
||||
releaseLatchedSelectedIndex = nil
|
||||
}
|
||||
|
||||
private func makeRadialMenuView() -> RadialMenuView {
|
||||
RadialMenuView(
|
||||
sectors: Self.menuSectors,
|
||||
isTouchSelectionEnabled: false,
|
||||
style: RadialMenuStyle.themed(
|
||||
for: ThemeManager.userInterfaceStyle(),
|
||||
accentColor: ThemeManager.appPrimaryColor
|
||||
),
|
||||
selectionState: selectionState
|
||||
)
|
||||
}
|
||||
|
||||
private static func keyWindow() -> UIWindow? {
|
||||
UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ?? UIApplication.shared.windows.first
|
||||
}
|
||||
|
||||
private static func normalizedDistance(xOffset: CGFloat, yOffset: CGFloat) -> CGFloat {
|
||||
let x = max(min(xOffset, 1), -1)
|
||||
let y = max(min(yOffset, 1), -1)
|
||||
return hypot(x, y)
|
||||
}
|
||||
|
||||
func navigateByController(forward: Bool) {}
|
||||
func navigateByController(downward: Bool) {}
|
||||
func uiWidgetActionForControllerNavigator(forward: Bool, from navigation: ControllerNavigationElement) {}
|
||||
func uiButtonActionForControllerNavigator(pressed: Bool, from navigation: ControllerNavigationElement) {}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "RelativeTouchHandler.h"
|
||||
#import "DataManager.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
@@ -13,12 +13,15 @@ import QuartzCore
|
||||
final class SafeTimer: NSObject {
|
||||
private var timer: DispatchSourceTimer?
|
||||
private let timerQueue: DispatchQueue // 私有串行队列
|
||||
private let timerQueueKey = DispatchSpecificKey<UInt8>()
|
||||
private let userHandler: () -> Void
|
||||
private let interval: TimeInterval
|
||||
private let delay: TimeInterval
|
||||
|
||||
private var shouldRunHandler: Bool = false // 控制逻辑上的暂停/恢复
|
||||
private var isCleaned: Bool = false // 是否已经 clean
|
||||
@objc private(set) var remainingMinimumRunCount: Int = 0
|
||||
private var shouldCleanAfterMinimumRuns: Bool = false
|
||||
|
||||
@objc init(interval: TimeInterval = 1.0,
|
||||
delay: TimeInterval = 0,
|
||||
@@ -29,6 +32,7 @@ final class SafeTimer: NSObject {
|
||||
self.delay = delay
|
||||
self.userHandler = handler
|
||||
self.timerQueue = DispatchQueue(label: queueLabel)
|
||||
self.timerQueue.setSpecific(key: timerQueueKey, value: 1)
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -39,6 +43,12 @@ final class SafeTimer: NSObject {
|
||||
guard let self = self, !self.isCleaned else { return }
|
||||
if !self.shouldRunHandler { return }
|
||||
self.userHandler()
|
||||
if self.remainingMinimumRunCount > 0 {
|
||||
self.remainingMinimumRunCount -= 1
|
||||
}
|
||||
if self.shouldCleanAfterMinimumRuns && self.remainingMinimumRunCount == 0 {
|
||||
self.cleanOnTimerQueue(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
t.resume() // timer 一开始就 resume,避免 suspend/resume 的计数问题
|
||||
@@ -46,7 +56,7 @@ final class SafeTimer: NSObject {
|
||||
}
|
||||
|
||||
deinit {
|
||||
clean()
|
||||
cleanForDeinit()
|
||||
}
|
||||
|
||||
/// 开始逻辑上的计时
|
||||
@@ -67,30 +77,71 @@ final class SafeTimer: NSObject {
|
||||
|
||||
/// 重置下一次触发时间,并开始执行 handler
|
||||
@objc func restart() {
|
||||
restart(minimumRunCount: 1)
|
||||
}
|
||||
|
||||
/// 重置下一次触发时间,并保证 clean 前至少执行 minimumRunCount 次 handler
|
||||
@objc(restartWithMinimumRunCount:)
|
||||
func restart(minimumRunCount: Int = 1) {
|
||||
timerQueue.async { [weak self] in
|
||||
guard let self = self, let t = self.timer, !self.isCleaned else { return }
|
||||
self.remainingMinimumRunCount = max(0, minimumRunCount)
|
||||
self.shouldCleanAfterMinimumRuns = false
|
||||
t.schedule(deadline: .now() + self.delay, repeating: self.interval)
|
||||
self.shouldRunHandler = true
|
||||
}
|
||||
}
|
||||
|
||||
@objc func isRunning() -> Bool {
|
||||
return shouldRunHandler;
|
||||
if DispatchQueue.getSpecific(key: timerQueueKey) != nil {
|
||||
return shouldRunHandler
|
||||
}
|
||||
|
||||
return timerQueue.sync {
|
||||
shouldRunHandler && !isCleaned
|
||||
}
|
||||
}
|
||||
|
||||
/// 彻底清理 timer,返回后保证 handler 不再执行
|
||||
/// 彻底清理 timer;如果 restart(minimumRunCount:) 尚未跑够次数,则延后到最低次数满足后清理
|
||||
@objc func clean() {
|
||||
if isCleaned { return }
|
||||
if DispatchQueue.getSpecific(key: timerQueueKey) != nil {
|
||||
cleanOnTimerQueue()
|
||||
return
|
||||
}
|
||||
|
||||
// 同步在 timerQueue 执行,保证已排队事件全部处理完(或跳过)
|
||||
timerQueue.sync {
|
||||
self.isCleaned = true
|
||||
self.shouldRunHandler = false
|
||||
cleanOnTimerQueue()
|
||||
}
|
||||
}
|
||||
|
||||
if let t = self.timer {
|
||||
t.setEventHandler {} // 清空 handler
|
||||
t.cancel() // 取消 timer
|
||||
self.timer = nil
|
||||
}
|
||||
private func cleanForDeinit() {
|
||||
if DispatchQueue.getSpecific(key: timerQueueKey) != nil {
|
||||
cleanOnTimerQueue(force: true)
|
||||
return
|
||||
}
|
||||
|
||||
timerQueue.sync {
|
||||
cleanOnTimerQueue(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanOnTimerQueue(force: Bool = false) {
|
||||
guard !isCleaned else { return }
|
||||
if !force && shouldRunHandler && remainingMinimumRunCount > 0 {
|
||||
shouldCleanAfterMinimumRuns = true
|
||||
return
|
||||
}
|
||||
|
||||
isCleaned = true
|
||||
shouldRunHandler = false
|
||||
remainingMinimumRunCount = 0
|
||||
shouldCleanAfterMinimumRuns = false
|
||||
|
||||
if let t = timer {
|
||||
t.setEventHandler {} // 清空 handler
|
||||
t.cancel() // 取消 timer
|
||||
timer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
|
||||
#import "ControllerSupport.h"
|
||||
#import "OnScreenControls.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
#import "StreamConfiguration.h"
|
||||
|
||||
@class PencilHandler;
|
||||
|
||||
@protocol UserInteractionDelegate <NSObject>
|
||||
|
||||
- (void)userInteractionBegan;
|
||||
@@ -29,11 +30,7 @@
|
||||
|
||||
@end
|
||||
|
||||
#if TARGET_OS_TV
|
||||
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate>
|
||||
#else
|
||||
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate, UIPointerInteractionDelegate, InputAccessoryBarDelegate>
|
||||
#endif
|
||||
@interface StreamView : UIView
|
||||
|
||||
@property (weak, nonatomic) UIView* streamFrameTopLayerView;
|
||||
@property (weak, nonatomic) id<UserInteractionDelegate> interactionDelegate;
|
||||
|
||||
@@ -28,7 +28,11 @@
|
||||
#import "StreamFrameViewController.h"
|
||||
|
||||
|
||||
@interface StreamView()
|
||||
#if TARGET_OS_TV
|
||||
@interface StreamView() <X1KitMouseDelegate, UITextFieldDelegate>
|
||||
#else
|
||||
@interface StreamView() <X1KitMouseDelegate, UITextFieldDelegate, UIPointerInteractionDelegate, InputAccessoryBarDelegate>
|
||||
#endif
|
||||
@property (weak, nonatomic) StreamFrameViewController* streamFrameVC;
|
||||
@end
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<entity name="Host" representedClassName="Host" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activeAddressPool" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="controllerNavigationHighlightedAppID" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="ipv6Address" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
|
||||
@@ -40,17 +41,19 @@
|
||||
<attribute name="controllerGyroBiasY" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerGyroBiasZ" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseExpo" optional="YES" attributeType="Float" defaultValueString="1.6" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseLeftButton" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseLeftButton" attributeType="Integer 32" defaultValueString="8" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMousePointerVelocity" optional="YES" attributeType="Float" defaultValueString="12" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseRightButton" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseStick" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseSwitch" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseRightButton" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseStick" attributeType="Integer 32" defaultValueString="8388640" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="ctrlDownForPinch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="customLocalRadialMenuButtonPosition" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="customStreamingRadialMenuButtonPosition" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="delayLeftClick" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="duckOtherApps" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="edgeSlidingSensitivity" optional="YES" attributeType="Float" defaultValueString="22" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="emulatedControllerType" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="enableControllerNavigation" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableFrameTimebase" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableGraphs" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
@@ -75,8 +78,8 @@
|
||||
<attribute name="leftClickDelayMs" optional="YES" attributeType="Float" defaultValueString="15" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="liftStreamViewForKeyboard" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="localMousePointerMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="localRadialMenuButton" attributeType="Integer 32" defaultValueString="256" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="localVolume" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="mapControllerToMouse" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="micVolume" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="mousePointerVelocityFactor" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="multiController" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
@@ -110,6 +113,8 @@
|
||||
<attribute name="softKeyboardHeight" attributeType="Float" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="statsOverlayLevel" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="streamingRadialMenuButton" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="streamingRadialMenuDelay" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="swapABXYButtons" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="touchMode" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="touchMoveEventInterval" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,7 +51,7 @@ class MenuSectionView: UIView {
|
||||
private var toggleButton: UIButton!
|
||||
private var toggleArea: UIButton!
|
||||
private var heightConstraint: NSLayoutConstraint!
|
||||
private var headerView: UIView!
|
||||
var headerView: UIView!
|
||||
private var titleOffset: CGFloat = 41.5
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@@ -69,8 +69,9 @@ class MenuSectionView: UIView {
|
||||
layer.masksToBounds = true
|
||||
backgroundColor = .clear
|
||||
|
||||
headerView = UIView()
|
||||
headerView = UIButton()
|
||||
headerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
headerView.accessibilityIdentifier = "sectionHeader"
|
||||
addSubview(headerView)
|
||||
|
||||
iconImageView = UIImageView()
|
||||
@@ -273,6 +274,8 @@ class MenuSectionView: UIView {
|
||||
persistedFoldState = true
|
||||
}
|
||||
setExpanded(MenuSectionView.overridePersistedFoldState ? expandable : persistedFoldState)
|
||||
|
||||
headerView.accessibilityIdentifier = "\(self.headerView.accessibilityIdentifier ?? "sectionHeader")-\(self.identifier ?? "")"
|
||||
}
|
||||
|
||||
func removeSubStackView(_ stackView: UIStackView) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#import "HttpRequest.h"
|
||||
#import "IdManager.h"
|
||||
#import "LocalizationHelper.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class ThemeManager: NSObject {
|
||||
|
||||
@objc class func setUserInterfaceStyle(_ style: UIUserInterfaceStyle) {
|
||||
_privateUserInterfaceStyle = style
|
||||
applyUserInterfaceStyleOverride(style)
|
||||
|
||||
if _userInterfaceStyle == style {
|
||||
return
|
||||
@@ -48,6 +49,27 @@ class ThemeManager: NSObject {
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc class func applyUserInterfaceStyleOverride(_ style: UIUserInterfaceStyle) {
|
||||
guard #available(iOS 13.0, *) else { return }
|
||||
|
||||
let applyOverride = {
|
||||
let sceneWindows = UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.flatMap { $0.windows }
|
||||
|
||||
let allWindows = sceneWindows + UIApplication.shared.windows
|
||||
Set(allWindows).forEach { window in
|
||||
window.overrideUserInterfaceStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
if Thread.isMainThread {
|
||||
applyOverride()
|
||||
} else {
|
||||
DispatchQueue.main.async(execute: applyOverride)
|
||||
}
|
||||
}
|
||||
|
||||
@objc static var menuBackgroundColor: UIColor {
|
||||
_ = setPublicUIStyle()
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// UIAppView.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/22/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TemporaryApp.h"
|
||||
|
||||
@protocol AppViewUpdateLoopDelegate <NSObject>
|
||||
- (bool)isInAppView;
|
||||
@end
|
||||
|
||||
|
||||
@protocol AppCallback <NSObject>
|
||||
|
||||
- (void) appClicked:(TemporaryApp*)app view:(UIView*)view;
|
||||
- (void) appLongClicked:(TemporaryApp*)app view:(UIView*)view;
|
||||
|
||||
@end
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@interface UIAppView : UIButton <UIContextMenuInteractionDelegate>
|
||||
#else
|
||||
@interface UIAppView : UIButton
|
||||
#endif
|
||||
|
||||
|
||||
@property (nonatomic, weak) id<AppViewUpdateLoopDelegate> updateLoopDelegate; // Delegate property
|
||||
|
||||
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
|
||||
- (void) updateAppImage;
|
||||
|
||||
@end
|
||||
@@ -1,291 +0,0 @@
|
||||
//
|
||||
// UIAppView.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/22/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Created by True砖家 on 2025.7.20.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "UIAppView.h"
|
||||
#import "AppAssetManager.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
static const float REFRESH_CYCLE = 1.0f;
|
||||
|
||||
@implementation UIAppView {
|
||||
TemporaryApp* _app;
|
||||
UILabel* _appLabel;
|
||||
UIImageView* _appOverlay;
|
||||
UIImageView* _appImage;
|
||||
NSCache* _artCache;
|
||||
id<AppCallback> _callback;
|
||||
}
|
||||
|
||||
static UIImage* noImage;
|
||||
|
||||
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback {
|
||||
self = [super init];
|
||||
_app = app;
|
||||
_callback = callback;
|
||||
_artCache = cache;
|
||||
|
||||
self.layer.cornerRadius = 16;
|
||||
if (@available(iOS 13.0, *)) self.layer.cornerCurve = kCACornerCurveContinuous;
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
// Cache the NoAppImage ourselves to avoid
|
||||
// having to load it each time
|
||||
if (noImage == nil) {
|
||||
noImage = [UIImage imageNamed:@"NoAppImage"];
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
self.frame = CGRectMake(0, 0, 200, 265);
|
||||
#else
|
||||
self.frame = CGRectMake(0, 0, 150, 200);
|
||||
#endif
|
||||
|
||||
[self setAlpha:app.hidden ? 0.4 : 1.0];
|
||||
|
||||
_appImage = [[UIImageView alloc] initWithFrame:self.frame];
|
||||
[_appImage setImage:noImage];
|
||||
|
||||
[self addSubview:_appImage];
|
||||
|
||||
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
|
||||
// for tvOS devices and iOS prior to 13.0.
|
||||
#if !TARGET_OS_TV
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIContextMenuInteraction* rightClickInteraction = [[UIContextMenuInteraction alloc] initWithDelegate:self];
|
||||
[self addInteraction:rightClickInteraction];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(appLongClicked:)];
|
||||
[self addGestureRecognizer:longPressRecognizer];
|
||||
}
|
||||
|
||||
[self addTarget:self action:@selector(appClicked:) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
|
||||
[self addTarget:self action:@selector(buttonSelected:) forControlEvents:UIControlEventTouchDown];
|
||||
[self addTarget:self action:@selector(buttonDeselected:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchDragExit];
|
||||
|
||||
#if TARGET_OS_TV
|
||||
_appImage.adjustsImageWhenAncestorFocused = YES;
|
||||
#else
|
||||
// Rasterizing the cell layer increases rendering performance by quite a bit
|
||||
// but we want it unrasterized for tvOS where it must be scaled.
|
||||
self.layer.shouldRasterize = NO;
|
||||
// self.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
|
||||
if (@available(iOS 13.4.1, *)) {
|
||||
// Allow the button style to change when moused over
|
||||
self.pointerInteractionEnabled = YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
[self updateAppImage];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didMoveToSuperview {
|
||||
// Start our update loop when we are added to our cell
|
||||
if (self.superview != nil) {
|
||||
[self updateLoop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) appClicked:(UIView *)view {
|
||||
[_callback appClicked:_app view:view];
|
||||
}
|
||||
|
||||
- (void) appLongClicked:(UILongPressGestureRecognizer*)gesture {
|
||||
if (gesture.state == UIGestureRecognizerStateBegan) {
|
||||
[_callback appLongClicked:_app view:self];
|
||||
}
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|
||||
configurationForMenuAtLocation:(CGPoint)location {
|
||||
// We don't want to trigger the primary action at this point, so cancel
|
||||
// tracking touch on this view now. This will also have the (intended)
|
||||
// effect of removing the touch highlight on this view.
|
||||
[self cancelTrackingWithEvent:nil];
|
||||
|
||||
[_callback appLongClicked:_app view:self];
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (bool)isCurrentApp{
|
||||
return [_app.id isEqualToString:_app.host.currentGame];
|
||||
}
|
||||
|
||||
- (void) updateAppImage {
|
||||
|
||||
if (_appOverlay != nil) {
|
||||
[_appOverlay removeFromSuperview];
|
||||
_appOverlay = nil;
|
||||
}
|
||||
if (_appLabel != nil) {
|
||||
[_appLabel removeFromSuperview];
|
||||
_appLabel = nil;
|
||||
}
|
||||
|
||||
BOOL noAppImage = false;
|
||||
|
||||
// First check the memory cache
|
||||
//UIImage* appImage = [_artCache objectForKey:_app];
|
||||
UIImage* appImage = nil;
|
||||
// NSLog(@"appImage instance: %lu", (uintptr_t)appImage);
|
||||
if (appImage == nil) {
|
||||
// Next try to load from the on disk cache
|
||||
appImage = [UIImage imageWithContentsOfFile:[AppAssetManager boxArtPathForApp:_app]];
|
||||
if (appImage != nil) {
|
||||
[_artCache setObject:appImage forKey:_app];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// [_artCache setObject:appImage forKey:_app];
|
||||
|
||||
if (appImage != nil) {
|
||||
// This size of image might be blank image received from GameStream.
|
||||
|
||||
// TODO: Improve no-app image detection
|
||||
if (!(appImage.size.width == 130.f && appImage.size.height == 180.f) && // GFE 2.0
|
||||
!(appImage.size.width == 628.f && appImage.size.height == 888.f)){
|
||||
|
||||
// if(true){ // GFE 3.0
|
||||
[_appImage setImage:appImage];
|
||||
} else {
|
||||
noAppImage = true;
|
||||
}
|
||||
} else {
|
||||
noAppImage = true;
|
||||
}
|
||||
|
||||
|
||||
if([self isCurrentApp]) {
|
||||
// Only create the app overlay if needed
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:23];
|
||||
UIImageView* playIcon = [[UIImageView alloc] initWithImage:[[UIImage systemImageNamed:@"play.circle.fill" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
|
||||
//playIcon.tintColor = [ThemeManager.widgetBackgroundColor colorWithAlphaComponent:0.85];
|
||||
playIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.55];
|
||||
|
||||
_appOverlay = playIcon;
|
||||
} else {
|
||||
UIImageView* playIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"play.circle.fill"]];
|
||||
playIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.55];
|
||||
_appOverlay = playIcon;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
//_appOverlay.layer.shadowOffset = CGSizeMake(1, 1);
|
||||
//_appOverlay.layer.shadowOpacity = 1;
|
||||
//_appOverlay.layer.shadowRadius = 1.3;
|
||||
//_appOverlay.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
else if(noAppImage){
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:70];
|
||||
UIImage* appIconImage = [[UIImage imageNamed:@"icon-pc-app"] imageWithConfiguration:config];
|
||||
UIImageView* appIcon = [[UIImageView alloc] initWithImage:[appIconImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
|
||||
|
||||
//layIcon.tintColor = [ThemeManager.widgetBackgroundColor colorWithAlphaComponent:0.85];
|
||||
appIcon.tintColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
|
||||
|
||||
_appOverlay = appIcon;
|
||||
}
|
||||
}
|
||||
|
||||
if(true) {
|
||||
_appLabel = [[UILabel alloc] init];
|
||||
_appLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.55];
|
||||
[_appLabel setTextColor:[[UIColor whiteColor] colorWithAlphaComponent:1]];
|
||||
//_appLabel.shadowColor = [UIColor blackColor];
|
||||
[_appLabel setText:[_app.name isEqualToString:@"Steam Big Picture"] ? @"Steam" : _app.name];
|
||||
[_appLabel setFont:[UIFont systemFontOfSize:15]];
|
||||
[_appLabel setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
|
||||
[_appLabel setTextAlignment:NSTextAlignmentCenter];
|
||||
[_appLabel setLineBreakMode:NSLineBreakByWordWrapping];
|
||||
[_appLabel setNumberOfLines:2];
|
||||
_appLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
|
||||
[self positionSubviews];
|
||||
|
||||
#if TARGET_OS_TV
|
||||
[_appImage.overlayContentView addSubview:_appLabel];
|
||||
[_appImage.overlayContentView addSubview:_appOverlay];
|
||||
#else
|
||||
[self addSubview:_appOverlay];
|
||||
[self addSubview:_appLabel];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) buttonSelected:(id)sender {
|
||||
_appImage.layer.opacity = 0.5f;
|
||||
}
|
||||
- (void) buttonDeselected:(id)sender {
|
||||
_appImage.layer.opacity = [self isCurrentApp] ? 0.75 : 1.0f;
|
||||
}
|
||||
|
||||
- (void) positionSubviews {
|
||||
// CGFloat padding = 28;
|
||||
CGFloat verticalPadding = 10;
|
||||
CGSize frameSize = _appImage.frame.size;
|
||||
// CGPoint center = _appImage.center;
|
||||
|
||||
[_appLabel setFrame:CGRectMake(0, frameSize.height-43, frameSize.width, 43)];
|
||||
//[_appLabel setFrame:CGRectMake(0, 0, 30, 12)];
|
||||
|
||||
|
||||
if (_appOverlay != nil) {
|
||||
_appOverlay.frame = [self isCurrentApp] ? CGRectMake(0, 0, frameSize.width / 2.39, frameSize.width / 2.39) : CGRectMake(0, 0, frameSize.width / 2, frameSize.width / 2);
|
||||
_appOverlay.center = CGPointMake(frameSize.width/2, frameSize.height/2 - 2 * verticalPadding);
|
||||
if([self isCurrentApp]) _appImage.layer.opacity = 0.75f;
|
||||
|
||||
//[_appLabel setFrame:CGRectMake(padding, _appOverlay.frame.size.height + padding, frameSize.width - 2 * padding, frameSize.height - _appOverlay.frame.size.height - 2 * padding)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateLoop {
|
||||
// Stop immediately if the view has been detached
|
||||
if (self.superview == nil || ![self.updateLoopDelegate isInAppView]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NSLog(@"appview update loop %f", CACurrentMediaTime());
|
||||
|
||||
// Update the app image if neccessary
|
||||
if ((_appOverlay != nil && ![_app.id isEqualToString:_app.host.currentGame]) ||
|
||||
(_appOverlay == nil && [_app.id isEqualToString:_app.host.currentGame])) {
|
||||
[self updateAppImage];
|
||||
}
|
||||
|
||||
// Show no shadow for hidden apps. Because we adjust the opacity of the
|
||||
// cells for hidden apps, it makes them look bad when the shadow draws
|
||||
// through the app tile.
|
||||
// self.superview.layer.shadowOpacity = _app.hidden ? 0.0f : 0.5f;
|
||||
self.superview.layer.shadowOpacity = 0;
|
||||
|
||||
// Update opacity if neccessary
|
||||
[self setAlpha:_app.hidden ? 0.4 : 1.0];
|
||||
|
||||
// Queue the next refresh cycle
|
||||
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// UIAppView.swift
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/22/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Created by True Zhuanjia on 2025.7.20.
|
||||
// Copyright 2025 True Zhuanjia @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
private let refreshCycle: TimeInterval = 1.0
|
||||
|
||||
@objc protocol AppViewUpdateLoopDelegate: AnyObject {
|
||||
func isInAppView() -> Bool
|
||||
}
|
||||
|
||||
@objc protocol AppCallback: AnyObject {
|
||||
@objc(appClicked:view:)
|
||||
func appClicked(_ app: TemporaryApp, view: UIView)
|
||||
|
||||
@objc(appLongClicked:view:)
|
||||
func appLongClicked(_ app: TemporaryApp, view: UIView)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
final class UIAppView: UIButton {
|
||||
weak var updateLoopDelegate: AppViewUpdateLoopDelegate?
|
||||
|
||||
private static var noImage: UIImage?
|
||||
|
||||
let app: TemporaryApp
|
||||
fileprivate weak var callback: AppCallback?
|
||||
private let artCache: NSCache<AnyObject, AnyObject>
|
||||
private var appLabel: UILabel?
|
||||
private var appOverlay: UIImageView?
|
||||
private var appImage: UIImageView
|
||||
private var contextMenuDelegate: AnyObject?
|
||||
|
||||
@objc(initWithApp:cache:andCallback:)
|
||||
init(app: TemporaryApp, cache: NSCache<AnyObject, AnyObject>, andCallback callback: AppCallback) {
|
||||
self.app = app
|
||||
self.callback = callback
|
||||
self.artCache = cache
|
||||
|
||||
if UIAppView.noImage == nil {
|
||||
UIAppView.noImage = UIImage(named: "NoAppImage")
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
let initialFrame = CGRect(x: 0, y: 0, width: 200, height: 265)
|
||||
#else
|
||||
let initialFrame = CGRect(x: 0, y: 0, width: 150, height: 200)
|
||||
#endif
|
||||
|
||||
self.appImage = UIImageView(frame: initialFrame)
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
layer.cornerRadius = 16
|
||||
if #available(iOS 13.0, tvOS 13.0, *) {
|
||||
layer.cornerCurve = .continuous
|
||||
}
|
||||
clipsToBounds = true
|
||||
frame = initialFrame
|
||||
alpha = app.hidden ? 0.4 : 1.0
|
||||
|
||||
appImage.image = UIAppView.noImage
|
||||
addSubview(appImage)
|
||||
|
||||
#if !os(tvOS)
|
||||
if #available(iOS 13.0, *) {
|
||||
let delegate = UIAppViewContextMenuDelegate(appView: self)
|
||||
contextMenuDelegate = delegate
|
||||
let rightClickInteraction = UIContextMenuInteraction(delegate: delegate)
|
||||
addInteraction(rightClickInteraction)
|
||||
} else {
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(appLongClicked(_:)))
|
||||
addGestureRecognizer(longPressRecognizer)
|
||||
}
|
||||
#else
|
||||
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(appLongClicked(_:)))
|
||||
addGestureRecognizer(longPressRecognizer)
|
||||
#endif
|
||||
|
||||
addTarget(self, action: #selector(appClicked(_:)), for: .primaryActionTriggered)
|
||||
addTarget(self, action: #selector(buttonSelected(_:)), for: .touchDown)
|
||||
addTarget(self, action: #selector(buttonDeselected(_:)), for: [.touchUpInside, .touchCancel, .touchDragExit])
|
||||
|
||||
#if os(tvOS)
|
||||
appImage.adjustsImageWhenAncestorFocused = true
|
||||
#else
|
||||
layer.shouldRasterize = false
|
||||
if #available(iOS 13.4, *) {
|
||||
isPointerInteractionEnabled = true
|
||||
}
|
||||
#endif
|
||||
|
||||
updateAppImage()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
if superview != nil {
|
||||
updateLoop()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func appClicked(_ view: UIView) {
|
||||
callback?.appClicked(app, view: view)
|
||||
}
|
||||
|
||||
@objc private func appLongClicked(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state == .began {
|
||||
callback?.appLongClicked(app, view: self)
|
||||
}
|
||||
}
|
||||
|
||||
private var isCurrentApp: Bool {
|
||||
app.id == app.host?.currentGame
|
||||
}
|
||||
|
||||
func updateAppImage() {
|
||||
appOverlay?.removeFromSuperview()
|
||||
appOverlay = nil
|
||||
appLabel?.removeFromSuperview()
|
||||
appLabel = nil
|
||||
|
||||
var noAppImage = false
|
||||
var loadedAppImage: UIImage?
|
||||
|
||||
if let boxArtPath = AppAssetManager.boxArtPath(for: app) {
|
||||
loadedAppImage = UIImage(contentsOfFile: boxArtPath)
|
||||
if let loadedAppImage {
|
||||
artCache.setObject(loadedAppImage, forKey: app)
|
||||
}
|
||||
}
|
||||
|
||||
if let loadedAppImage {
|
||||
let isGFE2BlankImage = loadedAppImage.size.width == 130.0 && loadedAppImage.size.height == 180.0
|
||||
let isGFE3BlankImage = loadedAppImage.size.width == 628.0 && loadedAppImage.size.height == 888.0
|
||||
if !isGFE2BlankImage && !isGFE3BlankImage {
|
||||
appImage.image = loadedAppImage
|
||||
} else {
|
||||
noAppImage = true
|
||||
}
|
||||
} else {
|
||||
noAppImage = true
|
||||
}
|
||||
|
||||
if isCurrentApp {
|
||||
if #available(iOS 13.0, tvOS 13.0, *) {
|
||||
let config = UIImage.SymbolConfiguration(pointSize: 23)
|
||||
let image = UIImage(systemName: "play.circle.fill", withConfiguration: config)?.withRenderingMode(.alwaysTemplate)
|
||||
let playIcon = UIImageView(image: image)
|
||||
playIcon.tintColor = UIColor.black.withAlphaComponent(0.55)
|
||||
appOverlay = playIcon
|
||||
} else {
|
||||
let playIcon = UIImageView(image: UIImage(named: "play.circle.fill"))
|
||||
playIcon.tintColor = UIColor.black.withAlphaComponent(0.55)
|
||||
appOverlay = playIcon
|
||||
}
|
||||
} else if noAppImage {
|
||||
if #available(iOS 13.0, tvOS 13.0, *) {
|
||||
let config = UIImage.SymbolConfiguration(pointSize: 70)
|
||||
let image = UIImage(named: "icon-pc-app")?.withConfiguration(config).withRenderingMode(.alwaysTemplate)
|
||||
let appIcon = UIImageView(image: image)
|
||||
appIcon.tintColor = UIColor.black.withAlphaComponent(0.5)
|
||||
appOverlay = appIcon
|
||||
}
|
||||
}
|
||||
|
||||
let label = UILabel()
|
||||
label.backgroundColor = UIColor.black.withAlphaComponent(0.55)
|
||||
label.textColor = UIColor.white
|
||||
label.text = app.name == "Steam Big Picture" ? "Steam" : app.name
|
||||
label.font = UIFont.systemFont(ofSize: 15)
|
||||
label.baselineAdjustment = .alignCenters
|
||||
label.textAlignment = .center
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.numberOfLines = 2
|
||||
appLabel = label
|
||||
|
||||
positionSubviews()
|
||||
|
||||
#if os(tvOS)
|
||||
appImage.overlayContentView.addSubview(label)
|
||||
if let appOverlay {
|
||||
appImage.overlayContentView.addSubview(appOverlay)
|
||||
}
|
||||
#else
|
||||
if let appOverlay {
|
||||
addSubview(appOverlay)
|
||||
}
|
||||
addSubview(label)
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc private func buttonSelected(_ sender: Any) {
|
||||
appImage.layer.opacity = 0.5
|
||||
}
|
||||
|
||||
@objc private func buttonDeselected(_ sender: Any) {
|
||||
appImage.layer.opacity = isCurrentApp ? 0.75 : 1.0
|
||||
}
|
||||
|
||||
private func positionSubviews() {
|
||||
let verticalPadding: CGFloat = 10
|
||||
let frameSize = appImage.frame.size
|
||||
|
||||
appLabel?.frame = CGRect(x: 0, y: frameSize.height - 43, width: frameSize.width, height: 43)
|
||||
|
||||
guard let appOverlay else { return }
|
||||
let overlaySize = isCurrentApp ? frameSize.width / 2.39 : frameSize.width / 2.0
|
||||
appOverlay.frame = CGRect(x: 0, y: 0, width: overlaySize, height: overlaySize)
|
||||
appOverlay.center = CGPoint(x: frameSize.width / 2.0, y: frameSize.height / 2.0 - 2.0 * verticalPadding)
|
||||
|
||||
if isCurrentApp {
|
||||
appImage.layer.opacity = 0.75
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func updateLoop() {
|
||||
guard superview != nil, updateLoopDelegate?.isInAppView() == true else {
|
||||
return
|
||||
}
|
||||
|
||||
if (appOverlay != nil && !isCurrentApp) || (appOverlay == nil && isCurrentApp) {
|
||||
updateAppImage()
|
||||
}
|
||||
|
||||
superview?.layer.shadowOpacity = 0
|
||||
alpha = app.hidden ? 0.4 : 1.0
|
||||
|
||||
perform(#selector(updateLoop), with: self, afterDelay: refreshCycle)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private final class UIAppViewContextMenuDelegate: NSObject, UIContextMenuInteractionDelegate {
|
||||
private weak var appView: UIAppView?
|
||||
|
||||
init(appView: UIAppView) {
|
||||
self.appView = appView
|
||||
}
|
||||
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
|
||||
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let appView else { return nil }
|
||||
appView.cancelTracking(with: nil)
|
||||
appView.callback?.appLongClicked(appView.app, view: appView)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,14 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
var hasNoPresentedVC: Bool {
|
||||
let hasPresentedVC = self.presentedViewController == nil && self.view.window != nil
|
||||
// let presentedVC = self.presentedViewController
|
||||
return hasPresentedVC
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
var parentViewController: UIViewController? {
|
||||
var responder: UIResponder? = self
|
||||
@@ -27,6 +35,15 @@ public extension CGPoint {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element: Equatable {
|
||||
@discardableResult
|
||||
mutating func appendIfNotContains(_ newElement: Element) -> Bool {
|
||||
guard !contains(newElement) else { return false }
|
||||
append(newElement)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public extension String {
|
||||
var localized: String {
|
||||
LocalizationHelper.localizedString(forKey: self)
|
||||
@@ -40,6 +57,35 @@ public extension String {
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIFont {
|
||||
static func roundedSystemFont(ofSize size: CGFloat, weight: UIFont.Weight) -> UIFont {
|
||||
let font = UIFont.systemFont(ofSize: size, weight: weight)
|
||||
if #available(iOS 13.0, *) {
|
||||
if let roundedDescriptor = font.fontDescriptor.withDesign(.rounded) {
|
||||
return UIFont(descriptor: roundedDescriptor, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
return font
|
||||
}
|
||||
}
|
||||
|
||||
public extension UISlider {
|
||||
func step(forward: Bool, visualStepRatio: Float) {
|
||||
let range = self.maximumValue - self.minimumValue
|
||||
guard range > 0 else { return }
|
||||
|
||||
let step = range * visualStepRatio
|
||||
let nextValue = self.value + (forward ? step : -step)
|
||||
|
||||
let clampedValue = min(max(nextValue, self.minimumValue), self.maximumValue)
|
||||
DispatchQueue.main.async {
|
||||
self.setValue(clampedValue, animated: false)
|
||||
self.sendActions(for: .valueChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var previousSelectedSegmentIndexKey: UInt8 = 0
|
||||
private var lastKnownSelectedSegmentIndexKey: UInt8 = 0
|
||||
public extension UISegmentedControl {
|
||||
@@ -104,7 +150,7 @@ public extension UISegmentedControl {
|
||||
|
||||
let currentValue = selectedSegmentIndex
|
||||
if currentValue != newValue {
|
||||
storePreviousSelectedSegmentIndex(newValue)
|
||||
storePreviousSelectedSegmentIndex(currentValue)
|
||||
storeLastKnownSelectedSegmentIndex(newValue)
|
||||
}
|
||||
|
||||
@@ -182,4 +228,10 @@ public extension UISegmentedControl {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
}
|
||||
|
||||
func nextIndex(forward: Bool) -> Int {
|
||||
var targetIndex = forward ? self.selectedSegmentIndex + 1 : self.selectedSegmentIndex - 1
|
||||
targetIndex = forward ? (targetIndex == self.numberOfSegments ? 0 : targetIndex) : (targetIndex == -1 ? self.numberOfSegments-1 : targetIndex)
|
||||
return targetIndex
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,39 @@ import UIKit
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func controllerMouseSymbol() -> String {
|
||||
if #available(iOS 17.0, tvOS 17.0, *) {
|
||||
return "pointer.arrow.ipad"
|
||||
}
|
||||
else if #available(iOS 16.0, tvOS 16.0, *) {
|
||||
return "pointer.arrow.ipad"
|
||||
}
|
||||
else {
|
||||
return "paperplane"
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func disableControllerMouseSymbol() -> String {
|
||||
if #available(iOS 17.0, tvOS 17.0, *) {
|
||||
return "pointer.arrow.slash.square"
|
||||
}
|
||||
else if #available(iOS 16.0, tvOS 16.0, *) {
|
||||
return "pointer.arrow"
|
||||
}
|
||||
else {
|
||||
return "paperplane"
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func quitSymbol() -> String {
|
||||
if #available(iOS 14.0, tvOS 14.0, *) {
|
||||
return "pip.exit"
|
||||
}
|
||||
else {
|
||||
return "arrow.left.square"
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func disconnectSymbolSize() -> CGFloat {
|
||||
if #available(iOS 26.0, tvOS 26.0, *) {
|
||||
return 15
|
||||
@@ -175,6 +208,36 @@ import UIKit
|
||||
|
||||
return rootViewController
|
||||
}
|
||||
|
||||
public static func runOnMain(_ work: @escaping () -> Void) {
|
||||
if Thread.isMainThread {
|
||||
work()
|
||||
} else {
|
||||
DispatchQueue.main.async(execute: work)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public static func controllerMouseExpoMappedOffset(_ offset: CGFloat, expo: CGFloat) -> CGFloat {
|
||||
guard offset != 0 else { return 0 }
|
||||
let magnitude = pow(abs(offset), expo)
|
||||
return offset > 0 ? magnitude : -magnitude
|
||||
}
|
||||
|
||||
@objc public static func controllerMouseVelocity(pointerVelocity: CGFloat, frameRate: CGFloat) -> CGFloat {
|
||||
guard frameRate > 0 else { return pointerVelocity }
|
||||
return pointerVelocity * 60 / frameRate
|
||||
}
|
||||
|
||||
@objc public static func controllerMouseMoveVector(stickX: CGFloat, stickY: CGFloat, velocity: CGFloat, expo: CGFloat) -> CGVector {
|
||||
return CGVector(
|
||||
dx: velocity * controllerMouseExpoMappedOffset(stickX, expo: expo),
|
||||
dy: -velocity * controllerMouseExpoMappedOffset(stickY, expo: expo)
|
||||
)
|
||||
}
|
||||
|
||||
@objc public static func controllerMouseScrollVector(stickX: CGFloat, stickY: CGFloat, multiplier: CGFloat) -> CGVector {
|
||||
return CGVector(dx: multiplier * stickX, dy: multiplier * stickY)
|
||||
}
|
||||
|
||||
@objc(openUrl:)
|
||||
static func openUrl(_ urlString: String) {
|
||||
|
||||
+198
-30
@@ -30,7 +30,8 @@ enum FileOperation: Int {
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
final class OSCProfilesTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UIGestureRecognizerDelegate {
|
||||
final class OSCProfilesTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIDocumentPickerDelegate, UIGestureRecognizerDelegate, ControllerUINavigationDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var profileTableViewNavigationBar: UINavigationBar!
|
||||
@IBOutlet private weak var profileTableViewNavigationItem: UINavigationItem!
|
||||
@@ -44,6 +45,9 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
|
||||
private var profilesManager: OSCProfilesManager!
|
||||
private var horizontalConstraintsConfigured = false
|
||||
private var profiles: NSMutableArray = []
|
||||
private var selectedProfileIndex = 0
|
||||
private var profilesLoadGeneration = 0
|
||||
|
||||
private func contentWidthMultiplier() -> CGFloat {
|
||||
PublicUtils.viewIsLandscape(view) ? (PublicUtils.isIPhone ? 0.8 : 0.65) : (PublicUtils.isIPhone ? 0.83 : 0.85)
|
||||
@@ -102,6 +106,13 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
NotificationCenter.default.post(name: Notification.Name("OscLayoutTableViewCloseNotification"), object: self)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
if #available(iOS 13.0, *) {
|
||||
ControllerNavigator.restorePreviousUINavigationDelegate(ifCurrentDelegateIs: self)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
@@ -144,6 +155,8 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
profileTableViewNavigationItem.leftBarButtonItems?.forEach { $0.hidesSharedBackground = true }
|
||||
profileTableViewNavigationItem.rightBarButtonItems?.forEach { $0.hidesSharedBackground = true }
|
||||
}
|
||||
|
||||
reloadProfilesAsync(scrollToSelected: true)
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
@@ -167,9 +180,17 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
horizontalConstraintsConfigured = true
|
||||
}
|
||||
configureTableView()
|
||||
if profilesManager.getAllProfiles().count > 0 {
|
||||
let indexPath = IndexPath(row: profilesManager.getIndexOfSelectedProfile(), section: 0)
|
||||
tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
|
||||
if profiles.count > 0 {
|
||||
scrollToSelectedProfile(animated: true)
|
||||
} else {
|
||||
reloadProfilesAsync(scrollToSelected: true)
|
||||
}
|
||||
|
||||
if (loadingMode == .selectProfileFromMainFrame || loadingMode == .selectProfileFromStreamView)
|
||||
&& ControllerUtil.primaryGCController != nil {
|
||||
if #available(iOS 13.0, *) {
|
||||
ControllerNavigator.setUINavigationDelegate(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +201,94 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
tableView.register(UINib(nibName: "ProfileTableViewCell", bundle: nil), forCellReuseIdentifier: "Cell")
|
||||
}
|
||||
|
||||
private func reloadProfilesAsync(scrollToSelected: Bool = false, notify: Bool = false) {
|
||||
profilesLoadGeneration += 1
|
||||
let generation = profilesLoadGeneration
|
||||
let manager = profilesManager
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||
guard let manager else { return }
|
||||
|
||||
let loadedProfiles = manager.getAllProfiles()
|
||||
let loadedSelectedIndex = Self.selectedProfileIndex(in: loadedProfiles)
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self, generation == self.profilesLoadGeneration else { return }
|
||||
|
||||
self.profiles = loadedProfiles
|
||||
self.selectedProfileIndex = loadedSelectedIndex
|
||||
self.tableView.reloadData()
|
||||
|
||||
if scrollToSelected {
|
||||
self.scrollToSelectedProfile(animated: true)
|
||||
}
|
||||
|
||||
if notify {
|
||||
self.notifyProfileViewDidRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func scrollToSelectedProfile(animated: Bool) {
|
||||
guard let selectedProfileIndex = validProfileIndex(selectedProfileIndex) else {
|
||||
return
|
||||
}
|
||||
|
||||
let indexPath = IndexPath(row: selectedProfileIndex, section: 0)
|
||||
tableView.scrollToRow(at: indexPath, at: .middle, animated: animated)
|
||||
}
|
||||
|
||||
private func moveSelectedProfile(by offset: Int) {
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
guard let self,
|
||||
self.profiles.count > 0,
|
||||
self.tableView.numberOfSections > 0,
|
||||
self.tableView.numberOfRows(inSection: 0) > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
let maxIndex = min(self.profiles.count, self.tableView.numberOfRows(inSection: 0)) - 1
|
||||
let currentIndex = min(max(self.selectedProfileIndex, 0), maxIndex)
|
||||
let nextIndex = min(max(currentIndex + offset, 0), maxIndex)
|
||||
let nextIndexPath = IndexPath(row: nextIndex, section: 0)
|
||||
|
||||
self.tableView.selectRow(at: nextIndexPath, animated: true, scrollPosition: .middle)
|
||||
self.tableView(self.tableView, didSelectRowAt: nextIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
private func validProfileIndex(_ index: Int) -> Int? {
|
||||
guard profiles.count > 0 else { return nil }
|
||||
return min(max(index, 0), profiles.count - 1)
|
||||
}
|
||||
|
||||
private func notifyProfileViewDidRefresh() {
|
||||
needToUpdateOscLayoutTVC?()
|
||||
if loadingMode != .selectProfileFromStreamView
|
||||
&& loadingMode != .pickProfile
|
||||
&& loadingMode != .pickProfileData
|
||||
&& loadingMode != .selectProfile
|
||||
{
|
||||
guard let selectedProfileIndex = validProfileIndex(selectedProfileIndex) else { return }
|
||||
NotificationCenter.default.post(name: Notification.Name("GameProfileSelectedNotification"), object: self.profiles[selectedProfileIndex])
|
||||
}
|
||||
}
|
||||
|
||||
private static func selectedProfileIndex(in profiles: NSMutableArray) -> Int {
|
||||
for index in 0..<profiles.count {
|
||||
guard let profile = profiles.object(at: index) as? OSCProfile else {
|
||||
continue
|
||||
}
|
||||
|
||||
if profile.isSelected {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@IBAction func duplicateTapped(_ sender: Any?) {
|
||||
let inputAlert = UIAlertController(
|
||||
title: LocalizationHelper.localizedString(forKey: "Enter the name you want to save this profile as"),
|
||||
@@ -252,15 +361,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
}
|
||||
|
||||
func profileViewRefresh() {
|
||||
tableView.reloadData()
|
||||
needToUpdateOscLayoutTVC?()
|
||||
if loadingMode != .selectProfileFromStreamView
|
||||
&& loadingMode != .pickProfile
|
||||
&& loadingMode != .pickProfileData
|
||||
&& loadingMode != .selectProfile
|
||||
{
|
||||
NotificationCenter.default.post(name: Notification.Name("GameProfileSelectedNotification"), object: self)
|
||||
}
|
||||
reloadProfilesAsync(notify: true)
|
||||
}
|
||||
|
||||
@IBAction func deleteTapped(_ sender: Any?) {
|
||||
@@ -312,7 +413,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
}
|
||||
try data.write(to: destinationURL, options: .atomic)
|
||||
} catch {
|
||||
NSLog("写入失败: \(error)")
|
||||
// NSLog("写入失败: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +432,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
|
||||
do {
|
||||
let fileData = try Data(contentsOf: sourceURL)
|
||||
NSLog("profile file read: \(UInt32(fileData.count))")
|
||||
// NSLog("profile file read: \(UInt32(fileData.count))")
|
||||
let classes: [AnyClass] = [NSMutableData.self, NSMutableArray.self]
|
||||
let profilesEncoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: classes, from: fileData) as? NSMutableArray
|
||||
|
||||
@@ -357,7 +458,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
}
|
||||
|
||||
profileViewRefresh()
|
||||
NSLog("profile test: \(UInt32(profilesEncoded?.count ?? 0))")
|
||||
// NSLog("profile test: \(UInt32(profilesEncoded?.count ?? 0))")
|
||||
} catch {
|
||||
restoreFailed = true
|
||||
let failedAlert = UIAlertController(
|
||||
@@ -368,17 +469,18 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
failedAlert.addAction(UIAlertAction(title: LocalizationHelper.localizedString(forKey: "OK"), style: .default))
|
||||
present(failedAlert, animated: true)
|
||||
profileViewRefresh()
|
||||
NSLog("解码失败: \(error)")
|
||||
// NSLog("解码失败: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
profilesManager.getAllProfiles().count
|
||||
profiles.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? ProfileTableViewCell,
|
||||
let profile = profilesManager.getAllProfiles().object(at: indexPath.row) as? OSCProfile,
|
||||
indexPath.row < profiles.count,
|
||||
let profile = profiles.object(at: indexPath.row) as? OSCProfile,
|
||||
let nameLabel = cell.name else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
@@ -395,7 +497,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
cell.contentView.backgroundColor = UIColor.clear
|
||||
|
||||
if loadingMode != .pickProfileData,
|
||||
indexPath.row == profilesManager.getIndexOfSelectedProfile() {
|
||||
indexPath.row == selectedProfileIndex {
|
||||
cell.accessoryType = UITableViewCell.AccessoryType.checkmark
|
||||
let checkmarkLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
|
||||
checkmarkLabel.text = "✓"
|
||||
@@ -421,7 +523,7 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
let profiles = profilesManager.getAllProfiles()
|
||||
guard indexPath.row < profiles.count else { return }
|
||||
|
||||
if let profile = profiles.object(at: indexPath.row) as? OSCProfile, profile.name == "Default" {
|
||||
let alert = UIAlertController(title: "", message: "Deleting the 'Default' profile is not allowed", preferredStyle: .alert)
|
||||
@@ -451,31 +553,58 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
tableView.reloadData()
|
||||
reloadProfilesAsync()
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard indexPath.section == 0,
|
||||
indexPath.row >= 0,
|
||||
indexPath.row < profiles.count else {
|
||||
return
|
||||
}
|
||||
|
||||
if loadingMode == .pickProfileData,
|
||||
let pickedProfile = profilesManager.getAllProfiles().object(at: indexPath.row) as? OSCProfile {
|
||||
let pickedProfile = profiles.object(at: indexPath.row) as? OSCProfile {
|
||||
pickedProfileDataHandler?(pickedProfile)
|
||||
dismiss(animated: false)
|
||||
return
|
||||
}
|
||||
|
||||
let selectedIndexPath = IndexPath(row: indexPath.row, section: 0)
|
||||
let lastSelectedIndexPath = IndexPath(row: profilesManager.getIndexOfSelectedProfile(), section: 0)
|
||||
let lastSelectedIndex = validProfileIndex(selectedProfileIndex)
|
||||
let lastSelectedIndexPath = lastSelectedIndex.map { IndexPath(row: $0, section: 0) }
|
||||
|
||||
if selectedIndexPath != lastSelectedIndexPath {
|
||||
let selectedCell = tableView.cellForRow(at: selectedIndexPath)
|
||||
selectedCell?.accessoryType = .checkmark
|
||||
selectedCell?.accessoryView?.tintColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0).withAlphaComponent(1.0)
|
||||
if let lastSelectedIndexPath {
|
||||
let previousCell = tableView.cellForRow(at: lastSelectedIndexPath)
|
||||
previousCell?.accessoryType = .none
|
||||
previousCell?.accessoryView = nil
|
||||
}
|
||||
|
||||
profilesManager.setProfileToSelected(UInt32(indexPath.row))
|
||||
selectedProfileIndex = indexPath.row
|
||||
|
||||
let lastSelectedCell = tableView.cellForRow(at: lastSelectedIndexPath)
|
||||
lastSelectedCell?.accessoryType = .none
|
||||
tableView.deselectRow(at: lastSelectedIndexPath, animated: true)
|
||||
let selectedCell = tableView.cellForRow(at: selectedIndexPath)
|
||||
selectedCell?.accessoryType = .checkmark
|
||||
let checkmarkLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
|
||||
checkmarkLabel.text = "✓"
|
||||
checkmarkLabel.font = .systemFont(ofSize: 25)
|
||||
checkmarkLabel.textAlignment = .center
|
||||
checkmarkLabel.textColor = .green
|
||||
checkmarkLabel.layer.zPosition = 0
|
||||
selectedCell?.accessoryView = checkmarkLabel
|
||||
|
||||
let rowsToReload = [lastSelectedIndexPath, selectedIndexPath]
|
||||
.compactMap { $0 }
|
||||
.filter {
|
||||
$0.section < tableView.numberOfSections &&
|
||||
$0.row >= 0 &&
|
||||
$0.row < tableView.numberOfRows(inSection: $0.section)
|
||||
}
|
||||
if !rowsToReload.isEmpty {
|
||||
tableView.reloadRows(at: rowsToReload, with: .none)
|
||||
}
|
||||
}
|
||||
profileViewRefresh()
|
||||
}
|
||||
@@ -491,4 +620,43 @@ final class OSCProfilesTableViewController: UIViewController, UITableViewDelegat
|
||||
}
|
||||
return touch.view == view
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func getNavigationElements() -> [ControllerNavigationElement] {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .y, action: "previousItem"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadDown : .a, action: "nextItem"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .x, action: "exit"))
|
||||
return elements
|
||||
}
|
||||
|
||||
func navigateByController(forward: Bool) {
|
||||
}
|
||||
|
||||
func navigateByController(downward: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func persistControllerNavigationHighlight() {}
|
||||
func restoreControllerNavigationHighlight() {}
|
||||
func restoreControllerNavigationHighlightAfterSettingsModeSwitch() {}
|
||||
|
||||
func uiWidgetActionForControllerNavigator(forward: Bool, from navigation: ControllerNavigationElement) {
|
||||
}
|
||||
|
||||
func uiButtonActionForControllerNavigator(pressed: Bool, from navigation: ControllerNavigationElement) {
|
||||
guard pressed else { return }
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
switch navigation.action {
|
||||
case "previousItem":
|
||||
self?.moveSelectedProfile(by: -1)
|
||||
case "nextItem":
|
||||
self?.moveSelectedProfile(by: 1)
|
||||
case "exit":
|
||||
self?.dismiss(animated: false)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@ class HostCollectionViewController: UICollectionViewController, UICollectionView
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
NSLog("View did appear")
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
@@ -209,6 +208,7 @@ class HostCollectionViewController: UICollectionViewController, UICollectionView
|
||||
if let host = items[indexPath.item] as? TemporaryHost {
|
||||
cell.configure(with: host)
|
||||
}
|
||||
applyControllerNavigationHighlight(to: cell, highlighted: indexPath == controllerNavigationSelectedIndexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
@@ -13,32 +13,43 @@
|
||||
#import "DiscoveryManager.h"
|
||||
#import "PairManager.h"
|
||||
#import "StreamConfiguration.h"
|
||||
#import "UIAppView.h"
|
||||
#import "AppAssetManager.h"
|
||||
#import "SWRevealViewController.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
|
||||
@class SettingsViewController;
|
||||
@class TemporaryApp;
|
||||
@class HostCollectionViewController;
|
||||
|
||||
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, UITextFieldDelegate>
|
||||
|
||||
@interface MainFrameViewController : UICollectionViewController <DiscoveryCallback, PairCallback, AppCallback, AppAssetCallback, NSURLConnectionDelegate, SWRevealViewControllerDelegate, HostCardActionDelegate, AppViewUpdateLoopDelegate, UITextFieldDelegate, ControllerNavigatorRadialMenuDelegate>
|
||||
|
||||
@property (nonatomic, strong) IBOutlet UIBarButtonItem *settingsButton;
|
||||
@property (weak, nonatomic) IBOutlet UIBarButtonItem *profilesButton;
|
||||
@property (weak, nonatomic) SettingsViewController *settingsViewController;
|
||||
@property (nonatomic, strong) HostCollectionViewController *hostCollectionVC;
|
||||
@property (nonatomic, strong, readonly) NSArray<TemporaryApp *> *sortedAppList;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@property (nonatomic, assign) bool settingsExpandedInStreamView;
|
||||
@property (nonatomic, strong) HostCollectionViewController *hostCollectionVC;
|
||||
@property (nonatomic, readonly) bool settingsViewExpanded;
|
||||
@property (weak, nonatomic) IBOutlet UINavigationItem *navigationItem;
|
||||
|
||||
|
||||
-(void)expandSettingsView;
|
||||
- (void)expandSettingsView;
|
||||
- (void)closeSettingViewAnimated:(BOOL)anaimated;
|
||||
- (void)reloadStreamConfig;
|
||||
- (bool)isIPhonePortrait;
|
||||
- (void)quitRunningApp;
|
||||
- (bool)isInAppView;
|
||||
- (bool)isStreaming;
|
||||
- (TemporaryApp*)findRunningApp:(TemporaryHost*)host;
|
||||
- (void)quitApp:(TemporaryApp* )app;
|
||||
- (void)quitLaunchedApp;
|
||||
- (void)launchApp:(TemporaryApp *)app;
|
||||
- (void)quitRunningAppAndStart:(TemporaryApp *)app;
|
||||
|
||||
- (NSInteger)requestForBitrate:(NSInteger)bitrateKbps;
|
||||
#endif
|
||||
- (void)fillResolutionTable:(CGSize*)resolutionTable externalDisplayMode:(NSInteger)externalDisplayMode;
|
||||
- (bool)isIPhone;
|
||||
- (void)setNeedsUpdateAllowedOrientation;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#import "Connection.h"
|
||||
#import "StreamManager.h"
|
||||
#import "Utils.h"
|
||||
#import "UIAppView.h"
|
||||
#import "DataManager.h"
|
||||
#import "TemporarySettings.h"
|
||||
#import "WakeOnLanManager.h"
|
||||
@@ -44,8 +43,10 @@
|
||||
#include <Limelight.h>
|
||||
|
||||
|
||||
@interface MainFrameViewController()
|
||||
@interface MainFrameViewController() <AppCallback, HostCardActionDelegate, AppViewUpdateLoopDelegate, ControllerNavigatorRadialMenuDelegate, ControllerUtilDelegate>
|
||||
@property (weak, nonatomic) LayoutOnScreenControlsViewController* gameProfileSelectorVC;
|
||||
@property (nonatomic, strong, readwrite) NSArray<TemporaryApp *> *sortedAppList;
|
||||
@property (nonatomic, assign) bool settingsViewExpanded;
|
||||
@end
|
||||
|
||||
@implementation MainFrameViewController {
|
||||
@@ -67,14 +68,11 @@
|
||||
UIAlertController* _pairAlert;
|
||||
LoadingFrameViewController* _loadingFrame;
|
||||
FrontViewPosition currentPosition;
|
||||
NSArray* _sortedAppList;
|
||||
NSCache* _boxArtCache;
|
||||
bool _background;
|
||||
bool _enteredAppView;
|
||||
bool _settingsViewExpanded;
|
||||
UIView* menuSeparator;
|
||||
UIView* snapshot;
|
||||
SettingsViewController* settingsViewController;
|
||||
__weak StreamFrameViewController* streamFrameViewController;
|
||||
id navBarAppearanceStandard;
|
||||
bool _viewJustAppeared;
|
||||
@@ -101,11 +99,13 @@ static NSMutableSet* hostList;
|
||||
message:[LocalizationHelper localizedStringForKey:@"Enter_PIN_Msg", PIN]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[self->_pairAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"] style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) {
|
||||
self->_pairAlert = nil;
|
||||
[self->_discMan startDiscovery];
|
||||
[self hideLoadingFrame: ^{
|
||||
[self switchToHostView];
|
||||
}];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_pairAlert = nil;
|
||||
[self->_discMan startDiscovery];
|
||||
[self hideLoadingFrame: ^{
|
||||
[self switchToHostView];
|
||||
}];
|
||||
});
|
||||
}]];
|
||||
[[self activeViewController] presentViewController:self->_pairAlert animated:YES completion:nil];
|
||||
});
|
||||
@@ -348,7 +348,7 @@ static NSMutableSet* hostList;
|
||||
[_appManager stopRetrieving];
|
||||
_showHiddenApps = NO;
|
||||
_selectedHost = nil;
|
||||
_sortedAppList = nil;
|
||||
self.sortedAppList = nil;
|
||||
|
||||
// [self.collectionView removeFromSuperview]; // necessary for new scroll host view reloading mechanism
|
||||
self.hostCollectionVC.view.hidden = NO;
|
||||
@@ -356,6 +356,15 @@ static NSMutableSet* hostList;
|
||||
[self updateTitle];
|
||||
self.navigationItem.rightBarButtonItems = @[_helpButton, _addHostButton];
|
||||
self.revealViewController.mainFrameIsInHostView = true; // to allow orientation change only in app view, tell top view controller the mainframe is not in host view
|
||||
|
||||
if (@available(iOS 13.0, *)){
|
||||
[GamepadNavigationIllustrationHud showInKeyWindow];
|
||||
[ControllerNavigator setUINavigationDelegate:self.isInAppView ? self.hostCollectionVC : self.hostCollectionVC];
|
||||
if(ControllerNavigator.radialMenuView.superview) [ControllerNavigator updateRadialMenu];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[ControllerNavigator restoreUINavigationHighlight];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void) receivedAssetForApp:(TemporaryApp*)app {
|
||||
@@ -378,6 +387,10 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
- (void)switchToAppView{
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[ControllerNavigator setUINavigationDelegate:self];
|
||||
}
|
||||
|
||||
_enteredAppView = true;
|
||||
//_appManager = [[AppAssetManager alloc] initWithCallback:self];
|
||||
[self.collectionView setCollectionViewLayout:self.collectionViewLayout];
|
||||
@@ -399,6 +412,12 @@ static NSMutableSet* hostList;
|
||||
// self.navigationController.navigationBar.backgroundColor = [ThemeManager appBackgroundColor];
|
||||
// self.navigationController.navigationBar.translucent = NO;
|
||||
//[self applyNavBarAppearance:navBarAppearance];
|
||||
|
||||
|
||||
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC));
|
||||
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
|
||||
if(@available(iOS 13.0, *)) if(ControllerUtil.primaryGCController) [ControllerNavigator restoreUINavigationHighlight];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)appButtonTappedForHost:(TemporaryHost *)host{
|
||||
@@ -411,12 +430,13 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
- (void)launchButtonTappedForHost:(TemporaryHost *)host {
|
||||
if(self.revealViewController.isStreaming) return;
|
||||
_selectedHost = host;
|
||||
if (host.state == StateOnline && host.pairState == PairStatePaired && host.appList.count > 0) {
|
||||
[self closeSettingViewAnimated:NO];
|
||||
// [self switchToAppView];
|
||||
[self updateAppsForHost:_selectedHost];
|
||||
[self prepareToStreamApp:_sortedAppList.firstObject];
|
||||
[self prepareToStreamApp:self.sortedAppList.firstObject];
|
||||
[self performSegueWithIdentifier:@"createStreamFrame" sender:nil];
|
||||
return;
|
||||
}
|
||||
@@ -755,14 +775,16 @@ static NSMutableSet* hostList;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
[self->_discMan discoverHost:hostAddress withCallback:^(TemporaryHost* host, NSString* error){
|
||||
if (host != nil) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self hideLoadingFrame:^{
|
||||
@synchronized(hostList) {
|
||||
[hostList addObject:host];
|
||||
}
|
||||
[self updateHosts];
|
||||
}];
|
||||
});
|
||||
[alertController dismissViewControllerAnimated:false completion:^{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self hideLoadingFrame:^{
|
||||
@synchronized(hostList) {
|
||||
[hostList addObject:host];
|
||||
}
|
||||
[self updateHosts];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
} else {
|
||||
unsigned int portTestResults = LiTestClientConnectivity([host.activeAddress UTF8String], 443,
|
||||
ML_PORT_FLAG_TCP_47984 | ML_PORT_FLAG_TCP_47989);
|
||||
@@ -774,13 +796,15 @@ static NSMutableSet* hostList;
|
||||
[Utils addHelpOptionToDialog:hostNotFoundAlert];
|
||||
[hostNotFoundAlert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self hideLoadingFrame:^{
|
||||
if([error isEqualToString:[LocalizationHelper localizedStringForKey:@"Host information updated"]]){
|
||||
DataManager* dataMan = [[DataManager alloc] init];
|
||||
[dataMan updateHost:host];
|
||||
}
|
||||
[[self activeViewController] presentViewController:hostNotFoundAlert animated:YES completion:nil];
|
||||
}];
|
||||
[alertController dismissViewControllerAnimated:true completion:^{
|
||||
[self hideLoadingFrame:^{
|
||||
if([error isEqualToString:[LocalizationHelper localizedStringForKey:@"Host information updated"]]){
|
||||
DataManager* dataMan = [[DataManager alloc] init];
|
||||
[dataMan updateHost:host];
|
||||
}
|
||||
[self presentViewController:hostNotFoundAlert animated:YES completion:nil];
|
||||
}];
|
||||
}];
|
||||
});
|
||||
}
|
||||
}];
|
||||
@@ -990,10 +1014,11 @@ static NSMutableSet* hostList;
|
||||
return quitResponse;
|
||||
}
|
||||
|
||||
- (void)quitRunningApp{
|
||||
- (void)quitApp:(TemporaryApp* )app{
|
||||
if(!app) return;
|
||||
[self showLoadingFrame: ^{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
HttpResponse* quitResponse = [self requestToQuitApp:self->launchedApp];
|
||||
HttpResponse* quitResponse = [self requestToQuitApp:app];
|
||||
// If it fails, display an error and stop the current operation
|
||||
if (quitResponse.statusCode != 200) {
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Quitting App Failed"]
|
||||
@@ -1001,7 +1026,7 @@ static NSMutableSet* hostList;
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateAppsForHost:self->launchedApp.host];
|
||||
[self updateAppsForHost:app.host];
|
||||
[self hideLoadingFrame: ^{
|
||||
[[self activeViewController] presentViewController:alert animated:YES completion:nil];
|
||||
}];
|
||||
@@ -1012,6 +1037,54 @@ static NSMutableSet* hostList;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)quitLaunchedApp {
|
||||
[self quitApp:launchedApp];
|
||||
}
|
||||
|
||||
- (void)quitRunningAppAndStart:(TemporaryApp *)app {
|
||||
TemporaryApp* currentRunningApp = [self findRunningApp:app.host];
|
||||
[self showLoadingFrame: ^{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
HttpResponse* quitResponse = [self requestToQuitApp:app];
|
||||
// If it fails, display an error and stop the current operation
|
||||
if (quitResponse.statusCode != 200) {
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Quitting App Failed"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"Failed to quit app. If this app was started by another device, you'll need to quit from that device."]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Ok"] style:UIAlertActionStyleDefault handler:nil]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateAppsForHost:app.host];
|
||||
[self hideLoadingFrame: ^{
|
||||
[[self activeViewController] presentViewController:alert animated:YES completion:nil];
|
||||
}];
|
||||
});
|
||||
}
|
||||
else {
|
||||
app.host.currentGame = @"0";
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// If it succeeds and we're to start streaming, segue to the stream
|
||||
if (![app.id isEqualToString:currentRunningApp.id]) {
|
||||
[self prepareToStreamApp:app];
|
||||
[self hideLoadingFrame: ^{
|
||||
[self performSegueWithIdentifier:@"createStreamFrame" sender:nil];
|
||||
}];
|
||||
}
|
||||
else {
|
||||
// Otherwise, just hide the loading icon
|
||||
[self hideLoadingFrame:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)launchApp:(TemporaryApp *)app {
|
||||
if(self.revealViewController.isStreaming) return;
|
||||
[self prepareToStreamApp:app];
|
||||
[self performSegueWithIdentifier:@"createStreamFrame" sender:nil];
|
||||
}
|
||||
|
||||
- (void)appLongClicked:(TemporaryApp *)app view:(UIView *)view {
|
||||
Log(LOG_D, @"Long clicked app: %@", app.name);
|
||||
|
||||
@@ -1188,15 +1261,15 @@ static NSMutableSet* hostList;
|
||||
countdown:6
|
||||
action:^{}
|
||||
completion:^{
|
||||
[self openGameProfileSeletor:false];
|
||||
[self openGameProfileSeletorWithAnimated:false];
|
||||
}];
|
||||
}
|
||||
else [self openGameProfileSeletor:false];
|
||||
else [self openGameProfileSeletorWithAnimated:false];
|
||||
}
|
||||
|
||||
- (void)openGameProfileSeletor:(bool)animated {
|
||||
if(settingsViewController){
|
||||
[settingsViewController mainFrameGameProfileButtonTapped:animated];
|
||||
- (void)openGameProfileSeletorWithAnimated:(bool)animated {
|
||||
if(self.settingsViewController){
|
||||
[self.settingsViewController mainFrameGameProfileButtonTapped:animated];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1235,11 +1308,17 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position {
|
||||
settingsViewController = (SettingsViewController*)[revealController rearViewController];
|
||||
revealController.navBarMenuDelegate = settingsViewController;
|
||||
self.settingsViewController = (SettingsViewController*)[revealController rearViewController];
|
||||
revealController.navBarMenuDelegate = self.settingsViewController;
|
||||
|
||||
_settingsViewExpanded = position != FrontViewPositionLeft;
|
||||
if (position == FrontViewPositionLeft) {
|
||||
if (@available(iOS 26.0, *)) {
|
||||
if (@available(iOS 13.0, *)){
|
||||
self.settingsExpandedInStreamView = false;
|
||||
if(!self.revealViewController.isStreaming) [ControllerNavigator setUINavigationDelegate: self.isInAppView ? self : self.hostCollectionVC];
|
||||
else [ControllerNavigator setUINavigationDelegate: [StreamFrameViewController sharedInstance]];
|
||||
}
|
||||
if (@available(iOS 26.0, *)) {
|
||||
_settingsButton.sharesBackground = false;
|
||||
_profilesButton.sharesBackground = false;
|
||||
}
|
||||
@@ -1255,11 +1334,13 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(self.revealViewController.isStreaming) self.settingsExpandedInStreamView = true; //notify mainFrameViewContorller that this is a setting expansion in stream view, some settings shall be disabled.
|
||||
if (@available(iOS 13.0, *)) [ControllerNavigator setUINavigationDelegate:self.settingsViewController];
|
||||
self.navigationItem.leftBarButtonItems = @[_profilesButton];
|
||||
[settingsViewController updateTheme];
|
||||
[self.settingsViewController updateTheme];
|
||||
}
|
||||
|
||||
settingsViewController.mainFrameViewController = self;
|
||||
self.settingsViewController.mainFrameViewController = self;
|
||||
// enable / disable widgets acoordingly: in streamview, disable, outside of streamview, enable.
|
||||
if(self.settingsExpandedInStreamView) [revealController buttonsInStreaming];
|
||||
else [revealController buttonsNotInStreaming];
|
||||
@@ -1268,71 +1349,72 @@ static NSMutableSet* hostList;
|
||||
// TemporarySettings* currentSettings = [dataMan getSettings];
|
||||
|
||||
[streamFrameViewController setUserInteractionEnabledForStreamView:!_settingsExpandedInStreamView || position == FrontViewPositionLeft];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.resolutionStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.fpsStack];
|
||||
// [settingsViewController widget:settingsViewController.bitrateSlider setEnabled:!self.settingsExpandedInStreamView];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.optimizeGamesStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.audioOnPcStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.sdrPerformanceWorkaroundStack];
|
||||
// [settingsViewController.touchModeSelector1 setEnabled:!_settingsExpandedInStreamView || !(settingsViewController.touchModeSelector1.selectedSegmentIndex == AbsoluteTouch && !settingsViewController.passthroughGesturesSwitch.isOn)];
|
||||
// [settingsViewController.touchModeSelector2 setEnabled:settingsViewController.touchModeSelector1.enabled];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.resolutionStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.fpsStack];
|
||||
// [self.settingsViewController widget:self.settingsViewController.bitrateSlider setEnabled:!self.settingsExpandedInStreamView];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.optimizeGamesStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.audioOnPcStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.sdrPerformanceWorkaroundStack];
|
||||
// [self.settingsViewController.touchModeSelector1 setEnabled:!_settingsExpandedInStreamView || !(self.settingsViewController.touchModeSelector1.selectedSegmentIndex == AbsoluteTouch && !self.settingsViewController.passthroughGesturesSwitch.isOn)];
|
||||
// [self.settingsViewController.touchModeSelector2 setEnabled:self.settingsViewController.touchModeSelector1.enabled];
|
||||
|
||||
[settingsViewController.codecSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
[self.settingsViewController.codecSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
if(_settingsExpandedInStreamView){
|
||||
[settingsViewController.yuv444Switch setEnabled:NO];
|
||||
[settingsViewController.fullColorRangeSwitch setEnabled:NO];
|
||||
[settingsViewController.hdrSwitch setEnabled:NO];
|
||||
[self.settingsViewController.yuv444Switch setEnabled:NO];
|
||||
[self.settingsViewController.fullColorRangeSwitch setEnabled:NO];
|
||||
[self.settingsViewController.hdrSwitch setEnabled:NO];
|
||||
}
|
||||
else [settingsViewController updateCodecDependentSwitches];
|
||||
else [self.settingsViewController updateCodecDependentSwitches];
|
||||
|
||||
[settingsViewController.gyroModeSelector setEnabled:!_settingsExpandedInStreamView || ![streamFrameViewController shallDisableGyroHotSwitch]];
|
||||
[settingsViewController.emulatedControllerTypeSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.citrixX1MouseStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.externalDisplayModeStack];
|
||||
[self.settingsViewController.gyroModeSelector setEnabled:!_settingsExpandedInStreamView || ![streamFrameViewController shallDisableGyroHotSwitch]];
|
||||
[self.settingsViewController.emulatedControllerTypeSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.citrixX1MouseStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.externalDisplayModeStack];
|
||||
|
||||
if(settingsViewController.audioConfigSelector.numberOfSegments>2){
|
||||
if(self.settingsViewController.audioConfigSelector.numberOfSegments>2){
|
||||
if(_settingsExpandedInStreamView){
|
||||
if(settingsViewController.audioConfigSelector.selectedSegmentIndex>=2){
|
||||
[settingsViewController.audioConfigSelector setEnabled:false];
|
||||
if(self.settingsViewController.audioConfigSelector.selectedSegmentIndex>=2){
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:false];
|
||||
}
|
||||
else {
|
||||
[settingsViewController.audioConfigSelector setEnabled:false forSegmentAtIndex:2];
|
||||
[settingsViewController.audioConfigSelector setEnabled:false forSegmentAtIndex:3];
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:false forSegmentAtIndex:2];
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:false forSegmentAtIndex:3];
|
||||
}
|
||||
}
|
||||
else{
|
||||
[settingsViewController.audioConfigSelector setEnabled:true];
|
||||
[settingsViewController.audioConfigSelector setEnabled:true forSegmentAtIndex:2];
|
||||
[settingsViewController.audioConfigSelector setEnabled:true forSegmentAtIndex:3];
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:true];
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:true forSegmentAtIndex:2];
|
||||
[self.settingsViewController.audioConfigSelector setEnabled:true forSegmentAtIndex:3];
|
||||
}
|
||||
}
|
||||
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.duckOtherAppStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.pipStack];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.appThemeStack];
|
||||
[settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.duckOtherAppStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.pipStack];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView forStack:self.settingsViewController.appThemeStack];
|
||||
[self.settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView];
|
||||
// Enable frame pacing mode selector only if not in stream view AND not in performance mode
|
||||
BOOL shouldEnableFramePacingSelector = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
|
||||
[settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacingSelector];
|
||||
// [settingsViewController.frameTimebaseSwitch setEnabled:shouldEnableFramePacing];
|
||||
[settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacingSelector];
|
||||
[settingsViewController setHidden:_settingsExpandedInStreamView || !(shouldEnableFramePacingSelector && settingsViewController.framePacingModeSelector.selectedSegmentIndex == FramePacingModeQueue) forStack:settingsViewController.frameQueueSizeStack];
|
||||
BOOL shouldEnableFramePacingSelector = !_settingsExpandedInStreamView && (self.settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
|
||||
[self.settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacingSelector];
|
||||
// [self.settingsViewController.frameTimebaseSwitch setEnabled:shouldEnableFramePacing];
|
||||
[self.settingsViewController.asyncFrameDequeueSwitch setEnabled:shouldEnableFramePacingSelector];
|
||||
[self.settingsViewController setHidden:_settingsExpandedInStreamView || !(shouldEnableFramePacingSelector && self.settingsViewController.framePacingModeSelector.selectedSegmentIndex == FramePacingModeQueue) forStack:self.settingsViewController.frameQueueSizeStack];
|
||||
|
||||
// Disable mic switch if sunshine does not support mic redirection
|
||||
[settingsViewController.redirectMicSwitch setEnabled:!_settingsExpandedInStreamView||streamFrameViewController.micStreamInitialized];
|
||||
if(_settingsExpandedInStreamView && !streamFrameViewController.micStreamInitialized) [settingsViewController.redirectMicSwitch setOn:false];
|
||||
[settingsViewController setHidden:!settingsViewController.redirectMicSwitch.isOn forStack:settingsViewController.useBuiltinMicStack];
|
||||
[settingsViewController.useBuiltinMicSwitch setEnabled:!_settingsExpandedInStreamView];
|
||||
// [settingsViewController.passthroughGesturesSwitch setEnabled:!_settingsExpandedInStreamView];
|
||||
[self.settingsViewController.redirectMicSwitch setEnabled:!_settingsExpandedInStreamView||streamFrameViewController.micStreamInitialized];
|
||||
if(_settingsExpandedInStreamView && !streamFrameViewController.micStreamInitialized) [self.settingsViewController.redirectMicSwitch setOn:false];
|
||||
[self.settingsViewController setHidden:!self.settingsViewController.redirectMicSwitch.isOn forStack:self.settingsViewController.useBuiltinMicStack];
|
||||
[self.settingsViewController.useBuiltinMicSwitch setEnabled:!_settingsExpandedInStreamView];
|
||||
// [self.settingsViewController.passthroughGesturesSwitch setEnabled:!_settingsExpandedInStreamView];
|
||||
}
|
||||
|
||||
- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position {
|
||||
// If we moved back to the center position, we should save the settings
|
||||
SettingsViewController* settingsViewController = (SettingsViewController*)[revealController rearViewController];
|
||||
settingsViewController.mainFrameViewController = self;
|
||||
self.settingsViewController = (SettingsViewController*)[revealController rearViewController];
|
||||
self.settingsViewController.mainFrameViewController = self;
|
||||
|
||||
if (position == FrontViewPositionLeft) {
|
||||
[settingsViewController saveSettings];
|
||||
if (@available(iOS 13.0, *)) [ControllerNavigator persistUINavigationHighlight];
|
||||
[self.settingsViewController saveSettings];
|
||||
_settingsButton.enabled = YES; // make sure these 2 buttons are enabled after closing setting view.
|
||||
_upButton.enabled = YES; // here is the select new host button
|
||||
}
|
||||
@@ -1343,7 +1425,7 @@ static NSMutableSet* hostList;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[self appClicked:_sortedAppList[indexPath.row] view:nil];
|
||||
[self appClicked:self.sortedAppList[indexPath.row] view:nil];
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1446,10 +1528,6 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
|
||||
- (bool)isIPhone{
|
||||
return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
|
||||
}
|
||||
|
||||
- (bool)isIPhonePortrait{
|
||||
bool isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
|
||||
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
|
||||
@@ -1492,7 +1570,9 @@ static NSMutableSet* hostList;
|
||||
|
||||
button.titleLabel.font = [UIFont systemFontOfSize:liquidGlassEnabled ? 16 : 16 weight:UIFontWeightMedium];
|
||||
// 文字颜色设置为 tintColor 控制
|
||||
if(liquidGlassEnabled) button.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0.9, 0);
|
||||
if(liquidGlassEnabled) {
|
||||
button.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0.9, 0);
|
||||
}
|
||||
button.tintColor = liquidGlassEnabled ? ThemeManager.appPrimaryColor : UIColor.whiteColor;
|
||||
[button setTitleColor:button.tintColor forState:UIControlStateNormal];
|
||||
// button.tintColor = UIColor.whiteColor;
|
||||
@@ -1511,7 +1591,7 @@ static NSMutableSet* hostList;
|
||||
|
||||
// 创建 UIBarButtonItem
|
||||
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
|
||||
|
||||
if (@available(iOS 26.0, *)) barItem.sharesBackground = true;
|
||||
return barItem;
|
||||
}
|
||||
|
||||
@@ -1548,6 +1628,9 @@ static NSMutableSet* hostList;
|
||||
|
||||
// 创建 UIBarButtonItem
|
||||
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
|
||||
|
||||
if (@available(iOS 26.0, *)) barItem.sharesBackground = true;
|
||||
|
||||
return barItem;
|
||||
}
|
||||
|
||||
@@ -1574,6 +1657,60 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyThemeToNavigationButton:(UIBarButtonItem *)barButtonItem {
|
||||
if (!barButtonItem) return;
|
||||
|
||||
barButtonItem.tintColor = ThemeManager.appPrimaryColor;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIUserInterfaceStyle style = ThemeManager.userInterfaceStyle;
|
||||
UIView *customView = barButtonItem.customView;
|
||||
customView.overrideUserInterfaceStyle = style;
|
||||
|
||||
if ([customView isKindOfClass:UIButton.class]) {
|
||||
UIButton *button = (UIButton *)customView;
|
||||
button.tintColor = ThemeManager.appPrimaryColor;
|
||||
[button setTitleColor:button.tintColor forState:UIControlStateNormal];
|
||||
[button setTitleColor:ThemeManager.textColorGray forState:UIControlStateHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
if (barButtonItem == _addHostButton && [barButtonItem.customView isKindOfClass:UIButton.class]) {
|
||||
UIButton *button = (UIButton *)barButtonItem.customView;
|
||||
button.backgroundColor = PublicUtils.liquidGlassEnabled ? UIColor.clearColor : ThemeManager.appPrimaryColor;
|
||||
button.tintColor = PublicUtils.liquidGlassEnabled ? ThemeManager.appPrimaryColor : UIColor.whiteColor;
|
||||
[button setTitleColor:button.tintColor forState:UIControlStateNormal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyThemeToNavigationControls {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIUserInterfaceStyle style = ThemeManager.userInterfaceStyle;
|
||||
self.overrideUserInterfaceStyle = style;
|
||||
self.view.overrideUserInterfaceStyle = style;
|
||||
self.navigationController.overrideUserInterfaceStyle = style;
|
||||
self.navigationController.view.overrideUserInterfaceStyle = style;
|
||||
self.navigationController.navigationBar.overrideUserInterfaceStyle = style;
|
||||
self.navigationItem.titleView.overrideUserInterfaceStyle = style;
|
||||
}
|
||||
|
||||
NSMutableArray<UIBarButtonItem *> *barButtonItems = [NSMutableArray array];
|
||||
if (self.navigationItem.leftBarButtonItems) [barButtonItems addObjectsFromArray:self.navigationItem.leftBarButtonItems];
|
||||
if (self.navigationItem.rightBarButtonItems) [barButtonItems addObjectsFromArray:self.navigationItem.rightBarButtonItems];
|
||||
if (_settingsButton) [barButtonItems addObject:_settingsButton];
|
||||
if (_profilesButton) [barButtonItems addObject:_profilesButton];
|
||||
if (_addHostButton) [barButtonItems addObject:_addHostButton];
|
||||
if (_helpButton) [barButtonItems addObject:_helpButton];
|
||||
if (_upButton) [barButtonItems addObject:_upButton];
|
||||
|
||||
for (UIBarButtonItem *barButtonItem in barButtonItems) {
|
||||
[self applyThemeToNavigationButton:barButtonItem];
|
||||
if (@available(iOS 26.0, *)) {
|
||||
barButtonItem.sharesBackground = barButtonItem == _addHostButton || barButtonItem == _helpButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupNavBar{
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UINavigationBarAppearance* appearance = [[UINavigationBarAppearance alloc] init];
|
||||
@@ -1645,14 +1782,15 @@ static NSMutableSet* hostList;
|
||||
_profilesButton.tintColor = ThemeManager.appPrimaryColor;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
[_profilesButton setTitle:[LocalizationHelper localizedStringForKey:@"Game Profile"]];
|
||||
}
|
||||
|
||||
if (@available(iOS 26.0, *)) {
|
||||
_settingsButton.sharesBackground = false;
|
||||
_profilesButton.sharesBackground = false;
|
||||
_settingsButton.sharesBackground = false;
|
||||
_profilesButton.sharesBackground = false;
|
||||
_addHostButton.sharesBackground = true;
|
||||
_helpButton.sharesBackground = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1692,15 +1830,20 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
_settingsButton.tintColor = ThemeManager.appPrimaryColor;
|
||||
_profilesButton.tintColor = ThemeManager.appPrimaryColor;
|
||||
_upButton.tintColor = ThemeManager.appPrimaryColor;
|
||||
((UIButton*)_addHostButton.customView).backgroundColor = PublicUtils.liquidGlassEnabled ? UIColor.clearColor : ThemeManager.appPrimaryColor;
|
||||
((UIButton*)_helpButton.customView).tintColor = ThemeManager.appPrimaryColor;
|
||||
[self applyThemeToNavigationControls];
|
||||
|
||||
[self applyNavBarAppearance];
|
||||
[self updateTitle];
|
||||
if (hostViewTitleLabel) {
|
||||
hostViewTitleLabel.textColor = ThemeManager.textColor;
|
||||
}
|
||||
|
||||
if (@available(iOS 13.0, *)) [GamepadNavigationIllustrationHud updateCurrentTheme];
|
||||
|
||||
[self.hostCollectionVC updateTheme];
|
||||
}
|
||||
|
||||
@@ -1847,8 +1990,8 @@ static NSMutableSet* hostList;
|
||||
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
|
||||
//[self updateResolutionAccordingly];
|
||||
|
||||
// SettingsViewController* settingsViewController = (SettingsViewController*)[self.revealViewController rearViewController];
|
||||
// [settingsViewController updateResolutionTable];
|
||||
// SettingsViewController* self.settingsViewController = (SettingsViewController*)[self.revealViewController rearViewController];
|
||||
// [self.settingsViewController updateResolutionTable];
|
||||
|
||||
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleMenuResize:)];
|
||||
longPress.delaysTouchesBegan = false;
|
||||
@@ -1856,7 +1999,7 @@ static NSMutableSet* hostList;
|
||||
[self.view addGestureRecognizer:longPress];
|
||||
|
||||
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[self isIPhone]?@"iPhone":@"iPad" bundle:nil];
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:PublicUtils.isIPhone?@"iPhone":@"iPad" bundle:nil];
|
||||
SettingsViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"settingsViewController"];
|
||||
// 强制加载视图
|
||||
__unused UIView *view = viewController.view;
|
||||
@@ -1927,6 +2070,7 @@ static NSMutableSet* hostList;
|
||||
-(void)viewDidLayoutSubviews{
|
||||
[super viewDidLayoutSubviews];
|
||||
[self updateHosts];
|
||||
// if (@available(iOS 13.0, *)) [GamepadNavigationIllustrationHud showInKeyWindow];
|
||||
}
|
||||
|
||||
// this will also be called back when device orientation changes
|
||||
@@ -2007,7 +2151,7 @@ static NSMutableSet* hostList;
|
||||
CGPoint point = [gestureRecognizer locationInView:self.collectionView];
|
||||
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
|
||||
if (indexPath != nil) {
|
||||
[self appLongClicked:_sortedAppList[indexPath.row] view:nil];
|
||||
[self appLongClicked:self.sortedAppList[indexPath.row] view:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2099,7 +2243,8 @@ static NSMutableSet* hostList;
|
||||
// [self setupHostViewTitle];
|
||||
// [self reloadScrollHostView]; //remove this for proper test
|
||||
[self attachWaterMark];
|
||||
|
||||
if (@available(iOS 13.0, *)) [GamepadNavigationIllustrationHud showInKeyWindow];
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
|
||||
[[self revealViewController] setPrimaryViewController:self];
|
||||
@@ -2133,13 +2278,26 @@ static NSMutableSet* hostList;
|
||||
if([self needPopupAboutView])[self helpButtonTapped];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[ControllerNavigator startWithSwapABXY:false];
|
||||
ControllerUtil.delegate = self;
|
||||
[ControllerUtil installControllerObserversIfNeeded];
|
||||
[ControllerNavigator setRadialMenuDelegate:self];
|
||||
DataManager* dataMan = [[DataManager alloc] init];
|
||||
Settings* settings = [dataMan retrieveSettings];
|
||||
ControllerNavigator.enabled = settings.enableControllerNavigation;
|
||||
ControllerNavigator.localRadialMenuButton = (ControllerElement)settings.localRadialMenuButton.intValue;
|
||||
ControllerNavigator.customPositionForLocalRadialMenuButton = (ControllerElementPosition)settings.customLocalRadialMenuButtonPosition.intValue;
|
||||
ControllerNavigator.customPositionForStreamingRadialMenuButton = (ControllerElementPosition)settings.customStreamingRadialMenuButtonPosition.intValue;
|
||||
ControllerNavigator.streamingRadialMenuButton = (ControllerElement)settings.streamingRadialMenuButton.intValue;
|
||||
ControllerNavigator.streamingRadialMenuDelay = (NSTimeInterval)settings.streamingRadialMenuDelay.floatValue;
|
||||
ControllerNavigator.controllerMouseStick = (ControllerElement)settings.controllerMouseStick.intValue;
|
||||
ControllerNavigator.controllerMouseLeftButton = (ControllerElement)settings.controllerMouseLeftButton.intValue;
|
||||
ControllerNavigator.controllerMouseRightButton = (ControllerElement)settings.controllerMouseRightButton.intValue;
|
||||
ControllerNavigator.controllerMouseExpo = settings.controllerMouseExpo.floatValue;
|
||||
if(ControllerNavigator.enabled) [ControllerNavigator start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated{
|
||||
NSLog(@"willDisappear");
|
||||
[super viewWillDisappear:animated];
|
||||
[_foregroundHostUpdateTimer invalidate];
|
||||
_foregroundHostUpdateTimer = nil;
|
||||
@@ -2177,6 +2335,7 @@ static NSMutableSet* hostList;
|
||||
[self.view addSubview:self.collectionView];
|
||||
[self initHostCollection];
|
||||
if(!_enteredAppView) [self switchToHostView];
|
||||
else if (@available(iOS 13.0, *)) [ControllerNavigator setUINavigationDelegate:self];
|
||||
|
||||
[self updateTheme];
|
||||
}
|
||||
@@ -2343,17 +2502,17 @@ static NSMutableSet* hostList;
|
||||
return;
|
||||
}
|
||||
|
||||
_sortedAppList = [host.appList allObjects];
|
||||
_sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)];
|
||||
self.sortedAppList = [host.appList allObjects];
|
||||
self.sortedAppList = [self.sortedAppList sortedArrayUsingSelector:@selector(compareName:)];
|
||||
|
||||
if (!_showHiddenApps) {
|
||||
NSMutableArray* visibleAppList = [NSMutableArray array];
|
||||
for (TemporaryApp* app in _sortedAppList) {
|
||||
for (TemporaryApp* app in self.sortedAppList) {
|
||||
if (!app.hidden) {
|
||||
[visibleAppList addObject:app];
|
||||
}
|
||||
}
|
||||
_sortedAppList = visibleAppList;
|
||||
self.sortedAppList = visibleAppList;
|
||||
}
|
||||
|
||||
[self.collectionView reloadData];
|
||||
@@ -2370,7 +2529,7 @@ static NSMutableSet* hostList;
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath];
|
||||
|
||||
TemporaryApp* app = _sortedAppList[indexPath.row];
|
||||
TemporaryApp* app = self.sortedAppList[indexPath.row];
|
||||
UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self];
|
||||
appView.updateLoopDelegate = (id<AppViewUpdateLoopDelegate>)self;
|
||||
|
||||
@@ -2404,16 +2563,16 @@ static NSMutableSet* hostList;
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
CGSize cellSize;
|
||||
if([self isIPhone]) cellSize.height = 0.365*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
if(PublicUtils.isIPhone) cellSize.height = 0.365*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
else cellSize.height = 0.272*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
TemporaryApp* app = _sortedAppList[indexPath.row];
|
||||
TemporaryApp* app = self.sortedAppList[indexPath.row];
|
||||
UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self];
|
||||
|
||||
cellSize.width = cellSize.height * (appView
|
||||
.bounds.size.width/appView
|
||||
.bounds.size.height);
|
||||
// cardSize.width =
|
||||
|
||||
|
||||
return cellSize;
|
||||
}
|
||||
|
||||
@@ -2422,8 +2581,8 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
if (_selectedHost != nil && _sortedAppList != nil) {
|
||||
return _sortedAppList.count;
|
||||
if (_selectedHost != nil && self.sortedAppList != nil) {
|
||||
return self.sortedAppList.count;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
@@ -2498,7 +2657,7 @@ static NSMutableSet* hostList;
|
||||
double delayInSeconds = 0.02;
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^{
|
||||
[self->settingsViewController hideDynamicLabelsWhenOverlapped:self->settingsViewController.parentStack];
|
||||
[self.settingsViewController hideDynamicLabelsWhenOverlapped:self.settingsViewController.parentStack];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2573,7 +2732,7 @@ static NSMutableSet* hostList;
|
||||
|
||||
- (CGSize)getHostCardSize{
|
||||
CGSize cardSize;
|
||||
if([self isIPhone]) cardSize.height = 0.37*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
if(PublicUtils.isIPhone) cardSize.height = 0.37*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
else cardSize.height = 0.25*MIN(CGRectGetHeight([[UIScreen mainScreen] bounds]),CGRectGetWidth([[UIScreen mainScreen] bounds]));
|
||||
TemporaryHost* dummyHost = [[TemporaryHost alloc] init];
|
||||
HostCardView* dummyCard = [[HostCardView alloc] initWithHost:dummyHost];
|
||||
@@ -2593,7 +2752,7 @@ static NSMutableSet* hostList;
|
||||
|
||||
if(self.hostCollectionVC.view.superview == nil){
|
||||
[self.view addSubview:self.hostCollectionVC.view];
|
||||
CGFloat leftPadding = [self isIPhone] ? 30 : 0;
|
||||
CGFloat leftPadding = PublicUtils.isIPhone ? 30 : 0;
|
||||
self.hostCollectionVC.view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.hostCollectionVC.view.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:0],
|
||||
@@ -2617,6 +2776,53 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controllerNavigatorDidSelectWithItem:(RadialMenuItem)item API_AVAILABLE(ios(13.0)){
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
switch (item) {
|
||||
case RadialMenuItemSettings:
|
||||
[[self revealViewController] revealToggleAnimated:YES];
|
||||
break;
|
||||
case RadialMenuItemAllSettings:
|
||||
[self.revealViewController allSettingSelected];
|
||||
break;
|
||||
case RadialMenuItemFavoriteSettings:
|
||||
[self.revealViewController favoriteSettingSelected];
|
||||
break;
|
||||
case RadialMenuItemGameProfiles:
|
||||
if([GenericUtils isFirstTappingGameProfileSelectorFromMainFrame]){
|
||||
DataManager* dataMan = [[DataManager alloc] init];
|
||||
Settings* settings = [dataMan retrieveSettings];
|
||||
NSString* edgeSide = settings.slideToSettingsScreenEdge.intValue != UIRectEdgeLeft ? [LocalizationHelper localizedStringForKey:@"left"] : [LocalizationHelper localizedStringForKey:@"right"];
|
||||
NSString* slideDist = [NSString stringWithFormat:@"%d%%", (int)(settings.slideToSettingsDistance.floatValue*100)];
|
||||
[AlertControllerUtil showAlertIn:self
|
||||
title:[LocalizationHelper localizedStringForKey:@"Game Profile"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"gameProfileIntroduction", edgeSide, slideDist]
|
||||
withCancel:NO
|
||||
buttonTitle:[LocalizationHelper localizedStringForKey:@"Got it!"]
|
||||
countdown:6
|
||||
action:^{}
|
||||
completion:^{
|
||||
if(!self.gameProfileSelectorVC && !self.settingsViewController.layoutOnScreenControlsVC) [self openGameProfileSeletorWithAnimated:true];
|
||||
}];
|
||||
}
|
||||
else if(!self.gameProfileSelectorVC && !self.settingsViewController.layoutOnScreenControlsVC) [self openGameProfileSeletorWithAnimated:true];
|
||||
else if (self.gameProfileSelectorVC) [self.gameProfileSelectorVC.oscProfilesTableViewController dismissViewControllerAnimated:true completion:^{}];
|
||||
else if (self.settingsViewController.layoutOnScreenControlsVC) [self.settingsViewController.layoutOnScreenControlsVC.oscProfilesTableViewController dismissViewControllerAnimated:true completion:^{}];
|
||||
break;
|
||||
case RadialMenuItemHostView:
|
||||
[self switchToHostView];
|
||||
break;
|
||||
case RadialMenuItemAddHost:
|
||||
[self addHostTapped];
|
||||
break;
|
||||
case RadialMenuItemAboutView:
|
||||
[self helpButtonTapped];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)controllerNavigatorDidSelectSettings {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@@ -2626,9 +2832,9 @@ static NSMutableSet* hostList;
|
||||
|
||||
- (void)controllerNavigatorDidSelectGameProfiles {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if(!self.gameProfileSelectorVC && !self->settingsViewController.layoutOnScreenControlsVC) [self openGameProfileSeletor:true];
|
||||
if(!self.gameProfileSelectorVC && !self.settingsViewController.layoutOnScreenControlsVC) [self openGameProfileSeletorWithAnimated:true];
|
||||
else if (self.gameProfileSelectorVC) [self.gameProfileSelectorVC.oscProfilesTableViewController dismissViewControllerAnimated:true completion:^{}];
|
||||
else if (self->settingsViewController.layoutOnScreenControlsVC) [self->settingsViewController.layoutOnScreenControlsVC.oscProfilesTableViewController dismissViewControllerAnimated:true completion:^{}];
|
||||
else if (self.settingsViewController.layoutOnScreenControlsVC) [self.settingsViewController.layoutOnScreenControlsVC.oscProfilesTableViewController dismissViewControllerAnimated:true completion:^{}];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2640,4 +2846,3 @@ static NSMutableSet* hostList;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -243,6 +243,9 @@ typedef NS_ENUM(NSInteger, SWRevealToggleAnimationType)
|
||||
// Thus, you can disable user interactions on your frontViewController view without affecting the tap recognizer.
|
||||
- (UITapGestureRecognizer*)tapGestureRecognizer;
|
||||
|
||||
- (void)favoriteSettingSelected;
|
||||
- (void)allSettingSelected;
|
||||
|
||||
/* The following properties are provided for further customization, they are set to default values on initialization,
|
||||
you do not generally have to set them */
|
||||
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "CustomEdgeSlideGestureRecognizer.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
#import "SWRevealViewController.h"
|
||||
|
||||
@class LayoutOnScreenControlsViewController;
|
||||
@class MainFrameViewController;
|
||||
@class MicHandler;
|
||||
@class WidgetPickerViewController;
|
||||
@protocol WidgetPickerViewControllerDelegate;
|
||||
|
||||
@interface SettingsViewController : UIViewController <RearNavigationBarMenuDelegate, MenuSectionDelegate, UITextFieldDelegate, WidgetPickerViewControllerDelegate>
|
||||
@interface SettingsViewController : UIViewController <RearNavigationBarMenuDelegate, UITextFieldDelegate>
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UINavigationBar *navigationBar;
|
||||
@property (strong, nonatomic) UIStackView *parentStack;
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *resolutionStack;
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *resolutionSelectorStack;
|
||||
@property (weak, nonatomic) IBOutlet UIStackView *customResolutionStack;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *fpsStack;
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *bitrateStack;
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *touchModeStack;
|
||||
@@ -103,7 +103,8 @@
|
||||
@property (strong, nonatomic) IBOutlet UILabel *mousePointerVelocityFactorUILabel;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *unlockDisplayOrientationSelector;
|
||||
@property (weak, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC;
|
||||
@property (nonatomic, strong) MainFrameViewController *mainFrameViewController;
|
||||
@property (nonatomic, weak) MainFrameViewController *mainFrameViewController;
|
||||
@property (strong, nonatomic) UIView *controllerNavigationHighlightOverlayView;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *localMousePointerModeSelector;
|
||||
@@ -231,8 +232,11 @@
|
||||
@property (weak, nonatomic) IBOutlet UIStackView *softKeyboardHeightStack;
|
||||
@property (weak, nonatomic) IBOutlet UISwitch *softKeyboardHeightSwitch;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *controllerToMouseStack;
|
||||
@property (strong, nonatomic) IBOutlet UISwitch *controllerToMouseSwitch;
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *controllerNavigationStack;
|
||||
@property (strong, nonatomic) IBOutlet UISwitch *controllerNavigationSwitch;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIStackView *streamingRadialMenuDelayStack;
|
||||
@property (weak, nonatomic) IBOutlet UISlider *streamingRadialMenuDelaySlider;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *controllerMouseVelocityStack;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *controllerMouseVelocitySlider;
|
||||
@@ -307,11 +311,13 @@
|
||||
|
||||
// This is okay because it's just an enum and access uses @available checks
|
||||
@property(nonatomic) UIUserInterfaceStyle overrideUserInterfaceStyle;
|
||||
@property(nonatomic, readonly) SettingsMenuMode currentSettingsMenuMode;
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (bool)hdrSupported;
|
||||
- (void)saveSettings;
|
||||
- (void)saveFavoriteSettingStackIdentifiers;
|
||||
+ (bool)isLandscapeNow;
|
||||
- (void)updateResolutionTable;
|
||||
- (void)widget:(UIView*)widget setEnabled:(bool)enabled;
|
||||
@@ -320,5 +326,7 @@
|
||||
- (void)setHidden:(BOOL)hidden forStack:(UIStackView* )stack;
|
||||
- (void)updateCodecDependentSwitches;
|
||||
- (void)mainFrameGameProfileButtonTapped:(bool)animated;
|
||||
- (void)addSettingToFavorite:(UIStackView* )settingStack;
|
||||
- (void)expandGamepadSection;
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
//
|
||||
|
||||
#import "SettingsViewController.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "CustomEdgeSlideGestureRecognizer.h"
|
||||
#import "TemporarySettings.h"
|
||||
#import "DataManager.h"
|
||||
#import "VoidLink-Swift.h"
|
||||
@@ -20,6 +22,10 @@
|
||||
|
||||
#import "LocalizationHelper.h"
|
||||
|
||||
@interface SettingsViewController () <MenuSectionDelegate, WidgetPickerViewControllerDelegate>
|
||||
@property(nonatomic, assign) SettingsMenuMode currentSettingsMenuMode;
|
||||
@end
|
||||
|
||||
@implementation SettingsViewController {
|
||||
TemporarySettings* tempSettings;
|
||||
DataManager* dataMan;
|
||||
@@ -41,7 +47,6 @@
|
||||
NSMutableDictionary *_settingStackDict;
|
||||
NSMutableArray *_favoriteSettingStackIdentifiers;
|
||||
bool settingStackWillBeRelocatedToLowestPosition;
|
||||
uint8_t currentSettingsMenuMode;
|
||||
UIView *snapshot;
|
||||
UIStackView* capturedStack;
|
||||
CADisplayLink *_autoScrollDisplayLink;
|
||||
@@ -54,11 +59,15 @@
|
||||
MenuSectionView *otherSection;
|
||||
MenuSectionView *experimentalSection;
|
||||
NSMutableSet* hiddenStacks;
|
||||
|
||||
// ControllerNavigator UI navigation state
|
||||
UIView *_controllerNavigationHighlightOverlayView;
|
||||
|
||||
GCController *capturedController;
|
||||
}
|
||||
|
||||
@dynamic overrideUserInterfaceStyle;
|
||||
@synthesize controllerNavigationHighlightOverlayView = _controllerNavigationHighlightOverlayView;
|
||||
|
||||
|
||||
//static NSString* bitrateFormat;
|
||||
@@ -373,7 +382,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
self.controllerGyroSwitchButtonSetter.selectedSegmentIndex = oscProfile.controllerGyroSwitchMode;
|
||||
if(self.controllerGyroSwitchButtonSetter.selectedSegmentIndex != ControllerGyroSwitchDisabled){
|
||||
bool bothButtonsSet = oscProfile.controllerGyroSwitchHold != ControllerButtonNull && oscProfile.controllerGyroSwitchToggle != ControllerButtonNull;
|
||||
bool bothButtonsSet = oscProfile.controllerGyroSwitchHold != ControllerElementNull && oscProfile.controllerGyroSwitchToggle != ControllerElementNull;
|
||||
[self findDynamicLabelFromStack:self.controllerGyroSwitchButtonStack].text = bothButtonsSet ? [LocalizationHelper localizedStringForKey:@" both set "] : @"";
|
||||
}
|
||||
[self setHidden:oscProfile.controllerGyroSwitchMode==ControllerGyroSwitchDisabled forStack:self.reverseHoldButtonStack];
|
||||
@@ -556,7 +565,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
else dispatch_async(dispatch_get_main_queue(), ^{[self widget:self.bitrateSlider setEnabled:true];});
|
||||
});
|
||||
|
||||
if(currentSettingsMenuMode == AllSettings && MenuSectionView.overridePersistedFoldState){
|
||||
if(self.currentSettingsMenuMode == AllSettings && MenuSectionView.overridePersistedFoldState){
|
||||
for(UIView *subview in _parentStack.arrangedSubviews){
|
||||
if([subview isKindOfClass:[MenuSectionView class]]){
|
||||
MenuSectionView* section = (MenuSectionView* )subview;
|
||||
@@ -619,11 +628,16 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
[self touchModeChanged:self.touchModeSelector1]; // a special fix for iOS 14 to set hidden for the "enableOswStack"
|
||||
|
||||
if(!settingsViewAlreadyAppeared && ![self contentOffsetRestored]) _scrollView.contentOffset = CGPointMake(_scrollView.contentOffset.x, tempSettings.settingsMenuOffset.floatValue);
|
||||
if(!settingsViewAlreadyAppeared) {
|
||||
if(![self contentOffsetRestored]) _scrollView.contentOffset = CGPointMake(_scrollView.contentOffset.x, tempSettings.settingsMenuOffset.floatValue);
|
||||
if(@available(iOS 13.0, *)) if(ControllerUtil.primaryGCController) [self restoreControllerNavigationHighlight];
|
||||
}
|
||||
_scrollView.hidden = false;
|
||||
|
||||
|
||||
settingsViewJustExpanded = false;
|
||||
settingsViewAlreadyAppeared = true;
|
||||
|
||||
if (@available(iOS 13.0, *)) if(ControllerNavigator.radialMenuView.superview) [ControllerNavigator updateRadialMenu];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated{
|
||||
@@ -637,11 +651,12 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
bool unlockDisplayOrientationFlipped = tempSettings.unlockDisplayOrientation != (_unlockDisplayOrientationSelector.selectedSegmentIndex == 1);
|
||||
if(unlockDisplayOrientationFlipped) [_mainFrameViewController setNeedsUpdateAllowedOrientation]; // handle allow portratit on & off
|
||||
if (@available(iOS 13.0, *)) if(ControllerNavigator.radialMenuView.superview) [ControllerNavigator updateRadialMenu];
|
||||
}
|
||||
|
||||
|
||||
- (SettingsMenuMode)getSettingsMenuMode{
|
||||
return currentSettingsMenuMode;
|
||||
return self.currentSettingsMenuMode;
|
||||
}
|
||||
|
||||
- (void)edgeSwiped {
|
||||
@@ -666,7 +681,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
if(!_parentStack.superview){
|
||||
[self.scrollView addSubview:_parentStack];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_parentStack.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor constant: currentSettingsMenuMode == AllSettings ? GenericUtils.settingsMenuNavigationBarHeight : GenericUtils.settingsMenuNavigationBarHeight+10],
|
||||
[_parentStack.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor constant: self.currentSettingsMenuMode == AllSettings ? GenericUtils.settingsMenuNavigationBarHeight : GenericUtils.settingsMenuNavigationBarHeight+10],
|
||||
[_parentStack.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor constant:-20],
|
||||
]];
|
||||
}
|
||||
@@ -841,7 +856,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[videoSection setSectionWithIcon:[UIImage systemImageNamed:@"waveform"] size:13 sizeConstraint:-15];
|
||||
}
|
||||
|
||||
self.resolutionStack.hasDynamicLabel = YES;
|
||||
[self addDynamicLabelForStack:self.resolutionSelectorStack];
|
||||
[self addSetting:self.resolutionStack ofId:@"resolutionStack" to:videoSection];
|
||||
|
||||
[self addSetting:self.fpsStack ofId:@"fpsStack" to:videoSection];
|
||||
@@ -933,6 +948,20 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[controllerSection setSectionWithIcon:[UIImage systemImageNamed:@"gamecontroller"] size:30 sizeConstraint:-10];
|
||||
}
|
||||
|
||||
self.controllerNavigationStack.hasInfoTag = YES;
|
||||
self.controllerNavigationStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerNavigationStack ofId:@"controllerNavigationStack" to:controllerSection];
|
||||
|
||||
self.streamingRadialMenuDelayStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.streamingRadialMenuDelayStack ofId:@"streamingRadialMenuDelayStack" to:controllerSection];
|
||||
|
||||
self.controllerMouseVelocityStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerMouseVelocityStack ofId:@"controllerMouseVelocityStack" to:controllerSection];
|
||||
|
||||
self.controllerMouseExpoStack.hasInfoTag = YES;
|
||||
self.controllerMouseExpoStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerMouseExpoStack ofId:@"controllerMouseExpoStack" to:controllerSection];
|
||||
|
||||
[self addSetting:self.swapAbxyStack ofId:@"swapAbaxyStack" to:controllerSection];
|
||||
[self addSetting:self.hapticEngineStack ofId:@"hapticEngineStack" to:controllerSection];
|
||||
@@ -960,17 +989,6 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
self.rightStickMinOffsetStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.rightStickMinOffsetStack ofId:@"rightStickMinOffsetStack" to:controllerSection];
|
||||
|
||||
self.controllerToMouseStack.hasInfoTag = YES;
|
||||
self.controllerToMouseStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerToMouseStack ofId:@"controllerToMouseStack" to:controllerSection];
|
||||
|
||||
self.controllerMouseVelocityStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerMouseVelocityStack ofId:@"controllerMouseVelocityStack" to:controllerSection];
|
||||
|
||||
self.controllerMouseExpoStack.hasInfoTag = YES;
|
||||
self.controllerMouseExpoStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.controllerMouseExpoStack ofId:@"controllerMouseExpoStack" to:controllerSection];
|
||||
|
||||
[controllerSection addToParentStack:_parentStack];
|
||||
|
||||
|
||||
@@ -1203,7 +1221,6 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
// self.touchMoveEventIntervalStack.hasDynamicLabel = YES;
|
||||
// [self addSetting:self.touchMoveEventIntervalStack ofId:@"touchMoveEventIntervalStack" to:experimentalSection];
|
||||
|
||||
self.relativeTouchSlideThresholdStack.hasInfoTag = YES;
|
||||
self.relativeTouchSlideThresholdStack.hasDynamicLabel = YES;
|
||||
[self addSetting:self.relativeTouchSlideThresholdStack ofId:@"relativeTouchSlideThresholdStack" to:experimentalSection];
|
||||
|
||||
@@ -1228,6 +1245,11 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[experimentalSection addToParentStack:_parentStack];
|
||||
}
|
||||
|
||||
- (void)expandGamepadSection {
|
||||
if(_currentSettingsMenuMode == FavoriteSettings) [self switchToAllSettings];
|
||||
[controllerSection setExpanded:true];
|
||||
}
|
||||
|
||||
- (void)handleAutoScroll:(CGPoint)location{
|
||||
bool scrollDown = location.y > self.view.bounds.size.height - 100;
|
||||
bool scrollUp = location.y < 150;
|
||||
@@ -1432,7 +1454,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture {
|
||||
CGPoint locationInParentStack = [gesture locationInView:_parentStack];
|
||||
CGPoint locationInRootView = [gesture locationInView:self.view.superview];
|
||||
if(currentSettingsMenuMode == AllSettings &&gesture.state == UIGestureRecognizerStateBegan) {
|
||||
if(self.currentSettingsMenuMode == AllSettings &&gesture.state == UIGestureRecognizerStateBegan) {
|
||||
[self findCapturedStackByTouchLocation:locationInParentStack];
|
||||
if(capturedStack == nil) return;
|
||||
[self highlightedBackgroundForView:capturedStack animateWithDuration:0 completion:nil];
|
||||
@@ -1452,7 +1474,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
_autoScrollDisplayLink = nil;
|
||||
}
|
||||
|
||||
if(currentSettingsMenuMode == FavoriteSettings){
|
||||
if(self.currentSettingsMenuMode == FavoriteSettings){
|
||||
switch (gesture.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
// 创建快照视图
|
||||
@@ -1517,7 +1539,9 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
if([_favoriteSettingStackIdentifiers containsObject:settingStack.accessibilityIdentifier]) return;
|
||||
|
||||
[_favoriteSettingStackIdentifiers addObject:settingStack.accessibilityIdentifier];
|
||||
if(settingStack.accessibilityIdentifier) [_favoriteSettingStackIdentifiers addObject:settingStack.accessibilityIdentifier];
|
||||
else if(settingStack.superview.accessibilityIdentifier) [_favoriteSettingStackIdentifiers addObject:settingStack.superview.accessibilityIdentifier];
|
||||
|
||||
for(NSString *identifier in _favoriteSettingStackIdentifiers){
|
||||
NSLog(@"favorite setting: %@", identifier);
|
||||
}
|
||||
@@ -1590,6 +1614,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
NSString* tipText = @"";
|
||||
NSString* onlineDocLink = @"";
|
||||
bool showOnlineDocAction = false;
|
||||
|
||||
|
||||
tipText = @"";
|
||||
if([sender.superview.accessibilityIdentifier isEqualToString: @"bitrateStack"]){
|
||||
@@ -1722,9 +1747,9 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
tipText = [LocalizationHelper localizedStringForKey:@"controllerGyroSwitchButtonStackTip", [ControllerUtil stringFor:oscProfile.controllerGyroSwitchToggle], [ControllerUtil stringFor:oscProfile.controllerGyroSwitchHold]];
|
||||
showOnlineDocAction = false;
|
||||
}
|
||||
if([sender.superview.accessibilityIdentifier isEqualToString: @"controllerToMouseStack"]){
|
||||
if([sender.superview.accessibilityIdentifier isEqualToString: @"controllerNavigationStack"]){
|
||||
tempSettings = [dataMan getSettings];
|
||||
tipText = [LocalizationHelper localizedStringForKey:@"controllerToMouseStackTip", [ControllerUtil stringFor:tempSettings.controllerMouseSwitch.intValue], [LocalizationHelper localizedStringForKey:tempSettings.controllerMouseStick.intValue == LeftStickToMouse ? @"Left stick" : @"Right stick"], [ControllerUtil stringFor:tempSettings.controllerMouseLeftButton.intValue], [ControllerUtil stringFor:tempSettings.controllerMouseRightButton.intValue]];
|
||||
tipText = [LocalizationHelper localizedStringForKey:@"controllerNavigationStackTip", [ControllerUtil stringFor:tempSettings.localRadialMenuButton.intValue], [ControllerUtil stringFor:tempSettings.streamingRadialMenuButton.intValue], [ControllerUtil stringFor:tempSettings.controllerMouseStick.intValue], [ControllerUtil stringFor:tempSettings.controllerMouseLeftButton.intValue], [ControllerUtil stringFor:tempSettings.controllerMouseRightButton.intValue]];
|
||||
showOnlineDocAction = false;
|
||||
}
|
||||
if([sender.superview.accessibilityIdentifier isEqualToString: @"reverseHoldButtonStack"]){
|
||||
@@ -1846,7 +1871,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
//switchToAll/Favorite 调用此方法时,这些hiddenStack已身处新的superView中, 可以正常执行hidden = YES
|
||||
for(UIStackView* stack in hiddenStacks) stack.hidden = YES;
|
||||
|
||||
if(currentSettingsMenuMode == AllSettings){
|
||||
if(self.currentSettingsMenuMode == AllSettings){
|
||||
for(MenuSectionView* section in _parentStack.arrangedSubviews) [section updateViewForFoldState];
|
||||
}
|
||||
[self hideDynamicLabelsWhenOverlapped:self.parentStack];
|
||||
@@ -1857,7 +1882,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
for(UIStackView* stack in hiddenStacks) {
|
||||
stack.hidden = NO;
|
||||
}
|
||||
if(currentSettingsMenuMode == AllSettings){
|
||||
if(self.currentSettingsMenuMode == AllSettings){
|
||||
for(UIView* view in _parentStack.arrangedSubviews){
|
||||
if([view isKindOfClass:[MenuSectionView class]]){
|
||||
MenuSectionView* section = (MenuSectionView* )view;
|
||||
@@ -1876,20 +1901,16 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
- (void)switchToFavoriteSettings{
|
||||
[self forceRestoreHeightTemporarilyForSettingStackParentView];
|
||||
[_parentStack removeFromSuperview];
|
||||
currentSettingsMenuMode = FavoriteSettings;
|
||||
self.currentSettingsMenuMode = FavoriteSettings;
|
||||
[self initParentStack];
|
||||
[self updateTheme];
|
||||
Settings *currentSettings = [dataMan retrieveSettings];
|
||||
currentSettings.settingsMenuMode = [NSNumber numberWithInteger:currentSettingsMenuMode];
|
||||
currentSettings.settingsMenuMode = [NSNumber numberWithInteger:self.currentSettingsMenuMode];
|
||||
[dataMan saveData];
|
||||
|
||||
_parentStack.spacing = [self isIPhone] ? 10 : 12;
|
||||
|
||||
[self loadFavoriteSettingStackIdentifiers];
|
||||
for(NSString* settingIdentifier in _favoriteSettingStackIdentifiers){
|
||||
[_parentStack addArrangedSubview:_settingStackDict[settingIdentifier]];
|
||||
}
|
||||
|
||||
for(NSString* settingIdentifier in _favoriteSettingStackIdentifiers){
|
||||
[_parentStack addArrangedSubview:_settingStackDict[settingIdentifier]];
|
||||
}
|
||||
@@ -1903,11 +1924,20 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
[self hideDynamicLabelsWhenOverlapped:self.parentStack];
|
||||
[self layoutSettingsView];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if(ControllerNavigator.radialMenuView.superview) [ControllerNavigator updateRadialMenu];
|
||||
[GamepadNavigationIllustrationHud updateHud];
|
||||
if(ControllerUtil.primaryGCController){
|
||||
[ControllerNavigator restoreSettingsModeSwitchHighlight];
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)switchToAllSettings{
|
||||
[self forceRestoreHeightTemporarilyForSettingStackParentView];
|
||||
currentSettingsMenuMode = AllSettings;
|
||||
self.currentSettingsMenuMode = AllSettings;
|
||||
[_parentStack removeFromSuperview];
|
||||
[self initParentStack];
|
||||
[self layoutSections];
|
||||
@@ -1915,13 +1945,22 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[self updateTheme];
|
||||
//[self doneRemoveSettingItem];
|
||||
Settings *currentSettings = [dataMan retrieveSettings];
|
||||
currentSettings.settingsMenuMode = [NSNumber numberWithInteger:currentSettingsMenuMode];
|
||||
currentSettings.settingsMenuMode = [NSNumber numberWithInteger:self.currentSettingsMenuMode];
|
||||
[dataMan saveData];
|
||||
[self layoutSettingsView];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (@available(iOS 13.0, *)){
|
||||
if(ControllerNavigator.radialMenuView.superview) [ControllerNavigator updateRadialMenu];
|
||||
[GamepadNavigationIllustrationHud updateHud];
|
||||
if(ControllerUtil.primaryGCController){
|
||||
[ControllerNavigator restoreSettingsModeSwitchHighlight];
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)enterRemoveSettingItemMode{
|
||||
currentSettingsMenuMode = RemoveSettingItem;
|
||||
self.currentSettingsMenuMode = RemoveSettingItem;
|
||||
for(UIStackView* stack in _parentStack.arrangedSubviews){
|
||||
for(UIView* view in stack.subviews){
|
||||
if([view.accessibilityIdentifier isEqualToString:@"infoButton"]) view.hidden = YES;
|
||||
@@ -1933,7 +1972,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
- (void)doneRemoveSettingItem{
|
||||
currentSettingsMenuMode = FavoriteSettings;
|
||||
self.currentSettingsMenuMode = FavoriteSettings;
|
||||
for(UIStackView* stack in _parentStack.arrangedSubviews){
|
||||
//stack.userInteractionEnabled = true;
|
||||
for(UIView* view in stack.subviews){
|
||||
@@ -1960,7 +1999,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
- (void)saveFavoriteSettingStackIdentifiers {
|
||||
|
||||
if(currentSettingsMenuMode != AllSettings){
|
||||
if(self.currentSettingsMenuMode != AllSettings){
|
||||
[_favoriteSettingStackIdentifiers removeAllObjects];
|
||||
//for(NSInteger i = 0; i < parentStack.arrangedSubviews.count; i++){
|
||||
for(NSInteger i = 0; i < _parentStack.arrangedSubviews.count; i++){
|
||||
@@ -2061,7 +2100,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
|
||||
self->currentSettingsMenuMode = self->tempSettings.settingsMenuMode.intValue;
|
||||
self.currentSettingsMenuMode = self->tempSettings.settingsMenuMode.intValue;
|
||||
[self loadFavoriteSettingStackIdentifiers];
|
||||
if(self->tempSettings.settingsMenuMode.intValue == FavoriteSettings) [self switchToFavoriteSettings];
|
||||
|
||||
@@ -2074,6 +2113,9 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
// CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
||||
// CGFloat fullScreenWidth = window.frame.size.width * screenScale;
|
||||
// CGFloat fullScreenHeight = window.frame.size.height * screenScale;
|
||||
|
||||
self.resolutionSelectorStack.accessibilityIdentifier = @"resolutionSelectorStack";
|
||||
self.customResolutionStack.accessibilityIdentifier = @"customResolutionStack";
|
||||
|
||||
[self.resolutionSelector removeSegmentAtIndex:0 animated:NO]; // remove 360p
|
||||
[self.resolutionSelector removeSegmentAtIndex:5 animated:NO]; // remove custom segment
|
||||
@@ -2418,15 +2460,15 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[self.onScreenWidgetSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
|
||||
/*
|
||||
// touch move event interval for native-touch.
|
||||
[self.touchMoveEventIntervalSlider setValue:self->tempSettings.touchMoveEventInterval.intValue animated:NO]; // Load old setting.
|
||||
[self.touchMoveEventIntervalSlider addTarget:self action:@selector(touchMoveEventIntervalSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
|
||||
[self touchMoveEventIntervalSliderMoved:self.touchMoveEventIntervalSlider];
|
||||
*/
|
||||
|
||||
// touch move event interval for native-touch.
|
||||
[self.leftClickDelaySlider setValue:self->tempSettings.leftClickDelayMs.intValue animated:NO]; // Load old setting.
|
||||
[self.leftClickDelaySlider addTarget:self action:@selector(leftClickDelaySliderMoved:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
|
||||
[self leftClickDelaySliderMoved:self.leftClickDelaySlider];
|
||||
*/
|
||||
[self.leftClickDelaySlider sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
|
||||
// touchMode refactored to game profile system
|
||||
[self.touchModeSelector1 addTarget:self action:@selector(touchMode1Changed:) forControlEvents:UIControlEventValueChanged];
|
||||
@@ -2437,12 +2479,16 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
[self.sendDummyEventSwitch setOn:self->tempSettings.sendDummyEvent];// Load old setting
|
||||
|
||||
[self.controllerToMouseSwitch setOn:self->tempSettings.mapControllerToMouse];
|
||||
[self.controllerToMouseSwitch addTarget:self action:@selector(controllerToMouseSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerToMouseSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerNavigationSwitch setOn:self->tempSettings.enableControllerNavigation];
|
||||
[self.controllerNavigationSwitch addTarget:self action:@selector(controllerNavigationSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerNavigationSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
|
||||
[self.controllerGyroSwitchButtonSetter addTarget:self action:@selector(controllerGyroSwitchModeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
[self.streamingRadialMenuDelaySlider setValue:self->tempSettings.streamingRadialMenuDelay.floatValue];
|
||||
[self.streamingRadialMenuDelaySlider addTarget:self action:@selector(streamingRadialMenuDelaySliderMoved:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.streamingRadialMenuDelaySlider sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
|
||||
[self.controllerMouseVelocitySlider setValue:self->tempSettings.controllerMousePointerVelocity.floatValue];
|
||||
[self.controllerMouseVelocitySlider addTarget:self action:@selector(controllerMouseVelocitySliderMoved:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerMouseVelocitySlider sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
@@ -2511,10 +2557,13 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
NSLog(@"OK button tapped with no change");
|
||||
} */
|
||||
|
||||
// Continue execution after the alert is dismissed
|
||||
if (!self->_mainFrameViewController.settingsExpandedInStreamView) {
|
||||
[self invokeOscLayout]; // Don't open osc layout tool immediately during streaming
|
||||
}
|
||||
[alertController dismissViewControllerAnimated:true completion:^{
|
||||
// Continue execution after the alert is dismissed
|
||||
if (!self->_mainFrameViewController.settingsExpandedInStreamView) {
|
||||
[self invokeOscLayout]; // Don't open osc layout tool immediately during streaming
|
||||
}
|
||||
}];
|
||||
|
||||
/*
|
||||
[self findDynamicLabelFromStack:self.onScreenWidgetStack].text = [self isCustomOswEnabled] ? [LocalizationHelper localizedStringForKey:@"%d finger tap", self->oswLayoutFingers] : @"";
|
||||
[self handleOswGestureChange]; */
|
||||
@@ -2725,16 +2774,18 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
[self->dataMan saveData];
|
||||
}];
|
||||
[AlertControllerUtil showAlertIn:self
|
||||
title:[LocalizationHelper localizedStringForKey:@"Drift Correction"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"Calibrating..."]
|
||||
withCancel:NO
|
||||
buttonTitle:[LocalizationHelper localizedStringForKey:@"Finished!"]
|
||||
countdown:6
|
||||
action:^{}
|
||||
completion:^{
|
||||
self.mapGyroToSelector.selectedSegmentIndex = self->oscProfile.mapGyroTo;
|
||||
[self.mapGyroToSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
[AlertControllerUtil.alertController dismissViewControllerAnimated:true completion:^{
|
||||
[AlertControllerUtil showAlertIn:self
|
||||
title:[LocalizationHelper localizedStringForKey:@"Drift Correction"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"Calibrating..."]
|
||||
withCancel:NO
|
||||
buttonTitle:[LocalizationHelper localizedStringForKey:@"Finished!"]
|
||||
countdown:6
|
||||
action:^{}
|
||||
completion:^{
|
||||
self.mapGyroToSelector.selectedSegmentIndex = self->oscProfile.mapGyroTo;
|
||||
[self.mapGyroToSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
@@ -2766,20 +2817,32 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
if(self->capturedController){
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
[ControllerUtil listenWithController:self->capturedController swapABXY:false handler:^(NSDictionary * buttonDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
[ControllerUtil listenWithController:self->capturedController swapABXY:false handler:^(NSDictionary * elementDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
if (!self) return;
|
||||
for(NSNumber* buttonFlagId in buttonDict){
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId];
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)element;
|
||||
if(button.isPressed){
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchPressToToggle) self->oscProfile.controllerGyroSwitchToggle = buttonFlagId.intValue;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchHoldDown) self->oscProfile.controllerGyroSwitchHold = buttonFlagId.intValue;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchPressToToggle) self->oscProfile.controllerGyroSwitchToggle = elementEnumInstance.intValue;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchHoldDown) self->oscProfile.controllerGyroSwitchHold = elementEnumInstance.intValue;
|
||||
|
||||
switchButtonCaptured = true;
|
||||
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Finished"];
|
||||
for(UIAlertAction* action in AlertControllerUtil.alertController.actions) {
|
||||
action.enabled = false;
|
||||
}
|
||||
gamepad.valueChangedHandler = nil;
|
||||
if(confirmAction) [confirmAction setValue:[LocalizationHelper localizedStringForKey:@"OK"] forKey:@"title"];
|
||||
[self->oscProfileMan replaceSelectedProfileWith:self->oscProfile overwriteDefault:true];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
[AlertControllerUtil.alertController dismissViewControllerAnimated:NO completion:nil];
|
||||
if(AlertControllerUtil.completion) AlertControllerUtil.completion();
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
@@ -2807,14 +2870,14 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
completion:^{
|
||||
if(!switchButtonCaptured){
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchPressToToggle) self->oscProfile.controllerGyroSwitchToggle = ControllerButtonNull;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchHoldDown) self->oscProfile.controllerGyroSwitchHold = ControllerButtonNull;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchPressToToggle) self->oscProfile.controllerGyroSwitchToggle = ControllerElementNull;
|
||||
if(sender.selectedSegmentIndex == ControllerGyroSwitchHoldDown) self->oscProfile.controllerGyroSwitchHold = ControllerElementNull;
|
||||
[self->oscProfileMan replaceSelectedProfileWith:self->oscProfile overwriteDefault:true];
|
||||
}
|
||||
|
||||
bool noSwitchButtonSet = self->oscProfile.controllerGyroSwitchHold == ControllerButtonNull && self->oscProfile.controllerGyroSwitchToggle == ControllerButtonNull;
|
||||
bool duplicatedButtons = self->oscProfile.controllerGyroSwitchHold == self->oscProfile.controllerGyroSwitchToggle && self->oscProfile.controllerGyroSwitchToggle != ControllerButtonNull;
|
||||
bool bothButtonsSet = self->oscProfile.controllerGyroSwitchHold != ControllerButtonNull && self->oscProfile.controllerGyroSwitchToggle != ControllerButtonNull && !duplicatedButtons;
|
||||
bool noSwitchButtonSet = self->oscProfile.controllerGyroSwitchHold == ControllerElementNull && self->oscProfile.controllerGyroSwitchToggle == ControllerElementNull;
|
||||
bool duplicatedButtons = self->oscProfile.controllerGyroSwitchHold == self->oscProfile.controllerGyroSwitchToggle && self->oscProfile.controllerGyroSwitchToggle != ControllerElementNull;
|
||||
bool bothButtonsSet = self->oscProfile.controllerGyroSwitchHold != ControllerElementNull && self->oscProfile.controllerGyroSwitchToggle != ControllerElementNull && !duplicatedButtons;
|
||||
tipLabel.text = @"";
|
||||
if(bothButtonsSet) tipLabel.text = [LocalizationHelper localizedStringForKey:@" both set "];
|
||||
if(duplicatedButtons){
|
||||
@@ -2824,7 +2887,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
if(noSwitchButtonSet) sender.selectedSegmentIndex = ControllerGyroSwitchDisabled;
|
||||
if(!noSwitchButtonSet && !bothButtonsSet && !duplicatedButtons){
|
||||
sender.selectedSegmentIndex = self->oscProfile.controllerGyroSwitchToggle != ControllerButtonNull ? ControllerGyroSwitchPressToToggle : ControllerGyroSwitchHoldDown;
|
||||
sender.selectedSegmentIndex = self->oscProfile.controllerGyroSwitchToggle != ControllerElementNull ? ControllerGyroSwitchPressToToggle : ControllerGyroSwitchHoldDown;
|
||||
}
|
||||
|
||||
[self setHidden:sender.selectedSegmentIndex==ControllerGyroSwitchDisabled forStack:self.reverseHoldButtonStack];
|
||||
@@ -2834,21 +2897,26 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controllerToMouseSwitchFlipped:(UISwitch* )sender{
|
||||
- (void)controllerNavigationSwitchFlipped:(UISwitch* )sender{
|
||||
|
||||
[self setHidden:!sender.isOn forStack:_streamingRadialMenuDelayStack];
|
||||
[self setHidden:!sender.isOn forStack:_controllerMouseVelocityStack];
|
||||
[self setHidden:!sender.isOn forStack:_controllerMouseExpoStack];
|
||||
if(!sender.isOn || settingsViewJustLoaded) return;
|
||||
[self.swapAbxySwitch setOn:false]; // swapAbxy can be enabled after mouse to controller is set
|
||||
__block bool switchButtonCaptured = false;
|
||||
__block bool localRadialMenuButtonCaptured = false;
|
||||
__block bool streamingRadialMenuButtonCaptured = false;
|
||||
__block bool previousTriggerPressed = false;
|
||||
__block bool stickCaptured = false;
|
||||
__block bool leftButtonCaptured = false;
|
||||
__block bool rightButtonCaptured = false;
|
||||
__block bool metRadialButtonWithUndefinedPosition = false;
|
||||
__block NSMutableSet *capturedButtons = [NSMutableSet new];
|
||||
Settings* currentSettings = [dataMan retrieveSettings];
|
||||
[AlertControllerUtil showAlertIn:self
|
||||
title:[LocalizationHelper localizedStringForKey:@"Map controller to mouse"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"mouseModeSwitchButtonTip"]
|
||||
withCancel:YES
|
||||
title:[LocalizationHelper localizedStringForKey:@"Controller Navigation"]
|
||||
message:[LocalizationHelper localizedStringForKey:@"radialMenuButtonTip"]
|
||||
withCancel:NO
|
||||
buttonTitle:@""
|
||||
countdown:0
|
||||
action:^{
|
||||
@@ -2861,42 +2929,152 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
element.preferredSystemGestureState = GCSystemGestureStateDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
[ControllerUtil stopListeningPrimaryControllerWithStopListenToRadialMenuButton:true];
|
||||
|
||||
[ControllerUtil listenWithController:self->capturedController swapABXY:false handler:^(NSDictionary * buttonDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
[ControllerUtil listenWithController:self->capturedController swapABXY:false handler:^(NSDictionary * elementDict, GCExtendedGamepad * gamepad, GCControllerElement * element) {
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
if (!self) return;
|
||||
|
||||
if(!switchButtonCaptured){
|
||||
for(NSNumber* buttonFlagId in buttonDict){
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId];
|
||||
if(button.isPressed && ![capturedButtons containsObject:buttonFlagId]){
|
||||
currentSettings.controllerMouseSwitch = buttonFlagId;
|
||||
[capturedButtons addObject:buttonFlagId];
|
||||
switchButtonCaptured = true;
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Move the stick you wish to use for mouse control. Another stick will be used for vertical & horizontal scroll."];
|
||||
if(!localRadialMenuButtonCaptured){
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
if (@available(iOS 13.0, *)) if(![ControllerNavigator.radialMenuButtonPool containsObject:elementEnumInstance]) continue;
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)element;
|
||||
if(button.isPressed){
|
||||
currentSettings.localRadialMenuButton = elementEnumInstance;
|
||||
[capturedButtons addObject:elementEnumInstance];
|
||||
ControllerElementPosition position = [ControllerUtil positionFor:(ControllerElement)currentSettings.localRadialMenuButton.intValue];
|
||||
metRadialButtonWithUndefinedPosition = position == ControllerElementPositionUndefined || position == ControllerElementPositionMiddle;
|
||||
localRadialMenuButtonCaptured = true;
|
||||
if(button == gamepad.leftTrigger || button == gamepad.rightTrigger) previousTriggerPressed = true;
|
||||
if(metRadialButtonWithUndefinedPosition) {
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Which side of the controller is this button on?"];
|
||||
if (@available(iOS 13.0, *)) [ControllerNavigator updateHudForCustomRadialMenuButtonPosition];
|
||||
return;
|
||||
}
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the same or a different button for controller navigation during streaming."];
|
||||
|
||||
return;
|
||||
}
|
||||
else if(button == gamepad.leftTrigger || button == gamepad.rightTrigger) previousTriggerPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(metRadialButtonWithUndefinedPosition && localRadialMenuButtonCaptured && !streamingRadialMenuButtonCaptured){
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)element;
|
||||
if(button.isPressed){
|
||||
switch (elementEnumInstance.intValue) {
|
||||
case ControllerElementDpadLeft:
|
||||
currentSettings.customLocalRadialMenuButtonPosition = @(ControllerElementPositionLeft);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
case ControllerElementDpadRight:
|
||||
currentSettings.customLocalRadialMenuButtonPosition = @(ControllerElementPositionRight);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
case ControllerElementDpadUp:
|
||||
currentSettings.customLocalRadialMenuButtonPosition = @(ControllerElementPositionMiddle);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!metRadialButtonWithUndefinedPosition){
|
||||
if (@available(iOS 13.0, *)) [GamepadNavigationIllustrationHud clearHud];
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the same or a different button for controller navigation during streaming."];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!streamingRadialMenuButtonCaptured && localRadialMenuButtonCaptured && !metRadialButtonWithUndefinedPosition){
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
if (@available(iOS 13.0, *)) if(![ControllerNavigator.radialMenuButtonPool containsObject:elementEnumInstance]) continue;
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)element;
|
||||
if(button == gamepad.leftTrigger || button == gamepad.rightTrigger) {
|
||||
if(button.isPressed == previousTriggerPressed) continue;
|
||||
}
|
||||
if(button.isPressed){
|
||||
currentSettings.streamingRadialMenuButton = elementEnumInstance;
|
||||
[capturedButtons addObject:elementEnumInstance];
|
||||
streamingRadialMenuButtonCaptured = true;
|
||||
ControllerElementPosition position = [ControllerUtil positionFor:(ControllerElement)currentSettings.streamingRadialMenuButton.intValue];
|
||||
metRadialButtonWithUndefinedPosition = currentSettings.streamingRadialMenuButton.intValue != currentSettings.localRadialMenuButton.intValue && (position == ControllerElementPositionUndefined || position == ControllerElementPositionMiddle);
|
||||
if(currentSettings.streamingRadialMenuButton.intValue == currentSettings.localRadialMenuButton.intValue) currentSettings.customStreamingRadialMenuButtonPosition = currentSettings.customLocalRadialMenuButtonPosition;
|
||||
if(metRadialButtonWithUndefinedPosition) {
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Which side of the controller is this button on?"];
|
||||
if (@available(iOS 13.0, *)) [ControllerNavigator updateHudForCustomRadialMenuButtonPosition];
|
||||
return;
|
||||
}
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Move the stick you want to use for mouse control. The other stick will be used for vertical and horizontal scrolling"];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!stickCaptured && switchButtonCaptured){
|
||||
if(metRadialButtonWithUndefinedPosition && streamingRadialMenuButtonCaptured && localRadialMenuButtonCaptured){
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)element;
|
||||
if(button.isPressed){
|
||||
switch (elementEnumInstance.intValue) {
|
||||
case ControllerElementDpadLeft:
|
||||
currentSettings.customStreamingRadialMenuButtonPosition = @(ControllerElementPositionLeft);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
case ControllerElementDpadRight:
|
||||
currentSettings.customStreamingRadialMenuButtonPosition = @(ControllerElementPositionRight);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
case ControllerElementDpadUp:
|
||||
currentSettings.customStreamingRadialMenuButtonPosition = @(ControllerElementPositionMiddle);
|
||||
metRadialButtonWithUndefinedPosition = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(!metRadialButtonWithUndefinedPosition){
|
||||
if (@available(iOS 13.0, *)) [GamepadNavigationIllustrationHud clearHud];
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Move the stick you want to use for mouse control. The other stick will be used for vertical and horizontal scrolling"];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!stickCaptured && streamingRadialMenuButtonCaptured && !metRadialButtonWithUndefinedPosition){
|
||||
float leftStickOffset = hypotf(gamepad.leftThumbstick.xAxis.value, gamepad.leftThumbstick.yAxis.value);
|
||||
float rightStickOffset = hypotf(gamepad.rightThumbstick.xAxis.value, gamepad.rightThumbstick.yAxis.value);
|
||||
if(leftStickOffset>0.1||rightStickOffset>0.1){
|
||||
ControllerMouseStick stick = leftStickOffset>rightStickOffset ? LeftStickToMouse : RightStickToMouse;
|
||||
ControllerElement stick = leftStickOffset>rightStickOffset ? ControllerElementLeftStick : ControllerElementRightStick;
|
||||
currentSettings.controllerMouseStick = @(stick);
|
||||
stickCaptured = true;
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the button for mouse left button"];
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(!leftButtonCaptured && stickCaptured){
|
||||
for(NSNumber* buttonFlagId in buttonDict){
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId];
|
||||
if(button.isPressed && ![capturedButtons containsObject:buttonFlagId]){
|
||||
currentSettings.controllerMouseLeftButton = buttonFlagId;
|
||||
[capturedButtons addObject:buttonFlagId];
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if(button.isPressed && ![capturedButtons containsObject:elementEnumInstance]){
|
||||
currentSettings.controllerMouseLeftButton = elementEnumInstance;
|
||||
[capturedButtons addObject:elementEnumInstance];
|
||||
leftButtonCaptured = true;
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Press the button for mouse right button"];
|
||||
return;
|
||||
@@ -2905,17 +3083,42 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
if(!rightButtonCaptured && leftButtonCaptured){
|
||||
for(NSNumber* buttonFlagId in buttonDict){
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)buttonDict[buttonFlagId];
|
||||
if(button.isPressed && ![capturedButtons containsObject:buttonFlagId]){
|
||||
currentSettings.controllerMouseRightButton = buttonFlagId;
|
||||
[capturedButtons addObject:buttonFlagId];
|
||||
for(NSNumber* elementEnumInstance in elementDict){
|
||||
GCControllerElement * element = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if (![element isKindOfClass:[GCControllerButtonInput class]]) continue;
|
||||
GCControllerButtonInput * button = (GCControllerButtonInput *)elementDict[elementEnumInstance];
|
||||
if(button.isPressed && ![capturedButtons containsObject:elementEnumInstance]){
|
||||
currentSettings.controllerMouseRightButton = elementEnumInstance;
|
||||
[capturedButtons addObject:elementEnumInstance];
|
||||
rightButtonCaptured = true;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
NSLog(@"custom posistion local %d , stream: %d", currentSettings.customLocalRadialMenuButtonPosition.intValue, currentSettings.customStreamingRadialMenuButtonPosition.intValue);
|
||||
|
||||
ControllerNavigator.localRadialMenuButton = (ControllerElement)currentSettings.localRadialMenuButton.intValue;
|
||||
ControllerNavigator.customPositionForLocalRadialMenuButton = (ControllerElementPosition)currentSettings.customLocalRadialMenuButtonPosition.intValue;
|
||||
ControllerNavigator.streamingRadialMenuButton = (ControllerElement)currentSettings.streamingRadialMenuButton.intValue;
|
||||
ControllerNavigator.customPositionForStreamingRadialMenuButton = (ControllerElementPosition)currentSettings.customStreamingRadialMenuButtonPosition.intValue;
|
||||
ControllerNavigator.streamingRadialMenuDelay = (NSTimeInterval)currentSettings.streamingRadialMenuDelay.floatValue;
|
||||
ControllerNavigator.enabled = true;
|
||||
[ControllerNavigator setUINavigationDelegate:self];
|
||||
// ControllerNavigator.controllerMouseStick = (ControllerElement)currentSettings.controllerMouseStick.intValue;
|
||||
// ControllerNavigator.controllerMouseLeftButton = (ControllerElement)currentSettings.controllerMouseLeftButton.intValue;
|
||||
// ControllerNavigator.controllerMouseRightButton = (ControllerElement)currentSettings.controllerMouseRightButton.intValue;
|
||||
// ControllerNavigator.controllerMouseExpo = currentSettings.controllerMouseExpo.floatValue;
|
||||
}
|
||||
|
||||
AlertControllerUtil.alertController.message = [LocalizationHelper localizedStringForKey:@"Finished"];
|
||||
gamepad.valueChangedHandler = nil;
|
||||
// deprecated:
|
||||
UIAlertAction* cancelAction = AlertControllerUtil.alertController.actions.firstObject;
|
||||
if(cancelAction) [cancelAction setValue:[LocalizationHelper localizedStringForKey:@"OK"] forKey:@"title"];
|
||||
//
|
||||
[self->dataMan saveData];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
if (@available(iOS 13.0, *)) [ControllerNavigator restartListening];
|
||||
[AlertControllerUtil.alertController dismissViewControllerAnimated:NO completion:nil];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2932,12 +3135,12 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
action:^{}
|
||||
completion:^{
|
||||
if(AlertControllerUtil.actionCancelled){
|
||||
[self.controllerToMouseSwitch setOn:NO];
|
||||
[self.controllerToMouseSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerNavigationSwitch setOn:NO];
|
||||
[self.controllerNavigationSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
return;
|
||||
}
|
||||
[AlertControllerUtil.alertController dismissViewControllerAnimated:NO completion:^{}];
|
||||
[self.controllerToMouseSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerNavigationSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
@@ -2945,8 +3148,8 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
completion:^{
|
||||
if(AlertControllerUtil.actionCancelled){
|
||||
if(!rightButtonCaptured){
|
||||
[self.controllerToMouseSwitch setOn:rightButtonCaptured];
|
||||
[self.controllerToMouseSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
[self.controllerNavigationSwitch setOn:rightButtonCaptured];
|
||||
[self.controllerNavigationSwitch sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
if(self->capturedController && self->capturedController.extendedGamepad) self->capturedController.extendedGamepad.valueChangedHandler = nil;
|
||||
}
|
||||
@@ -2972,25 +3175,25 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
- (void)rollSensitivitySliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:_rollSensitivityStack].text = [NSString stringWithFormat:@" %d%% ", (int16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (int16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]];
|
||||
}
|
||||
|
||||
- (void)gyroMinStickOffsetSliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:_gyroToStickMinOffsetStack].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
if(settingsViewJustExpanded) return;
|
||||
LiSendControllerEvent(0, 0, 0, _rollToLeftStickSwitch.isOn?sender.value:0, 0, _yawPitchToRightStickSwitch.isOn?sender.value:0, 0);
|
||||
OnScreenWidgetView.gamepadArrivalReported = true;
|
||||
}
|
||||
|
||||
- (void)leftStickMinOffsetSliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:_leftStickMinOffsetStack].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
if(settingsViewJustExpanded) return;
|
||||
LiSendControllerEvent(0, 0, 0, sender.value, 0, 0, 0);
|
||||
OnScreenWidgetView.gamepadArrivalReported = true;
|
||||
}
|
||||
|
||||
- (void)rightStickMinOffsetSliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:_rightStickMinOffsetStack].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value];
|
||||
if(settingsViewJustExpanded) return;
|
||||
LiSendControllerEvent(0, 0, 0, 0, 0, sender.value, 0);
|
||||
OnScreenWidgetView.gamepadArrivalReported = true;
|
||||
@@ -3056,24 +3259,24 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
- (void) pointerVelocityModeDividerSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:self.pointerVelocityDividerStack].text = [NSString stringWithFormat:@" | %d%% | %d%% | ", (uint8_t)sender.value, 100-(uint8_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" | %d%% | %d%% | ", (uint8_t)sender.value, 100-(uint8_t)sender.value];
|
||||
}
|
||||
|
||||
- (void) touchPointerVelocityFactorSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:self.pointerVelocityFactorStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]]; // Update label display
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]]; // Update label display
|
||||
}
|
||||
|
||||
- (void) gyroSensitivitySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:self.gyroSensitivityStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
}
|
||||
|
||||
- (void) localVolumeSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:self.localVolumeStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
if(_mainFrameViewController.settingsExpandedInStreamView) [Connection setVolume:sender.value/100];
|
||||
}
|
||||
|
||||
- (void) micVolumeSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:self.micVolumeStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
|
||||
if(_mainFrameViewController.settingsExpandedInStreamView) [MicHandler setVolume:sender.value/100];
|
||||
}
|
||||
|
||||
@@ -3082,7 +3285,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
labelString = [LocalizationHelper localizedStringForKey:@" keep %d min ", (uint16_t)sender.value];
|
||||
if(sender.value == 0) labelString = [LocalizationHelper localizedStringForKey:@" disconnect "];
|
||||
if(sender.value == sender.maximumValue) labelString = [LocalizationHelper localizedStringForKey:@" keep alive "];
|
||||
[self findDynamicLabelFromStack:self.backgroundSessionTimerStack].text = labelString; // Update label display
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = labelString; // Update label display
|
||||
}
|
||||
|
||||
- (void)appThemeChanged:(UISegmentedControl* )sender{
|
||||
@@ -3110,31 +3313,35 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
- (void) mousePointerVelocityFactorSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_mousePointerVelocityStack].text = [NSString stringWithFormat:@" %d%% ",(uint16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ",(uint16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]];
|
||||
}
|
||||
|
||||
- (void) singleTapSensitivitySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_singleTapSensitivityStack].text = [NSString stringWithFormat:@" %.1f ",sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f ",sender.value];
|
||||
}
|
||||
|
||||
- (void) scrollSensitivitySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_scrollSensitivityStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)(sender.value*100)];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)(sender.value*100)];
|
||||
}
|
||||
|
||||
- (void) pinchSensitivitySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_pinchSensitivityStack].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)(sender.value*100)];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)(sender.value*100)];
|
||||
}
|
||||
|
||||
- (void) relativeTouchSlideThresholdSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_relativeTouchSlideThresholdStack].text = [NSString stringWithFormat:@" %.1f ",sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f ",sender.value];
|
||||
}
|
||||
|
||||
- (void) controllerMouseVelocitySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_controllerMouseVelocityStack].text = [NSString stringWithFormat:@" %.1f ", sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f ", sender.value];
|
||||
}
|
||||
|
||||
- (void) streamingRadialMenuDelaySliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f s ", sender.value];
|
||||
}
|
||||
|
||||
- (void) controllerMouseExpoSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:_controllerMouseExpoStack].text = [NSString stringWithFormat:@" %.1f ", sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f ", sender.value];
|
||||
}
|
||||
|
||||
- (uint32_t) getScreenEdgeFromSelector {
|
||||
@@ -3580,27 +3787,27 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
NSInteger width = [self getChosenStreamWidth];
|
||||
NSInteger height = [self getChosenStreamHeight];
|
||||
|
||||
[self findDynamicLabelFromStack:_resolutionStack].text = [NSString stringWithFormat:@"%ld × %ld", (long)width, (long)height];
|
||||
[self findDynamicLabelFromStack:_resolutionSelectorStack].text = [NSString stringWithFormat:@"%ld × %ld", (long)width, (long)height];
|
||||
}
|
||||
|
||||
- (void) touchMoveEventIntervalSliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:self.touchMoveEventIntervalStack].text = sender.enabled ?
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = sender.enabled ?
|
||||
[NSString stringWithFormat:@" %d μs ", (uint16_t)self.touchMoveEventIntervalSlider.value] : @"";
|
||||
}
|
||||
|
||||
- (void) leftClickDelaySliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:self.leftClickDelayStack].text = [NSString stringWithFormat:@" %d ms ", (uint16_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d ms ", (uint16_t)sender.value];
|
||||
}
|
||||
|
||||
- (void) slideToMenuDistanceSliderMoved:(UISlider* )sender{
|
||||
UILabel* displayLabel = [self findDynamicLabelFromStack:_slideToSettingsDistanceStack];
|
||||
UILabel* displayLabel = [self findDynamicLabelFromStack:(UIStackView*)sender.superview];
|
||||
// displayLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightMedium];
|
||||
NSString* labelText = [LocalizationHelper localizedStringForKey:@"%d%% screen width", (uint8_t)(sender.value * 100)];
|
||||
displayLabel.text = [NSString stringWithFormat:@" %@ ", labelText];
|
||||
}
|
||||
|
||||
- (void) edgeSlidingSensitivitySliderMoved:(UISlider* )sender{
|
||||
UILabel* displayLabel = [self findDynamicLabelFromStack:_edgeSlidingSensitivityStack];
|
||||
UILabel* displayLabel = [self findDynamicLabelFromStack:(UIStackView*)sender.superview];
|
||||
// displayLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightMedium];
|
||||
NSString* labelText = [LocalizationHelper localizedStringForKey:@" %d ", (uint8_t)sender.value];
|
||||
displayLabel.text = [NSString stringWithFormat:@" %@ ", labelText];
|
||||
@@ -3782,7 +3989,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
- (void) frameQueueSizeSliderMoved:(UISlider* )sender {
|
||||
assert(self.frameQueueSizeSlider.value >= 0 && self.frameQueueSizeSlider.value <= 5);
|
||||
int queueSize = self.frameQueueSizeSlider.value;
|
||||
[self findDynamicLabelFromStack:_frameQueueSizeStack].text = queueSize==0 ? [LocalizationHelper localizedStringForKey:@"lowest latency"] : [NSString stringWithFormat: @" %d ", queueSize];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = queueSize==0 ? [LocalizationHelper localizedStringForKey:@"lowest latency"] : [NSString stringWithFormat: @" %d ", queueSize];
|
||||
}
|
||||
|
||||
- (void) enableGraphsChanged:(UISwitch* )sender {
|
||||
@@ -3811,6 +4018,8 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
if(![MicHandler permissionGranted]) [self.redirectMicSwitch setOn:false];
|
||||
|
||||
[self saveGameProfileConfigs];
|
||||
|
||||
if (@available(iOS 13.0, *)) if(!self.controllerNavigationSwitch.isOn) ControllerNavigator.enabled = false;
|
||||
}
|
||||
|
||||
- (void)pencilTickModeChanged:(UISegmentedControl* )sender{
|
||||
@@ -3863,7 +4072,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
}
|
||||
|
||||
- (void)pencilTickIntervalSliderMoved:(UISlider* )sender{
|
||||
[self findDynamicLabelFromStack:self.pencilTickIntervalStack].text = [NSString stringWithFormat:@" %d μs ", (uint16_t)sender.value];
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d μs ", (uint16_t)sender.value];
|
||||
}
|
||||
|
||||
- (void)pressureCurveSwitchFlipped:(UISwitch* )sender{
|
||||
@@ -3964,7 +4173,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
self.pencilTickSelector.selectedSegmentIndex = tempSettings.pencilTickMode.intValue;
|
||||
[self.pencilTickSelector addTarget:self action:@selector(pencilTickModeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.pencilTickSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
|
||||
|
||||
[self.pencilTickIntervalSlider setValue:tempSettings.pencilTickIntervalUs.floatValue];
|
||||
[self.pencilTickIntervalSlider addTarget:self action:@selector(pencilTickIntervalSliderMoved:) forControlEvents:UIControlEventValueChanged];
|
||||
[self.pencilTickIntervalSlider sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
@@ -4079,7 +4288,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
BOOL ctrlDownForPinch = self.ctrlDownForPinchSwitch.isOn;
|
||||
CGFloat leftClickDelayMs = self.leftClickDelaySlider.value;
|
||||
BOOL passthroughGestures = self.passthroughGesturesSwitch.isOn;
|
||||
BOOL mapControllerToMouse = self.controllerToMouseSwitch.isOn;
|
||||
BOOL enableControllerNavigation = self.controllerNavigationSwitch.isOn;
|
||||
CGFloat controllerMousePointerVelocity = self.controllerMouseVelocitySlider.value;
|
||||
CGFloat controllerMouseExpo = self.controllerMouseExpoSlider.value;
|
||||
NSInteger controllerGyroSwitchMode = self.controllerGyroSwitchButtonSetter.selectedSegmentIndex;
|
||||
@@ -4087,6 +4296,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
BOOL asyncFrameDequeue = self.asyncFrameDequeueSwitch.isOn;
|
||||
CGFloat softKeyboardHeight = self.softKeyboardHeightSwitch.isOn ? self->softKeyboardHeight : 0;
|
||||
BOOL globeAsEscape = self.globeAsEscapeSwitch.isOn;
|
||||
CGFloat streamingRadialMenuDelay = self.streamingRadialMenuDelaySlider.value;
|
||||
NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value;
|
||||
|
||||
[dataMan saveSettings:currentSettings
|
||||
@@ -4156,7 +4366,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
leftClickDelayMs:leftClickDelayMs
|
||||
settingsMenuOffset:settingsMenuOffset
|
||||
passthroughGestures:passthroughGestures
|
||||
mapControllerToMouse:mapControllerToMouse
|
||||
enableControllerNavigation:enableControllerNavigation
|
||||
controllerMousePointerVelocity:controllerMousePointerVelocity
|
||||
controllerMouseExpo:controllerMouseExpo
|
||||
controllerGyroSwitchMode:controllerGyroSwitchMode
|
||||
@@ -4164,7 +4374,8 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
asyncFrameDequeue:asyncFrameDequeue
|
||||
sdrPerformanceWorkaround:sdrPerformanceWorkaround
|
||||
softKeyboardHeight:softKeyboardHeight
|
||||
globeAsEscape:globeAsEscape
|
||||
globeAsEscape:globeAsEscape
|
||||
streamingRadialMenuDelay:streamingRadialMenuDelay
|
||||
backgroundSessionTimer:backgroundSessionTimer];
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, AVPictureInPictureControllerDelegate>
|
||||
#else
|
||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate, OnScreenFunctionalWidgetDelegate, AbstractGamepadOverlayCloseButtonDelegate>
|
||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, AVPictureInPictureControllerDelegate>
|
||||
|
||||
#endif
|
||||
@property (nonatomic, strong) StreamManager* streamMan;
|
||||
@@ -48,14 +48,19 @@
|
||||
@property (nonatomic, assign) CGFloat streamViewMagnifierZoomScale;
|
||||
|
||||
|
||||
+ (StreamFrameViewController *)sharedInstance;
|
||||
|
||||
- (void)updatePreferredDisplayMode:(BOOL)streamActive;
|
||||
- (void)setUserInteractionEnabledForStreamView:(bool)enabled;
|
||||
- (bool)shallDisableGyroHotSwitch;
|
||||
- (void)loadGameProfileConfigs:(OSCProfile* )profile;
|
||||
- (void)openWidgetProfileTableWithPickProfile:(BOOL)pickProfile;
|
||||
- (void)toggleGamepadOverlayWithOverlayEnabled:(BOOL)overlayEnabled API_AVAILABLE(ios(13.0));
|
||||
- (void)loadAbstractGamepadOverlayIfNeeded API_AVAILABLE(ios(13.0));
|
||||
- (void)restorePersistedStreamViewOffsetAndScaleWithProfile:(OSCProfile* )profile;
|
||||
- (void)updateMagnifierViewportMetrics;
|
||||
- (void)setMagnifierViewportInteractionEnabled:(BOOL)enabled;
|
||||
- (void)resetMagnifierStreamViewWithAnimated:(BOOL)animated;
|
||||
- (void)returnToMainFrame;
|
||||
|
||||
@end
|
||||
|
||||
@@ -76,6 +76,11 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
}
|
||||
|
||||
|
||||
@interface StreamFrameViewController () <ToolboxSpecialEntryDelegate, OnScreenFunctionalWidgetDelegate, AbstractGamepadOverlayCloseButtonDelegate>
|
||||
@end
|
||||
|
||||
static __weak StreamFrameViewController *VLSharedStreamFrameViewController = nil;
|
||||
|
||||
@implementation StreamFrameViewController {
|
||||
ControllerSupport *_controllerSupport;
|
||||
TemporarySettings *_settings;
|
||||
@@ -130,6 +135,10 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
|
||||
}
|
||||
|
||||
+ (StreamFrameViewController *)sharedInstance {
|
||||
return VLSharedStreamFrameViewController;
|
||||
}
|
||||
|
||||
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
||||
_streamView.hidden = YES;
|
||||
if (self.imguiView) {
|
||||
@@ -660,6 +669,12 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
[self setMagnifierViewportInteractionEnabled:_oscProfile.touchMode == AbsoluteTouch && !_settings.passthroughGestures];
|
||||
|
||||
GenericUtils.globeAsEscape = _settings.globeAsEscape;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
ControllerNavigator.localRadialMenuButton = (ControllerElement)_settings.localRadialMenuButton.intValue;
|
||||
ControllerNavigator.streamingRadialMenuButton = (ControllerElement)_settings.streamingRadialMenuButton.intValue;
|
||||
ControllerNavigator.streamingRadialMenuDelay = (NSTimeInterval)_settings.streamingRadialMenuDelay.floatValue;
|
||||
}
|
||||
|
||||
NSLog(@"frameview gestures: %d", (uint32_t)[self.view.gestureRecognizers count]);
|
||||
NSLog(@"streamview gestures: %d", (uint32_t)[_streamView.gestureRecognizers count]);
|
||||
@@ -828,6 +843,7 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
VLSharedStreamFrameViewController = self;
|
||||
_viewJustLoaded = true;
|
||||
viewIsBeingResized = false;
|
||||
_magnifierViewportInteractionActive = false;
|
||||
@@ -1005,8 +1021,6 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
OnScreenWidgetView.enableFolderAnimation = true;
|
||||
});
|
||||
|
||||
[ControllerNavigator stop];
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification{
|
||||
@@ -1118,10 +1132,12 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
if(!profile){
|
||||
profile = [OSCProfilesManager sharedManager:CGRectZero].getSelectedProfile;
|
||||
}
|
||||
[self setMagnifierViewportInteractionEnabled:true];
|
||||
CGPoint streamViewOffset = CGPointMake(profile.normalizedStreamViewOffset.x*self.view.bounds.size.width, profile.normalizedStreamViewOffset.y*self.view.bounds.size.height);
|
||||
[self restoreMagnifierStreamViewWithOffset:streamViewOffset scale:profile.streamViewScale animated:YES];
|
||||
[self setMagnifierViewportInteractionEnabled:profile.touchMode == AbsoluteTouch && !_settings.passthroughGestures];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self setMagnifierViewportInteractionEnabled:true];
|
||||
CGPoint streamViewOffset = CGPointMake(profile.normalizedStreamViewOffset.x*self.view.bounds.size.width, profile.normalizedStreamViewOffset.y*self.view.bounds.size.height);
|
||||
[self restoreMagnifierStreamViewWithOffset:streamViewOffset scale:profile.streamViewScale animated:YES];
|
||||
[self setMagnifierViewportInteractionEnabled:profile.touchMode == AbsoluteTouch && !self->_settings.passthroughGestures];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)restoreMagnifierStreamViewWithOffset:(CGPoint)offset scale:(CGFloat)scale {
|
||||
@@ -1289,10 +1305,10 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
- (void)setupOverlayView{
|
||||
if (_overlayView == nil) {
|
||||
_overlayView = [[PaddedLabel alloc] initWithFrame:CGRectZero];
|
||||
[_overlayView setTextInsets:UIEdgeInsetsMake([_mainFrameViewcontroller isIPhone]?4:6, 12, [_mainFrameViewcontroller isIPhone]?4:6, 12)];
|
||||
[_overlayView setTextInsets:UIEdgeInsetsMake(PublicUtils.isIPhone?4:6, 12, PublicUtils.isIPhone?4:6, 12)];
|
||||
[_overlayView setUserInteractionEnabled:NO];
|
||||
[_overlayView setNumberOfLines:100];
|
||||
[_overlayView.layer setCornerRadius:[_mainFrameViewcontroller isIPhone]?7:10];
|
||||
[_overlayView.layer setCornerRadius:PublicUtils.isIPhone?7:10];
|
||||
[_overlayView.layer setMasksToBounds:YES];
|
||||
|
||||
// HACK: If not using stats overlay, center the text
|
||||
@@ -1305,13 +1321,13 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
#if TARGET_OS_TV
|
||||
[_overlayView setFont:[UIFont systemFontOfSize:24 weight:UIFontWeightMedium]];
|
||||
#else
|
||||
[_overlayView setFont:[UIFont systemFontOfSize: [_mainFrameViewcontroller isIPhone]?10:12 weight:UIFontWeightMedium]];
|
||||
[_overlayView setFont:[UIFont systemFontOfSize: PublicUtils.isIPhone?10:12 weight:UIFontWeightMedium]];
|
||||
#endif
|
||||
[_overlayView setAlpha:(float)[_settings.graphOpacity intValue]/ 100.0];
|
||||
[self.view addSubview:_overlayView];
|
||||
}
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if(overlayLevel == 1) _overlayView.font = [UIFont monospacedSystemFontOfSize:[_mainFrameViewcontroller isIPhone]?10:12 weight:UIFontWeightMedium];
|
||||
if(overlayLevel == 1) _overlayView.font = [UIFont monospacedSystemFontOfSize:PublicUtils.isIPhone?10:12 weight:UIFontWeightMedium];
|
||||
}
|
||||
|
||||
[_overlayView setHidden:YES];
|
||||
@@ -1336,7 +1352,18 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void) returnToMainFrame {
|
||||
- (void)returnToMainFrame {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// [ControllerNavigator setUINavigationDelegate:[_mainFrameViewcontroller isInAppView] ? _mainFrameViewcontroller : _mainFrameViewcontroller.hostCollectionVC];
|
||||
[ControllerNavigator restorePreviousUINavigationDelegateWithIfCurrentDelegateIs:self];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if(ControllerNavigator.radialMenuView) [ControllerNavigator updateRadialMenu];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[ControllerNavigator restoreUINavigationHighlight];
|
||||
});
|
||||
}
|
||||
|
||||
[_streamView saveStreamingGameProfileChanges];
|
||||
[_streamView clearOnScreenWidgets];
|
||||
if(micHandler) [micHandler clean];
|
||||
@@ -1347,11 +1374,11 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[SceneDelegate clearExternalDisplayRenderView];
|
||||
}
|
||||
|
||||
|
||||
if (_settings.enablePIP) {
|
||||
[self cleanupPiPController];
|
||||
}
|
||||
|
||||
|
||||
[_statsUpdateTimer invalidate];
|
||||
_statsUpdateTimer = nil;
|
||||
|
||||
@@ -1360,8 +1387,10 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
_extWindow = nil;
|
||||
|
||||
if(_streamConfig.redirectMic) [micHandler stopTappingWithStopEngine:true];
|
||||
|
||||
|
||||
self.mainFrameViewcontroller.settingsExpandedInStreamView = false; // reset this flag to false
|
||||
|
||||
[ControllerUtil disableSysGestures: ControllerUtil.primaryGCController];
|
||||
}
|
||||
|
||||
// External Screen connected
|
||||
@@ -1569,7 +1598,6 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
}
|
||||
|
||||
- (void)expandSettingsView{
|
||||
self.mainFrameViewcontroller.settingsExpandedInStreamView = true; //notify mainFrameViewContorller that this is a setting expansion in stream view, some settings shall be disabled.
|
||||
[_streamView saveStreamingGameProfileChanges];
|
||||
[self.mainFrameViewcontroller expandSettingsView];
|
||||
}
|
||||
@@ -1593,7 +1621,7 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
sleep(1.5);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.mainFrameViewcontroller quitRunningApp];
|
||||
[self.mainFrameViewcontroller quitLaunchedApp];
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2258,6 +2286,9 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
|
||||
|
||||
- (void)dealloc {
|
||||
if (VLSharedStreamFrameViewController == self) {
|
||||
VLSharedStreamFrameViewController = nil;
|
||||
}
|
||||
NSLog(@"dealloc StreamFrameViewController %f", CACurrentMediaTime());
|
||||
}
|
||||
|
||||
@@ -2271,4 +2302,5 @@ static NSString* VLTerminationHintForErrorCode(int errorCode) {
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -463,7 +463,7 @@ import UIKit
|
||||
|
||||
|
||||
private func sendKeyboardCommand(_ cmd: RemoteCommand) {
|
||||
print("Sending key-value")
|
||||
// print("Sending key-value")
|
||||
let keyboardCmdStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: cmd.cmdString)
|
||||
CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: keyboardCmdStrings!)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
//
|
||||
|
||||
#include <Limelight.h>
|
||||
#import "SettingsViewController.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "StreamFrameViewController.h"
|
||||
#import "ControllerSupport.h"
|
||||
#import "OnScreenControls.h"
|
||||
#import "OnScreenButtonState.h"
|
||||
#import "DataManager.h"
|
||||
#import "AppAssetManager.h"
|
||||
#import "LocalizationHelper.h"
|
||||
#import "OSCProfile.h"
|
||||
#import "LayoutOnScreenControls.h"
|
||||
|
||||
@@ -733,6 +733,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"9d1-RN-lqW.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Streaming Radial Menu Delay\"; ObjectID = \"9d1-RN-lqW\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Streaming Radial Menu Delay"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手柄菜单延迟"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手把選單延遲"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手把選單延遲"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"9pX-93-bC2.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Background Session\"; ObjectID = \"9pX-93-bC2\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
@@ -5313,31 +5343,31 @@
|
||||
}
|
||||
},
|
||||
"P1g-rW-Mgp.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller → Mouse\"; ObjectID = \"P1g-rW-Mgp\";",
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller Navigation\"; ObjectID = \"P1g-rW-Mgp\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Controller → Mouse"
|
||||
"value" : "Controller Navigation"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手柄映射为鼠标"
|
||||
"value" : "手柄导航"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手把映射為滑鼠"
|
||||
"value" : "手把導覽"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手把映射為滑鼠"
|
||||
"value" : "手把導覽"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6959,31 +6989,31 @@
|
||||
}
|
||||
},
|
||||
"Xnd-C4-8N9.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Pointer Velocity\"; ObjectID = \"Xnd-C4-8N9\";",
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller Mouse Velocity\"; ObjectID = \"Xnd-C4-8N9\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Pointer Velocity"
|
||||
"value" : "Controller Mouse Velocity"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "指针速度"
|
||||
"value" : "手柄鼠标速度"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "needs_review",
|
||||
"value" : "指針速度"
|
||||
"state" : "translated",
|
||||
"value" : "手把滑鼠速度"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "needs_review",
|
||||
"value" : "指針速度"
|
||||
"state" : "translated",
|
||||
"value" : "手把滑鼠速度"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1621,6 +1621,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bPX-Kf-kdP.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Streaming Radial Menu Delay\"; ObjectID = \"bPX-Kf-kdP\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Streaming Radial Menu Delay"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手柄菜单延迟"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手把選單延遲"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "串流手把選單延遲"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"BQ1-w6-Opg.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Touch Point Tracking\"; ObjectID = \"BQ1-w6-Opg\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
@@ -2402,31 +2432,31 @@
|
||||
}
|
||||
},
|
||||
"F0O-Ht-fW5.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller → Mouse\"; ObjectID = \"F0O-Ht-fW5\";",
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller Navigation\"; ObjectID = \"F0O-Ht-fW5\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Controller → Mouse"
|
||||
"value" : "Controller Navigation"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手柄映射为鼠标"
|
||||
"value" : "手柄导航"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手把映射為滑鼠"
|
||||
"value" : "手把導覽"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手把映射為滑鼠"
|
||||
"value" : "手把導覽"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2738,31 +2768,31 @@
|
||||
}
|
||||
},
|
||||
"H3s-ud-yqh.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Pointer Velocity\"; ObjectID = \"H3s-ud-yqh\";",
|
||||
"comment" : "Class = \"UILabel\"; text = \"Controller Mouse Velocity\"; ObjectID = \"H3s-ud-yqh\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Pointer Velocity"
|
||||
"value" : "Controller Mouse Velocity"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "指针速度"
|
||||
"value" : "手柄鼠标速度"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "指針速度"
|
||||
"value" : "手把滑鼠速度"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "指針速度"
|
||||
"value" : "手把滑鼠速度"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ struct AbstractGamepadView: View {
|
||||
applyTriggerSelection(widget)
|
||||
applyDS4TouchSelection(widget)
|
||||
|
||||
print("selectedCmd =", AbstractGamepadView.selectedCmd)
|
||||
// print("selectedCmd =", AbstractGamepadView.selectedCmd)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -733,8 +733,8 @@ struct AbstractGamepadView: View {
|
||||
if liveSnapshot.pressedButtons.contains(.b) { result.insert(.b) }
|
||||
if liveSnapshot.pressedButtons.contains(.x) { result.insert(.x) }
|
||||
if liveSnapshot.pressedButtons.contains(.y) { result.insert(.y) }
|
||||
if liveSnapshot.pressedButtons.contains(.menu) { result.insert(.start) }
|
||||
if liveSnapshot.pressedButtons.contains(.back) { result.insert(.select) }
|
||||
if liveSnapshot.pressedButtons.contains(.start) { result.insert(.start) }
|
||||
if liveSnapshot.pressedButtons.contains(.select) { result.insert(.select) }
|
||||
if liveSnapshot.pressedButtons.contains(.special) { result.insert(.home) }
|
||||
if liveSnapshot.pressedButtons.contains(.leftShoulder) { result.insert(.leftShoulder) }
|
||||
if liveSnapshot.pressedButtons.contains(.rightShoulder) { result.insert(.rightShoulder) }
|
||||
|
||||
@@ -10,25 +10,42 @@ import SwiftUI
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
public enum RadialMenuItem {
|
||||
@objc public enum RadialMenuItem:Int {
|
||||
case settings
|
||||
case favoriteSettings
|
||||
case allSettings
|
||||
case addHost
|
||||
case aboutView
|
||||
case hostView
|
||||
case gameProfiles
|
||||
case disconnect
|
||||
case mouse
|
||||
case quitApp
|
||||
case theme
|
||||
case more
|
||||
case navigationSettings
|
||||
case exit
|
||||
}
|
||||
|
||||
@objc public enum RadialMenuState:Int {
|
||||
case main
|
||||
case mouseModeEnabled
|
||||
case disconnectAndQuit
|
||||
case moreOptions
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
struct RadialMenuSector: Identifiable, Equatable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let subtitle: String
|
||||
let systemImageName: String
|
||||
let symbolName: String
|
||||
let item: RadialMenuItem
|
||||
|
||||
init(title: String, subtitle: String, systemImageName: String, item: RadialMenuItem) {
|
||||
init(title: String, subtitle: String, symbol: String, item: RadialMenuItem) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.systemImageName = systemImageName
|
||||
self.symbolName = symbol
|
||||
self.item = item
|
||||
}
|
||||
}
|
||||
@@ -38,7 +55,7 @@ struct RadialMenuStyle: Equatable {
|
||||
var ringColor = Color(white: 0.86)
|
||||
var selectedRingColor = Color(red: 0.19, green: 0.72, blue: 0.96)
|
||||
var ringStrokeColor = Color.white.opacity(0.72)
|
||||
var centerFillColor = Color.white
|
||||
var centerFillColor = Color(ThemeManager.hostViewBackgroundColor.withAlphaComponent(0.5))
|
||||
var iconColor = Color(white: 0.23)
|
||||
var selectedIconColor = Color(white: 0.16)
|
||||
var titleColor = Color(white: 0.24)
|
||||
@@ -46,35 +63,97 @@ struct RadialMenuStyle: Equatable {
|
||||
var shadowColor = Color.black.opacity(0.18)
|
||||
var ringWidthRatio: CGFloat = 0.41
|
||||
var segmentGapWidth: CGFloat = 1
|
||||
var centerIconScale: CGFloat = 0.16
|
||||
var centerIconScale: CGFloat = 0.13
|
||||
var segmentIconScale: CGFloat = 0.085
|
||||
|
||||
static func themed(for userInterfaceStyle: UIUserInterfaceStyle, accentColor: UIColor) -> RadialMenuStyle {
|
||||
switch userInterfaceStyle {
|
||||
case .dark:
|
||||
return RadialMenuStyle(
|
||||
ringColor: Color(UIColor(red: 44.0 / 255.0, green: 44.0 / 255.0, blue: 46.0 / 255.0, alpha: 0.88)),
|
||||
selectedRingColor: Color(accentColor.withAlphaComponent(0.52)),
|
||||
ringStrokeColor: Color.white.opacity(0.26),
|
||||
// centerFillColor: Color(UIColor(red: 28.0 / 255.0, green: 28.0 / 255.0, blue: 30.0 / 255.0, alpha: 0.96)),
|
||||
centerFillColor: Color(ThemeManager.hostViewBackgroundColor.withAlphaComponent(0.5)),
|
||||
iconColor: Color(UIColor(white: 0.86, alpha: 1)),
|
||||
selectedIconColor: Color.white,
|
||||
titleColor: Color(UIColor(white: 0.92, alpha: 1)),
|
||||
subtitleColor: Color(UIColor(white: 0.70, alpha: 1)),
|
||||
shadowColor: Color(accentColor.withAlphaComponent(0)),
|
||||
ringWidthRatio: 0.41,
|
||||
segmentGapWidth: 1,
|
||||
centerIconScale: 0.16,
|
||||
segmentIconScale: 0.085
|
||||
)
|
||||
default:
|
||||
return RadialMenuStyle(
|
||||
selectedRingColor: Color(accentColor.withAlphaComponent(0.86))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
enum RadialMenuSelectionChangeReason {
|
||||
case began
|
||||
case moved
|
||||
case released
|
||||
case unchanged
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
final class RadialMenuSelectionState: ObservableObject {
|
||||
@Published private(set) var selectedIndex: Int?
|
||||
@Published private(set) var hasReceivedJoystickInput = false
|
||||
@Published private(set) var lastChangeReason: RadialMenuSelectionChangeReason = .unchanged
|
||||
|
||||
private var itemCount = 0
|
||||
|
||||
func updateSelection(xOffset: Float, yOffset: Float) {
|
||||
func updateSelection(xOffset: CGFloat, yOffset: CGFloat) {
|
||||
updateSelection(xOffset: xOffset, yOffset: yOffset, deadZone: 0.1)
|
||||
}
|
||||
|
||||
func updateSelection(xOffset: Float, yOffset: Float, deadZone: Float) {
|
||||
func updateSelection(xOffset: CGFloat, yOffset: CGFloat, deadZone: CGFloat) {
|
||||
let x = max(min(xOffset, 1), -1)
|
||||
let y = max(min(yOffset, 1), -1)
|
||||
let distance = hypot(x, y)
|
||||
if distance < deadZone {
|
||||
guard !hasReceivedJoystickInput || selectedIndex != nil else {
|
||||
lastChangeReason = .unchanged
|
||||
return
|
||||
}
|
||||
|
||||
hasReceivedJoystickInput = true
|
||||
lastChangeReason = selectedIndex == nil ? .unchanged : .released
|
||||
selectedIndex = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Near center, tiny return-to-center offsets have unstable angles. Keep the
|
||||
// last high-confidence sector until the stick fully enters the dead zone.
|
||||
let selectionUpdateDeadZone = max(deadZone, 0.35)
|
||||
guard distance >= selectionUpdateDeadZone else {
|
||||
hasReceivedJoystickInput = true
|
||||
lastChangeReason = .unchanged
|
||||
return
|
||||
}
|
||||
|
||||
let nextSelectedIndex = Self.selectedIndex(
|
||||
xOffset: xOffset,
|
||||
yOffset: yOffset,
|
||||
xOffset: x,
|
||||
yOffset: y,
|
||||
itemCount: itemCount,
|
||||
deadZone: deadZone
|
||||
)
|
||||
|
||||
guard !hasReceivedJoystickInput || nextSelectedIndex != selectedIndex else {
|
||||
lastChangeReason = .unchanged
|
||||
return
|
||||
}
|
||||
|
||||
let previousSelectedIndex = selectedIndex
|
||||
hasReceivedJoystickInput = true
|
||||
selectedIndex = nextSelectedIndex
|
||||
lastChangeReason = Self.changeReason(from: previousSelectedIndex, to: nextSelectedIndex)
|
||||
}
|
||||
|
||||
fileprivate func configureItemCount(_ itemCount: Int) {
|
||||
@@ -84,7 +163,7 @@ final class RadialMenuSelectionState: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
static func selectedIndex(xOffset: Float, yOffset: Float, itemCount: Int, deadZone: Float = 0.1) -> Int? {
|
||||
static func selectedIndex(xOffset: CGFloat, yOffset: CGFloat, itemCount: Int, deadZone: CGFloat = 0.1) -> Int? {
|
||||
guard itemCount > 0 else { return nil }
|
||||
|
||||
let x = max(min(xOffset, 1), -1)
|
||||
@@ -104,6 +183,19 @@ final class RadialMenuSelectionState: ObservableObject {
|
||||
let remainder = value.truncatingRemainder(dividingBy: divisor)
|
||||
return remainder >= 0 ? remainder : remainder + divisor
|
||||
}
|
||||
|
||||
private static func changeReason(from previousIndex: Int?, to nextIndex: Int?) -> RadialMenuSelectionChangeReason {
|
||||
switch (previousIndex, nextIndex) {
|
||||
case (nil, .some):
|
||||
return .began
|
||||
case (.some(let previous), .some(let next)) where previous != next:
|
||||
return .moved
|
||||
case (.some, nil):
|
||||
return .released
|
||||
default:
|
||||
return .unchanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@@ -151,7 +243,7 @@ struct RadialMenuView: View {
|
||||
self.joystickSelectionState.configureItemCount(sectors.count)
|
||||
}
|
||||
|
||||
func updateSelection(xOffset: Float, yOffset: Float) {
|
||||
func updateSelection(xOffset: CGFloat, yOffset: CGFloat) {
|
||||
joystickSelectionState.configureItemCount(sectors.count)
|
||||
joystickSelectionState.updateSelection(xOffset: xOffset, yOffset: yOffset)
|
||||
}
|
||||
@@ -192,9 +284,13 @@ struct RadialMenuView: View {
|
||||
|
||||
ForEach(sectors.indices, id: \.self) { index in
|
||||
let item = sectors[index]
|
||||
Image(systemName: item.systemImageName)
|
||||
.font(.system(size: max(diameter * style.segmentIconScale, 18), weight: .medium))
|
||||
.foregroundColor(index == effectiveSelectedIndex ? style.selectedIconColor : style.iconColor)
|
||||
RadialMenuIconView(
|
||||
symbolName: item.symbolName,
|
||||
fallbackSystemName: "circle",
|
||||
size: max(diameter * style.segmentIconScale, 18),
|
||||
weight: .medium,
|
||||
color: index == effectiveSelectedIndex ? style.selectedIconColor : style.iconColor
|
||||
)
|
||||
.scaleEffect(index == effectiveSelectedIndex ? 1.18 : 1)
|
||||
.position(iconPosition(index: index, count: sectors.count, center: center, radius: (outerRadius + innerRadius) / 2))
|
||||
.animation(.spring(response: 0.24, dampingFraction: 0.72), value: effectiveSelectedIndex)
|
||||
@@ -203,7 +299,7 @@ struct RadialMenuView: View {
|
||||
RadialMenuCenterView(item: selectedItem, style: style, diameter: diameter)
|
||||
.frame(width: innerRadius * 1.72, height: innerRadius * 1.72)
|
||||
.position(center)
|
||||
.animation(.easeInOut(duration: 0.16), value: effectiveSelectedIndex)
|
||||
.animation(.easeInOut(duration: selectedItem?.item == .gameProfiles ? 0 : 0.16), value: effectiveSelectedIndex)
|
||||
}
|
||||
.frame(width: proxy.size.width, height: proxy.size.height)
|
||||
.contentShape(Circle())
|
||||
@@ -266,18 +362,25 @@ private struct RadialMenuCenterView: View {
|
||||
let diameter: CGFloat
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: max(diameter * 0.018, 4)) {
|
||||
Image(systemName: item?.systemImageName ?? "circle.grid.cross")
|
||||
.font(.system(size: max(diameter * style.centerIconScale, 28), weight: .medium))
|
||||
.foregroundColor(style.iconColor)
|
||||
VStack(spacing: 0) {
|
||||
RadialMenuIconView(
|
||||
symbolName: item?.symbolName ?? "circle.grid.cross",
|
||||
fallbackSystemName: "circle.grid.cross",
|
||||
size: centerIconSize,
|
||||
weight: .medium,
|
||||
color: style.iconColor
|
||||
)
|
||||
.id(item?.id)
|
||||
.transition(.opacity)
|
||||
.frame(width: centerIconSize * 1.35, height: centerIconSize * 1.2)
|
||||
.padding(.bottom, centerIconTitleSpacing)
|
||||
|
||||
Text(item?.title ?? "")
|
||||
.font(.system(size: max(diameter * 0.047, 13), weight: .medium))
|
||||
.foregroundColor(style.titleColor)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.62)
|
||||
.padding(.bottom, item?.subtitle.isEmpty == false ? centerTextSpacing : 0)
|
||||
|
||||
if item?.subtitle.isEmpty == false {
|
||||
Text(item?.subtitle ?? "")
|
||||
@@ -290,6 +393,114 @@ private struct RadialMenuCenterView: View {
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, diameter * 0.035)
|
||||
}
|
||||
|
||||
private var centerIconSize: CGFloat {
|
||||
max(diameter * style.centerIconScale, 28)
|
||||
}
|
||||
|
||||
private var centerTextSpacing: CGFloat {
|
||||
max(diameter * 0.018, 4)
|
||||
}
|
||||
|
||||
private var centerIconTitleSpacing: CGFloat {
|
||||
if #available(iOS 14.0, *) {
|
||||
return centerTextSpacing
|
||||
}
|
||||
|
||||
return max(diameter * 0.034, 8)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private struct RadialMenuIconView: View {
|
||||
let symbolName: String
|
||||
let fallbackSystemName: String
|
||||
let size: CGFloat
|
||||
let weight: Font.Weight
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if UIImage(systemName: symbolName) != nil {
|
||||
Image(systemName: symbolName)
|
||||
.font(.system(size: size, weight: weight))
|
||||
} else if let assetImage = RadialMenuIconImageRenderer.templateImage(named: symbolName, pointSize: size) {
|
||||
Image(uiImage: assetImage)
|
||||
.renderingMode(.template)
|
||||
} else {
|
||||
Image(systemName: fallbackSystemName)
|
||||
.font(.system(size: size, weight: weight))
|
||||
}
|
||||
}
|
||||
.foregroundColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private enum RadialMenuIconImageRenderer {
|
||||
private static let cache = NSCache<NSString, UIImage>()
|
||||
private static let assetCanvasScale: CGFloat = 1.05
|
||||
private static let assetVisualScale: CGFloat = 1
|
||||
|
||||
static func templateImage(named name: String, pointSize: CGFloat) -> UIImage? {
|
||||
let scale = UIScreen.main.scale
|
||||
let normalizedPointSize = max(ceil(pointSize * 2) / 2, 1)
|
||||
let cacheKey = "\(name)-\(normalizedPointSize)-\(scale)-\(assetCanvasScale)-\(assetVisualScale)" as NSString
|
||||
|
||||
if let cachedImage = cache.object(forKey: cacheKey) {
|
||||
return cachedImage
|
||||
}
|
||||
|
||||
guard let sourceImage = UIImage(named: name) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let canvasSize = CGSize(
|
||||
width: normalizedPointSize * assetCanvasScale,
|
||||
height: normalizedPointSize * assetCanvasScale
|
||||
)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = scale
|
||||
format.opaque = false
|
||||
|
||||
let renderedImage = UIGraphicsImageRenderer(size: canvasSize, format: format).image { _ in
|
||||
let drawBounds = scaledRect(
|
||||
CGRect(origin: .zero, size: canvasSize),
|
||||
by: assetVisualScale
|
||||
)
|
||||
sourceImage.draw(in: aspectFitRect(for: sourceImage.size, in: drawBounds))
|
||||
}.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
cache.setObject(renderedImage, forKey: cacheKey)
|
||||
return renderedImage
|
||||
}
|
||||
|
||||
private static func scaledRect(_ rect: CGRect, by scale: CGFloat) -> CGRect {
|
||||
let width = rect.width * scale
|
||||
let height = rect.height * scale
|
||||
return CGRect(
|
||||
x: rect.midX - width / 2,
|
||||
y: rect.midY - height / 2,
|
||||
width: width,
|
||||
height: height
|
||||
)
|
||||
}
|
||||
|
||||
private static func aspectFitRect(for sourceSize: CGSize, in bounds: CGRect) -> CGRect {
|
||||
guard sourceSize.width > 0, sourceSize.height > 0 else {
|
||||
return bounds
|
||||
}
|
||||
|
||||
let scale = min(bounds.width / sourceSize.width, bounds.height / sourceSize.height)
|
||||
let width = sourceSize.width * scale
|
||||
let height = sourceSize.height * scale
|
||||
return CGRect(
|
||||
x: bounds.midX - width / 2,
|
||||
y: bounds.midY - height / 2,
|
||||
width: width,
|
||||
height: height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@@ -387,10 +598,10 @@ struct RadialMenuDemoView: View {
|
||||
@SwiftUI.State private var isTouchSelectionEnabled = true
|
||||
|
||||
static let menuItems = [
|
||||
RadialMenuSector(title: "Settings Menu".localized, subtitle: "", systemImageName: "sidebar.left", item: .settings),
|
||||
RadialMenuSector(title: "Host View".localized, subtitle: "", systemImageName: PublicUtils.liquidGlassEnabled ? "macwindow.on.rectangle" : "tv", item: .hostView),
|
||||
RadialMenuSector(title: "Game Profiles".localized, subtitle: "", systemImageName: "gamecontroller.circle", item: .gameProfiles),
|
||||
RadialMenuSector(title: "Exit/Disconnect".localized, subtitle: "", systemImageName: "personalhotspot.slash", item: .gameProfiles),
|
||||
RadialMenuSector(title: "Settings Menu".localized, subtitle: "", symbol: "sidebar.left", item: .settings),
|
||||
RadialMenuSector(title: "Host View".localized, subtitle: "", symbol: PublicUtils.liquidGlassEnabled ? "macwindow.on.rectangle" : "tv", item: .hostView),
|
||||
RadialMenuSector(title: "Game Profiles".localized, subtitle: "", symbol: "gamecontroller.circle", item: .gameProfiles),
|
||||
RadialMenuSector(title: "Exit/Disconnect".localized, subtitle: "", symbol: "personalhotspot.slash", item: .gameProfiles),
|
||||
]
|
||||
|
||||
private var visibleItems: [RadialMenuSector] {
|
||||
|
||||
@@ -1568,7 +1568,7 @@ struct KeyView: View {
|
||||
.onEnded { _ in
|
||||
isPressed = false
|
||||
action?()
|
||||
print("cmdString \(String(describing: cmdString)) code \(String(describing: keyCode))")
|
||||
// print("cmdString \(String(describing: cmdString)) code \(String(describing: keyCode))")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -777,7 +777,7 @@ struct WidgetPickerView: View {
|
||||
FunctionalButtonOption(
|
||||
localizationKey: "=disableTilt",
|
||||
cmd: "DISABLETILT",
|
||||
tip: LocalizationHelper.localizedString(forKey: "Disable tilt for apple pencil"),
|
||||
tip: "Disable tilt for apple pencil".localized,
|
||||
allowsKeyboardCombination: false,
|
||||
allowsGamepadCombination: false,
|
||||
allowsSkillCombo: false,
|
||||
|
||||
Reference in New Issue
Block a user