- fixed memory leaks caused by on-screen button auto-tap timer

- feat: Inertial RsvPad/LsvPad
This commit is contained in:
TrueZhuanjia
2025-12-09 16:38:28 +08:00
parent adafacbf81
commit 7caa778ff8
11 changed files with 217 additions and 75 deletions
+4
View File
@@ -25,6 +25,7 @@
18951D1D2EBB6D2300A86E12 /* TouchPadGestureHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */; };
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
18A765BD2ECD51FB004784AA /* ControllerUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A765BC2ECD51FB004784AA /* ControllerUtil.swift */; };
18B317032EE67A3A009B62C2 /* InertialScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B317022EE67A39009B62C2 /* InertialScroller.swift */; };
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 */; };
@@ -182,6 +183,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>"; };
18A765BC2ECD51FB004784AA /* ControllerUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerUtil.swift; sourceTree = "<group>"; };
18B317022EE67A39009B62C2 /* InertialScroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InertialScroller.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>"; };
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
@@ -714,6 +716,7 @@
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */,
18B317022EE67A39009B62C2 /* InertialScroller.swift */,
18951D1C2EBB6D2300A86E12 /* TouchPadGestureHandler.swift */,
18A765BC2ECD51FB004784AA /* ControllerUtil.swift */,
1850C8292E667E4000845308 /* MicHandler.swift */,
@@ -1106,6 +1109,7 @@
1888C1922E24C33100108730 /* AboutViewController.swift in Sources */,
FBD349621A0089F6002D2A60 /* DataManager.m in Sources */,
97E20DE72E216510000D5DCA /* ImGuiRenderer.mm in Sources */,
18B317032EE67A3A009B62C2 /* InertialScroller.swift in Sources */,
FB4A23B81A9D3637004D2EF2 /* LoadingFrameViewController.m in Sources */,
FB9AFD3A1A7E05CE00872C98 /* ServerInfoResponse.m in Sources */,
84351B2B2C29AD6100A77989 /* LayoutOnScreenControlsViewController.m in Sources */,
+1
View File
@@ -125,6 +125,7 @@ import UIKit
@objc public static let stickTouchPads: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD"]
@objc public static let nonVectorStickPads: [String] = ["LSPAD", "RSPAD"]
@objc public static let vectorTouchPads: [String] = ["LSVPAD", "RSVPAD", "MOUSEPAD", "TRACKBALL"]
@objc public static let inertialTouchPads: [String] = ["LSVPAD", "RSVPAD", "TRACKBALL"]
@objc public static let verticalTouchPads: [String] = ["LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let bidirectionalVerticalTouchPads: [String] = ["LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let functionalButtonCmds: [String] = ["SETTINGS", "TOOLBOX", "WIDGETTOOL", "WIDGETPROFILES", "PROFILES", "SOFTKEYBOARD", "ABSTCHDRAG"]
-1
View File
@@ -1014,7 +1014,6 @@ double rc_expo(double x, double expo) {
- (void)sendStickToMouseMoveEventWithStickX:(float)stickX stickY:(float)stickY expo:(float)expo {
CGFloat mouseDeltaX = _stickToMouseVelocity*rc_expo(stickX, expo);
CGFloat mouseDeltaY = _stickToMouseVelocity*rc_expo(stickY, expo);
NSLog(@"mouseDeltaX %f, mouseDeltaY %f, %f, %ld", mouseDeltaX, mouseDeltaY, CACurrentMediaTime(), (long)_displayLink.preferredFramesPerSecond);
LiSendMouseMoveEvent(mouseDeltaX, -mouseDeltaY);
}
@@ -406,7 +406,7 @@ static CGRect layoutViewBounds;
buttonState.yawFactor = widgetView.yawFactor;
buttonState.pitchFactor = widgetView.pitchFactor;
buttonState.rollFactor = widgetView.rollFactor;
buttonState.decelerationRate = widgetView.trackballDecelerationRate;
buttonState.decelerationRate = widgetView.decelerationRate;
buttonState.stickIndicatorOffset = widgetView.stickIndicatorOffset;
buttonState.widgetShape = widgetView.shape;
buttonState.minStickOffset = widgetView.minStickOffset;
+35
View File
@@ -0,0 +1,35 @@
//
// InertialScroller.swift
// VoidLink
//
// Created by True砖家 on 2025/11/5.
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
//
class InertialScroller {
public var decelerationRate: CGFloat
public lazy var timer: SafeTimer? = {
SafeTimer(interval: 1/displayLinkRate) { [weak self] in
guard let self = self, handler != nil else {return}
let deceratedDeltaX = self.vector.dx * decelerationRate
let deceratedDeltaY = self.vector.dy * decelerationRate
self.vector = CGVector(dx: deceratedDeltaX, dy: deceratedDeltaY)
if abs(deceratedDeltaX) < self.timerSuspendThreshold && abs(deceratedDeltaY) < self.timerSuspendThreshold {
self.timer?.pause()
}
(self.handler ?? {})()
}
}()
public var vector: CGVector = .zero
public var displayLinkRate: CGFloat
public var timerSuspendThreshold: CGFloat = 0.06
public var handler: (() -> Void)?
init(decelerationRate: CGFloat = 0.93, displayLinkRate: CGFloat = 60, handler: (() -> Void)? = nil) {
self.decelerationRate = decelerationRate
self.displayLinkRate = displayLinkRate
self.handler = handler
}
}
+2 -2
View File
@@ -162,7 +162,7 @@ public class MicHandler: NSObject {
@objc public func startTapping() {
// recordedBuffers.removeAll()
isRecording = true
self.timer?.resume()
self.timer?.start()
/*
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { [weak self] in
guard let self = self else { return }
@@ -175,7 +175,7 @@ public class MicHandler: NSObject {
@objc public func stopTapping(stopEngine:Bool) {
isRecording = false
self.timer?.suspend()
self.timer?.pause()
// engine.inputNode.removeTap(onBus: 0)
if(stopEngine){
playerNode.stop()
+92 -28
View File
@@ -42,9 +42,14 @@ import UIKit
case touchPad
}
private static let MinAutotapInterval:Int = 50
private let oscProfileMan: OSCProfilesManager = OSCProfilesManager.sharedManager(CGRectZero)
private var oscProfile: OSCProfile
private let dataMan: DataManager = DataManager()
private let tempSettings: TemporarySettings
@objc public var widgetType: WidgetTypeEnum = WidgetTypeEnum.uninitialized
@objc static public var editMode: Bool = false
@@ -93,6 +98,9 @@ import UIKit
private var vibrationGenerator = UIImpactFeedbackGenerator(style: .light)
private var vibrationOn: Bool = false
private var inertialScroller:InertialScroller
@objc public var displayLinkRate:CGFloat = 60;
// for movable buttons during streaming
@objc public var relocatedDuringStreaming: Bool = false
@@ -120,7 +128,7 @@ import UIKit
@objc public var hasAutoTap: Bool = false
@objc public var isMousePadWithButtonActions: Bool = false
@objc public var hasTrackBall: Bool = false
@objc public var hasInertia: Bool = false
@objc public var isFuncationalButton: Bool = false
@objc public var hasHapticFeedback: Bool = false
@objc public var isDirectionPad: Bool = false
@@ -206,7 +214,7 @@ import UIKit
// trackball
private var trackballVelocity: CGPoint = .zero
private var trackballDecelerationTimer: Timer?
@objc public var trackballDecelerationRate: CGFloat = 0.93
@objc public var decelerationRate: CGFloat = 0.93
private let trackballVelocityThreshold: CGFloat = 0.1
@objc public var slideThreshold: CGFloat = 6.0
@@ -324,6 +332,8 @@ import UIKit
}
self.activePointerIds = []
self.oscProfile = oscProfileMan.getSelectedProfile()
self.tempSettings = dataMan.getSettings()
self.inertialScroller = InertialScroller()
super.init(frame: .zero)
// helps widget panel to hide/show stacks
@@ -375,7 +385,7 @@ import UIKit
self.hasAutoTap = self.widgetType == WidgetTypeEnum.button && self.functionalButtonString == "" && self.motionControlButtonString == ""
self.isMousePadWithButtonActions = CommandManager.mousePadWithButtonActions.contains(self.touchPadString) && widgetType == WidgetTypeEnum.touchPad
self.hasTrackBall = self.touchPadString == "TRACKBALL"
self.hasInertia = CommandManager.inertialTouchPads.contains(self.touchPadString)
self.isFuncationalButton = self.functionalButtonString != ""
self.hasHapticFeedback = !self.comboButtonStrings.isEmpty || CommandManager.directionPads.contains(self.touchPadString)
self.isDirectionPad = self.widgetType == WidgetTypeEnum.touchPad && CommandManager.directionPads.contains(self.touchPadString)
@@ -384,8 +394,9 @@ import UIKit
// ======================================================================================================
@objc public func setupAutoTapTimer() {
if self.widgetType == WidgetTypeEnum.button {
if self.widgetType == WidgetTypeEnum.button, autoTapInterval >= OnScreenWidgetView.MinAutotapInterval {
self.autoTapTimer = SafeTimer(interval:0.001 * Double(autoTapInterval)) {
// print("timer instance \(Unmanaged.passUnretained(self.autoTapTimer!).toOpaque()), \(CACurrentMediaTime())")
self.handleButtonDown()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.025) {
self.handleButtonUp()
@@ -394,6 +405,12 @@ import UIKit
}
}
@objc public func setupInertialScroller() {
if self.hasInertia {
self.inertialScroller = InertialScroller(decelerationRate: self.decelerationRate, displayLinkRate: CGFloat(self.tempSettings.framerate.intValue))
}
}
@objc public func setVibration(style: Int) {
if #available(iOS 13.0, *) {
vibrationOn = style < UIImpactFeedbackGenerator.FeedbackStyle.rigid.rawValue + 1
@@ -1106,29 +1123,22 @@ import UIKit
//mousepad-trackball behavior========================================================
private func startTrackballMomentum() {
stopTrackballMomentum()
// stopTrackballMomentum()
trackballDecelerationTimer = Timer.scheduledTimer(withTimeInterval: 1/Double(UIScreen.main.maximumFramesPerSecond), repeats: true) { [weak self] _ in
guard let self = self else { return }
LiSendMouseMoveEvent(
Int16(truncatingIfNeeded: Int(self.trackballVelocity.x)),
Int16(truncatingIfNeeded: Int(self.trackballVelocity.y))
)
self.trackballVelocity.x *= self.trackballDecelerationRate
self.trackballVelocity.y *= self.trackballDecelerationRate
if abs(self.trackballVelocity.x) < self.trackballVelocityThreshold &&
abs(self.trackballVelocity.y) < self.trackballVelocityThreshold {
self.stopTrackballMomentum()
if self.inertialScroller.handler == nil {
self.inertialScroller.handler = {
LiSendMouseMoveEvent(
Int16(self.inertialScroller.vector.dx*1.7),
Int16(self.inertialScroller.vector.dy*1.7)
)
}
}
self.inertialScroller.timer?.restart()
}
private func stopTrackballMomentum() {
trackballDecelerationTimer?.invalidate()
trackballDecelerationTimer = nil
self.inertialScroller.timer?.pause()
}
@@ -1136,7 +1146,7 @@ import UIKit
//==== Button widget tap down=============================================
private func handleTapDownOrSlidein() {
if autoTapInterval < 50 {
if autoTapInterval < OnScreenWidgetView.MinAutotapInterval {
handleButtonDown()
}
else{
@@ -1145,11 +1155,11 @@ import UIKit
}
private func handleFingerUpOrSlideout() {
if autoTapInterval < 50 {
if autoTapInterval < OnScreenWidgetView.MinAutotapInterval {
handleButtonUp()
}
else{
self.autoTapTimer?.suspend()
self.autoTapTimer?.pause()
self.handleButtonUp()
}
}
@@ -1448,7 +1458,19 @@ import UIKit
if quickDoubleTapDetected {
self.showl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "LSVPAD","RSVPAD","DS4TOUCH":
case "LSVPAD":
self.clearLeftStickTouchPadFlag()
self.inertialScroller.timer?.pause()
if quickDoubleTapDetected {
self.showl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "RSVPAD":
self.clearRightStickTouchPadFlag()
self.inertialScroller.timer?.pause()
if quickDoubleTapDetected {
self.showl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "DS4TOUCH":
if quickDoubleTapDetected {
self.showl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
@@ -1694,6 +1716,11 @@ import UIKit
self.deltaX = currentTouchLocation.x - self.latestTouchLocation.x
self.deltaY = currentTouchLocation.y - self.latestTouchLocation.y
if self.hasInertia && firstTouchMoved {
self.inertialScroller.vector = UITouchUtil.vector(of: touch, in: self)
}
self.offSetX = currentTouchLocation.x - self.touchBeganLocation.x
self.offSetY = currentTouchLocation.y - self.touchBeganLocation.y
}
@@ -1988,6 +2015,7 @@ import UIKit
self.handleMousePadButtonActionUp()
}
/*
if !OnScreenWidgetView.editMode && self.widgetType == WidgetTypeEnum.touchPad && self.touchPadString == "TRACKBALL" && allSpawnedTouchesCount == 1 && !twoTouchesDetected {
if(mousePointerMoved){
self.startTrackballMomentum()
@@ -1996,7 +2024,7 @@ import UIKit
else{
self.stopTrackballMomentum()
}
}
}*/
if !OnScreenWidgetView.editMode && self.widgetType == WidgetTypeEnum.touchPad && CommandManager.mousePadWithButtonActions.contains(self.touchPadString) && twoTouchesDetected && touches.count == allSpawnedTouchesCount { // need to enable multi-touch first
// touches.count == allCapturedTouchesCount means allfingers are lifting
@@ -2015,9 +2043,41 @@ import UIKit
self.clearRightStickTouchPadFlag()
if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndHideIndicator()}
case "LSVPAD":
self.clearLeftStickTouchPadFlag()
if self.decelerationRate > 0.50001 {
if(!firstTouchMoved) {self.inertialScroller.vector = CGVector(dx: 0, dy: 0)}
if self.inertialScroller.handler == nil {
self.inertialScroller.handler = {
let weightedDeltaX = self.inertialScroller.vector.dx*1.5167*self.sensitivityFactorX
let weightedDeltaY = self.inertialScroller.vector.dy*1.5167*self.sensitivityFactorY
self.sendLeftStickTouchPadEvent(inputX: weightedDeltaX, inputY: weightedDeltaY)
}
}
self.inertialScroller.timer?.restart()
}
else {self.clearLeftStickTouchPadFlag()}
case "RSVPAD":
self.clearRightStickTouchPadFlag()
if self.decelerationRate > 0.50001 {
if(!firstTouchMoved) {self.inertialScroller.vector = CGVector(dx: 0, dy: 0)}
if self.inertialScroller.handler == nil {
self.inertialScroller.handler = {
let weightedDeltaX = self.inertialScroller.vector.dx*1.5167*self.sensitivityFactorX
let weightedDeltaY = self.inertialScroller.vector.dy*1.5167*self.sensitivityFactorY
self.sendRightStickTouchPadEvent(inputX: weightedDeltaX, inputY: weightedDeltaY)
}
}
self.inertialScroller.timer?.restart()
}
else {self.clearRightStickTouchPadFlag()}
case "TRACKBALL":
if allSpawnedTouchesCount == 1, self.decelerationRate > 0 {
if(mousePointerMoved){
self.startTrackballMomentum()
mousePointerMoved = false //reset flag
}
else{
self.stopTrackballMomentum()
}
}
case "LTPAD":
self.onScreenControls.updateLeftTrigger(0x00)
case "RTPAD":
@@ -2205,6 +2265,10 @@ import UIKit
leftIndicator.removeFromSuperlayer()
rightIndicator.removeFromSuperlayer()
lrudIndicatorBall.removeFromSuperlayer()
self.inertialScroller.timer?.clean()
self.clearLeftStickTouchPadFlag()
self.clearRightStickTouchPadFlag()
self.autoTapTimer?.clean()
}
else{
self.superViewWidth = (superview?.bounds.size.width)!
+73 -35
View File
@@ -6,47 +6,85 @@
// Copyright © 2025 True砖家@Bilibili. All rights reserved.
//
import Foundation
import QuartzCore
class SafeTimer {
private var timer: DispatchSourceTimer
private(set) var isRunning = false
private var interval: TimeInterval = 0
private var delay: TimeInterval = 0
init(interval: TimeInterval = 1.0, queue: DispatchQueue = .global(), delay: TimeInterval = 0, handler: @escaping () -> Void) {
timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + delay, repeating: interval)
timer.setEventHandler(handler: handler)
/// SafeTimer: 高精度、可暂停/重启、可彻底清理
final class SafeTimer {
private var timer: DispatchSourceTimer?
private let timerQueue: DispatchQueue // 私有串行队列
private let userHandler: () -> Void
private let interval: TimeInterval
private let delay: TimeInterval
private var shouldRunHandler: Bool = false // 控制逻辑上的暂停/恢复
private var isCleaned: Bool = false // 是否已经 clean
init(interval: TimeInterval = 1.0,
delay: TimeInterval = 0,
queueLabel: String = "com.example.safetimer",
handler: @escaping () -> Void) {
self.interval = interval
self.delay = delay
// 注意:这里不调用 resume,保持 suspended,等 start() 时才运行
}
public func resume() {
guard !isRunning else { return }
timer.resume()
isRunning = true
}
public func suspend() {
guard isRunning else { return }
timer.suspend()
isRunning = false
}
func restart() {
if isRunning {
timer.suspend()
isRunning = false
self.userHandler = handler
self.timerQueue = DispatchQueue(label: queueLabel)
let t = DispatchSource.makeTimerSource(queue: timerQueue)
t.schedule(deadline: .now() + delay, repeating: interval)
t.setEventHandler { [weak self] in
guard let self = self, !self.isCleaned else { return }
if !self.shouldRunHandler { return }
self.userHandler()
}
timer.schedule(deadline: .now() + self.delay, repeating: self.interval) // 重置时间
timer.resume()
isRunning = true
t.resume() // timer 一开始就 resume,避免 suspend/resume 的计数问题
self.timer = t
}
deinit {
if !timer.isCancelled {
timer.cancel()
clean()
}
/// 开始逻辑上的计时
func start() {
timerQueue.async { [weak self] in
guard let self = self, !self.isCleaned else { return }
self.shouldRunHandler = true
}
}
/// 暂停逻辑上的计时
func pause() {
timerQueue.async { [weak self] in
guard let self = self, !self.isCleaned else { return }
self.shouldRunHandler = false
}
}
/// 重置下一次触发时间,并开始执行 handler
func restart() {
timerQueue.async { [weak self] in
guard let self = self, let t = self.timer, !self.isCleaned else { return }
t.schedule(deadline: .now() + self.delay, repeating: self.interval)
self.shouldRunHandler = true
}
}
/// 彻底清理 timer,返回后保证 handler 不再执行
func clean() {
if isCleaned { return }
// 同步在 timerQueue 执行,保证已排队事件全部处理完(或跳过)
timerQueue.sync {
self.isCleaned = true
self.shouldRunHandler = false
if let t = self.timer {
t.setEventHandler {} // 清空 handler
t.cancel() // 取消 timer
self.timer = nil
}
}
}
}
+2 -3
View File
@@ -568,7 +568,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.yawFactor = buttonState.yawFactor;
widgetView.pitchFactor = buttonState.pitchFactor;
widgetView.rollFactor = buttonState.rollFactor;
widgetView.trackballDecelerationRate = buttonState.decelerationRate;
widgetView.decelerationRate = buttonState.decelerationRate;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.buttonMode = buttonState.buttonMode;
@@ -585,6 +585,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
[widgetView tweakLabelAlphaWithAlpha:buttonState.labelAlpha];
[widgetView tweakBorderAlphaWithAlpha:buttonState.borderAlpha];
[widgetView setupAutoTapTimer];
[widgetView setupInertialScroller];
}
}
}
@@ -838,8 +839,6 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
- (BOOL)handleMouseButtonEvent:(int)buttonAction forTouches:(NSSet *)touches withEvent:(UIEvent *)event {
// NSLog(@"mouse click time: %f", CACurrentMediaTime()*1000);
#if !TARGET_OS_TV
if (@available(iOS 13.4, *)) {
UITouch* touch = [touches anyObject];
@@ -157,7 +157,7 @@ typedef NS_ENUM(NSUInteger, BorderWidthSliderMode) {
widgetView.yawFactor = buttonState.yawFactor;
widgetView.pitchFactor = buttonState.pitchFactor;
widgetView.rollFactor = buttonState.rollFactor;
widgetView.trackballDecelerationRate = buttonState.decelerationRate;
widgetView.decelerationRate = buttonState.decelerationRate;
widgetView.stickIndicatorOffset = buttonState.stickIndicatorOffset;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.buttonMode = buttonState.buttonMode;
@@ -731,7 +731,7 @@ typedef NS_ENUM(NSUInteger, BorderWidthSliderMode) {
newWidget.yawFactor = widget.yawFactor;
newWidget.pitchFactor = widget.pitchFactor;
newWidget.rollFactor = widget.rollFactor;
newWidget.trackballDecelerationRate = widget.trackballDecelerationRate;
newWidget.decelerationRate = widget.decelerationRate;
newWidget.stickIndicatorOffset = widget.stickIndicatorOffset;
newWidget.minStickOffset = widget.minStickOffset;
[newWidget setVibrationWithStyle:widget.vibrationStyle];
@@ -977,8 +977,8 @@ typedef NS_ENUM(NSUInteger, BorderWidthSliderMode) {
}];
[self autoFitLabel:self.autoTapLabel];
self.decelerationRateStack.hidden = !selectedWidgetView.hasTrackBall;
[self.decelerationRateSlider setValue:selectedWidgetView.trackballDecelerationRate];
self.decelerationRateStack.hidden = !selectedWidgetView.hasInertia;
[self.decelerationRateSlider setValue:selectedWidgetView.decelerationRate];
[self autoFitLabel:self.decelerationRateLabel];
[self decelerationRateSliderMoved:self.decelerationRateSlider];
@@ -1210,7 +1210,7 @@ typedef NS_ENUM(NSUInteger, BorderWidthSliderMode) {
- (void)decelerationRateSliderMoved:(UISlider* )sender{
[self.decelerationRateLabel setText:[LocalizationHelper localizedStringForKey:@"Deceleration Rate: %.3f ", sender.value]];
if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.trackballDecelerationRate = sender.value;
if(self->selectedWidgetView != nil && self->widgetViewSelected) self->selectedWidgetView.decelerationRate = sender.value;
return;
}
@@ -927,6 +927,8 @@
}
- (void) returnToMainFrame {
[_streamView clearOnScreenWidgets];
// Reset display mode back to default
[self updatePreferredDisplayMode:NO];
if (@available(iOS 13.0, *)) {