advancedPencilSupport-demo1

This commit is contained in:
TrueZhuanjia
2025-12-02 17:15:56 +08:00
parent 6dc9ee569b
commit 1262a7b41f
16 changed files with 473 additions and 45 deletions
+7 -3
View File
@@ -26,6 +26,7 @@
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
18A765BD2ECD51FB004784AA /* ControllerUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A765BC2ECD51FB004784AA /* ControllerUtil.swift */; };
18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E4BCE92E891895000A8657 /* MotionHandler.swift */; };
18FCADAF2ECAC23200787A96 /* PencilHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FCADAE2ECAC23200787A96 /* PencilHandler.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 */; };
@@ -184,6 +185,7 @@
18A765BC2ECD51FB004784AA /* ControllerUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerUtil.swift; 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>"; };
18FCADAE2ECAC23200787A96 /* PencilHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PencilHandler.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>"; };
@@ -713,6 +715,7 @@
9819CC21254F180F008A7C8E /* AbsoluteTouchHandler.m */,
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
18FCADAE2ECAC23200787A96 /* PencilHandler.swift */,
18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */,
18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */,
18A765BC2ECD51FB004784AA /* ControllerUtil.swift */,
@@ -1022,6 +1025,7 @@
97E20DE02E2164A9000D5DCA /* FrameQueue.m in Sources */,
18951D1D2EBB6D2300A86E12 /* TouchPadGestureHandler.swift in Sources */,
97E20DE12E2164A9000D5DCA /* FloatBuffer.m in Sources */,
18FCADAF2ECAC23200787A96 /* PencilHandler.swift in Sources */,
97E20DE22E2164A9000D5DCA /* PaddedLabel.m in Sources */,
97E20DE32E2164A9000D5DCA /* BandwidthTracker.m in Sources */,
84591A512BE1625E006AAB10 /* CustomEdgeSlideGestureRecognizer.m in Sources */,
@@ -1303,7 +1307,7 @@
"$(PROJECT_DIR)/Limelight/Database",
);
INFOPLIST_FILE = "VoidLink/Limelight-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "VoidLink - beta";
INFOPLIST_KEY_CFBundleDisplayName = VoidLink;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1369,7 +1373,7 @@
"$(PROJECT_DIR)/Limelight/Database",
);
INFOPLIST_FILE = "VoidLink/Limelight-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "VoidLink - beta";
INFOPLIST_KEY_CFBundleDisplayName = VoidLink;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1395,7 +1399,7 @@
"$(PROJECT_DIR)/libs/FFmpeg/lib/iOS-Sim",
);
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.voidlink.tf.iOS;
PRODUCT_BUNDLE_IDENTIFIER = com.voidlink.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
SKIP_INSTALL = NO;
+77 -28
View File
@@ -61,7 +61,7 @@
<viewControllerLayoutGuide type="bottom" id="XvO-F3-j4u"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="WRy-3f-gEP" customClass="UIScrollView">
<rect key="frame" x="0.0" y="0.0" width="2165" height="5000"/>
<rect key="frame" x="0.0" y="0.0" width="3700" height="5000"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="zpX-lo-MLA" userLabel="Resolutionstak">
@@ -1150,7 +1150,7 @@
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="RGd-TV-mkU" userLabel="sendDummyEvent">
<rect key="frame" x="432" y="2264" width="299.5" height="31"/>
<rect key="frame" x="432" y="2264" width="589.5" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Dummy Event" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Gus-CK-wpc" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="149.5" height="31"/>
@@ -1164,6 +1164,26 @@
<constraint firstAttribute="width" constant="130" id="bOP-xq-rYt"/>
</constraints>
</switch>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="jCF-si-KLC" userLabel="useBuiltinMicStack">
<rect key="frame" x="319.5" y="0.0" width="270" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Use Built-in Mic" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bfV-QT-eKw" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="120" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="H3f-qy-ySA" userLabel="useBuiltinMicSwitch">
<rect key="frame" x="140" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="e7s-Xg-mNQ"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="GWg-Dx-N44"/>
</constraints>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="Gs7-g5-VMU"/>
@@ -1189,26 +1209,6 @@
<constraint firstAttribute="width" constant="380" id="Oij-e2-w81"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="jCF-si-KLC" userLabel="useBuiltinMicStack">
<rect key="frame" x="432" y="2310" width="270" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Use Built-in Mic" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bfV-QT-eKw" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="120" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="H3f-qy-ySA" userLabel="useBuiltinMicSwitch">
<rect key="frame" x="140" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="e7s-Xg-mNQ"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="GWg-Dx-N44"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="gBG-8F-6F8">
<rect key="frame" x="432" y="2349" width="281" height="31"/>
<subviews>
@@ -1862,10 +1862,55 @@
<constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="zuF-a5-GOI">
<rect key="frame" x="3145" y="31" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Pencil Tick" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qat-gL-qiL">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="bVv-CW-kN8">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments>
<segment title="Manual"/>
<segment title="Timestamp"/>
<segment title="Off"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
</segmentedControl>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="56.5" id="DvL-nr-jOU"/>
<constraint firstAttribute="width" constant="380" id="ZCX-ga-WNg"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="O2s-9s-ABi">
<rect key="frame" x="3145" y="113" width="380" height="55.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Tick Interval" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xE0-v2-n9r" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="380" height="16.5"/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="4000" translatesAutoresizingMaskIntoConstraints="NO" id="IOG-l4-gtQ">
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
<color key="tintColor" systemColor="linkColor"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="55.5" id="Jg0-mh-DYq"/>
<constraint firstAttribute="width" constant="380" id="gdZ-zL-Wl7"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<size key="freeformSize" width="2165" height="5000"/>
<size key="freeformSize" width="3700" height="5000"/>
<connections>
<outlet property="appThemeSelector" destination="wi6-L4-KwS" id="mf9-YY-eyE"/>
<outlet property="appThemeStack" destination="Wf6-BA-Mdv" id="hlq-bB-S8N"/>
@@ -1959,6 +2004,10 @@
<outlet property="optimizeGamesSwitch" destination="Sui-Ig-YxN" id="Lxu-si-ugy"/>
<outlet property="passthroughGesturesStack" destination="wUP-2q-5vh" id="a4w-16-lLs"/>
<outlet property="passthroughGesturesSwitch" destination="eO1-kV-e9j" id="5r8-o4-t9J"/>
<outlet property="pencilTickIntervalSlider" destination="IOG-l4-gtQ" id="3Kb-uT-ILa"/>
<outlet property="pencilTickIntervalStack" destination="O2s-9s-ABi" id="0kk-q4-ppy"/>
<outlet property="pencilTickSelector" destination="bVv-CW-kN8" id="Ymr-VI-Ilj"/>
<outlet property="pencilTickStack" destination="zuF-a5-GOI" id="Nn7-GA-yQM"/>
<outlet property="performanceGraphStack" destination="H8p-G9-pnl" id="9RJ-uO-IpF"/>
<outlet property="pinchGestureStack" destination="uEq-Pc-Fum" id="68g-K6-P87"/>
<outlet property="pinchGestureSwitch" destination="TJy-Zp-DKM" id="Haj-ha-vNy"/>
@@ -2051,7 +2100,7 @@
</items>
</navigationBar>
</objects>
<point key="canvasLocation" x="-503.95683453237416" y="1977.520661157025"/>
<point key="canvasLocation" x="-1023" y="1983"/>
</scene>
<!--Reveal View Controller-->
<scene sceneID="rR7-ZT-bc7">
@@ -2870,7 +2919,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"/>
@@ -2879,13 +2928,13 @@
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.67450980392156867" green="0.49803921568627452" 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.0" green="0.76470588235294112" blue="0.81568627450980391" 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>
+8 -1
View File
@@ -83,7 +83,14 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) {
transitionWithOrientation,
};
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
typedef NS_ENUM(NSInteger, PencilTickMode) {
ManualTick,
TimestampTick,
PencilTickDisabled
};
- (void) saveSettings:(Settings*)settings
withBitrate:(NSInteger)bitrate
framerate:(NSInteger)framerate
height:(NSInteger)height
width:(NSInteger)width
+5 -3
View File
@@ -55,7 +55,8 @@
return uid;
}
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
- (void) saveSettings:(Settings*)settings
withBitrate:(NSInteger)bitrate
framerate:(NSInteger)framerate
height:(NSInteger)height
width:(NSInteger)width
@@ -125,9 +126,10 @@
controllerMouseExpo:(CGFloat)controllerMouseExpo
controllerGyroSwitchMode:(NSInteger)controllerGyroSwitchMode
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
__block Settings* settingsToSave = settings;
[_managedObjectContext performBlockAndWait:^{
Settings* settingsToSave = [self retrieveSettings];
if(!settingsToSave) settingsToSave = [self retrieveSettings];
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
settingsToSave.height = [NSNumber numberWithInteger:height];
+6
View File
@@ -103,6 +103,12 @@
@property (nonatomic, retain) NSNumber * controllerMousePointerVelocity;
@property (nonatomic, retain) NSNumber * controllerMouseExpo;
// Pencil settings:
@property (nonatomic, retain) NSNumber * pencilTickMode;
@property (nonatomic, retain) NSNumber * pencilTickIntervalUs;
- (id) initFromSettings:(Settings*)settings;
@end
+5
View File
@@ -158,6 +158,11 @@
self.controllerMouseStick = settings.controllerMouseStick;
self.controllerMousePointerVelocity = settings.controllerMousePointerVelocity;
self.controllerMouseExpo = settings.controllerMouseExpo;
// Pencil settings:
self.pencilTickMode = settings.pencilTickMode;
self.pencilTickIntervalUs = settings.pencilTickIntervalUs;
#endif
+163
View File
@@ -0,0 +1,163 @@
//
// PencilHandler.swift
// VoidLink
//
// Created by True砖家 on 2025/11/17.
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
//
import UIKit
@objc class PencilHandler: UIResponder {
/// 用来处理所有 touch 事件的宿主视图
weak var streamView: UIView?
// private var tickTimer: SafeTimer
private var streamAspectRatio: Float
private var tickInterval: TimeInterval
private var manualTick: Bool
@objc init(streamView: UIView, settings: TemporarySettings) {
self.streamView = streamView
streamAspectRatio = settings.width.floatValue/settings.height.floatValue
tickInterval = TimeInterval(settings.pencilTickIntervalUs.floatValue/1000000)
manualTick = settings.pencilTickMode.intValue == PencilTickMode.ManualTick.rawValue
super.init()
}
// MARK: - Touch Events
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let event = event else { return }
for touch in touches {
let coalesced = event.coalescedTouches(for: touch) ?? []
_ = self.sendStylusEvent(touchBatch: coalesced)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let event = event else { return }
for touch in touches {
let coalesced = event.coalescedTouches(for: touch) ?? []
_ = self.sendStylusEvent(touchBatch: coalesced)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let event = event else { return }
for touch in touches {
let coalesced = event.coalescedTouches(for: touch) ?? []
_ = self.sendStylusEvent(touchBatch: coalesced)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchesEnded(touches, with: event)
}
// MARK: - Core Logic
func getVideoAreaSize()-> CGSize {
guard let streamView = streamView else { return CGSize(width: 0, height: 0) }
if streamView.bounds.size.width > streamView.bounds.size.height * CGFloat(streamAspectRatio) {
return CGSizeMake(streamView.bounds.size.height * CGFloat(streamAspectRatio), streamView.bounds.size.height);
} else {
return CGSizeMake(streamView.bounds.size.width, streamView.bounds.size.width / CGFloat(streamAspectRatio));
}
}
func adjustCoordinatesForVideoArea(point: CGPoint) -> CGPoint {
guard let streamView = streamView else { return CGPoint(x: 0, y: 0) }
var x = point.x - streamView.bounds.origin.x
var y = point.y - streamView.bounds.origin.y
if x < streamView.bounds.width / 2 {
x -= 1
} else {
x += 1
}
if y < streamView.bounds.height / 2 {
y -= 1
} else {
y += 1
}
let videoSize = getVideoAreaSize() // 返回 CGSize
let videoOrigin = CGPoint(
x: streamView.bounds.width / 2 - videoSize.width / 2,
y: streamView.bounds.height / 2 - videoSize.height / 2
)
let confinedX = min(max(x, videoOrigin.x), videoOrigin.x + videoSize.width) - videoOrigin.x
let confinedY = min(max(y, videoOrigin.y), videoOrigin.y + videoSize.height) - videoOrigin.y
return CGPoint(x: confinedX, y: confinedY)
}
func getRotation(fromAzimuthAngle azimuthAngle: Float) -> UInt16 {
// iOS 的 azimuthAngle 为 0 表示指向西
// VoidLink 期望 0 表示指向北
// 所以顺时针旋转 90 度来转换
var rotationAngle = (azimuthAngle - .pi / 2) * (180.0 / .pi) // 弧度转角度
if rotationAngle < 0 {
rotationAngle += 360
}
return UInt16(rotationAngle)
}
func getTilt(fromAltitudeAngle altitudeAngle: Float) -> UInt8 {
// iOS 的 altitudeAngle 为 0 表示笔与触摸面平行
// VoidLink 期望 tilt 为 0 表示笔垂直触摸面
// 所以 tilt = 90 - altitude
let altitudeDegs = abs(Int16(altitudeAngle * (180.0 / .pi)))
return UInt8(90 - min(90, Int(altitudeDegs)))
}
private func sendStylusEvent(touchBatch: [UITouch], with event: UIEvent? = nil) -> DispatchTime {
guard let streamView = streamView, touchBatch.count>0 else { return .now() }
var tickMoment: TimeInterval = 0
var previousTimeStamp: TimeInterval = 0
let dispatchMoment: DispatchTime = .now()
var delay:TimeInterval = 0
for touch in touchBatch {
if previousTimeStamp == 0 {previousTimeStamp = touch.timestamp}
tickMoment += (manualTick ? tickInterval : touch.timestamp - previousTimeStamp)
let point = touch.location(in: streamView)
let azimuth = touch.azimuthAngle(in: streamView)
let altitude = touch.altitudeAngle
let location = self.adjustCoordinatesForVideoArea(point: point)
let videoSize = self.getVideoAreaSize()
let eventType:UInt8
switch touch.phase {
case .began:
eventType = UInt8(LI_TOUCH_EVENT_DOWN)
case .moved:
eventType = UInt8(LI_TOUCH_EVENT_MOVE)
case .ended:
eventType = UInt8(LI_TOUCH_EVENT_UP)
case .cancelled:
eventType = UInt8(LI_TOUCH_EVENT_UP)
default:
eventType = UInt8(LI_TOUCH_EVENT_MOVE)
}
delay = eventType == UInt8(LI_TOUCH_EVENT_UP) ? 0.0086 : 0
DispatchQueue.global().asyncAfter(deadline: dispatchMoment + tickMoment + delay) {
LiSendPenEvent(eventType, UInt8(LI_TOOL_TYPE_PEN), 0, Float(location.x/videoSize.width), Float(location.y/videoSize.height), Float(touch.force/touch.maximumPossibleForce)/sin(Float(altitude)), 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude)))
}
}
return dispatchMoment + tickMoment + delay
}
}
+30 -5
View File
@@ -58,7 +58,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
int localMousePointerMode;
UIResponder* touchHandler;
PencilHandler* pencilHandler;
id<UserInteractionDelegate> interactionDelegate;
NSTimer* interactionTimer;
BOOL hasUserInteracted;
@@ -127,6 +128,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
// tvOS requires RelativeTouchHandler to manage Apple Remote input
self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self];
#else
pencilHandler = [[PencilHandler alloc] initWithStreamView:self settings:settings];
// iOS uses touch Mode depending on user preference
switch (settings.touchMode.intValue) {
@@ -753,7 +757,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if (@available(iOS 13.4, *)) { // now only pencil events are restricted
for (UITouch* touch in touches) {
if (touch.type == UITouchTypePencil) {
if ([self sendStylusEvent:touch]) return;
if(settings.pencilTickMode.intValue == PencilTickDisabled){
[self sendStylusEvent:touch];
return;
}
else{
[pencilHandler touchesBegan:touches withEvent:event];
return;
}
}
}
}
@@ -910,7 +921,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
for (UITouch* touch in touches) {
if (touch.type == UITouchTypePencil) {
if ([self sendStylusEvent:touch]) return;
if(settings.pencilTickMode.intValue == PencilTickDisabled){
[self sendStylusEvent:touch];
return;
}
else{
[pencilHandler touchesMoved:touches withEvent:event];
return;
}
}
if (@available(iOS 13.4, *)) {
UITouch *touch = [touches anyObject];
@@ -1015,7 +1033,14 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if (@available(iOS 13.4, *)){ //now only pencil events are restricted
for (UITouch* touch in touches) {
if (touch.type == UITouchTypePencil) {
if ([self sendStylusEvent:touch]) return;
if(settings.pencilTickMode.intValue == PencilTickDisabled){
[self sendStylusEvent:touch];
return;
}
else{
[pencilHandler touchesEnded:touches withEvent:event];
return;
}
}
}
}
@@ -1046,7 +1071,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
if (@available(iOS 13.4, *)){ //now only pencil events are restricted
for (UITouch* touch in touches) {
if (touch.type == UITouchTypePencil) {
if ([self sendStylusEvent:touch]) return;
[self touchesEnded:touches withEvent:event];
}
}
}
@@ -72,6 +72,8 @@
<attribute name="optimizeGames" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
<attribute name="oscLayoutToolFingers" optional="YES" attributeType="Integer 16" defaultValueString="4" usesScalarValueType="NO" syncable="YES"/>
<attribute name="passthroughGestures" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="pencilTickIntervalUs" optional="YES" attributeType="Float" defaultValueString="2500" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pencilTickMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pinchSensitivity" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="pointerVelocityModeDivider" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
@@ -4528,6 +4528,29 @@
}
}
},
"Pencil" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "手写笔"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "手寫筆"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "手寫筆"
}
}
}
},
"PerfModeTip" : {
"extractionState" : "manual",
"localizations" : {
+1
View File
@@ -41,6 +41,7 @@
// 方法
- (void)setSectionWithIcon:(UIImage *)icon andSize:(CGFloat)size;
- (void)setSectionWithIcon:(UIImage *)icon size:(CGFloat)size weight:(UIImageSymbolWeight)weight API_AVAILABLE(ios(13.0));
- (void)addSubStackView:(UIStackView *)stackView;
- (void)addToParentStack:(UIStackView *)parentStack;
- (void)removeSubStackView:(UIStackView *)stackView;
+17
View File
@@ -228,6 +228,23 @@ static BOOL overridePersistedFoldState = YES;
[self updateLayout];
}
- (void)setSectionWithIcon:(UIImage *)icon size:(CGFloat)size weight:(UIImageSymbolWeight)weight API_AVAILABLE(ios(13.0)){
if (@available(iOS 13.0, *)) {
UIImageSymbolConfiguration *config;
if(icon.isSymbolImage) config = [UIImageSymbolConfiguration configurationWithPointSize:size weight:weight];
else{
config = [UIImageSymbolConfiguration configurationWithPointSize:size];
icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
_iconImageView.image = [icon imageWithConfiguration:config];
} else {
_iconImageView.image = icon;
}
_iconImageView.tintColor = [ThemeManager textColor];
_iconImageView.hidden = (icon == nil);
[self updateLayout];
}
- (void)setLeadingTrailingPadding:(CGFloat)leadingTrailingPadding {
_leadingTrailingPadding = leadingTrailingPadding;
[self updateLayout];
@@ -27,6 +27,7 @@ CGFloat startPointX;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
if(touch.type != UITouchTypeDirect) return;
capturedUITouch = touch;
startPointX = [capturedUITouch locationInView:self.view].x;
CGFloat streamFrameViewWidthInPoints = self.view.frame.size.width;
@@ -50,7 +51,7 @@ CGFloat startPointX;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [super touchesEnded:touches withEvent:event];
if(touches.anyObject.type != UITouchTypeDirect) return;
if([touches containsObject:capturedUITouch]){
CGFloat _endPointX = [capturedUITouch locationInView:self.view].x;
CGFloat screenWidthInPoints = self.view.frame.size.width;
@@ -240,9 +240,19 @@
@property (strong, nonatomic) IBOutlet UIStackView *testStack;
@property (nonatomic, strong) MicHandler *micHandler;
@property (strong, nonatomic) IBOutlet UIStackView *pencilTickStack;
@property (strong, nonatomic) IBOutlet UISegmentedControl *pencilTickSelector;
@property (strong, nonatomic) IBOutlet UIStackView *pencilTickIntervalStack;
@property (strong, nonatomic) IBOutlet UISlider *pencilTickIntervalSlider;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
@@ -555,6 +555,10 @@ BOOL isCustomResolution(int resolutionSelected) {
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone;
}
- (BOOL)isIPad {
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
}
- (CGFloat)getStandardNavBarHeight{
return [self isIPhone] ? UINavigationBarHeightIPhone : UINavigationBarHeightIPad;
}
@@ -820,10 +824,25 @@ BOOL isCustomResolution(int resolutionSelected) {
[self addSetting:self.gyroToStickMinOffsetStack ofId:@"gyroToStickMinOffsetStack" withInfoTag:NO withDynamicLabel:YES to:motionControlSection];
[self addSetting:self.synthPhysicalInputStack ofId:@"synthPhysicalInputStack" withInfoTag:NO withDynamicLabel:NO to:motionControlSection];
[motionControlSection addToParentStack:_parentStack];
if([self isIPad]){
MenuSectionView* pencilSection = [[MenuSectionView alloc] init];
pencilSection.delegate = self;
pencilSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Pencil"];
pencilSection.identifier = @"SettingsSectionMotionControl";
if (@available(iOS 13.0, *)) {
[pencilSection setSectionWithIcon:[UIImage systemImageNamed:@"pencil.and.outline"] size:19 weight:UIImageSymbolWeightHeavy];
}
[self addSetting:self.pencilTickStack ofId:@"pencilTickStack" withInfoTag:YES withDynamicLabel:NO to:pencilSection];
[self addSetting:self.pencilTickIntervalStack ofId:@"pencilTickIntervalStack" withInfoTag:NO withDynamicLabel:YES to:pencilSection];
[pencilSection addToParentStack:_parentStack];
}
MenuSectionView *gesturesSection = [[MenuSectionView alloc] init];
gesturesSection.delegate = self;
gesturesSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Gestures"];
gesturesSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Gestures"];
gesturesSection.identifier = @"SettingsSectionGestures";
if (@available(iOS 13.0, *)) {
[gesturesSection setSectionWithIcon:[UIImage systemImageNamed:@"hand.draw"] andSize:23];
@@ -2071,7 +2090,11 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.gyroToStickMinOffsetSlider addTarget:self action:@selector(gyroMinStickOffsetSliderMoved:) forControlEvents:UIControlEventValueChanged];
//Motion control settings
// pencil support settings:
[self loadPencilSettings:self->tempSettings];
self->settingsViewJustLoaded = false;
}];
@@ -3257,11 +3280,40 @@ BOOL isCustomResolution(int resolutionSelected) {
[self saveGameProfileConfigs];
}
- (void)pencilTickModeChanged:(UISegmentedControl* )sender{
[self setHidden:sender.selectedSegmentIndex != ManualTick forStack:self.pencilTickIntervalStack];
}
- (void)pencilTickIntervalSliderMoved:(UISlider* )sender{
[self findDynamicLabelFromStack:self.pencilTickIntervalStack].text = [NSString stringWithFormat:@" %d μs ", (uint16_t)sender.value];
}
- (void)loadPencilSettings:(TemporarySettings*) tempSettings{
if([self isIPad]){
self.pencilTickSelector.selectedSegmentIndex = tempSettings.pencilTickMode.intValue;
[self.pencilTickSelector addTarget:self action:@selector(pencilTickModeChanged:) forControlEvents:UIControlEventValueChanged];
[self pencilTickModeChanged:self.pencilTickSelector];
[self.pencilTickIntervalSlider setValue:tempSettings.pencilTickIntervalUs.floatValue];
[self.pencilTickIntervalSlider addTarget:self action:@selector(pencilTickIntervalSliderMoved:) forControlEvents:UIControlEventValueChanged];
[self pencilTickIntervalSliderMoved:self.pencilTickIntervalSlider];
}
}
- (void)populatePencilSettings:(Settings*)currentSettings{
if([self isIPad]){
currentSettings.pencilTickMode = @(self.pencilTickSelector.selectedSegmentIndex);
currentSettings.pencilTickIntervalUs = @(self.pencilTickIntervalSlider.value);
}
}
- (void) saveSettings {
[self preSavingActions];
Settings* currentSettings = [dataMan retrieveSettings];
[self populatePencilSettings:currentSettings];
CGFloat settingsMenuOffset = _rememberFoldStateSwitch.isOn ? _scrollView.contentOffset.y : 0;
NSInteger height = self.mainFrameViewController.settingsExpandedInStreamView ? currentSettings.height.intValue : [self getChosenStreamHeight];
@@ -3343,7 +3395,8 @@ BOOL isCustomResolution(int resolutionSelected) {
NSInteger controllerGyroSwitchMode = self.controllerGyroSwitchButtonSetter.selectedSegmentIndex;
NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value;
[dataMan saveSettingsWithBitrate:_bitrate
[dataMan saveSettings:currentSettings
withBitrate:_bitrate
framerate:framerate
height:height
width:width
+60
View File
@@ -1627,6 +1627,42 @@
}
}
},
"bVv-CW-kN8.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[0] = \"Manual\"; ObjectID = \"bVv-CW-kN8\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Manual"
}
}
}
},
"bVv-CW-kN8.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[1] = \"Timestamp\"; ObjectID = \"bVv-CW-kN8\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Timestamp"
}
}
}
},
"bVv-CW-kN8.segmentTitles[2]" : {
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[2] = \"Off\"; ObjectID = \"bVv-CW-kN8\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Off"
}
}
}
},
"c1r-Hf-b7t.text" : {
"comment" : "Class = \"UILabel\"; text = \"Down button\"; ObjectID = \"c1r-Hf-b7t\";",
"extractionState" : "extracted_with_value",
@@ -4465,6 +4501,18 @@
}
}
},
"Qat-gL-qiL.text" : {
"comment" : "Class = \"UILabel\"; text = \"Pencil Tick\"; ObjectID = \"Qat-gL-qiL\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Pencil Tick"
}
}
}
},
"QdG-9a-5AZ.text" : {
"comment" : "Class = \"UILabel\"; text = \"Soft Keyboard Toolbar\"; ObjectID = \"QdG-9a-5AZ\";",
"extractionState" : "extracted_with_value",
@@ -5209,6 +5257,18 @@
}
}
},
"xE0-v2-n9r.text" : {
"comment" : "Class = \"UILabel\"; text = \"Tick Interval\"; ObjectID = \"xE0-v2-n9r\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Tick Interval"
}
}
}
},
"xfv-K0-1CO.text" : {
"comment" : "Class = \"UILabel\"; text = \"Tap any widget to load configuration (per widget)\\n\"; ObjectID = \"xfv-K0-1CO\";",
"extractionState" : "extracted_with_value",