- motion control demo1

- feat: movable on-screen functional buttons
This commit is contained in:
TrueZhuanjia
2025-10-04 16:44:43 +08:00
parent b96e2d1bdf
commit 67af68fcf1
27 changed files with 731 additions and 245 deletions
+8
View File
@@ -15,11 +15,13 @@
1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */; };
18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; };
1850C82A2E667E4000845308 /* MicHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850C8292E667E4000845308 /* MicHandler.swift */; };
18729EDE2E8BD68800101194 /* UUIDHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18729EDD2E8BD68800101194 /* UUIDHelper.swift */; };
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; };
1888C1922E24C33100108730 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1912E24C33100108730 /* AboutViewController.swift */; };
1888C1932E24C41000108730 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1888C1902E24C2C800108730 /* AboutView.swift */; };
188973D32E386A670025E48C /* widgetTemplatesIPhone.bin in Resources */ = {isa = PBXBuildFile; fileRef = 188973D22E386A670025E48C /* widgetTemplatesIPhone.bin */; };
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E4BCE92E891895000A8657 /* MotionHandler.swift */; };
840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; };
841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 841BAC912DE35D6B00DA639E /* ThemeManager.m */; };
84203F232C5F24C00032EEED /* OnScreenWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84203F222C5F24C00032EEED /* OnScreenWidgetView.swift */; };
@@ -164,6 +166,7 @@
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = "<group>"; };
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = "<group>"; };
1850C8292E667E4000845308 /* MicHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicHandler.swift; sourceTree = "<group>"; };
18729EDD2E8BD68800101194 /* UUIDHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UUIDHelper.swift; sourceTree = "<group>"; };
187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = "<group>"; };
187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WidgetPanelStackView.m; sourceTree = "<group>"; };
1888C1902E24C2C800108730 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
@@ -172,6 +175,7 @@
189AD2962DE743B700E24B88 /* HostCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HostCollectionViewController.h; sourceTree = "<group>"; };
189AD2992DE7446000E24B88 /* HostCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HostCollectionViewController.m; sourceTree = "<group>"; };
18BF141D2E0D7A00009DEE4F /* VoidLink v1.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "VoidLink v1.0.xcdatamodel"; sourceTree = "<group>"; };
18E4BCE92E891895000A8657 /* MotionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionHandler.swift; sourceTree = "<group>"; };
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
840751B12BF49B5A0096B657 /* CustomTapGestureRecognizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomTapGestureRecognizer.h; sourceTree = "<group>"; };
840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomTapGestureRecognizer.m; sourceTree = "<group>"; };
@@ -701,6 +705,7 @@
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
1850C8292E667E4000845308 /* MicHandler.swift */,
18E4BCE92E891895000A8657 /* MotionHandler.swift */,
84E1CDEB2C4F67D300547904 /* CommandManager.swift */,
84203F222C5F24C00032EEED /* OnScreenWidgetView.swift */,
1817B61D2E6FD04D000D938D /* SafeTimer.swift */,
@@ -743,6 +748,7 @@
FB89461E19F646E200339C8A /* Utility */ = {
isa = PBXGroup;
children = (
18729EDD2E8BD68800101194 /* UUIDHelper.swift */,
97E20DD42E2164A9000D5DCA /* BandwidthTracker.h */,
97E20DD52E2164A9000D5DCA /* BandwidthTracker.m */,
97E20DD62E2164A9000D5DCA /* FloatBuffer.h */,
@@ -1067,6 +1073,7 @@
84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */,
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */,
1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */,
18729EDE2E8BD68800101194 /* UUIDHelper.swift in Sources */,
FB89462819F646E200339C8A /* CryptoManager.m in Sources */,
DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */,
FB89462E19F646E200339C8A /* PairManager.m in Sources */,
@@ -1091,6 +1098,7 @@
18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */,
FB89463119F646E200339C8A /* StreamManager.m in Sources */,
84351B2D2C29AD6100A77989 /* ToolBarContainerView.m in Sources */,
18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */,
988FCD41293B091B003050E2 /* KeyboardInputField.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
BIN
View File
Binary file not shown.
+13 -12
View File
@@ -746,7 +746,7 @@
<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" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="150" minValue="0.0" maxValue="3600" translatesAutoresizingMaskIntoConstraints="NO" id="4gD-tc-95I" userLabel="touchMoveEventIntervalSlider">
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="2667" minValue="0.0" maxValue="20000" translatesAutoresizingMaskIntoConstraints="NO" id="4gD-tc-95I" userLabel="touchMoveEventIntervalSlider">
<rect key="frame" x="-2" y="25.5" width="384" height="32"/>
<color key="tintColor" systemColor="linkColor"/>
</slider>
@@ -1426,7 +1426,7 @@
<viewControllerLayoutGuide type="bottom" id="eaS-aj-VtA"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Acw-S4-f8a">
<rect key="frame" x="0.0" y="0.0" width="1300" height="1194"/>
<rect key="frame" x="0.0" y="0.0" width="834" height="1210"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fZU-G0-fgM" userLabel="Toolbar Root View" customClass="ToolBarContainerView">
@@ -1838,7 +1838,7 @@
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LGW-oJ-5Zf" userLabel="triggerModeStack">
<rect key="frame" x="0.0" y="518" width="650" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Trigger mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qNQ-fB-jhY" userLabel="uiLabel">
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Button mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qNQ-fB-jhY" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="P7b-Jz-LEK"/>
@@ -1848,21 +1848,22 @@
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" alpha="0.76999998092651367" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="cKS-fy-Zpg">
<rect key="frame" x="130" y="0.0" width="385" height="32"/>
<rect key="frame" x="130" y="0.0" width="420" height="32"/>
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<segments>
<segment title="slide to toggle"/>
<segment title="slide &amp; hold"/>
<segment title="regular"/>
<segment title="tap to toggle"/>
<segment title="movable"/>
</segments>
<color key="tintColor" systemColor="systemGray3Color"/>
<color key="selectedSegmentTintColor" red="0.23087820410000001" green="0.4576539993" blue="0.51765710119999997" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</segmentedControl>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" " textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dOF-FW-cBU" userLabel="uiLabel">
<rect key="frame" x="515" y="0.0" width="135" height="31"/>
<rect key="frame" x="550" y="0.0" width="100" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="135" id="hfV-um-PjK"/>
<constraint firstAttribute="width" constant="100" id="hfV-um-PjK"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1893,6 +1894,8 @@
<outlet property="autoTapSlider" destination="ksz-Dq-bUf" id="nbG-ye-X7e"/>
<outlet property="autoTapStack" destination="e4b-0v-36Z" id="PZd-KE-lPb"/>
<outlet property="borderWidthAlphaStack" destination="9ZS-28-txW" id="qxV-2E-g8m"/>
<outlet property="buttonModeSelector" destination="cKS-fy-Zpg" id="uIq-60-t7E"/>
<outlet property="buttonModeStack" destination="LGW-oJ-5Zf" id="Jn6-Mu-XYp"/>
<outlet property="chevronImageView" destination="iVZ-Fd-dY7" id="403-Nj-wRx"/>
<outlet property="chevronView" destination="hKp-ds-86I" id="Og9-aT-zNH"/>
<outlet property="currentProfileLabel" destination="iel-xl-xd9" id="PJ0-FX-z6w"/>
@@ -1920,8 +1923,6 @@
<outlet property="toolbarStackView" destination="YLK-IE-ajS" id="j4o-Kp-7Z8"/>
<outlet property="toolbarTopConstraintiPad" destination="Ppb-J1-lXg" id="laN-Zh-lHm"/>
<outlet property="trashCanButton" destination="qJs-n8-eV7" id="ubK-qm-Cox"/>
<outlet property="triggerModeSelector" destination="cKS-fy-Zpg" id="uIq-60-t7E"/>
<outlet property="triggerModeStack" destination="LGW-oJ-5Zf" id="Jn6-Mu-XYp"/>
<outlet property="undoButton" destination="F7X-l4-cl3" id="vAI-XQ-hZw"/>
<outlet property="widgetAlphaLabel" destination="Pwa-YK-6Q2" id="IPF-Es-yTr"/>
<outlet property="widgetAlphaSlider" destination="ii1-Iq-jI4" id="Rev-dn-n3F"/>
@@ -2033,7 +2034,7 @@
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<image name="video.and.waveform" catalog="system" width="128" height="94"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="secondarySystemGroupedBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -2042,13 +2043,13 @@
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray3Color">
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+21 -20
View File
@@ -459,7 +459,7 @@
<stackView contentMode="scaleToFill" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Ejp-Jq-phL" userLabel="stickIndicatorOffsetStack">
<rect key="frame" x="0.0" y="415" width="550" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stick Indicator" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcy-zp-LGa" userLabel="stickIndicatorOffsetLabel">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stick indicator" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcy-zp-LGa" userLabel="stickIndicatorOffsetLabel">
<rect key="frame" x="0.0" y="0.0" width="160" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="2JU-sF-hdJ"/>
@@ -548,16 +548,16 @@
<rect key="frame" x="0.0" y="523" width="550" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Vibration style" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="u3w-Se-xzH" userLabel="sensitivityFactorLabel">
<rect key="frame" x="0.0" y="0.0" width="160" height="20"/>
<rect key="frame" x="0.0" y="0.0" width="130" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="jAW-3B-uPL"/>
<constraint firstAttribute="width" constant="130" id="jAW-3B-uPL"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="QRV-je-HEZ">
<rect key="frame" x="160" y="0.0" width="390" height="21"/>
<rect key="frame" x="130" y="0.0" width="420" height="21"/>
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<segments>
<segment title="light"/>
@@ -579,16 +579,16 @@
<rect key="frame" x="0.0" y="550" width="550" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Button down" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ozU-7C-qKE" userLabel="uilabel">
<rect key="frame" x="0.0" y="0.0" width="160" height="20"/>
<rect key="frame" x="0.0" y="0.0" width="120" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="Sii-hk-yFR"/>
<constraint firstAttribute="width" constant="120" id="Sii-hk-yFR"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="tE5-Bm-4OB">
<rect key="frame" x="160" y="0.0" width="390" height="21"/>
<rect key="frame" x="120" y="0.0" width="430" height="21"/>
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<segments>
<segment title="hovering"/>
@@ -608,31 +608,32 @@
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jE7-IB-GTS" userLabel="slidableStack">
<rect key="frame" x="0.0" y="577" width="550" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Trigger Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="o9I-Gt-YRd" userLabel="uilabel">
<rect key="frame" x="0.0" y="0.0" width="160" height="20"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Button mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="o9I-Gt-YRd" userLabel="uilabel">
<rect key="frame" x="0.0" y="0.0" width="120" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="160" id="W2q-kp-7ce"/>
<constraint firstAttribute="width" constant="120" id="W2q-kp-7ce"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="ojp-aS-m8w">
<rect key="frame" x="160" y="0.0" width="350" height="21"/>
<rect key="frame" x="120" y="0.0" width="420" height="21"/>
<color key="backgroundColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<segments>
<segment title="slide to toggle"/>
<segment title="slide &amp; hold"/>
<segment title="regular"/>
<segment title="tap to toggle"/>
<segment title="movable"/>
</segments>
<color key="tintColor" systemColor="systemGray2Color"/>
<color key="selectedSegmentTintColor" red="0.23136288839999999" green="0.45776400810000001" blue="0.5149240678" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" " textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KZ3-vJ-E8J" userLabel="uilabel">
<rect key="frame" x="510" y="0.0" width="40" height="20"/>
<rect key="frame" x="540" y="0.0" width="10" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="BZO-g1-Px9"/>
<constraint firstAttribute="width" constant="10" id="BZO-g1-Px9"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -663,6 +664,8 @@
<outlet property="autoTapSlider" destination="tsE-QI-xLw" id="WlR-z2-iXM"/>
<outlet property="autoTapStack" destination="tDJ-cy-BYU" id="moT-DJ-jwF"/>
<outlet property="borderWidthAlphaStack" destination="50p-sC-ObU" id="2Xq-p7-31z"/>
<outlet property="buttonModeSelector" destination="ojp-aS-m8w" id="ACc-Sd-Edq"/>
<outlet property="buttonModeStack" destination="jE7-IB-GTS" id="hKb-gp-eem"/>
<outlet property="chevronImageView" destination="76O-3K-aa8" id="yGv-fM-Ohf"/>
<outlet property="chevronView" destination="Zll-IP-PFt" id="MGR-C0-h57"/>
<outlet property="currentProfileLabel" destination="f46-Pt-0Pv" id="BjA-jF-t86"/>
@@ -686,8 +689,6 @@
<outlet property="toolbarStackView" destination="4he-pe-kYB" id="eVm-jM-dcv"/>
<outlet property="toolbarTopConstraintiPhone" destination="wNr-7F-S0i" id="iXe-d3-R7i"/>
<outlet property="trashCanButton" destination="dhG-Fo-0GK" id="dv6-TN-2Bj"/>
<outlet property="triggerModeSelector" destination="ojp-aS-m8w" id="ACc-Sd-Edq"/>
<outlet property="triggerModeStack" destination="jE7-IB-GTS" id="hKb-gp-eem"/>
<outlet property="undoButton" destination="Pdg-l4-BvF" id="emD-GI-v2v"/>
<outlet property="vibrationStyleSelector" destination="QRV-je-HEZ" id="cEF-HG-B4h"/>
<outlet property="vibrationStyleStack" destination="gsG-6n-BAb" id="etn-BF-xqj"/>
@@ -1067,7 +1068,7 @@
<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" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="150" minValue="0.0" maxValue="3600" translatesAutoresizingMaskIntoConstraints="NO" id="Qqh-Oh-dOV" userLabel="touchMoveEventIntervalSlider">
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="2667" minValue="0.0" maxValue="20000" translatesAutoresizingMaskIntoConstraints="NO" id="Qqh-Oh-dOV" userLabel="touchMoveEventIntervalSlider">
<rect key="frame" x="-2" y="25.5" width="384" height="32"/>
<color key="tintColor" systemColor="linkColor"/>
</slider>
@@ -2060,19 +2061,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.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="secondarySystemGroupedBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray2Color">
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+2 -2
View File
@@ -119,7 +119,7 @@ import UIKit
@objc public static let directionPads: [String] = ["DPAD", "WASDPAD", "ARROWPAD"]
@objc public static let stickTouchPads: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD"]
@objc public static let nonVectorStickPads: [String] = ["LSPAD", "RSPAD"]
@objc public static let specialOverlayButtonCmds: [String] = ["SETTINGS","GYRO"]
@objc public static let functionalButtonCmds: [String] = ["SETTINGS","GYRO","ACCEL","MOTION"]
// @objc public static let specialGameWidgets: [String] = ["YSRSV", "YSLT", "YSRT", "YSRB", "YSB", "YSRT2", "YSRB2", "YSB2", "YSEM", "YSML", "YSMR", "YSWASD"]
@@ -463,7 +463,7 @@ import UIKit
let combinedStrings = [CommandManager.keyboardButtonMappings.keys.map { $0 as String },
CommandManager.oscButtonMappings.keys.map { $0 as String },
CommandManager.mouseButtonMappings.keys.map { $0 as String },
CommandManager.specialOverlayButtonCmds.map { $0 as String },
CommandManager.functionalButtonCmds.map { $0 as String },
CommandManager.touchPadCmds.map { $0 as String }
]
.lazy
+1 -1
View File
@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface OSCProfile : NSObject <NSCoding, NSSecureCoding>
@property NSString *name;
@property NSMutableArray <OnScreenButtonState *> *buttonStates;
@property NSMutableArray <NSData *> *buttonStatesEncoded;
@property BOOL isSelected;
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected;
+3 -3
View File
@@ -16,7 +16,7 @@
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected {
if ((self = [self init])) {
self.name = name;
self.buttonStates = buttonStates;
self.buttonStatesEncoded = buttonStates;
self.isSelected = isSelected;
}
@@ -29,14 +29,14 @@
- (void) encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.buttonStates forKey:@"buttonStates"];
[encoder encodeObject:self.buttonStatesEncoded forKey:@"buttonStates"];
[encoder encodeBool:self.isSelected forKey:@"isSelected"];
}
- (id) initWithCoder:(NSCoder*)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.buttonStates = [decoder decodeObjectForKey:@"buttonStates"];
self.buttonStatesEncoded = [decoder decodeObjectForKey:@"buttonStates"];
self.isSelected = [decoder decodeBoolForKey:@"isSelected"];
}
@@ -45,8 +45,10 @@ NS_ASSUME_NONNULL_BEGIN
* Returns the index of the 'selected' profile within the array it's in
*/
- (NSInteger) getIndexOfSelectedProfile;
- (void)replaceSelectedProfileWith:(OSCProfile*)newProfile;
- (NSMutableArray *) getEncodedProfiles;
- (NSData* )getSelectedEncodedProfile;
- (void) importEncodedProfiles:(NSMutableArray* )profilesEncoded;
- (OnScreenButtonState *)unarchiveButtonStateEncoded:(NSData *)data;
- (void) importDefaultTemplates;
@@ -74,6 +76,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL) profileNameAlreadyExist:(NSString*)name;
- (OSCProfile *) findProfileByName:(NSString*) name inProfileArray:(NSMutableArray*)profiles;
- (CGPoint)normalizeWidgetPosition:(CGPoint)position;
@end
+23 -13
View File
@@ -105,6 +105,21 @@ static CGRect layoutViewBounds;
}
- (void)replaceSelectedProfileWith:(OSCProfile*)newProfile{
NSInteger index = 0;
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
if (profile.isSelected == YES) {
index = [profiles indexOfObject:profile];
}
}
if(index>0) profiles[index] = newProfile;
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:profilesEncoded requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"OSCProfiles"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
/**
* Replaces one 'OSCProfile' object for another in the 'OSCProfile' objects array stored in persistent storage
@@ -139,8 +154,11 @@ static CGRect layoutViewBounds;
}
- (NSMutableArray *) getEncodedProfiles {
NSMutableArray *profiles = [self getAllProfiles];
NSMutableArray *profilesEncoded = [self encodedProfilesFromArray:profiles]; // encode each 'profile' object in the array and add them to a new array
NSData *profilesArrayEncoded = [[NSUserDefaults standardUserDefaults] objectForKey: @"OSCProfiles"]; // Get the encoded array of encoded OSC profiles from persistent storage
NSSet *classes = [NSSet setWithObjects:[NSString class], [NSMutableData class], [NSMutableArray class], [OSCProfile class], [OnScreenButtonState class], nil];
NSMutableArray *profilesEncoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:profilesArrayEncoded error:nil]; // Decode the encoded array itself, NOT the objects contained in the array
return profilesEncoded;
}
@@ -260,12 +278,7 @@ static CGRect layoutViewBounds;
buttonStates:buttonStatesEncoded isSelected:YES]; // create a new 'OSCProfile'. Set the array of encoded button states created above to the 'buttonStates' property of the new profile, along with a 'name'. Set 'isSelected' argument to YES which will set this saved profile as the one that will show up in the game stream view
/* set all saved OSCProfiles 'isSelected' property to NO since the new profile you're adding will be set as the selected profile */
NSMutableArray *profiles = [self getAllProfiles];
for (OSCProfile *profile in profiles) {
profile.isSelected = NO;
}
[self replaceProfile:[self getSelectedProfile] withProfile:newProfile];
[self replaceSelectedProfileWith:newProfile];
return true;
}
@@ -373,11 +386,8 @@ static CGRect layoutViewBounds;
CGPoint normalizedPosition = [self normalizeWidgetPosition:widgetView.center];
OnScreenButtonState *buttonState = [[OnScreenButtonState alloc] initWithButtonName:widgetView.cmdString buttonType:CustomOnScreenWidget andPosition:normalizedPosition];
buttonState.alias = widgetView.widgetLabel;
// buttonState.sizeReference = layoutViewBounds.size.width > layoutViewBounds.size.height ? longSide : shortSide;
//widgetView.sizeReference = buttonState.sizeReference; // testttttttttt ???
NSLog(@"sizeRef: %d", buttonState.sizeReference);
buttonState.identifier = widgetView.identifier;
buttonState.widthFactor = [self normalizeSizeWidthFactorWith:widgetView and:buttonState];
// NSLog(@"logging widthFactor %f", buttonState.widthFactor);
buttonState.heightFactor = [self normalizeSizeHeightFactor:widgetView and:buttonState];
buttonState.backgroundAlpha = widgetView.backgroundAlpha;
buttonState.borderWidth = widgetView.borderWidth;
@@ -390,7 +400,7 @@ static CGRect layoutViewBounds;
buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset;
buttonState.widgetShape = widgetView.shape;
buttonState.minStickOffset = widgetView.minStickOffset;
buttonState.buttonTriggerMode = widgetView.buttonTriggerMode;
buttonState.buttonMode = widgetView.buttonMode;
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
[buttonStatesEncoded addObject: buttonStateEncoded];
@@ -24,10 +24,11 @@ NS_ASSUME_NONNULL_BEGIN
@property NSString *name;
@property NSString *alias;
@property NSString *identifier;
@property CGPoint position;
@property (nonatomic, assign) BOOL isHidden;
@property (nonatomic, assign) uint8_t buttonTriggerMode;
@property (nonatomic, assign) uint8_t buttonType;
@property (nonatomic, assign) uint8_t buttonMode;
@property (nonatomic, assign) uint8_t widgetType;
@property (nonatomic, assign) uint8_t sizeReference;
@property (nonatomic, assign) CGFloat widthFactor; // for OnScreenWidgetView
@property (nonatomic, assign) CGFloat heightFactor; // for OnScreenWidgetView
@@ -66,11 +67,12 @@ typedef NS_ENUM(NSInteger, MouseButtonAction) {
noClick
};
typedef NS_ENUM(NSInteger, ButtonTriggerMode) {
typedef NS_ENUM(NSInteger, ButtonMode) {
slideToToggle,
slideAndHold,
regular,
tapToToggle
tapToToggle,
movable
};
- (id) initWithButtonName:(NSString*)name buttonType:(uint8_t)buttonType andPosition:(CGPoint)position;
@@ -10,6 +10,7 @@
//
#import "OnScreenButtonState.h"
#import "VoidLink-Swift.h"
@implementation OnScreenButtonState
@@ -19,7 +20,7 @@
self.name = name;
//self.isHidden = isHidden;
self.position = position;
self.buttonType = buttonType;
self.widgetType = buttonType;
}
return self;
@@ -31,12 +32,13 @@
- (void) encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:([self.identifier isEqualToString:@""]||!self.identifier) ? [UUIDHelper newUUID] : self.identifier forKey:@"identifier"];
[encoder encodeObject:self.alias forKey:@"alias"];
[encoder encodeInt:self.buttonType forKey:@"buttonType"];
[encoder encodeInt:self.widgetType forKey:@"buttonType"]; // keep original key
[encoder encodeInt:self.sizeReference forKey:@"sizeReference"];
[encoder encodeInt:self.vibrationStyle forKey:@"vibrationStyle"];
[encoder encodeInt:self.mouseButtonAction forKey:@"mouseButtonAction"];
[encoder encodeInt:self.buttonTriggerMode forKey:@"slideMode"]; // triggerMode: previously slideMode
[encoder encodeInt:self.buttonMode forKey:@"slideMode"]; // buttonMode: previously slideMode, keep it for consistency
[encoder encodeInt:self.autoTapInterval forKey:@"autoTapInterval"];
[encoder encodeCGPoint:self.position forKey:@"position"];
[encoder encodeBool:self.isHidden forKey:@"isHidden"];
@@ -56,12 +58,13 @@
- (id) initWithCoder:(NSCoder*)decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.identifier = [decoder containsValueForKey:@"identifier"] ? [decoder decodeObjectForKey:@"identifier"] : [UUIDHelper newUUID];
self.alias = [decoder decodeObjectForKey:@"alias"];
self.buttonType = [decoder decodeIntForKey:@"buttonType"];
self.widgetType = [decoder decodeIntForKey:@"buttonType"];
self.sizeReference = [decoder containsValueForKey:@"sizeReference"] ? [decoder decodeIntForKey:@"sizeReference"] : longSide;
self.vibrationStyle = [decoder decodeIntForKey:@"vibrationStyle"];
self.mouseButtonAction = [decoder decodeIntForKey:@"mouseButtonAction"];
self.buttonTriggerMode = [decoder containsValueForKey:@"slideMode"] ? [decoder decodeIntForKey:@"slideMode"] : 0;
self.buttonMode = [decoder containsValueForKey:@"slideMode"] ? [decoder decodeIntForKey:@"slideMode"] : 0;
self.autoTapInterval = [decoder containsValueForKey:@"autoTapInterval"] ? [decoder decodeIntForKey:@"autoTapInterval"] : 45;
self.position = [decoder decodeCGPointForKey:@"position"];
self.isHidden = [decoder decodeBoolForKey:@"isHidden"];
+216
View File
@@ -0,0 +1,216 @@
//
// MicHandler.swift
// VoidLink
//
// Created by True砖家 on 2025/9/28.
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
//
import Foundation
import CoreMotion
@objc public protocol OnScreenWidgetStickMixedInputDelegate: AnyObject {
func mixRightStickAndGyroInput(x: CGFloat, y: CGFloat)
func mixLeftStickAndGyroInput(x: CGFloat, y: CGFloat)
func gyroMixInputStarted() -> Bool
}
@objc class MotionHandler: NSObject, OscInstanceProviderDelegate, OnScreenWidgetStickMixedInputDelegate{
func mixRightStickAndGyroInput(x: CGFloat, y: CGFloat) {
rightStickTouchInputX = x
rightStickTouchInputY = y
}
func mixLeftStickAndGyroInput(x: CGFloat, y: CGFloat) {
leftStickTouchInputX = x
leftStickTouchInputY = y
}
func gyroMixInputStarted() -> Bool {
return gyroControlStarted
}
func getOnScreenControlsInstance(_ sender: Any!) {
if let controls = sender as? OnScreenControls {
self.onScreenControls = controls
print("ClassA received OnScreenControls instance: \(controls)")
} else {
print("ClassA received an unknown sender")
}
}
static let shared = MotionHandler()
@objc class func sharedInstance() -> MotionHandler {
return MotionHandler.shared
}
private var gyroControlStarted: Bool = false
private var accelControlStarted: Bool = false
private let motionManager = CMMotionManager()
public var sensitvityYaw:CGFloat = 1.0
public var sensitvityPitch:CGFloat = 1.0
public var sensitvityRoll:CGFloat = 1.0
private var windowScene: Any?
private var onScreenControls: OnScreenControls = OnScreenControls.init()
public let stickMaxOffset: CGFloat = 0x7FFE
private var stickInputScale: CGFloat = 35
var yaw:Double = 0
var pitch:Double = 0
var roll:Double = 0
var rightStickTouchInputX:Double = 0
var rightStickTouchInputY:Double = 0
var leftStickTouchInputX:Double = 0
var leftStickTouchInputY:Double = 0
var updateInterval: TimeInterval = 1.0 / 120.0 {
didSet {
motionManager.accelerometerUpdateInterval = updateInterval
motionManager.gyroUpdateInterval = updateInterval
}
}
@objc public override init() {
super.init()
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
self.windowScene = scene
} else {
// Fallback on earlier versions
}
motionManager.accelerometerUpdateInterval = updateInterval
motionManager.gyroUpdateInterval = updateInterval
}
/// 启动传感器数据更新
@objc public func startGyroUpdate() {
gyroControlStarted = true
if motionManager.isGyroAvailable {
motionManager.startGyroUpdates(to: .main) { [weak self] gyroData, _ in
guard let self = self, let data = gyroData else { return }
self.handleGyroData(x: data.rotationRate.x,
y: data.rotationRate.y,
z: data.rotationRate.z)
}
}
}
@objc public func startAccelUpdate() {
gyroControlStarted = true
if motionManager.isAccelerometerAvailable {
motionManager.startAccelerometerUpdates(to: .main) { [weak self] accelData, _ in
guard let self = self, let data = accelData else { return }
self.handleAccelerometerData(x: data.acceleration.x,
y: data.acceleration.y,
z: data.acceleration.z)
}
}
}
/// 停止更新
@objc public func stopGyroUpdate() {
gyroControlStarted = false
if motionManager.isAccelerometerActive {
motionManager.stopAccelerometerUpdates()
}
self.onScreenControls.clearLeftStickTouchPadFlag()
self.onScreenControls.clearRightStickTouchPadFlag()
}
@objc public func stopAccelUpdate() {
accelControlStarted = false
if motionManager.isAccelerometerActive {
motionManager.stopAccelerometerUpdates()
}
self.onScreenControls.clearLeftStickTouchPadFlag()
self.onScreenControls.clearRightStickTouchPadFlag()
}
// MARK: - 私有方法处理数据
private func handleAccelerometerData(x: Double, y: Double, z: Double) {
// 在这里处理加速度数据,比如计算方向、存储或驱动逻辑
//print("加速度计: x=\(x), y=\(y), z=\(z)")
}
private func handleGyroData(x: Double, y: Double, z: Double) {
var yawSource:Double = 0
var pitchSource:Double = 0
var rollSource:Double = 0
if #available(iOS 13.0, *) {
let orientation = (windowScene as! UIWindowScene).interfaceOrientation
print("界面方向变化: \(orientation)")
switch orientation {
case .landscapeLeft:
yawSource = x
pitchSource = -y
rollSource = -z
case .landscapeRight:
yawSource = -x
pitchSource = y
rollSource = -z
case .portrait:
yawSource = -y
pitchSource = -x
rollSource = -z
case .portraitUpsideDown:
yawSource = y
pitchSource = x
rollSource = -z
default:
yawSource = x
pitchSource = -y
rollSource = -z
}
} else {
yawSource = x
pitchSource = -y
rollSource = z
}
if !gyroControlStarted {
self.onScreenControls.clearLeftStickTouchPadFlag()
self.onScreenControls.clearRightStickTouchPadFlag()
print("Gyro: stopped")
return
}
if true {
yaw = rightStickTouchInputX + gyroInputToStickInput(input:yawSource*sensitvityYaw*10)
yaw = self.clampStickInput(input: yaw)
pitch = rightStickTouchInputY - gyroInputToStickInput(input:pitchSource*sensitvityPitch*10)
pitch = self.clampStickInput(input: pitch)
self.onScreenControls.sendRightStickTouchPadEvent(yaw, pitch)
}
if true {
roll = leftStickTouchInputX + gyroInputToStickInput(input:rollSource*sensitvityRoll*10)
roll = self.clampStickInput(input: roll)
self.onScreenControls.sendLeftStickTouchPadEvent(roll, leftStickTouchInputY)
}
if false {
yaw = yawSource*sensitvityYaw*30
pitch = pitchSource*sensitvityPitch*30
LiSendMouseMoveEvent(Int16(yaw),Int16(pitch))
}
if false {
roll = rollSource*sensitvityPitch*100
LiSendMouseMoveEvent(Int16(roll),0)
}
}
private func clampStickInput(input: CGFloat) -> CGFloat{
return fmax(fmin(input, stickMaxOffset),-stickMaxOffset)
}
private func gyroInputToStickInput(input: CGFloat) -> CGFloat{
return self.clampStickInput(input: stickMaxOffset * input / 8)
}
}
+6 -32
View File
@@ -14,13 +14,11 @@
#include <Limelight.h>
@implementation NativeTouchHandler {
StreamView* streamView;
TemporarySettings* currentSettings;
bool activateCoordSelector;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
bool asyncNativeTouch;
unsigned int touchDownQos;
@@ -43,6 +41,8 @@
CGFloat EDGE_TOLERANCE;
CGRect streamViewBounds;
int64_t moveEventIntervalNSec;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
@@ -50,7 +50,7 @@
self->streamView = view;
self->currentSettings = settings;
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
self->touchMoveEventIntervalUs = currentSettings.touchMoveEventInterval.intValue;
self->moveEventIntervalNSec = (int64_t)(currentSettings.touchMoveEventInterval.intValue * 1000);;
self->streamViewBounds = view.bounds;
self->pointerIdDict = [NSMutableDictionary dictionary];
@@ -213,7 +213,6 @@
if (asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchDownQos, 0), ^{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self handleTouchDown:touch]; //generate & populate pointerId
if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
@@ -222,7 +221,6 @@
else{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self handleTouchDown:touch]; //generate & populate pointerId
if(self->activateCoordSelector) [self populatePointerObjIntoDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_DOWN];
@@ -231,50 +229,26 @@
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchMoveQos, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
usleep(self->touchMoveEventIntervalUs);
[[self getPointerObjFromDict:touch] doesNeedResetCoords];
}
});
else {
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
// usleep(self->touchMoveEventIntervalUs);
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchEndQos, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
[self->blacklistedTouches removeObject:@((uintptr_t)touch)];
}
//if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count])
});
else{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if([OnScreenControls.touchAddrsCapturedByOnScreenControls containsObject:@((uintptr_t)touch)]) continue;
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+2 -2
View File
@@ -19,7 +19,7 @@
@class StreamConfiguration;
// sending self as an instance to OnScreenWidgetView or other related classes
@protocol InstanceProviderDelegate <NSObject>
@protocol OscInstanceProviderDelegate <NSObject>
- (void)getOnScreenControlsInstance:(id)sender; // Renamed delegate method
@end
@@ -54,7 +54,7 @@ typedef NS_ENUM(NSInteger, OnScreenControlsLevel) {
@property (nonatomic, weak) id<InstanceProviderDelegate> delegate; // Delegate property
@property (nonatomic, weak) id<OscInstanceProviderDelegate> instanceReceiverDelegate; // Delegate property
- (void)sendInstance;
// @property (nonatomic, assign) CustomTapGestureRecognizer* mouseRightClickTapRecognizer; // this object will be passed to onscreencontrols class for areVirtualControllerTaps flag setting
+8 -8
View File
@@ -197,8 +197,8 @@ static float L3_Y;
// sending self as an instance to OnScreenWidgetView
- (void)sendInstance{
NSLog(@"OnScreenControls is sending its instance...");
if ([self.delegate respondsToSelector:@selector(getOnScreenControlsInstance:)]) {
[self.delegate getOnScreenControlsInstance:self];
if ([self.instanceReceiverDelegate respondsToSelector:@selector(getOnScreenControlsInstance:)]) {
[self.instanceReceiverDelegate getOnScreenControlsInstance:self];
} else {
NSLog(@"Delegate not set or does not respond to getOnScreenControlsInstance:");
}
@@ -373,11 +373,11 @@ static float L3_Y;
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
[_activeCustomOscButtonPositionDict removeAllObjects]; //reset the Dict.
// NSLog(@"_activeCustomOscButtonPositionDict update: STARTOVER");
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
[OnScreenControls.layerVibrationStyleDic setObject:@(buttonState.vibrationStyle) forKey:buttonState.name];
if(!buttonState.isHidden && [validPositionButtonNames containsObject:buttonState.name] && (buttonState.buttonType == LegacyOnScreenControls || [profilesManager getIndexOfSelectedProfile] == 0 ) ){
if(!buttonState.isHidden && [validPositionButtonNames containsObject:buttonState.name] && (buttonState.widgetType == LegacyOnScreenControls || [profilesManager getIndexOfSelectedProfile] == 0 ) ){
[_activeCustomOscButtonPositionDict setObject:[NSValue valueWithCGPoint:buttonState.position] forKey:buttonState.name]; // we got a buttonname -> position dict here
// NSLog(@"_activeCustomOscButtonPositionDict update, button name:%@, position: %f, %f", buttonState.name, buttonState.position.x, buttonState.position.y);
}
@@ -730,7 +730,7 @@ static float L3_Y;
- (void) setDPadCenter {
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if ([buttonState.name isEqualToString:@"dPad"]) {
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
@@ -746,7 +746,7 @@ static float L3_Y;
- (void) setAnalogStickPositions {
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; // returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
if ([buttonState.name isEqualToString:@"leftStickBackground"]) {
@@ -770,7 +770,7 @@ static float L3_Y;
OSCProfile *oscProfile = [profilesManager getSelectedProfile];
bool defaultProfileSelected = [profilesManager getIndexOfSelectedProfile] == 0;
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState *buttonStateDecoded = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
@@ -832,7 +832,7 @@ static float L3_Y;
OSCProfile *oscProfile = [profilesManager getSelectedProfile];
// bool defaultProfileSelected = [profilesManager getIndexOfSelectedProfile] == 0;
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonStateDecoded = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
for (CALayer *buttonLayer in self.OSCButtonLayers) { // iterate through each button layer on screen
+160 -49
View File
@@ -8,7 +8,7 @@
import UIKit
@objc class OnScreenWidgetView: UIView, InstanceProviderDelegate {
@objc class OnScreenWidgetView: UIView, OscInstanceProviderDelegate {
// receiving the OnScreenControls instance from delegate
@objc func getOnScreenControlsInstance(_ sender: Any) {
if let controls = sender as? OnScreenControls {
@@ -20,11 +20,31 @@ import UIKit
}
@objc public weak var guidelineDelegate: OnScreenWidgetGuidelineUpdateDelegate?
@objc protocol OnScreenWidgetGuidelineUpdateDelegate: AnyObject {
func updateGuidelinesForOnScreenWidget(_ sender: Any)
}
@objc public weak var mixInputDelegate: OnScreenWidgetStickMixedInputDelegate?
@objc protocol OnScreenWidgetStickMixedInputDelegate: AnyObject {
func mixRightStickAndGyroInput(x: CGFloat, y: CGFloat)
func mixLeftStickAndGyroInput(x: CGFloat, y: CGFloat)
func gyroMixInputStarted()->Bool
}
@objc public weak var functionalButtonDelegate: OnScreenFunctionalButtonDelegate?
@objc protocol OnScreenFunctionalButtonDelegate: AnyObject {
func startGyroUpdate()
func startAccelUpdate()
func stopGyroUpdate()
func stopAccelUpdate()
func openWidgetLayoutTool()
func switchWidgetProfile()
func bringUpSoftKeyboard()
func enterPip()
func toggleStatsOverlay()
func disconnectAndQuitApp()
}
@objc enum WidgetTypeEnum: UInt8 {
case uninitialized
case button
@@ -37,17 +57,19 @@ import UIKit
@objc static public var buttonVisualFeedbackEnabled: Bool = true
@objc public var widgetLabel: String
@objc public var cmdString: String
@objc public var identifier: String = ""
private var buttonString: String = ""
private var functionalButtonString: String = ""
private var touchPadString: String = ""
// super combo key string set
private var comboButtonStrings: [String] = []
@objc public var comboButtonStrings: [String] = []
private var comboKeyTimeIntervalMs: UInt32 = 0
@objc public var pressed: Bool
@objc public var widthFactor: CGFloat = 1.0
@objc public var heightFactor: CGFloat = 1.0
@objc public var buttonTriggerMode: Int = 0
@objc public var buttonMode: Int = 0
@objc private var tapToToggleFlag: Bool = true
@objc public var sizeReference: Int = WidgetSizeReference.longSide.rawValue
@@ -74,6 +96,9 @@ import UIKit
private var vibrationGenerator = UIImpactFeedbackGenerator(style: .light)
private var vibrationOn: Bool = false
// for movable buttons during streaming
@objc public var relocatedDuringStreaming: Bool = false
// for all touchPad or buttons hybrid with touchPads
@objc public var hasStickIndicator: Bool = false
@objc public var hasSensitivityTweak: Bool = false
@@ -198,7 +223,8 @@ import UIKit
self.comboButtonStrings = comboStrings.filter{$0 != touchPadString}
self.touchPadString = touchPadString
self.buttonString = self.comboButtonStrings.first ?? ""
self.functionalButtonString = Set(self.comboButtonStrings).intersection(Set(CommandManager.functionalButtonCmds)).first ?? ""
// let stickAndMouseTouchpads = ["LSPAD", "RSPAD", "LSVPAD", "RSVPAD", "MOUSEPAD"]
let nonVectorStickPads = ["LSPAD", "RSPAD"]
// if CommandManager.touchPadCmds.contains(self.touchPadString) {self.hasSensitivityTweak = true}
@@ -278,20 +304,26 @@ import UIKit
if self.widgetType == WidgetTypeEnum.button {
if !self.touchPadString.isEmpty {
self.mouseButtonAction = MouseButtonAction.noClick
self.buttonTriggerMode = ButtonTriggerMode.regular.rawValue
self.buttonMode = ButtonMode.regular.rawValue
}
if !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)) {
self.buttonTriggerMode = ButtonTriggerMode.regular.rawValue
if !self.functionalButtonString.isEmpty {
self.buttonMode = ButtonMode.movable.rawValue
if (self.functionalButtonString == "GYRO" ||
self.functionalButtonString == "ACCEL" ||
self.functionalButtonString == "MOTION")
{self.buttonMode = ButtonMode.tapToToggle.rawValue}
}
}
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ======================================================================================================
@objc public func setupAutoTapTimer() {
if self.widgetType == WidgetTypeEnum.button {
@@ -488,11 +520,15 @@ import UIKit
label.centerYAnchor.constraint(equalTo: centerYAnchor),])
if self.shape != "round"{
let shortSideLen = min(self.layer.bounds.size.width, self.layer.bounds.size.height)
self.layer.cornerRadius = shortSideLen/2 < 16 ? shortSideLen/3.2 : 16
self.setSquareWidgetCornerRadius()
}
}
private func setSquareWidgetCornerRadius(){
let shortSideLen = min(self.layer.bounds.size.width, self.layer.bounds.size.height)
self.layer.cornerRadius = shortSideLen/2 < 16 ? shortSideLen/3.2 : 16
}
private func setupView() {
label.text = self.widgetLabel
label.font = UIFont.boldSystemFont(ofSize: 19)
@@ -508,7 +544,7 @@ import UIKit
self.translatesAutoresizingMaskIntoConstraints = true // this is mandatory to prevent unexpected key view location change
self.layer.cornerRadius = 16
self.setSquareWidgetCornerRadius()
self.layer.borderWidth = self.borderWidth
self.tweakAlpha()
@@ -534,8 +570,8 @@ import UIKit
}
}
if !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)){
self.layer.borderWidth = 0
if !self.functionalButtonString.isEmpty{
self.layer.borderWidth = self.borderWidth
}
if self.shape == "round" {
@@ -1054,6 +1090,11 @@ import UIKit
private func handleButtonDown() {
if !OnScreenWidgetView.editMode {self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
if !OnScreenWidgetView.editMode, !self.functionalButtonString.isEmpty {self.handleFunctionalButtonDown()}
// sadflkasdfl;
if OnScreenWidgetView.buttonVisualFeedbackEnabled {
CATransaction.begin()
CATransaction.setDisableActions(true)
@@ -1072,6 +1113,11 @@ import UIKit
private func handleButtonUp() {
if !OnScreenWidgetView.editMode {self.sendComboButtonsUpEvent(comboStrings: self.comboButtonStrings)}
if !OnScreenWidgetView.editMode && !self.functionalButtonString.isEmpty{
self.handleFunctionalButtonUp()
}
if OnScreenWidgetView.buttonVisualFeedbackEnabled {
CATransaction.begin()
CATransaction.setDisableActions(true)
@@ -1123,10 +1169,8 @@ import UIKit
//=========================================send on screen controller stick events
private func touchInputToStickInput(input: CGFloat) -> CGFloat{
var target = stickMaxOffset * input / stickInputScale
if target > stickMaxOffset {target = stickMaxOffset}
if target < -stickMaxOffset {target = -stickMaxOffset}
return target
let target = stickMaxOffset * input / stickInputScale
return fmax(fmin(target, stickMaxOffset),-stickMaxOffset)
}
private func touchInputToStickBallCoord(input: CGFloat) -> CGFloat {
@@ -1145,7 +1189,12 @@ import UIKit
// vertical input must be inverted
targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset)
targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset)
self.onScreenControls.sendRightStickTouchPadEvent(targetX, targetY)
if self.mixInputDelegate?.gyroMixInputStarted() != true {
self.onScreenControls.sendRightStickTouchPadEvent(targetX, targetY)
}
else{
self.mixInputDelegate?.mixRightStickAndGyroInput(x: targetX, y: targetY)
}
}
private func sendLeftStickTouchPadEvent(inputX: CGFloat, inputY: CGFloat){
@@ -1153,7 +1202,12 @@ import UIKit
var targetY = -self.touchInputToStickInput(input: inputY)
targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset)
targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset)
self.onScreenControls.sendLeftStickTouchPadEvent(targetX, targetY)
if self.mixInputDelegate?.gyroMixInputStarted() != true {
self.onScreenControls.sendLeftStickTouchPadEvent(targetX, targetY)
}
else{
self.mixInputDelegate?.mixLeftStickAndGyroInput(x: targetX, y: targetY)
}
}
//==========================================================================================================
@@ -1228,6 +1282,7 @@ import UIKit
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchBegan = true
self.firstTouchMoved = false
super.touchesBegan(touches, with: event)
@@ -1244,8 +1299,13 @@ import UIKit
quickDoubleTapDetected = touchTapTimeInterval < QUICK_TAP_TIME_INTERVAL
let touch = touches.first
if OnScreenWidgetView.editMode || !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)) {self.touchBeganLocation = touch!.location(in: superview)}
else {self.touchBeganLocation = touch!.location(in: self)}
// get touchBeganLocation
if OnScreenWidgetView.editMode {self.touchBeganLocation = touch!.location(in: superview)}
else {
if self.buttonMode == ButtonMode.movable.rawValue {self.touchBeganLocation = touch!.location(in: superview)}
else {self.touchBeganLocation = touch!.location(in: self)}
}
self.latestTouchLocation = touchBeganLocation
}
@@ -1317,7 +1377,7 @@ import UIKit
// this will also deal with button events
if self.widgetType == WidgetTypeEnum.button && !self.comboButtonStrings.isEmpty {
if self.buttonTriggerMode != ButtonTriggerMode.tapToToggle.rawValue {
if self.buttonMode != ButtonMode.tapToToggle.rawValue {
self.handleTapDownOrSlidein()
setLock.lock()
self.capturedTouches.union(touches)
@@ -1348,8 +1408,11 @@ import UIKit
private func moveByTouch(touch: UITouch){
let currentLocation: CGPoint
if OnScreenWidgetView.editMode || !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)) {currentLocation = touch.location(in: superview)}
else {currentLocation = touch.location(in: self)}
if OnScreenWidgetView.editMode {currentLocation = touch.location(in: superview)}
else {
if self.buttonMode == ButtonMode.movable.rawValue {currentLocation = touch.location(in: superview)}
else {return}
}
if !firstTouchMoved {
// First move event
@@ -1366,6 +1429,8 @@ import UIKit
center = CGPoint(x: outOfBoundsX ? center.x : center.x+offsetX, y: outOfBoundsY ? center.y : center.y+offsetY)
latestTouchLocation = currentLocation
relocatedDuringStreaming = true
// center = currentLocation;
//NSLog("x coord: %f, y coord: %f", self.frame.origin.x, self.frame.origin.y)
if OnScreenWidgetView.editMode {
@@ -1415,7 +1480,7 @@ import UIKit
setLock.lock()
let captured = widget.capturedTouches.contains(touch)
setLock.unlock()
if !captured || widget.buttonTriggerMode == ButtonTriggerMode.regular.rawValue {continue}
if !captured || widget.buttonMode == ButtonMode.regular.rawValue {continue}
widget.handleFingerUpOrSlideout()
setLock.lock()
widget.capturedTouches.remove(touch)
@@ -1431,7 +1496,7 @@ import UIKit
for subview in self.superview?.subviews ?? [] {
if let widget = subview as? OnScreenWidgetView{
if widget.widgetType != WidgetTypeEnum.button {continue}
let isSlidableButton = widget.buttonTriggerMode == ButtonTriggerMode.slideToToggle.rawValue || widget.buttonTriggerMode == ButtonTriggerMode.slideAndHold.rawValue
let isSlidableButton = widget.buttonMode == ButtonMode.slideToToggle.rawValue || widget.buttonMode == ButtonMode.slideAndHold.rawValue
let pointInSubview = widget.convert(locationInSuperView, from: self.superview)
if widget.bounds.contains(pointInSubview){
setLock.lock()
@@ -1451,13 +1516,13 @@ import UIKit
setLock.unlock()
if !captured || !isSlidableButton {continue}
// print("UIButton: \(widget.buttonLabel) out test, \(widget.touchPadString), \(CACurrentMediaTime())")
if(widget.buttonTriggerMode == ButtonTriggerMode.slideToToggle.rawValue){
if(widget.buttonMode == ButtonMode.slideToToggle.rawValue){
widget.handleFingerUpOrSlideout()
setLock.lock()
widget.capturedTouches.remove(touch)
setLock.unlock()
}
if(widget.buttonTriggerMode == ButtonTriggerMode.slideAndHold.rawValue){
if(widget.buttonMode == ButtonMode.slideAndHold.rawValue){
// do nothing here
}
}
@@ -1468,6 +1533,7 @@ import UIKit
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
if !OnScreenWidgetView.editMode {
if !self.touchPadString.isEmpty{
@@ -1475,15 +1541,14 @@ import UIKit
}
if self.widgetType == WidgetTypeEnum.button {
if self.buttonTriggerMode == ButtonTriggerMode.slideToToggle.rawValue || self.buttonTriggerMode == ButtonTriggerMode.slideAndHold.rawValue {self.handleButtonSliding(touches: touches)}
if self.buttonMode == ButtonMode.slideToToggle.rawValue || self.buttonMode == ButtonMode.slideAndHold.rawValue {self.handleButtonSliding(touches: touches)}
}
if !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)){
if self.buttonMode == ButtonMode.movable.rawValue{
if let touch = touches.first {
NSLog("touchTapTimeStamp %f", self.touchTapTimeStamp)
if CACurrentMediaTime() - self.touchTapTimeStamp > 0.3 { // temporarily relocate special buttons
self.moveByTouch(touch: touch)
self.handleButtonUp()
}
}
}
@@ -1570,6 +1635,56 @@ import UIKit
}
}
private func handleFunctionalButtonDown(){
switch self.functionalButtonString {
case "GYRO":
self.functionalButtonDelegate?.startGyroUpdate()
case "ACCEL":
self.functionalButtonDelegate?.startAccelUpdate()
case "MOTION":
self.functionalButtonDelegate?.startGyroUpdate()
self.functionalButtonDelegate?.startAccelUpdate()
default:
break
}
}
private func handleFunctionalButtonUp(){
let longPressed = CACurrentMediaTime() - self.touchTapTimeStamp > 0.3
switch self.functionalButtonString {
case "SETTINGS":
if longPressed, buttonMode == ButtonMode.movable.rawValue {break}
NotificationCenter.default.post(name: Notification.Name("SettingsOverlayButtonPressedNotification"), object:nil) // inform layout tool controller to fetch button size factors. self will be passed as the object of the notification
case "GYRO":
self.functionalButtonDelegate?.stopGyroUpdate()
case "ACCEL":
self.functionalButtonDelegate?.stopAccelUpdate()
case "MOTION":
self.functionalButtonDelegate?.stopGyroUpdate()
self.functionalButtonDelegate?.stopAccelUpdate()
default:
break
}
}
private func clearRightStickTouchPadFlag(){
if self.mixInputDelegate?.gyroMixInputStarted() != true {
self.onScreenControls.clearRightStickTouchPadFlag()
}
else{
self.mixInputDelegate?.mixRightStickAndGyroInput(x: 0, y: 0)
}
}
private func clearLeftStickTouchPadFlag(){
if self.mixInputDelegate?.gyroMixInputStarted() != true {
self.onScreenControls.clearLeftStickTouchPadFlag()
}
else{
self.mixInputDelegate?.mixRightStickAndGyroInput(x: 0, y: 0)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesEnded(touches, with: event)
}
@@ -1633,15 +1748,15 @@ import UIKit
if !OnScreenWidgetView.editMode && !self.touchPadString.isEmpty {
switch self.touchPadString{
case "LSPAD":
self.onScreenControls.clearLeftStickTouchPadFlag()
self.clearLeftStickTouchPadFlag()
if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndHideIndicator()}
case "RSPAD":
self.onScreenControls.clearRightStickTouchPadFlag()
self.clearRightStickTouchPadFlag()
if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndHideIndicator()}
case "LSVPAD":
self.onScreenControls.clearLeftStickTouchPadFlag()
self.clearLeftStickTouchPadFlag()
case "RSVPAD":
self.onScreenControls.clearRightStickTouchPadFlag()
self.clearRightStickTouchPadFlag()
case "WASDPAD":
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["W"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["A"]!,Int8(KEY_ACTION_UP), 0)
@@ -1677,7 +1792,7 @@ import UIKit
}
if !OnScreenWidgetView.editMode && !self.cmdString.contains("+") && !self.comboButtonStrings.isEmpty { // if the command(keystring contains "+", it's a legacy multi-key command
if self.buttonTriggerMode == ButtonTriggerMode.slideToToggle.rawValue || self.buttonTriggerMode == ButtonTriggerMode.slideAndHold.rawValue {
if self.buttonMode == ButtonMode.slideToToggle.rawValue || self.buttonMode == ButtonMode.slideAndHold.rawValue {
self.handleFingerUpAfterSliding(touches: touches)
setLock.lock()
self.capturedTouches.minus(touches)
@@ -1685,17 +1800,6 @@ import UIKit
}
}
if !OnScreenWidgetView.editMode && !Set(CommandManager.specialOverlayButtonCmds).isDisjoint(with: Set(self.comboButtonStrings)){
if CACurrentMediaTime() - self.touchTapTimeStamp < 0.3 {
switch self.cmdString {
case "SETTINGS":
NotificationCenter.default.post(name: Notification.Name("SettingsOverlayButtonPressedNotification"), object:nil) // inform layout tool controller to fetch button size factors. self will be passed as the object of the notification
default:
break
}
}
}
CATransaction.commit()
if OnScreenWidgetView.editMode {
@@ -1734,13 +1838,20 @@ import UIKit
}
}
if self.buttonTriggerMode != ButtonTriggerMode.tapToToggle.rawValue {self.handleFingerUpOrSlideout()}
if self.buttonMode != ButtonMode.tapToToggle.rawValue {self.handleFingerUpOrSlideout()}
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview == nil {
self.handleFingerUpOrSlideout()
if self.functionalButtonString == "GYRO" {self.functionalButtonDelegate?.stopGyroUpdate()}
if self.functionalButtonString == "ACCEL" {self.functionalButtonDelegate?.stopAccelUpdate()}
if self.functionalButtonString == "MOTION" {
self.functionalButtonDelegate?.stopGyroUpdate()
self.functionalButtonDelegate?.stopAccelUpdate()
}
buttonDownVisualEffectLayer.borderWidth = 0
buttonDownVisualEffectLayer.borderColor = defaultBorderColor
crossMarkLayer.removeFromSuperlayer()
stickBallLayer.removeFromSuperlayer()
}
+6 -27
View File
@@ -19,7 +19,6 @@
TemporarySettings* currentSettings;
bool activateCoordSelector;
CGFloat pointerVelocityDividerLocationByPoints;
uint16_t touchMoveEventIntervalUs;
bool asyncNativeTouch;
unsigned int touchDownQos;
@@ -42,6 +41,8 @@
CGFloat EDGE_TOLERANCE;
CGRect streamViewBounds;
int64_t moveEventIntervalNSec;
}
- (id)initWithView:(StreamView*)view andSettings:(TemporarySettings*)settings{
@@ -49,7 +50,7 @@
self->streamView = view;
self->currentSettings = settings;
self->activateCoordSelector = currentSettings.pointerVelocityModeDivider.floatValue != 1.0;
self->touchMoveEventIntervalUs = currentSettings.touchMoveEventInterval.intValue;
self->moveEventIntervalNSec = (int64_t)(currentSettings.touchMoveEventInterval.intValue * 1000);;
self->streamViewBounds = view.bounds;
self->pointerIdDict = [NSMutableDictionary dictionary];
@@ -228,46 +229,24 @@
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchMoveQos, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
usleep(self->touchMoveEventIntervalUs);
[[self getPointerObjFromDict:touch] doesNeedResetCoords];
}
});
else {
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
// usleep(self->touchMoveEventIntervalUs);
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(asyncNativeTouch) dispatch_async(dispatch_get_global_queue(touchEndQos, 0), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, moveEventIntervalNSec), dispatch_get_main_queue(), ^{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
[self->blacklistedTouches removeObject:@((uintptr_t)touch)];
}
//if(self->touchPointSpawnedAtUpperScreenEdge && [[event allTouches] count] == [touches count])
});
else{
for (UITouch* touch in touches){
// continue to the next loop if current touch is already captured by OSC. works only in regular native touch
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_UP]; //send touch event before remove pointerId
[self removePointerId:touch]; //then remove pointerId
if(self->activateCoordSelector) [self removePointerObjFromDict:touch];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+2 -1
View File
@@ -37,7 +37,7 @@
@property (assign, nonatomic) CGFloat streamAspectRatio;
@property (assign, nonatomic) CGRect originalFrame;
@property (assign, nonatomic) bool widgetToolOpened;
@property (strong, nonatomic) OnScreenControls* onScreenControls;
- (void) setupStreamView:(ControllerSupport*)controllerSupport
interactionDelegate:(id<UserInteractionDelegate>)interactionDelegate
@@ -53,6 +53,7 @@
andConfig:(StreamConfiguration*)streamConfig;
- (void) clearOnScreenWidgets;
- (void) reloadOnScreenWidgetViews;
- (void) saveRelocatedWidgetViews;
- (CGSize) getVideoAreaSize;
- (CGPoint) adjustCoordinatesForVideoArea:(CGPoint)point;
+75 -28
View File
@@ -35,16 +35,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
*/
@implementation StreamView {
UIView* streamFrameTopLayerView;
UIViewController* streamFrameVC;
TemporarySettings* settings;
OnScreenControls* onScreenControls;
KeyboardInputField* keyInputField;
BOOL isInputingText;
NSMutableSet* keysDown;
float streamAspectRatio;
// iOS 13.4 mouse support
NSInteger lastMouseButtonMask;
@@ -78,6 +76,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
UIKeyModifierFlags comboKeyModifierFlags;
WidgetSizeTransition _widgetSizeTransition;
OSCProfilesManager* profilesManager;
}
- (void) setupStreamView:(ControllerSupport*)controllerSupport
@@ -156,7 +155,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
// we'll render on-screen controls on the toplayer too:
onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig]; // don't delete, this is mandatory
_onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig]; // don't delete, this is mandatory
/*
// here we pass the tap recognizer to the onscreencontrols obj
if (settings.touchMode.intValue == RelativeTouch){
@@ -167,7 +166,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue];
if (settings.touchMode.intValue != RelativeTouch && settings.touchMode.intValue != NativeTouch ) {
Log(LOG_I, @"On-screen controls disabled in non-relative touch mode");
[onScreenControls setLevel:OnScreenControlsLevelOff];
[_onScreenControls setLevel:OnScreenControlsLevelOff];
//pass touchesCaptureByOnScreenButtons Set to the native touchhandler, this NSSet is init witihin onscreencontrols class, don't do it again in native touch handler class
[OnScreenControls.touchAddrsCapturedByOnScreenControls removeAllObjects]; // reset the attribute to nil
@@ -182,11 +181,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
//else if (level == OnScreenControlsLevelAuto) {
else if (false) { // level auto cancelled in settings
[controllerSupport initAutoOnScreenControlMode:onScreenControls];
[controllerSupport initAutoOnScreenControlMode:_onScreenControls];
}
else {
Log(LOG_I, @"Setting manual on-screen controls level: %d", (int)level);
[onScreenControls setLevel:level];
[_onScreenControls setLevel:level];
}
// It would be nice to just use GCMouse on iOS 14+ and the older API on iOS 13
// but unfortunately that isn't possible today. GCMouse doesn't recognize many
@@ -263,6 +262,17 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
NSLog(@"keyboard will show %f", CACurrentMediaTime());
}
- (UIViewController *)parentViewController {
UIResponder *responder = self;
while (responder) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = responder.nextResponder;
}
return nil;
}
// this method also deals with recovering streamview when local keyboard is turned off
- (void)keyboardWillHide{
// NSLog(@"keyboard will hide markmark %f", CACurrentMediaTime());
@@ -401,7 +411,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (void) showOnScreenControls {
#if !TARGET_OS_TV
if(!_widgetToolOpened) [onScreenControls show];
if(!_widgetToolOpened) [_onScreenControls show];
#endif
}
@@ -411,7 +421,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (void) disableOnScreenControls {
#if !TARGET_OS_TV
[onScreenControls setLevel:OnScreenControlsLevelOff];
[_onScreenControls setLevel:OnScreenControlsLevelOff];
#endif
}
@@ -438,7 +448,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
andConfig:(StreamConfiguration*)streamConfig {
// we'll render on-screen controllers on the toplayer too.
onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig];
_onScreenControls = [[OnScreenControls alloc] initWithView:self->streamFrameTopLayerView controllerSup:controllerSupport streamConfig:streamConfig];
/*
// pass mouseRightClickTapRecognizer to onScreenControls obj here:
if([self isOscEnabled]){
@@ -446,7 +456,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
RelativeTouchHandler* relativeTouchHandler = (RelativeTouchHandler *)touchHandler;
onScreenControls.mouseRightClickTapRecognizer = relativeTouchHandler.mouseRightClickTapRecognizer;
} */
if([self isOscEnabled]) [onScreenControls setLevel:(OnScreenControlsLevel)settings.onscreenControls.intValue];
if([self isOscEnabled]) [_onScreenControls setLevel:(OnScreenControlsLevel)settings.onscreenControls.intValue];
}
@@ -477,6 +487,35 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
else return longSide;
}
- (void)saveRelocatedWidgetViews{
NSMutableDictionary* relocatedWidgetDict = [NSMutableDictionary dictionary];
for(UIView* subview in self->streamFrameTopLayerView.subviews){
if([subview isKindOfClass:[OnScreenWidgetView class]]){
OnScreenWidgetView* widget = (OnScreenWidgetView* )subview;
if(widget.relocatedDuringStreaming) [relocatedWidgetDict setObject:widget forKey:widget.identifier];
}
}
if(relocatedWidgetDict.allKeys.count == 0) return;
profilesManager = [OSCProfilesManager sharedManager:self->streamFrameTopLayerView.bounds];
OSCProfile *newProfile = [profilesManager getSelectedProfile];
for (NSInteger i = 0; i < newProfile.buttonStatesEncoded.count; i++) {
NSData* buttonStateEncoded = newProfile.buttonStatesEncoded[i];
OnScreenButtonState *newButtonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if([relocatedWidgetDict.allKeys containsObject:newButtonState.identifier]){
OnScreenWidgetView* widget = [relocatedWidgetDict objectForKey:newButtonState.identifier];
newButtonState.position = [profilesManager normalizeWidgetPosition:widget.center];
NSData *newButtonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:newButtonState requiringSecureCoding:YES error:nil];
newProfile.buttonStatesEncoded[i] = newButtonStateEncoded;
}
}
[profilesManager replaceSelectedProfileWith:newProfile];
}
- (void) reloadOnScreenWidgetViews{
// NSLog(@"reload on screen keyboard buttons here");
@@ -484,25 +523,33 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
// remove all keyboard widget views first
[self clearOnScreenWidgets];
// get streamFrameVC
if(!streamFrameVC) streamFrameVC = [self parentViewController];
// bool customOscEnabled = [self isOscEnabled] && settings.onscreenControls.intValue == OnScreenControlsLevelCustom;
if(![self isOnScreenWidgetEnabled]) return;
OnScreenWidgetView.buttonVisualFeedbackEnabled = settings.buttonVisualFeedback;
OSCProfilesManager* profilesManager = [OSCProfilesManager sharedManager: self.bounds];
if(!profilesManager) profilesManager = [OSCProfilesManager sharedManager:self->streamFrameTopLayerView.bounds];
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
if(!OnScreenWidgetView.editMode){ // in edit mode, keyboard widget view will be updated within layoutool view controller.
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if(buttonState.buttonType == CustomOnScreenWidget){
if(buttonState.widgetType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape]; //reconstruct widgetView
//--------------------------------------------------
onScreenControls.delegate = widgetView; // connecting onScreenControls to OnScreenWidgetView, sending the active instance for touchPad stick control
[onScreenControls sendInstance];
_onScreenControls.instanceReceiverDelegate = widgetView; // connecting onScreenControls to OnScreenWidgetView, sending the active instance for touchPad stick control
[_onScreenControls sendInstance];
//--------------------------------------------------
widgetView.functionalButtonDelegate = (id<OnScreenFunctionalButtonDelegate>)streamFrameVC;
widgetView.mixInputDelegate = (id<OnScreenWidgetStickMixedInputDelegate>) [MotionHandler sharedInstance];
widgetView.identifier = buttonState.identifier;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.widthFactor = buttonState.widthFactor;
widgetView.heightFactor = buttonState.heightFactor;
@@ -515,7 +562,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.trackballDecelerationRate = buttonState.decelerationRate;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.buttonTriggerMode = buttonState.buttonTriggerMode;
widgetView.buttonMode = buttonState.buttonMode;
// Add the widgetView to the view controller's view
[self->streamFrameTopLayerView addSubview:widgetView]; // add keyboard button to the stream frame view. must add it to the target view before setting location.
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
@@ -533,11 +580,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
- (OnScreenControlsLevel) getCurrentOscState {
if (onScreenControls == nil) {
if (_onScreenControls == nil) {
return OnScreenControlsLevelOff;
}
else {
return [onScreenControls getLevel];
return [_onScreenControls getLevel];
}
}
@@ -714,10 +761,10 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
[self startInteractionTimer];
if(settings.touchMode.intValue == NativeTouch || settings.touchMode.intValue == RelativeTouch){
[self->onScreenControls handleTouchDownEvent:touches];
[self->_onScreenControls handleTouchDownEvent:touches];
[self->touchHandler touchesBegan:touches withEvent:event];
}
else if(![onScreenControls handleTouchDownEvent:touches]) [touchHandler touchesBegan:touches withEvent:event];
else if(![_onScreenControls handleTouchDownEvent:touches]) [touchHandler touchesBegan:touches withEvent:event];
}
- (UIBarButtonItem *)createButtonWithImageNamed:(NSString *)imageName backgroundColor:(UIColor *)backgroundColor target:(id)target action:(SEL)action keyCode:(NSInteger)keyCode isToggleable:(BOOL)isToggleable {
@@ -880,9 +927,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if(self->settings.touchMode.intValue == NativeTouch || self->settings.touchMode.intValue == RelativeTouch){
[self->touchHandler touchesMoved:touches withEvent:event];
[self->onScreenControls handleTouchMovedEvent:touches];
[self->_onScreenControls handleTouchMovedEvent:touches];
}
else if(![self->onScreenControls handleTouchMovedEvent:touches]) [self->touchHandler touchesMoved:touches withEvent:event];
else if(![self->_onScreenControls handleTouchMovedEvent:touches]) [self->touchHandler touchesMoved:touches withEvent:event];
}
- (void) handleKeyCombos:(UIPress*) press{
@@ -976,9 +1023,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if(settings.touchMode.intValue == NativeTouch || settings.touchMode.intValue == RelativeTouch){
[self->touchHandler touchesEnded:touches withEvent:event]; // when touches ended, must call the native touchhandler before onScreenControls, since the NSSet of touches captured by on screen button shall be updated later
[self->onScreenControls handleTouchUpEvent:touches];
[self->_onScreenControls handleTouchUpEvent:touches];
}
else if(![onScreenControls handleTouchUpEvent:touches]) [touchHandler touchesEnded:touches withEvent:event];
else if(![_onScreenControls handleTouchUpEvent:touches]) [touchHandler touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+17
View File
@@ -0,0 +1,17 @@
//
// UUIDHelper.swift
// VoidLink
//
// Created by True砖家 on 2025/9/30.
// Copyright © 2025 True砖家@Bilibili. All rights reserved.
//
import Foundation
@objc public class UUIDHelper: NSObject {
@objc public static func newUUID() -> String {
return UUID().uuidString
}
}
@@ -94,8 +94,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) IBOutlet UIStackView *mouseDownButtonStack;
@property (strong, nonatomic) IBOutlet UISegmentedControl *mouseButtonDownSelector;
@property (strong, nonatomic) IBOutlet UIStackView *triggerModeStack;
@property (strong, nonatomic) IBOutlet UISegmentedControl *triggerModeSelector;
@property (strong, nonatomic) IBOutlet UIStackView *buttonModeStack;
@property (strong, nonatomic) IBOutlet UISegmentedControl *buttonModeSelector;
@property (strong, nonatomic) IBOutlet UIStackView *autoTapStack;
@property (strong, nonatomic) IBOutlet UILabel *autoTapLabel;
@@ -122,11 +122,13 @@
// _activeCustomOscButtonPositionDict will be updated every time when the osc profile is reloaded
OSCProfile *oscProfile = [profilesManager getSelectedProfile]; //returns the currently selected OSCProfile
for (NSData *buttonStateEncoded in oscProfile.buttonStates) {
for (NSData *buttonStateEncoded in oscProfile.buttonStatesEncoded) {
// OnScreenButtonState* buttonState = [NSKeyedUnarchiver unarchivedObjectOfClass:[OnScreenButtonState class] fromData:buttonStateEncoded error:nil];
OnScreenButtonState* buttonState = [profilesManager unarchiveButtonStateEncoded:buttonStateEncoded];
if(buttonState.buttonType == CustomOnScreenWidget){
if(buttonState.widgetType == CustomOnScreenWidget){
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:buttonState.name buttonLabel:buttonState.alias shape:buttonState.widgetShape]; //reconstruct widgetView
widgetView.identifier = buttonState.identifier;
NSLog(@"cmd: %@, id: %@", buttonState.name, widgetView.identifier);
widgetView.guidelineDelegate = (id<OnScreenWidgetGuidelineUpdateDelegate>)self;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.widthFactor = buttonState.widthFactor;
@@ -140,7 +142,7 @@
widgetView.trackballDecelerationRate = buttonState.decelerationRate;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.buttonTriggerMode = buttonState.buttonTriggerMode;
widgetView.buttonMode = buttonState.buttonMode;
// Add the widgetView to the view controller's view
[self.view insertSubview:widgetView belowSubview:self.widgetPanelStack];
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
@@ -609,8 +611,8 @@
bool noValidMouseButtonString = ![CommandManager.mouseButtonMappings.allKeys containsObject:cmdString];
bool noValidTouchPadString = ![CommandManager.touchPadCmds containsObject:cmdString];
bool noValidOscButtonString = ![CommandManager.oscButtonMappings.allKeys containsObject:cmdString];
bool noValidSpecialButtonString = ![CommandManager.specialOverlayButtonCmds containsObject:cmdString];
bool paramInvalid = noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidSpecialButtonString && noValidSuperComboButtonString;
bool noValidFunctionalButtonString = ![CommandManager.functionalButtonCmds containsObject:cmdString];
bool paramInvalid = noValidKeyboardString && noValidMouseButtonString && noValidTouchPadString && noValidOscButtonString && noValidFunctionalButtonString && noValidSuperComboButtonString;
if([buttonLabel isEqualToString:@""]) widgetInitParams[@"buttonLabel"] = [[cmdString lowercaseString] capitalizedString];
@@ -634,6 +636,7 @@
- (void) updateWidget:(OnScreenWidgetView* )widget byParams:(NSMutableDictionary* )widgetInitParams createNew:(bool)createNew{
if(![self isWidgetParamsValid:widgetInitParams]) return;
OnScreenWidgetView* newWidget = [[OnScreenWidgetView alloc] initWithCmdString:widgetInitParams[@"cmdString"] buttonLabel:widgetInitParams[@"buttonLabel"] shape:widgetInitParams[@"shape"]]; //reconstruct widgetView
newWidget.identifier = createNew ? [UUIDHelper newUUID] : widget.identifier;
newWidget.guidelineDelegate = (id<OnScreenWidgetGuidelineUpdateDelegate>)self;
newWidget.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
newWidget.widthFactor = widget.widthFactor;
@@ -647,7 +650,7 @@
newWidget.minStickOffset = [widgetInitParams[@"minStickOffsetString"] floatValue];
[newWidget setVibrationWithStyle:widget.vibrationStyle];
newWidget.mouseButtonAction = widget.mouseButtonAction;
newWidget.buttonTriggerMode = widget.buttonTriggerMode;
newWidget.buttonMode = widget.buttonMode;
[self.view insertSubview:newWidget belowSubview:self.widgetPanelStack];
if(createNew) [newWidget setLocationWithPosition:CGPointMake(90, 130)];
@@ -669,6 +672,7 @@
if(![self isWidgetParamsValid:widgetInitParams]) return;
//saving & present the keyboard button:
OnScreenWidgetView* widgetView = [[OnScreenWidgetView alloc] initWithCmdString:widgetInitParams[@"cmdString"] buttonLabel:widgetInitParams[@"buttonLabel"] shape:widgetInitParams[@"shape"]];
widgetView.identifier = [UUIDHelper newUUID];
widgetView.guidelineDelegate = (id<OnScreenWidgetGuidelineUpdateDelegate>)self;
widgetView.translatesAutoresizingMaskIntoConstraints = NO; // weird but this is mandatory, or you will find no key views added to the right place
widgetView.minStickOffset = [widgetInitParams[@"minStickOffsetString"] floatValue];
@@ -768,7 +772,7 @@
// hide irrelevant stacks
self.autoTapStack.hidden = selectedWidgetView.widgetType != WidgetTypeEnumButton;
self.triggerModeStack.hidden = selectedWidgetView.widgetType != WidgetTypeEnumButton;
self.buttonModeStack.hidden = selectedWidgetView.widgetType != WidgetTypeEnumButton;
bool showSensitivityFactorStack = selectedWidgetView.hasSensitivityTweak;
bool showStickIndicatorOffsetStack = selectedWidgetView.hasStickIndicator;
@@ -790,8 +794,6 @@
self.mouseDownButtonStack.hidden = !([selectedWidgetView.cmdString containsString:@"MOUSEPAD"] && selectedWidgetView.widgetType == WidgetTypeEnumTouchPad);
self.decelerationRateStack.hidden = !([selectedWidgetView.cmdString containsString:@"TRACKBALL"] && selectedWidgetView.widgetType == WidgetTypeEnumTouchPad);
[self autoFitStack:self.widgetPanelStack];
// setup values
@@ -840,8 +842,17 @@
[self decelerationRateSliderMoved:self.decelerationRateSlider];
self.mouseButtonDownSelector.selectedSegmentIndex = selectedWidgetView.mouseButtonAction;
[self.triggerModeSelector setSelectedSegmentIndex:selectedWidgetView.buttonTriggerMode];
NSSet *specialCmds = [NSSet setWithArray:CommandManager.functionalButtonCmds];
NSSet *comboButtonStrings = [NSSet setWithArray:widgetView.comboButtonStrings];
bool isFuncationalButton = [specialCmds intersectsSet:comboButtonStrings];
[self.buttonModeSelector setEnabled:!isFuncationalButton forSegmentAtIndex:0];
[self.buttonModeSelector setEnabled:!isFuncationalButton forSegmentAtIndex:1];
if(isFuncationalButton) [self.autoTapStack setHidden:YES];
[self.buttonModeSelector setSelectedSegmentIndex:selectedWidgetView.buttonMode];
[self autoFitStack:self.widgetPanelStack];
if([self isIPhone]){
self.vibrationStyleStack.hidden =
[widgetView.cmdString containsString:@"MOUSEPAD"] ||
@@ -959,7 +970,7 @@
- (void)slideModeChanged:(UISegmentedControl* )sender{
if(self->selectedWidgetView != nil && self->widgetViewSelected){
selectedWidgetView.buttonTriggerMode = _triggerModeSelector.selectedSegmentIndex;
selectedWidgetView.buttonMode = _buttonModeSelector.selectedSegmentIndex;
}
}
@@ -1139,9 +1150,9 @@
[self.mouseButtonDownSelector setTitleTextAttributes:whiteFontAttributes forState:UIControlStateNormal];
self.mouseDownButtonStack.hidden = YES;
[self.triggerModeSelector addTarget:self action:@selector(slideModeChanged:) forControlEvents:(UIControlEventValueChanged)];
[self.triggerModeSelector setTitleTextAttributes:whiteFontAttributes forState:UIControlStateNormal];
self.triggerModeStack.hidden = YES;
[self.buttonModeSelector addTarget:self action:@selector(slideModeChanged:) forControlEvents:(UIControlEventValueChanged)];
[self.buttonModeSelector setTitleTextAttributes:whiteFontAttributes forState:UIControlStateNormal];
self.buttonModeStack.hidden = YES;
if([self isIPhone]){
@@ -381,6 +381,8 @@ BOOL isCustomResolution(int resolutionSelected) {
}
}
}
if(![self manuallyChangedFPS]) [self framerateChanged];
}
- (void)viewDidAppear:(BOOL)animated{
@@ -1603,7 +1605,8 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.resolutionSelector addTarget:self action:@selector(newResolutionChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate];
[self.framerateSelector addTarget:self action:@selector(updateBitrate) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector addTarget:self action:@selector(framerateChanged) forControlEvents:UIControlEventValueChanged];
[self.bitrateSlider setMinimumValue:0];
[self.bitrateSlider setMaximumValue:(sizeof(bitrateTable) / sizeof(*bitrateTable)) - 1];
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
@@ -2126,6 +2129,23 @@ BOOL isCustomResolution(int resolutionSelected) {
[self handleOswGestureChange];
}
- (BOOL)manuallyChangedFPS {
NSString *key = @"manuallyChangedFPS";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
return [defaults objectForKey:key] ? [defaults boolForKey:key] : NO;
}
- (void)framerateChanged{
NSString *key = @"manuallyChangedFPS";
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
NSInteger fps = [self getChosenFrameRate];
[self.touchMoveEventIntervalSlider setValue: fps>60 ? 1/((CGFloat)fps)*0.5*1000000-1500 : 3530];
[self touchMoveEventIntervalSliderMoved:self.touchMoveEventIntervalSlider];
[self updateBitrate];
}
- (void) updateBitrate {
NSInteger fps = [self getChosenFrameRate];
NSInteger width = [self getChosenStreamWidth];
@@ -27,7 +27,7 @@
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, AVPictureInPictureControllerDelegate>
#else
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate>
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate, OnScreenFunctionalButtonDelegate>
#endif
@property (nonatomic) StreamManager* streamMan;
@@ -40,8 +40,8 @@
@property (nonatomic, strong) MetalViewController *metalViewController;
@property (nonatomic, strong) ImGuiRenderer *imguiView;
-(void)updatePreferredDisplayMode:(BOOL)streamActive;
-(void)reConfigStreamViewRealtime:(BOOL)reloadSettings;
- (void)updatePreferredDisplayMode:(BOOL)streamActive;
- (void)setUserInteractionEnabledForStreamView:(bool)enabled;
- (bool)shallDisableGyroHotSwitch;
@@ -45,8 +45,6 @@
- (id)initWithRefreshRate:(float)arg1 videoDynamicRange:(int)arg2;
@end
@implementation StreamFrameViewController {
ControllerSupport *_controllerSupport;
StreamManager *_streamMan;
@@ -91,6 +89,7 @@
LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC;
ToolboxViewController* toolBoxViewController;
MicHandler* micHandler;
MotionHandler *_motionHandler;
#else
UITapGestureRecognizer *_menuTapGestureRecognizer;
@@ -332,7 +331,6 @@
// Insert at index 0 to ensure it doesn't cover OSC controls (CALayers)
if([_streamView.superview isKindOfClass:[UIScrollView class]]){
[_streamView removeFromSuperview];
NSLog(@"removeFromSuperview %f", CACurrentMediaTime());
}
[self.view insertSubview:_streamView atIndex:0];
@@ -435,6 +433,10 @@
[self startDisplayLink];
}
_motionHandler = [MotionHandler sharedInstance];
_streamView.onScreenControls.instanceReceiverDelegate = _motionHandler;
[_streamView.onScreenControls sendInstance];
NSLog(@"frameview gestures: %d", (uint32_t)[self.view.gestureRecognizers count]);
NSLog(@"streamview gestures: %d", (uint32_t)[_streamView.gestureRecognizers count]);
}
@@ -775,6 +777,7 @@
}
- (void)openWidgetLayoutTool{
[_streamView saveRelocatedWidgetViews];
_streamView.widgetToolOpened = true;
[self->_streamView disableOnScreenControls];
[self->_streamView clearOnScreenWidgets]; // clear all onScreenKeyboardButtons before entering edit mode
@@ -785,6 +788,7 @@
}
- (void)switchWidgetProfile{
[_streamView saveRelocatedWidgetViews];
_streamView.widgetToolOpened = true;
[self->_streamView disableOnScreenControls];
[self->_streamView clearOnScreenWidgets]; // clear all onScreenKeyboardButtons before entering edit mode
@@ -1063,6 +1067,7 @@
- (void)applicationWillResignActive:(NSNotification *)notification {
//[self.pipController startPictureInPicture];
//sleep(1);
[_streamView saveRelocatedWidgetViews];
#if !TARGET_OS_TV
#endif
@@ -1143,6 +1148,7 @@
- (void)expandSettingsView{
self.mainFrameViewcontroller.settingsExpandedInStreamView = true; //notify mainFrameViewContorller that this is a setting expansion in stream view, some settings shall be disabled.
[_streamView saveRelocatedWidgetViews];
[self.mainFrameViewcontroller expandSettingsView];
}
@@ -1556,6 +1562,22 @@
[self reConfigStreamViewRealtime];
}
- (void)startGyroUpdate{
[_motionHandler startGyroUpdate];
}
- (void)startAccelUpdate{
[_motionHandler startAccelUpdate];
}
- (void)stopGyroUpdate{
[_motionHandler stopGyroUpdate];
}
- (void)stopAccelUpdate{
[_motionHandler stopAccelUpdate];
}
#if !TARGET_OS_TV
// Require a confirmation when streaming to activate a system gesture
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
+35 -5
View File
@@ -1453,6 +1453,36 @@
}
}
},
"cKS-fy-Zpg.segmentTitles[4]" : {
"comment" : "Class = \"UISegmentedControl\"; cKS-fy-Zpg.segmentTitles[4] = \"movable\"; ObjectID = \"cKS-fy-Zpg\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "movable"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移动"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移動"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移動"
}
}
}
},
"CpY-Sb-BDc.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"CpY-Sb-BDc\";",
"extractionState" : "extracted_with_value",
@@ -3272,31 +3302,31 @@
}
},
"qNQ-fB-jhY.text" : {
"comment" : "Class = \"UILabel\"; text = \"Trigger mode\"; ObjectID = \"qNQ-fB-jhY\";",
"comment" : "Class = \"UILabel\"; text = \"Button mode\"; ObjectID = \"qNQ-fB-jhY\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Trigger mode"
"value" : "Button mode"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "触发模式"
"value" : "按键模式"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "觸發模式"
"value" : "按鍵模式"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "觸發模式"
"value" : "按鍵模式"
}
}
}
+40 -10
View File
@@ -1580,13 +1580,13 @@
}
},
"fcy-zp-LGa.text" : {
"comment" : "Class = \"UILabel\"; text = \"Stick Indicator\"; ObjectID = \"fcy-zp-LGa\";",
"comment" : "Class = \"UILabel\"; text = \"Stick indicator\"; ObjectID = \"fcy-zp-LGa\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Stick Indicator"
"value" : "Stick indicator"
}
}
}
@@ -2312,31 +2312,31 @@
}
},
"o9I-Gt-YRd.text" : {
"comment" : "Class = \"UILabel\"; text = \"Trigger Mode\"; ObjectID = \"o9I-Gt-YRd\";",
"comment" : "Class = \"UILabel\"; text = \"Button mode\"; ObjectID = \"o9I-Gt-YRd\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Trigger Mode"
"value" : "Button mode"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "滑动触发"
"state" : "translated",
"value" : "按键模式"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "滑動觸發"
"state" : "translated",
"value" : "按鍵模式"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "滑動觸發"
"state" : "translated",
"value" : "按鍵模式"
}
}
}
@@ -2521,6 +2521,36 @@
}
}
},
"ojp-aS-m8w.segmentTitles[4]" : {
"comment" : "Class = \"UISegmentedControl\"; ojp-aS-m8w.segmentTitles[4] = \"movable\"; ObjectID = \"ojp-aS-m8w\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "movable"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移动"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移動"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "可移動"
}
}
}
},
"ozU-7C-qKE.text" : {
"comment" : "Class = \"UILabel\"; text = \"Button down\"; ObjectID = \"ozU-7C-qKE\";",
"extractionState" : "extracted_with_value",