From 1262a7b41fbaf4233e4c5aba8819a146338ff437 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Tue, 2 Dec 2025 17:15:56 +0800 Subject: [PATCH] advancedPencilSupport-demo1 --- VoidLink.xcodeproj/project.pbxproj | 10 +- VoidLink/Base.lproj/iPad.storyboard | 105 ++++++++--- VoidLink/Database/DataManager.h | 9 +- VoidLink/Database/DataManager.m | 8 +- VoidLink/Database/TemporarySettings.h | 6 + VoidLink/Database/TemporarySettings.m | 5 + VoidLink/Input/PencilHandler.swift | 163 ++++++++++++++++++ VoidLink/Input/StreamView.m | 35 +++- .../VoidLink v1.0.xcdatamodel/contents | 2 + VoidLink/Localization/Localizable.xcstrings | 23 +++ VoidLink/MenuSectionView.h | 1 + VoidLink/MenuSectionView.m | 17 ++ .../CustomEdgeSlideGestureRecognizer.m | 3 +- .../ViewControllers/SettingsViewController.h | 10 ++ .../ViewControllers/SettingsViewController.m | 61 ++++++- VoidLink/mul.lproj/iPad.xcstrings | 60 +++++++ 16 files changed, 473 insertions(+), 45 deletions(-) create mode 100644 VoidLink/Input/PencilHandler.swift diff --git a/VoidLink.xcodeproj/project.pbxproj b/VoidLink.xcodeproj/project.pbxproj index c008dd6d..b9b9f719 100644 --- a/VoidLink.xcodeproj/project.pbxproj +++ b/VoidLink.xcodeproj/project.pbxproj @@ -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 = ""; }; 18BF141D2E0D7A00009DEE4F /* VoidLink v1.0.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "VoidLink v1.0.xcdatamodel"; sourceTree = ""; }; 18E4BCE92E891895000A8657 /* MotionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionHandler.swift; sourceTree = ""; }; + 18FCADAE2ECAC23200787A96 /* PencilHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PencilHandler.swift; sourceTree = ""; }; 693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 840751B12BF49B5A0096B657 /* CustomTapGestureRecognizer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomTapGestureRecognizer.h; sourceTree = ""; }; 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomTapGestureRecognizer.m; sourceTree = ""; }; @@ -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; diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index 0affe068..b9d2e79f 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -61,7 +61,7 @@ - + @@ -1150,7 +1150,7 @@ - + @@ -1189,26 +1209,6 @@ - - - - - - - - - - - - - - - @@ -1862,10 +1862,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1959,6 +2004,10 @@ + + + + @@ -2051,7 +2100,7 @@ - + @@ -2870,7 +2919,7 @@ - + @@ -2879,13 +2928,13 @@ - + - + - + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index b7ce2a2e..eab89149 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -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 diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index 404f675d..36705484 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -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]; diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index 3cb49b8e..198a5a89 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -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 diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index 2ba9712d..7cd4f308 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -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 diff --git a/VoidLink/Input/PencilHandler.swift b/VoidLink/Input/PencilHandler.swift new file mode 100644 index 00000000..bf36c416 --- /dev/null +++ b/VoidLink/Input/PencilHandler.swift @@ -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, 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, 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, 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, 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 + } +} diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index 67420196..cea7f0c4 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -58,7 +58,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; int localMousePointerMode; UIResponder* touchHandler; - + PencilHandler* pencilHandler; + id 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]; } } } diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index e71b7d9d..1b21bc83 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -72,6 +72,8 @@ + + diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 6c948f68..7e9661d1 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -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" : { diff --git a/VoidLink/MenuSectionView.h b/VoidLink/MenuSectionView.h index 827944d4..16f12728 100644 --- a/VoidLink/MenuSectionView.h +++ b/VoidLink/MenuSectionView.h @@ -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; diff --git a/VoidLink/MenuSectionView.m b/VoidLink/MenuSectionView.m index 40e4c1f5..f7e380ba 100644 --- a/VoidLink/MenuSectionView.m +++ b/VoidLink/MenuSectionView.m @@ -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]; diff --git a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m index 29bfbf9f..2a2ce21a 100644 --- a/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m +++ b/VoidLink/ViewControllers/CustomEdgeSlideGestureRecognizer.m @@ -27,6 +27,7 @@ CGFloat startPointX; - (void)touchesBegan:(NSSet *)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 *)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; diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 8576fce1..09c1b5a8 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -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" diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 139ea844..1d73fd82 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -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 diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index a14352a0..0686c3d9 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -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",