From 8d54ed93164bfd8cbc4be2fa3b1e15a6329a0260 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Wed, 11 Feb 2026 21:43:33 +0800 Subject: [PATCH] feat: on screen widget touch track point --- VoidLink/Base.lproj/iPad.storyboard | 1566 +++++++++-------- VoidLink/Base.lproj/iPhone.storyboard | 62 +- VoidLink/Database/DataManager.h | 1 + VoidLink/Database/DataManager.m | 4 +- VoidLink/Database/TemporarySettings.h | 1 + VoidLink/Database/TemporarySettings.m | 1 + VoidLink/Input/OnScreenWidgetView.swift | 47 + VoidLink/Input/StreamView.m | 4 +- .../VoidLink v1.0.xcdatamodel/contents | 1 + VoidLink/Utility/GraphicUtils.swift | 32 +- .../ViewControllers/SettingsViewController.h | 2 + .../ViewControllers/SettingsViewController.m | 11 + .../StreamFrameViewController.m | 1 + VoidLink/mul.lproj/iPad.xcstrings | 30 + VoidLink/mul.lproj/iPhone.xcstrings | 30 + 15 files changed, 995 insertions(+), 798 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index d2a7de0b..d8a56a06 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -157,52 +157,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -280,237 +234,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -829,288 +552,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1131,26 +572,6 @@ - - - - - - - - - - - - - - - @@ -1271,139 +692,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1450,26 +738,6 @@ - - - - - - - - - - - - - - @@ -1784,46 +1052,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2068,6 +1296,798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2254,6 +2274,8 @@ + + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index b62b877d..026ed9a5 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -1121,7 +1121,7 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2236,7 +2236,7 @@ - + @@ -2282,7 +2282,7 @@ - + - + - + - + + + + + + + + + + + + + + + + @@ -3054,6 +3074,8 @@ + + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index 14436263..935edf12 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -110,6 +110,7 @@ typedef NS_ENUM(NSInteger, PencilTickMode) { optimizeGames:(BOOL)optimizeGames multiController:(BOOL)multiController buttonVisualFeedback:(BOOL)buttonVisualFeedback + touchPointTracking:(BOOL)touchPointTracking swapABXYButtons:(BOOL)swapABXYButtons audioOnPC:(BOOL)audioOnPC redirectMic:(BOOL)redirectMic diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index a7796e9f..be5570a2 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -81,7 +81,8 @@ showKeyboardToolbar:(BOOL)showKeyboardToolbar optimizeGames:(BOOL)optimizeGames multiController:(BOOL)multiController - buttonVisualFeedback:(BOOL)buttonVisualFeedback + buttonVisualFeedback:(BOOL)buttonVisualFeedback + touchPointTracking:(BOOL)touchPointTracking swapABXYButtons:(BOOL)swapABXYButtons audioOnPC:(BOOL)audioOnPC redirectMic:(BOOL)redirectMic @@ -160,6 +161,7 @@ settingsToSave.optimizeGames = optimizeGames; settingsToSave.multiController = multiController; settingsToSave.buttonVisualFeedback = buttonVisualFeedback; + settingsToSave.touchPointTracking = touchPointTracking; settingsToSave.swapABXYButtons = swapABXYButtons; settingsToSave.playAudioOnPC = audioOnPC; settingsToSave.redirectMic = redirectMic; diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index a5ee5362..213b40da 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -60,6 +60,7 @@ @property (nonatomic, retain) NSNumber * asyncNativeTouchPriority; @property (nonatomic) BOOL multiController; @property (nonatomic) BOOL buttonVisualFeedback; +@property (nonatomic) BOOL touchPointTracking; @property (nonatomic) BOOL swapABXYButtons; @property (nonatomic) BOOL playAudioOnPC; @property (nonatomic) BOOL redirectMic; diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index 9bf2d0a1..e2d2158e 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -101,6 +101,7 @@ self.optimizeGames = settings.optimizeGames; self.multiController = settings.multiController; self.buttonVisualFeedback = settings.buttonVisualFeedback; + self.touchPointTracking = settings.touchPointTracking; self.swapABXYButtons = settings.swapABXYButtons; self.onscreenControls = settings.onscreenControls; self.gyroMode = settings.gyroMode; diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index ab669f0b..e7cfabe5 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -468,6 +468,10 @@ import SVGKit self.hasComponent = self.isStickWheel self.hasL3R3Indicator = !self.isStickWheel && !self.isDirectionPad && self.widgetType == WidgetTypeEnum.touchPad + + self.hasTrackPoint = (CommandManager.vectorTouchPads.contains(self.touchPadString) + || (self.widgetType == WidgetTypeEnum.button + && (buttonMode == .slideAndHold || buttonMode == .slideToToggle))) } // ====================================================================================================== @@ -866,6 +870,7 @@ import SVGKit if CommandManager.stickTouchPads.contains(touchPadString) {setupL3R3Indicator()} if CommandManager.verticalTouchPads.contains(touchPadString) {setupL3R3Indicator()} if CommandManager.mousePadWithButtonActions.contains(self.touchPadString) {setupL3R3Indicator()} + if self.hasTrackPoint {setupTrackPoint()} if self.hasStickIndicator { if self.crossMarkLayer.superlayer == nil {self.crossMarkLayer = createCrossMark()} if self.lrudIndicatorBall.superlayer == nil {self.lrudIndicatorBall = createStickBall()} @@ -905,6 +910,21 @@ import SVGKit CATransaction.commit() } + @objc var hasTrackPoint: Bool = false + @objc static var trackPointEnabled: Bool = false + private var trackPoint:CAShapeLayer = CAShapeLayer() + @objc public func setupTrackPoint() { + CATransaction.begin() + CATransaction.setDisableActions(true) + // 1. 创建圆形路径 + + + trackPoint.removeFromSuperlayer() + trackPoint = GraphicUtils.makeTouchTrackpoint(in: self) + + CATransaction.commit() + } + private func showl3r3Indicator(){ if OnScreenWidgetView.buttonVisualFeedbackEnabled { CATransaction.begin() @@ -1740,6 +1760,7 @@ import SVGKit self.firstTouchMoved = false self.tickFlag = 0 super.touchesBegan(touches, with: event) + self.isMultipleTouchEnabled = self.widgetType == WidgetTypeEnum.button || CommandManager.mousePadWithButtonActions.contains(self.touchPadString); if !OnScreenWidgetView.editMode && self.touchPadString == "TRACKBALL" { @@ -1863,6 +1884,16 @@ import SVGKit OnScreenWidgetView.updateStreamViewGuidelines(for: self) } } + + if self.hasTrackPoint, self.widgetType == .touchPad, OnScreenWidgetView.trackPointEnabled { + CATransaction.begin() + CATransaction.setDisableActions(true) + if let touch = touches.first { + trackPoint.isHidden = false + trackPoint.position = touch.location(in: self) + } + CATransaction.commit() + } } // here is in edit mode: else{ @@ -2068,6 +2099,18 @@ import SVGKit } } } + + if self.hasTrackPoint, OnScreenWidgetView.trackPointEnabled { + if let touch = touches.first { + CATransaction.begin() + CATransaction.setDisableActions(true) + if trackPoint.isHidden { + trackPoint.isHidden = false + } + trackPoint.position = touch.location(in: self) + CATransaction.commit() + } + } } // Move the widgetView based on touch movement in relocation mode @@ -2609,6 +2652,10 @@ import SVGKit } } + if self.hasTrackPoint, OnScreenWidgetView.trackPointEnabled { + trackPoint.isHidden = true + } + CATransaction.commit() if OnScreenWidgetView.editMode { diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index ae0bf7c4..286cd959 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -543,7 +543,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; - (void) reloadOnScreenWidgetViews:(bool)reload{ NSLog(@"reloadOnScreenWidgets in streamview %f", CACurrentMediaTime()); - + dispatch_async(dispatch_get_main_queue(), ^{ if(!self->oscProfileMan) self->oscProfileMan = [OSCProfilesManager sharedManager:self->_streamFrameTopLayerView.bounds]; OSCProfile *oscProfile = [self->oscProfileMan getSelectedProfile]; //returns the currently selected OSCProfile @@ -626,6 +626,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; buttonState.sequence = widgetView.sequence; oscProfile.buttonStatesEncoded[i] = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil]; } + + [widgetView accessWidgetAttributes]; } else if(buttonState.widgetType == LegacyOnScreenControls) hasLegacyWidget = true; } diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index 0acfa002..df3a2f35 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -104,6 +104,7 @@ + diff --git a/VoidLink/Utility/GraphicUtils.swift b/VoidLink/Utility/GraphicUtils.swift index 03c3f5c0..186e7551 100644 --- a/VoidLink/Utility/GraphicUtils.swift +++ b/VoidLink/Utility/GraphicUtils.swift @@ -11,7 +11,7 @@ import Foundation import SVGKit @objc public class GraphicUtils: NSObject { - @objc public class func makeSVGLayer( + @objc public static func makeSVGLayer( from file: String, in container: CALayer, at normalizedPosition: CGPoint = .zero, @@ -35,7 +35,7 @@ import SVGKit ) } - public class func makeCenteredSVGLayer( + public static func makeCenteredSVGLayer( from svg: SVGKImage, in container: CALayer, targetSize: CGSize @@ -47,7 +47,7 @@ import SVGKit ) } - @objc public class func _makeSVGLayer( + @objc public static func _makeSVGLayer( from svg: SVGKImage, in container: CALayer, at normalizedPosition: CGPoint = .zero, @@ -106,11 +106,35 @@ import SVGKit return wrappedLayer } - @objc public class func changeColor(layer: CALayer, color: UIColor) { + @objc public static func changeColor(layer: CALayer, color: UIColor) { if let shape = layer as? CAShapeLayer { shape.fillColor = color.cgColor shape.strokeColor = color.cgColor } layer.sublayers?.forEach { changeColor(layer: $0, color: color) } } + + @objc public static func makeTouchTrackpoint(in view:UIView) -> CAShapeLayer { + var trackPoint = CAShapeLayer() + let path = UIBezierPath( + arcCenter: CGPoint(x: 0, y: 0), + radius: Utils.isIPhone() ? 12 : 15, + startAngle: 0, + endAngle: CGFloat.pi * 2, + clockwise: true + ) + + trackPoint = CAShapeLayer() + trackPoint.path = path.cgPath + + trackPoint.fillColor = UIColor.white.withAlphaComponent(0.3).cgColor + trackPoint.strokeColor = UIColor.clear.cgColor + trackPoint.lineWidth = 0 + + view.layer.addSublayer(trackPoint) + trackPoint.position = view.center + trackPoint.isHidden = true + + return trackPoint + } } diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index f12be519..e4250a2d 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -252,6 +252,8 @@ @property (weak, nonatomic) IBOutlet UIStackView *sdrPerformanceWorkaroundStack; @property (weak, nonatomic) IBOutlet UISwitch *sdrPerformanceWorkaroundSwitch; +@property (weak, nonatomic) IBOutlet UIStackView *trackTouchPointStack; +@property (weak, nonatomic) IBOutlet UISwitch *trackTouchPointSwitch; @property (strong, nonatomic) IBOutlet UIStackView *testStack; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 9f9112fc..18abe7c2 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -822,6 +822,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.mousePointerVelocityStack ofId:@"mousePointerVelocityStack" withInfoTag:NO withDynamicLabel:YES to:touchControlSection]; [self addSetting:self.onScreenWidgetStack ofId:@"onScreenWidgetStack" withInfoTag:YES withDynamicLabel:YES to:touchControlSection]; [self addSetting:self.buttonVisualFeedbackStack ofId:@"buttonVisualFeedbackStack" withInfoTag:NO withDynamicLabel:NO to:touchControlSection]; + [self addSetting:self.trackTouchPointStack ofId:@"trackTouchPointStack" withInfoTag:NO withDynamicLabel:NO to:touchControlSection]; [touchControlSection addToParentStack:_parentStack]; // [touchAndControlSection setExpanded:NO]; @@ -1920,6 +1921,10 @@ BOOL isCustomResolution(int resolutionSelected) { [self.multiControllerSwitch setOn:self->tempSettings.multiController]; [self.swapAbxySwitch setOn:self->tempSettings.swapABXYButtons]; [self.buttonVisualFeedbackSwitch setOn:self->tempSettings.buttonVisualFeedback]; + + [self.trackTouchPointSwitch setOn:self->tempSettings.touchPointTracking]; + [self.trackTouchPointSwitch addTarget:self action:@selector(trackTouchPointSwitchFlipped:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved. + [self.delayLeftClickSwitch setOn:self->tempSettings.delayLeftClick]; [self.hapticEngineSelector setSelectedSegmentIndex:self->tempSettings.hapticEngine.intValue]; @@ -2997,6 +3002,10 @@ BOOL isCustomResolution(int resolutionSelected) { [self handleOswGestureChange]; } +- (void)trackTouchPointSwitchFlipped:(UISwitch *)sender{ + OnScreenWidgetView.trackPointEnabled = sender.isOn; +} + - (BOOL)manuallyChangedFPS { NSString *key = @"manuallyChangedFPS"; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; @@ -3571,6 +3580,7 @@ BOOL isCustomResolution(int resolutionSelected) { BOOL multiController = self.multiControllerSwitch.isOn; BOOL swapABXYButtons = self.swapAbxySwitch.isOn; BOOL buttonVisualFeedback = self.buttonVisualFeedbackSwitch.isOn; + BOOL touchPointTracking = self.trackTouchPointSwitch.isOn; NSInteger gyroMode = self.gyroModeSelector.selectedSegmentIndex; NSInteger emulatedControllerType = [self segmentIndexToControllerType:self.emulatedControllerTypeSelector.selectedSegmentIndex]; //self.emulatedControllerTypeSelector.selectedSegmentIndex; BOOL audioOnPC = self.audioOnPcSwitch.isOn; @@ -3648,6 +3658,7 @@ BOOL isCustomResolution(int resolutionSelected) { optimizeGames:optimizeGames multiController:multiController buttonVisualFeedback:buttonVisualFeedback + touchPointTracking:touchPointTracking swapABXYButtons:swapABXYButtons audioOnPC:audioOnPC redirectMic:redirectMic diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 6452a0bc..b5df6527 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -392,6 +392,7 @@ bool onScreenWidgetSwitched = previousOnScreenWidgetEnabled != [_streamView isOnScreenWidgetEnabled]; bool needReload = onScreenWidgetSwitched && !previousOnScreenWidgetEnabled; + OnScreenWidgetView.trackPointEnabled = _settings.touchPointTracking; [_streamView reloadOnScreenWidgetViews:_viewJustLoaded||reloadOnscreenWidgets||needReload]; //reload keyboard buttons here. the keyboard widget view will be added to the streamframe view instead streamview, the highest layer, which saves a lot of reengineering if(onScreenWidgetSwitched && previousOnScreenWidgetEnabled) [_streamView clearOnScreenWidgets]; diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 9d4a97c3..49e1845f 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -343,6 +343,36 @@ } } }, + "5q5-ug-Fg4.text" : { + "comment" : "Class = \"UILabel\"; text = \"Touch Point Tracking\"; ObjectID = \"5q5-ug-Fg4\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Touch Point Tracking" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "触点跟踪" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸點追蹤" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸點追蹤" + } + } + } + }, "6Cy-aH-DXp.text" : { "comment" : "Class = \"UILabel\"; text = \"Curve Exponent\"; ObjectID = \"6Cy-aH-DXp\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index bae80c8f..836c3ca4 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -1483,6 +1483,36 @@ } } }, + "BQ1-w6-Opg.text" : { + "comment" : "Class = \"UILabel\"; text = \"Touch Point Tracking\"; ObjectID = \"BQ1-w6-Opg\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Touch Point Tracking" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "触点跟踪" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸點追蹤" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸點追蹤" + } + } + } + }, "bsU-vw-dTl.text" : { "comment" : "Class = \"UILabel\"; text = \"Slide Distance Threshold\"; ObjectID = \"bsU-vw-dTl\";", "extractionState" : "extracted_with_value",