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