pressure curve implementation
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
18A0EE2C2EFBCCFD00BA853A /* GraphicUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A0EE2B2EFBCCFD00BA853A /* GraphicUtils.swift */; };
|
||||
18A765BD2ECD51FB004784AA /* ControllerUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A765BC2ECD51FB004784AA /* ControllerUtil.swift */; };
|
||||
18B317032EE67A3A009B62C2 /* InertialScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B317022EE67A39009B62C2 /* InertialScroller.swift */; };
|
||||
18D43D7F2F050240008C326F /* PressureCurve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D43D7E2F050240008C326F /* PressureCurve.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 */; };
|
||||
@@ -171,7 +172,6 @@
|
||||
180345E02E819877004AF36F /* libopus-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopus-sim.a"; sourceTree = "<group>"; };
|
||||
180E6E362E93BFDB0067C73A /* AlertControllerUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertControllerUtil.swift; sourceTree = "<group>"; };
|
||||
1817B61D2E6FD04D000D938D /* SafeTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeTimer.swift; sourceTree = "<group>"; };
|
||||
182042252EFEE46700309FF6 /* touchCentered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = touchCentered.swift; sourceTree = "<group>"; };
|
||||
1829CE3D2E2154DF0074A99A /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = "<group>"; };
|
||||
1829CE3E2E2154DF0074A99A /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = "<group>"; };
|
||||
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = "<group>"; };
|
||||
@@ -191,6 +191,7 @@
|
||||
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>"; };
|
||||
18D43D7E2F050240008C326F /* PressureCurve.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCurve.swift; sourceTree = "<group>"; };
|
||||
18E4BCE92E891895000A8657 /* MotionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionHandler.swift; sourceTree = "<group>"; };
|
||||
18FCADAE2ECAC23200787A96 /* PencilHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PencilHandler.swift; sourceTree = "<group>"; };
|
||||
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
@@ -585,7 +586,6 @@
|
||||
FB1A67542132419700507771 /* Moonlight TV */,
|
||||
FB290CF019B2C406004C83CF /* Frameworks */,
|
||||
FB290CEF19B2C406004C83CF /* Products */,
|
||||
182042252EFEE46700309FF6 /* touchCentered.swift */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -730,6 +730,7 @@
|
||||
9819CC21254F180F008A7C8E /* AbsoluteTouchHandler.m */,
|
||||
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
|
||||
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
|
||||
18D43D7E2F050240008C326F /* PressureCurve.swift */,
|
||||
18FCADAE2ECAC23200787A96 /* PencilHandler.swift */,
|
||||
18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */,
|
||||
18B317022EE67A39009B62C2 /* InertialScroller.swift */,
|
||||
@@ -1094,6 +1095,7 @@
|
||||
840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */,
|
||||
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */,
|
||||
FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */,
|
||||
18D43D7F2F050240008C326F /* PressureCurve.swift in Sources */,
|
||||
180E6E372E93BFDB0067C73A /* AlertControllerUtil.swift in Sources */,
|
||||
841BAC922DE35D6B00DA639E /* ThemeManager.m in Sources */,
|
||||
98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */,
|
||||
|
||||
@@ -20,6 +20,7 @@ import UIKit
|
||||
|
||||
@objc public static var actionCancelled:Bool = false
|
||||
@objc public static var alertController:UIAlertController = UIAlertController()
|
||||
@objc public static var cancelButtonString:String = "Cancel"
|
||||
|
||||
@objc class func showAlert(
|
||||
in viewController: UIViewController,
|
||||
@@ -38,11 +39,13 @@ import UIKit
|
||||
let confirmAction = UIAlertAction(title: "\(remainingSeconds)", style: .default) { _ in
|
||||
actionCancelled = false
|
||||
completion?()
|
||||
cancelButtonString = "Cancel"
|
||||
}
|
||||
|
||||
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: "Cancel"), style: .cancel) { _ in
|
||||
let cancelAction = UIAlertAction(title: SwiftLocalizationHelper.localizedString(forKey: cancelButtonString), style: .cancel) { _ in
|
||||
actionCancelled = true
|
||||
completion?()
|
||||
cancelButtonString = "Cancel"
|
||||
}
|
||||
|
||||
confirmAction.isEnabled = false
|
||||
|
||||
@@ -1898,8 +1898,8 @@
|
||||
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="bVv-CW-kN8">
|
||||
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
|
||||
<segments>
|
||||
<segment title="Manual"/>
|
||||
<segment title="Off"/>
|
||||
<segment title="Manual"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
|
||||
@@ -1920,7 +1920,7 @@
|
||||
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="4000" translatesAutoresizingMaskIntoConstraints="NO" id="IOG-l4-gtQ">
|
||||
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1500" minValue="1500" maxValue="4000" translatesAutoresizingMaskIntoConstraints="NO" id="IOG-l4-gtQ">
|
||||
<rect key="frame" x="0.0" y="21.5" width="380" height="34"/>
|
||||
<color key="tintColor" systemColor="linkColor"/>
|
||||
</slider>
|
||||
@@ -1930,6 +1930,26 @@
|
||||
<constraint firstAttribute="width" constant="380" id="gdZ-zL-Wl7"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Bp6-7U-E9H">
|
||||
<rect key="frame" x="3145" y="230" width="266.5" height="31"/>
|
||||
<subviews>
|
||||
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Pressure Curve" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="b1N-z8-12w" userLabel="uiLabel">
|
||||
<rect key="frame" x="0.0" y="0.0" width="116.5" height="31"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c8a-2j-qVf">
|
||||
<rect key="frame" x="136.5" y="0.0" width="132" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="130" id="oDG-RR-5Wd"/>
|
||||
</constraints>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="31" id="RRk-QS-MEj"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
@@ -2045,6 +2065,8 @@
|
||||
<outlet property="pointerVelocityDividerStack" destination="b1U-nD-gYE" id="ep1-K3-rKj"/>
|
||||
<outlet property="pointerVelocityFactorStack" destination="vdk-mK-ztu" id="Cik-Rh-pGS"/>
|
||||
<outlet property="pointerVelocityModeDividerSlider" destination="AcZ-2i-GI4" id="dGf-ZU-w3S"/>
|
||||
<outlet property="pressureCurveStack" destination="Bp6-7U-E9H" id="tHX-hP-A78"/>
|
||||
<outlet property="pressureCurveSwitch" destination="c8a-2j-qVf" id="1aS-J3-Mav"/>
|
||||
<outlet property="redirectMicStack" destination="hcl-DT-peO" id="g8Y-dq-CiW"/>
|
||||
<outlet property="redirectMicSwitch" destination="9Gg-3U-Oa7" id="39M-xy-hl8"/>
|
||||
<outlet property="relativeTouchSlideThresholdSlider" destination="Zut-jY-AFj" id="HFz-5k-1yq"/>
|
||||
@@ -2988,7 +3010,7 @@
|
||||
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
|
||||
<image name="video.and.waveform" catalog="system" width="128" height="94"/>
|
||||
<systemColor name="linkColor">
|
||||
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="secondarySystemGroupedBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -2997,13 +3019,13 @@
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBrownColor">
|
||||
<color red="0.67450980392156867" green="0.49803921568627452" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemGray3Color">
|
||||
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemTealColor">
|
||||
<color red="0.0" green="0.76470588235294112" blue="0.81568627450980391" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -79,8 +79,8 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, PencilTickMode) {
|
||||
ManualTick,
|
||||
PencilTickDisabled
|
||||
PencilTickDisabled,
|
||||
ManualTick
|
||||
};
|
||||
|
||||
- (void) saveSettings:(Settings*)settings
|
||||
|
||||
@@ -48,7 +48,8 @@ typedef NS_ENUM(NSInteger, MapGyroTo) {
|
||||
@property (nonatomic, assign) bool reverseGyroHoldButton;
|
||||
@property (nonatomic, assign) int controllerGyroSwitchHold;
|
||||
@property (nonatomic, assign) int controllerGyroSwitchToggle;
|
||||
|
||||
@property NSArray<NSNumber *> *pressureCurvePoints;
|
||||
@property (nonatomic, assign) bool pressureCurveEnabled;
|
||||
|
||||
- (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected;
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
[encoder encodeBool:self.yawPitchToRightStick forKey:@"yawPitchToRightStick"];
|
||||
[encoder encodeBool:self.rollToLeftStick forKey:@"rollToLeftStick"];
|
||||
[encoder encodeBool:self.synthesizePhysicalStick forKey:@"synthesizePhysicalStick"];
|
||||
[encoder encodeInt64:self.mapGyroTo forKey:@"mapGyroTo"];
|
||||
[encoder encodeFloat:self.gyroSensitivityYaw forKey:@"gyroSensitivityYaw"];
|
||||
[encoder encodeFloat:self.gyroSensitivityPitch forKey:@"gyroSensitivityPitch"];
|
||||
[encoder encodeFloat:self.gyroSensitivityRoll forKey:@"gyroSensitivityRoll"];
|
||||
@@ -50,12 +49,21 @@
|
||||
[encoder encodeBool:self.reverseGyroHoldButton forKey:@"reverseGyroHoldButton"];
|
||||
[encoder encodeInt:self.controllerGyroSwitchHold forKey:@"controllerGyroSwitchHold"];
|
||||
[encoder encodeInt:self.controllerGyroSwitchToggle forKey:@"controllerGyroSwitchToggle"];
|
||||
[encoder encodeObject:self.pressureCurvePoints forKey:@"pressureCurvePoints"];
|
||||
[encoder encodeBool:self.pressureCurveEnabled forKey:@"pressureCurveEnabled"];
|
||||
}
|
||||
|
||||
- (id) initWithCoder:(NSCoder*)decoder {
|
||||
if (self = [super init]) {
|
||||
self.name = [decoder decodeObjectForKey:@"name"];
|
||||
self.buttonStatesEncoded = [decoder decodeObjectForKey:@"buttonStates"];
|
||||
self.buttonStatesEncoded =
|
||||
[decoder decodeObjectOfClasses:
|
||||
[NSSet setWithObjects:
|
||||
[NSMutableArray class],
|
||||
[NSArray class],
|
||||
[NSData class],
|
||||
nil]
|
||||
forKey:@"buttonStates"];
|
||||
self.isSelected = [decoder decodeBoolForKey:@"isSelected"];
|
||||
self.mapGyroTo = [decoder containsValueForKey:@"mapGyroTo"] ? [decoder decodeInt64ForKey:@"mapGyroTo"] : mapGyroToMouse;
|
||||
self.yawPitchToRightStick = [decoder containsValueForKey:@"yawPitchToRightStick"] ? [decoder decodeBoolForKey:@"yawPitchToRightStick"] : true;
|
||||
@@ -74,6 +82,16 @@
|
||||
self.reverseGyroHoldButton = [decoder containsValueForKey:@"reverseGyroHoldButton"] ? [decoder decodeBoolForKey:@"reverseGyroHoldButton"] : false;
|
||||
self.controllerGyroSwitchHold = [decoder containsValueForKey:@"controllerGyroSwitchHold"] ? [decoder decodeIntForKey:@"controllerGyroSwitchHold"] : ControllerButtonNull;
|
||||
self.controllerGyroSwitchToggle = [decoder containsValueForKey:@"controllerGyroSwitchToggle"] ? [decoder decodeIntForKey:@"controllerGyroSwitchToggle"] : ControllerButtonNull;
|
||||
self.pressureCurvePoints =
|
||||
[decoder containsValueForKey:@"pressureCurvePoints"]
|
||||
? [decoder decodeObjectOfClasses:
|
||||
[NSSet setWithObjects:
|
||||
[NSArray class],
|
||||
[NSNumber class],
|
||||
nil]
|
||||
forKey:@"pressureCurvePoints"]
|
||||
: @[@0.0, @0.0, @1.0, @1.0];
|
||||
self.pressureCurveEnabled = [decoder containsValueForKey:@"pressureCurveEnabled"] ? [decoder decodeBoolForKey:@"pressureCurveEnabled"] : false;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -101,6 +119,8 @@
|
||||
copy.reverseGyroHoldButton = self.reverseGyroHoldButton;
|
||||
copy.controllerGyroSwitchHold = self.controllerGyroSwitchHold;
|
||||
copy.controllerGyroSwitchToggle = self.controllerGyroSwitchToggle;
|
||||
copy.pressureCurvePoints = [[NSMutableArray alloc] initWithArray:self.pressureCurvePoints copyItems:YES];
|
||||
copy.pressureCurveEnabled = self.pressureCurveEnabled;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,22 +10,43 @@
|
||||
import UIKit
|
||||
|
||||
@objc class PencilHandler: UIResponder {
|
||||
|
||||
@objc static var sharedInstance: PencilHandler?
|
||||
|
||||
/// 用来处理所有 touch 事件的宿主视图
|
||||
weak var streamView: UIView?
|
||||
// private var tickTimer: SafeTimer
|
||||
private var streamAspectRatio: Float
|
||||
private var tickInterval: TimeInterval
|
||||
private var manualTick: Bool
|
||||
private var manualhoverFlag: Bool = false
|
||||
private var pencilTickEnabled: Bool
|
||||
private var pressureCurveEnabled: Bool = false
|
||||
private var manualHoverFlag: Bool = false
|
||||
private var autoHoverFlag: Bool = false
|
||||
|
||||
private var pressureLUT = PressureCurveLUT(curve: PressureCurve())
|
||||
|
||||
@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
|
||||
pencilTickEnabled = settings.pencilTickMode.intValue != PencilTickMode.PencilTickDisabled.rawValue
|
||||
super.init()
|
||||
setupPressureLUT()
|
||||
PencilHandler.sharedInstance = self
|
||||
}
|
||||
|
||||
public func setupPressureLUT(){
|
||||
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
|
||||
let oscProfile = oscProfileMan.getSelectedProfile()
|
||||
pressureCurveEnabled = oscProfile.pressureCurveEnabled
|
||||
let persistedCurvePoints = PressureCurve.importCurvePoints(oscProfile.pressureCurvePoints)
|
||||
let curve = PressureCurve()
|
||||
curve.polylinePoints = persistedCurvePoints
|
||||
curve.buildCurveSegments()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()) {
|
||||
self.pressureLUT = PressureCurveLUT(curve: curve)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Touch Events
|
||||
@@ -34,16 +55,15 @@ import UIKit
|
||||
guard let event = event else { return }
|
||||
for touch in touches {
|
||||
let coalesced = event.coalescedTouches(for: touch) ?? []
|
||||
_ = self.sendStylusEvent(touchBatch: coalesced)
|
||||
_ = self.sendStylusEvent(touchBatch: pencilTickEnabled ? coalesced : [touch])
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard let event = event else { return }
|
||||
|
||||
for touch in touches {
|
||||
let coalesced = event.coalescedTouches(for: touch) ?? []
|
||||
_ = self.sendStylusEvent(touchBatch: coalesced)
|
||||
_ = self.sendStylusEvent(touchBatch: pencilTickEnabled ? coalesced : [touch])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +71,7 @@ import UIKit
|
||||
guard let event = event else { return }
|
||||
for touch in touches {
|
||||
let coalesced = event.coalescedTouches(for: touch) ?? []
|
||||
_ = self.sendStylusEvent(touchBatch: coalesced)
|
||||
_ = self.sendStylusEvent(touchBatch: pencilTickEnabled ? coalesced : [touch])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +122,6 @@ import UIKit
|
||||
|
||||
|
||||
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
|
||||
@@ -113,9 +130,6 @@ import UIKit
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
@@ -125,13 +139,10 @@ import UIKit
|
||||
|
||||
var tickMoment: TimeInterval = 0
|
||||
var previousTimeStamp: TimeInterval = 0
|
||||
let dispatchMoment: DispatchTime = .now()
|
||||
var delay:TimeInterval = 0
|
||||
var dispatchMoment: DispatchTime = .now()
|
||||
|
||||
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
|
||||
@@ -139,14 +150,15 @@ import UIKit
|
||||
let videoSize = self.getVideoAreaSize()
|
||||
let normalizedLocation = CGPoint(x: location.x/videoSize.width, y: location.y/videoSize.height)
|
||||
let force = Float(touch.force/touch.maximumPossibleForce)/sin(Float(altitude))
|
||||
|
||||
let targetForce = pressureCurveEnabled ? self.pressureLUT.value(at: force) : force
|
||||
|
||||
let eventType:UInt8
|
||||
|
||||
switch touch.phase {
|
||||
case .began:
|
||||
eventType = UInt8(LI_TOUCH_EVENT_DOWN)
|
||||
case .moved:
|
||||
eventType = UInt8(manualhoverFlag ? LI_TOUCH_EVENT_HOVER : LI_TOUCH_EVENT_MOVE)
|
||||
eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER : LI_TOUCH_EVENT_MOVE)
|
||||
case .ended:
|
||||
eventType = UInt8(LI_TOUCH_EVENT_UP)
|
||||
case .cancelled:
|
||||
@@ -158,8 +170,17 @@ import UIKit
|
||||
delay = manualTick ? 0.0086 : 0.017
|
||||
delay = eventType == UInt8(LI_TOUCH_EVENT_UP) ? delay : 0
|
||||
|
||||
if previousTimeStamp == 0 {
|
||||
previousTimeStamp = touch.timestamp
|
||||
tickMoment = 0
|
||||
dispatchMoment = .now()
|
||||
}
|
||||
else {
|
||||
tickMoment += (manualTick ? tickInterval : touch.timestamp - previousTimeStamp)
|
||||
}
|
||||
|
||||
DispatchQueue.global().asyncAfter(deadline: dispatchMoment + tickMoment + delay) {
|
||||
LiSendPenEvent(eventType, UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), force, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude)))
|
||||
LiSendPenEvent(eventType, UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), targetForce, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,15 +188,15 @@ import UIKit
|
||||
}
|
||||
|
||||
@objc public func switchPencilHoever(){
|
||||
manualhoverFlag = !manualhoverFlag
|
||||
manualHoverFlag = !manualHoverFlag
|
||||
}
|
||||
|
||||
@objc public func enablePencilHover(){
|
||||
manualhoverFlag = true
|
||||
manualHoverFlag = true
|
||||
}
|
||||
|
||||
@objc public func disablePencilHover(){
|
||||
manualhoverFlag = false
|
||||
manualHoverFlag = false
|
||||
}
|
||||
|
||||
private func attachHoverLeave(normalizedLocation:CGPoint){
|
||||
|
||||
@@ -0,0 +1,663 @@
|
||||
//
|
||||
// PressureCurve.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 2026/1/5.
|
||||
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: - PressureCurve
|
||||
final class PressureCurve {
|
||||
|
||||
var polylinePoints: [CGPoint] = [
|
||||
CGPoint(x: 0, y: 0.0),
|
||||
CGPoint(x: 1.0, y: 1.0)
|
||||
]
|
||||
|
||||
private var factor: CGFloat = 0.39
|
||||
|
||||
private var cachedSegments: [(p0: CGPoint, c1: CGPoint, c2: CGPoint, p1: CGPoint)] = []
|
||||
|
||||
private var curveCutPoints: [CGPoint] {
|
||||
guard polylinePoints.count >= 2 else { return polylinePoints }
|
||||
var cuts: [CGPoint] = []
|
||||
cuts.append(polylinePoints[0]) // 起点
|
||||
for i in 0..<(polylinePoints.count - 2) {
|
||||
let mid = CGPoint(
|
||||
x: (polylinePoints[i+1].x + polylinePoints[i+2].x)/2,
|
||||
y: (polylinePoints[i+1].y + polylinePoints[i+2].y)/2
|
||||
)
|
||||
if i < polylinePoints.count - 3 {
|
||||
cuts.append(mid)
|
||||
}
|
||||
}
|
||||
cuts.append(polylinePoints.last!) // 终点
|
||||
return cuts
|
||||
}
|
||||
|
||||
private func curveFactor(for middlePointCount: Int) -> CGFloat {
|
||||
guard middlePointCount > 0 else { return 2.0 / 3.0 }
|
||||
let n = CGFloat(middlePointCount)
|
||||
return 0.30538319 * pow(n, -2.1518917) + 0.36128348
|
||||
}
|
||||
|
||||
public func buildCurveSegments() {
|
||||
let cuts = curveCutPoints
|
||||
var segments: [(CGPoint, CGPoint, CGPoint, CGPoint)] = []
|
||||
|
||||
factor = curveFactor(for: polylinePoints.count-2)
|
||||
|
||||
for i in 0..<(cuts.count - 1) {
|
||||
let p0 = cuts[i]
|
||||
let p1 = cuts[i+1]
|
||||
|
||||
// segment index
|
||||
let startIndex = i
|
||||
let endIndex = i+1
|
||||
|
||||
let tangentStart: CGPoint
|
||||
if startIndex < polylinePoints.count - 1 {
|
||||
tangentStart = CGPoint(
|
||||
x: (polylinePoints[startIndex + 1].x - polylinePoints[startIndex].x) * factor,
|
||||
y: (polylinePoints[startIndex + 1].y - polylinePoints[startIndex].y) * factor
|
||||
)
|
||||
} else {
|
||||
tangentStart = .zero
|
||||
}
|
||||
|
||||
let tangentEnd: CGPoint
|
||||
if endIndex < polylinePoints.count - 1 {
|
||||
tangentEnd = CGPoint(
|
||||
x: (polylinePoints[endIndex + 1].x - polylinePoints[endIndex].x) * factor,
|
||||
y: (polylinePoints[endIndex + 1].y - polylinePoints[endIndex].y) * factor
|
||||
)
|
||||
} else {
|
||||
tangentEnd = .zero
|
||||
}
|
||||
|
||||
let c1 = CGPoint(x: p0.x + tangentStart.x, y: p0.y + tangentStart.y)
|
||||
let c2 = CGPoint(x: p1.x - tangentEnd.x, y: p1.y - tangentEnd.y)
|
||||
|
||||
segments.append((p0: p0, c1: c1, c2: c2, p1: p1))
|
||||
}
|
||||
|
||||
cachedSegments = segments
|
||||
}
|
||||
|
||||
/// 采样 曲线
|
||||
func sampleCurve(stepsPerSegment: Int = 50) -> [CGPoint] {
|
||||
var points: [CGPoint] = []
|
||||
buildCurveSegments()
|
||||
for seg in cachedSegments {
|
||||
let sampled = sampleCurve(p0: seg.p0, c1: seg.c1, c2: seg.c2, p1: seg.p1, steps: stepsPerSegment)
|
||||
if !points.isEmpty { points.removeLast() } // 避免重复点
|
||||
points.append(contentsOf: sampled)
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
private func sampleCurve(p0: CGPoint, c1: CGPoint, c2: CGPoint, p1: CGPoint, steps: Int) -> [CGPoint] {
|
||||
var result: [CGPoint] = []
|
||||
for i in 0...steps {
|
||||
let t = CGFloat(i)/CGFloat(steps)
|
||||
let mt = 1-t
|
||||
let x = mt*mt*mt*p0.x + 3*mt*mt*t*c1.x + 3*mt*t*t*c2.x + t*t*t*p1.x
|
||||
let y = mt*mt*mt*p0.y + 3*mt*mt*t*c1.y + 3*mt*t*t*c2.y + t*t*t*p1.y
|
||||
result.append(CGPoint(x:x, y:y))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var tangentPoints: [CGPoint] { polylinePoints }
|
||||
|
||||
func value(at x: CGFloat) -> CGFloat {
|
||||
|
||||
let xClamped = min(max(x, 0), 1)
|
||||
let segments = cachedSegments
|
||||
|
||||
for seg in segments {
|
||||
if xClamped >= seg.p0.x && xClamped <= seg.p1.x {
|
||||
let t = solveT(forX: xClamped,
|
||||
p0: seg.p0.x,
|
||||
c1: seg.c1.x,
|
||||
c2: seg.c2.x,
|
||||
p1: seg.p1.x)
|
||||
return cubic(p0: seg.p0.y,
|
||||
c1: seg.c1.y,
|
||||
c2: seg.c2.y,
|
||||
p1: seg.p1.y,
|
||||
t: t)
|
||||
}
|
||||
}
|
||||
|
||||
return segments.last?.p1.y ?? 0
|
||||
}
|
||||
|
||||
private func solveT(
|
||||
forX x: CGFloat,
|
||||
p0: CGFloat,
|
||||
c1: CGFloat,
|
||||
c2: CGFloat,
|
||||
p1: CGFloat,
|
||||
iterations: Int = 18
|
||||
) -> CGFloat {
|
||||
|
||||
var low: CGFloat = 0
|
||||
var high: CGFloat = 1
|
||||
var t: CGFloat = 0.5
|
||||
|
||||
for _ in 0..<iterations {
|
||||
t = (low + high) * 0.5
|
||||
let xt = cubic(p0: p0, c1: c1, c2: c2, p1: p1, t: t)
|
||||
|
||||
if xt < x {
|
||||
low = t
|
||||
} else {
|
||||
high = t
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
private func cubic(
|
||||
p0: CGFloat,
|
||||
c1: CGFloat,
|
||||
c2: CGFloat,
|
||||
p1: CGFloat,
|
||||
t: CGFloat
|
||||
) -> CGFloat {
|
||||
|
||||
let mt = 1 - t
|
||||
return mt*mt*mt*p0
|
||||
+ 3*mt*mt*t*c1
|
||||
+ 3*mt*t*t*c2
|
||||
+ t*t*t*p1
|
||||
}
|
||||
|
||||
static func exportCurvePoints(_ curve: PressureCurve) -> [NSNumber] {
|
||||
var arr: [NSNumber] = []
|
||||
for p in curve.polylinePoints {
|
||||
arr.append(NSNumber(value: Float(p.x)))
|
||||
arr.append(NSNumber(value: Float(p.y)))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
static func importCurvePoints(_ numbers: [NSNumber]) -> [CGPoint] {
|
||||
var pts: [CGPoint] = []
|
||||
let count = numbers.count / 2
|
||||
|
||||
for i in 0..<count {
|
||||
let x = CGFloat(numbers[i*2].floatValue)
|
||||
let y = CGFloat(numbers[i*2 + 1].floatValue)
|
||||
pts.append(CGPoint(x: x, y: y))
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
static func generateLinearCurvePoints(from pressures: [CGFloat]) -> [CGPoint] {
|
||||
guard !pressures.isEmpty else { return [] }
|
||||
|
||||
let samples = pressures.map { min(max($0, 0), 1) }
|
||||
let maxPressure = samples.max() ?? 0
|
||||
|
||||
if maxPressure < 1 {
|
||||
let platformX = maxPressure
|
||||
|
||||
return [
|
||||
CGPoint(x: 0, y: 0),
|
||||
CGPoint(x: platformX, y: 1),
|
||||
CGPoint(x: platformX, y: 1),
|
||||
CGPoint(x: 1, y: 1)
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
CGPoint(x: 0, y: 0),
|
||||
CGPoint(x: 1, y: 1)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - PressureCurveView
|
||||
final class PressureCurveView: UIView {
|
||||
|
||||
let curve = PressureCurve()
|
||||
|
||||
private var draggingIndex: Int? = nil
|
||||
private let hitRadius: CGFloat = 22
|
||||
|
||||
enum PressureTestStage: UInt8 {
|
||||
case drawingStage
|
||||
case curveStage
|
||||
}
|
||||
public var testStage: PressureTestStage = .curveStage
|
||||
private var pressures:[CGFloat] = []
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .black
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
public var showGraph: Bool = false {
|
||||
didSet { setNeedsDisplay() } // 改变开关立即刷新
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let ctx = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
ctx.saveGState()
|
||||
|
||||
// 缩放和平移
|
||||
let scale: CGFloat = 0.95
|
||||
let dx = (bounds.width - bounds.width * scale) / 2
|
||||
let dy = (bounds.height - bounds.height * scale) / 2
|
||||
ctx.translateBy(x: dx, y: dy)
|
||||
ctx.scaleBy(x: scale, y: scale)
|
||||
|
||||
if showGraph {
|
||||
drawGrid(ctx)
|
||||
drawCurve(ctx)
|
||||
drawTangents(ctx)
|
||||
}
|
||||
|
||||
ctx.restoreGState()
|
||||
}
|
||||
|
||||
private func drawGrid(_ ctx: CGContext) {
|
||||
ctx.setStrokeColor(UIColor.darkGray.cgColor)
|
||||
ctx.setLineWidth(1)
|
||||
for i in 0...4 {
|
||||
let t = CGFloat(i)/4
|
||||
let x = bounds.width*t
|
||||
let y = bounds.height*t
|
||||
ctx.move(to: CGPoint(x:x, y:0))
|
||||
ctx.addLine(to: CGPoint(x:x, y:bounds.height))
|
||||
ctx.move(to: CGPoint(x:0, y:y))
|
||||
ctx.addLine(to: CGPoint(x:bounds.width, y:y))
|
||||
}
|
||||
ctx.strokePath()
|
||||
}
|
||||
|
||||
private func drawCurve(_ ctx: CGContext) {
|
||||
ctx.setStrokeColor(UIColor.systemBlue.cgColor)
|
||||
ctx.setLineWidth(3)
|
||||
let points = curve.sampleCurve(stepsPerSegment: 60)
|
||||
for (i,p) in points.enumerated() {
|
||||
let mapped = map(p)
|
||||
if i==0 { ctx.move(to:mapped) } else { ctx.addLine(to:mapped) }
|
||||
}
|
||||
ctx.strokePath()
|
||||
}
|
||||
|
||||
private func drawTangents(_ ctx: CGContext) {
|
||||
let pts = curve.tangentPoints
|
||||
ctx.setStrokeColor(UIColor.systemPurple.cgColor)
|
||||
ctx.setLineWidth(1.5)
|
||||
for i in 0..<pts.count {
|
||||
let p = map(pts[i])
|
||||
if i==0 { ctx.move(to:p) } else { ctx.addLine(to:p) }
|
||||
}
|
||||
ctx.strokePath()
|
||||
|
||||
for p in pts {
|
||||
let c = map(p)
|
||||
let r: CGFloat = 4
|
||||
ctx.setFillColor(UIColor.systemPurple.cgColor)
|
||||
ctx.fill(CGRect(x:c.x-r, y:c.y-r, width:r*2, height:r*2))
|
||||
}
|
||||
}
|
||||
|
||||
private func map(_ p: CGPoint) -> CGPoint {
|
||||
CGPoint(x:p.x*bounds.width, y:(1-p.y)*bounds.height)
|
||||
}
|
||||
|
||||
private func unmap(_ p: CGPoint) -> CGPoint {
|
||||
CGPoint(
|
||||
x: p.x / bounds.width,
|
||||
y: 1 - (p.y / bounds.height)
|
||||
)
|
||||
}
|
||||
|
||||
private func hitTestPoint(_ location: CGPoint) -> Int? {
|
||||
let pts = curve.tangentPoints
|
||||
for (i, p) in pts.enumerated() {
|
||||
let c = map(p)
|
||||
let dx = c.x - location.x
|
||||
let dy = c.y - location.y
|
||||
if dx*dx + dy*dy <= hitRadius*hitRadius {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
guard let touch = touches.first else { return }
|
||||
let location = touch.location(in: self)
|
||||
|
||||
if testStage == .drawingStage {
|
||||
return
|
||||
}
|
||||
|
||||
draggingIndex = hitTestPoint(location)
|
||||
if draggingIndex == nil {
|
||||
guard
|
||||
let touch = touches.first
|
||||
else { return }
|
||||
let location = touch.location(in: self)
|
||||
var point = unmap(location)
|
||||
|
||||
point.x = min(max(point.x, 0), 1)
|
||||
point.y = min(max(point.y, 0), 1)
|
||||
var insertIdx:Int = -1
|
||||
|
||||
for (idx, polylinePoint) in curve.polylinePoints.enumerated() {
|
||||
if point.x <= polylinePoint.x {
|
||||
insertIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
if insertIdx >= 0 {
|
||||
curve.polylinePoints.insert(point, at: insertIdx)
|
||||
draggingIndex = insertIdx
|
||||
}
|
||||
setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
|
||||
|
||||
if testStage == .drawingStage {
|
||||
let altitude = touches.first!.altitudeAngle
|
||||
let force = (touches.first!.force/touches.first!.maximumPossibleForce)/sin(altitude)
|
||||
pressures.append(force)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard
|
||||
let touch = touches.first,
|
||||
let idx = draggingIndex
|
||||
else { return }
|
||||
|
||||
let location = touch.location(in: self)
|
||||
var point = unmap(location)
|
||||
|
||||
if draggingIndex == 0 {
|
||||
point.x = 0
|
||||
point.y = min(max(point.y, 0), 1)
|
||||
}
|
||||
else if draggingIndex == curve.polylinePoints.count-1 {
|
||||
point.x = 1
|
||||
point.y = min(max(point.y, 0), 1)
|
||||
}
|
||||
else {
|
||||
let nextPoint = curve.polylinePoints[idx+1]
|
||||
let previousPoint = curve.polylinePoints[idx-1]
|
||||
point.x = min(max(point.x, previousPoint.x), nextPoint.x)
|
||||
point.y = min(max(point.y, 0), 1)
|
||||
}
|
||||
|
||||
// print("currenPoint \(point)")
|
||||
|
||||
curve.polylinePoints[idx] = point
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if testStage == .drawingStage {
|
||||
curve.polylinePoints = PressureCurve.generateLinearCurvePoints(from: pressures)
|
||||
testStage = .curveStage
|
||||
showGraph = true
|
||||
let parentVC = parentViewController as! PressureCurveViewController
|
||||
parentVC.setupNavigationBar()
|
||||
parentVC.showCurveStageTips()
|
||||
}
|
||||
draggingIndex = nil
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
draggingIndex = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class PressureCurveLUT {
|
||||
static let levels = 2048
|
||||
public let table: [Float]
|
||||
|
||||
init(curve: PressureCurve) {
|
||||
curve.buildCurveSegments()
|
||||
|
||||
var t = [Float](repeating: 0, count: Self.levels)
|
||||
|
||||
let maxIndex = Float(Self.levels - 1)
|
||||
|
||||
for i in 0..<Self.levels {
|
||||
let x = CGFloat(Float(i) / maxIndex)
|
||||
let y = curve.value(at: x)
|
||||
t[i] = Float(y)
|
||||
}
|
||||
|
||||
self.table = t
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
func value(at x: Float) -> Float {
|
||||
let clamped = min(max(x, 0), 1)
|
||||
let index = Int((clamped * Float(Self.levels - 1)).rounded())
|
||||
return table[index]
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
var parentViewController: UIViewController? {
|
||||
var responder: UIResponder? = self
|
||||
while let r = responder {
|
||||
if let vc = r as? UIViewController {
|
||||
return vc
|
||||
}
|
||||
responder = r.next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewController
|
||||
class PressureCurveViewController: UIViewController {
|
||||
|
||||
private var navBar = UINavigationBar()
|
||||
|
||||
private var container = UIView()
|
||||
private var curveView = PressureCurveView()
|
||||
|
||||
private var minPressure: CGFloat = 0
|
||||
private var maxPressure: CGFloat = 1
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.isOpaque = false
|
||||
self.view.backgroundColor = UIColor.clear // 半透明遮罩
|
||||
|
||||
let curveWidth: CGFloat = 400
|
||||
let curveHeight: CGFloat = 400
|
||||
curveView = PressureCurveView(frame: CGRect(x: 0, y: 0, width: curveWidth, height: curveHeight))
|
||||
curveView.center = self.view.center
|
||||
curveView.backgroundColor = .white
|
||||
curveView.layer.cornerRadius = 0
|
||||
curveView.layer.masksToBounds = true
|
||||
|
||||
container = UIView(frame: CGRect(x: 0, y: 0, width: curveWidth+10, height: curveHeight+50))
|
||||
container.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY-20)
|
||||
container.backgroundColor = .white
|
||||
container.layer.cornerRadius = 12
|
||||
container.layer.masksToBounds = true
|
||||
container.layer.borderWidth = 2
|
||||
container.layer.borderColor = ThemeManager.separatorColor().cgColor
|
||||
container.addSubview(curveView)
|
||||
self.loadPersistedCurve()
|
||||
self.curveView.showGraph = true
|
||||
|
||||
self.setupNavigationBar()
|
||||
|
||||
self.view.addSubview(container)
|
||||
|
||||
curveView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
curveView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
curveView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
|
||||
curveView.widthAnchor.constraint(equalToConstant: curveWidth),
|
||||
curveView.heightAnchor.constraint(equalToConstant: curveHeight)
|
||||
])
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
AlertControllerUtil.cancelButtonString = SwiftLocalizationHelper.localizedString(forKey: "No")
|
||||
AlertControllerUtil.showAlert(
|
||||
in: self,
|
||||
title: SwiftLocalizationHelper.localizedString(forKey: "Pen Pressure Curve"),
|
||||
message: SwiftLocalizationHelper.localizedString(forKey:"Reset the pen pressure curve by starting from the pen pressure test?"),
|
||||
withCancel: true,
|
||||
buttonTitle: SwiftLocalizationHelper.localizedString(forKey: "Yes"),
|
||||
countdown: 0,
|
||||
action: {},
|
||||
completion: {
|
||||
if AlertControllerUtil.actionCancelled {
|
||||
self.curveView.showGraph = true
|
||||
self.curveView.testStage = .curveStage
|
||||
self.curveView.setNeedsDisplay()
|
||||
self.setupNavigationBar()
|
||||
self.showCurveStageTips()
|
||||
}
|
||||
else {
|
||||
self.pressureRangeTest()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private func loadPersistedCurve() {
|
||||
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
|
||||
let persistedCurvePoints = PressureCurve.importCurvePoints(oscProfileMan.getSelectedProfile().pressureCurvePoints)
|
||||
curveView.curve.polylinePoints = persistedCurvePoints
|
||||
curveView.setNeedsDisplay()
|
||||
}
|
||||
|
||||
public func setupNavigationBar() {
|
||||
// 1. 创建 UINavigationBar
|
||||
navBar.removeFromSuperview()
|
||||
navBar = UINavigationBar()
|
||||
navBar.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addSubview(navBar)
|
||||
|
||||
var navItem = UINavigationItem()
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
let appearance = UINavigationBarAppearance()
|
||||
appearance.configureWithTransparentBackground() // 透明背景
|
||||
appearance.shadowColor = nil // 去掉底部细线
|
||||
appearance.backgroundColor = .clear // 可以额外设置完全透明
|
||||
|
||||
navBar.standardAppearance = appearance
|
||||
navBar.scrollEdgeAppearance = appearance
|
||||
} else {
|
||||
navBar.setBackgroundImage(UIImage(), for: .default)
|
||||
navBar.shadowImage = UIImage() // 去掉底部线
|
||||
navBar.isTranslucent = true
|
||||
navBar.backgroundColor = .clear
|
||||
}
|
||||
|
||||
|
||||
// 2. 添加约束 (顶部,左右)
|
||||
NSLayoutConstraint.activate([
|
||||
navBar.topAnchor.constraint(equalTo: container.safeAreaLayoutGuide.topAnchor),
|
||||
navBar.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
||||
navBar.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||||
navBar.heightAnchor.constraint(equalToConstant: 42) // 导航栏标准高度
|
||||
])
|
||||
|
||||
|
||||
if curveView.testStage == .curveStage {
|
||||
let resetButton = UIBarButtonItem(title: SwiftLocalizationHelper.localizedString(forKey: "Reset"), style: .plain, target: self, action: #selector(resetTapped))
|
||||
let rangeTestButton = UIBarButtonItem(title: SwiftLocalizationHelper.localizedString(forKey: "Pressure-range-test"), style: .plain, target: self, action: #selector(pressureRangeTest))
|
||||
let saveButton = UIBarButtonItem(title: SwiftLocalizationHelper.localizedString(forKey: "Save"), style: .plain, target: self, action: #selector(saveTapped))
|
||||
let quitButton = UIBarButtonItem(title: SwiftLocalizationHelper.localizedString(forKey: "Exit"), style: .plain, target: self, action: #selector(exitTapped))
|
||||
navItem.leftBarButtonItems = [resetButton, rangeTestButton]
|
||||
navItem.rightBarButtonItems = [quitButton, saveButton]
|
||||
}
|
||||
|
||||
if curveView.testStage == .drawingStage {
|
||||
navItem = UINavigationItem(title: SwiftLocalizationHelper.localizedString(forKey: "Pressure Range Test"))
|
||||
}
|
||||
|
||||
// 4. 将 UINavigationItem 添加到导航栏
|
||||
navBar.items = [navItem]
|
||||
}
|
||||
|
||||
public func showCurveStageTips() {
|
||||
AlertControllerUtil.showAlert(
|
||||
in: self,
|
||||
title: SwiftLocalizationHelper.localizedString(forKey: "Pen Pressure Curve"),
|
||||
message: SwiftLocalizationHelper.localizedString(forKey:"Adjust the pressure curve by adding or dragging the purple squares."),
|
||||
withCancel: false,
|
||||
buttonTitle: SwiftLocalizationHelper.localizedString(forKey: "OK"),
|
||||
countdown: 0
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func resetTapped() {
|
||||
curveView.curve.polylinePoints = [CGPoint(x: 0, y: 0), CGPoint(x: 1, y: 1)]
|
||||
curveView.setNeedsDisplay()
|
||||
}
|
||||
|
||||
@objc private func pressureRangeTest() {
|
||||
AlertControllerUtil.showAlert(
|
||||
in: self,
|
||||
title: SwiftLocalizationHelper.localizedString(forKey: "Pressure Range Test"),
|
||||
message: SwiftLocalizationHelper.localizedString(forKey:"Draw freely and vary pen pressure naturally."),
|
||||
withCancel: false,
|
||||
buttonTitle: SwiftLocalizationHelper.localizedString(forKey: "OK"),
|
||||
countdown: 0,
|
||||
completion:{
|
||||
self.curveView.showGraph = false
|
||||
self.curveView.testStage = .drawingStage
|
||||
self.curveView.setNeedsDisplay()
|
||||
self.setupNavigationBar()
|
||||
})
|
||||
}
|
||||
|
||||
@objc private func saveTapped() {
|
||||
|
||||
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
|
||||
let selectedProfile = oscProfileMan.getSelectedProfile()
|
||||
selectedProfile.pressureCurvePoints = PressureCurve.exportCurvePoints(curveView.curve)
|
||||
oscProfileMan.replaceSelectedProfile(with: selectedProfile, overwriteDefault: true)
|
||||
|
||||
AlertControllerUtil.showAlert(
|
||||
in: self,
|
||||
title: SwiftLocalizationHelper.localizedString(forKey: "Pen Pressure Curve"),
|
||||
message: SwiftLocalizationHelper.localizedString(forKey:"Pressure curve saved with current on-screen widget profile!"),
|
||||
withCancel: false,
|
||||
buttonTitle: SwiftLocalizationHelper.localizedString(forKey: "OK"),
|
||||
countdown: 0,
|
||||
completion: {
|
||||
PencilHandler.sharedInstance?.setupPressureLUT()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func exitTapped() {
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@
|
||||
@property (assign, nonatomic) CGRect originalFrame;
|
||||
@property (assign, nonatomic) bool widgetToolOpened;
|
||||
@property (strong, nonatomic) OnScreenControls* onScreenControls;
|
||||
@property (weak, nonatomic) PencilHandler* pencilHandler;
|
||||
|
||||
|
||||
- (void) setupStreamView:(ControllerSupport*)controllerSupport
|
||||
interactionDelegate:(id<UserInteractionDelegate>)interactionDelegate
|
||||
|
||||
+14
-31
@@ -58,7 +58,6 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
int localMousePointerMode;
|
||||
|
||||
UIResponder* touchHandler;
|
||||
PencilHandler* pencilHandler;
|
||||
|
||||
id<UserInteractionDelegate> interactionDelegate;
|
||||
NSTimer* interactionTimer;
|
||||
@@ -129,8 +128,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self];
|
||||
#else
|
||||
|
||||
pencilHandler = [[PencilHandler alloc] initWithStreamView:self settings:settings];
|
||||
|
||||
PencilHandler.sharedInstance = [[PencilHandler alloc] initWithStreamView:self settings:settings];
|
||||
_pencilHandler = PencilHandler.sharedInstance;
|
||||
|
||||
// iOS uses touch Mode depending on user preference
|
||||
|
||||
switch (settings.touchMode.intValue) {
|
||||
@@ -728,6 +728,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
CGPoint location = [self adjustCoordinatesForVideoArea:[event locationInView:self]];
|
||||
CGSize videoSize = [self getVideoAreaSize];
|
||||
|
||||
CGFloat pressure = (event.force / event.maximumPossibleForce) / sin(event.altitudeAngle);
|
||||
|
||||
return LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height,
|
||||
(event.force / event.maximumPossibleForce) / sin(event.altitudeAngle),
|
||||
0.0f, 0.0f,
|
||||
@@ -788,14 +790,8 @@ 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(settings.pencilTickMode.intValue == PencilTickDisabled){
|
||||
[self sendStylusEvent:touch];
|
||||
return;
|
||||
}
|
||||
else{
|
||||
[pencilHandler touchesBegan:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
[_pencilHandler touchesBegan:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -950,14 +946,8 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
|
||||
for (UITouch* touch in touches) {
|
||||
if (touch.type == UITouchTypePencil) {
|
||||
if(settings.pencilTickMode.intValue == PencilTickDisabled){
|
||||
[self sendStylusEvent:touch];
|
||||
return;
|
||||
}
|
||||
else{
|
||||
[pencilHandler touchesMoved:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
[_pencilHandler touchesMoved:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
if (@available(iOS 13.4, *)) {
|
||||
UITouch *touch = [touches anyObject];
|
||||
@@ -1065,14 +1055,8 @@ 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(settings.pencilTickMode.intValue == PencilTickDisabled){
|
||||
[self sendStylusEvent:touch];
|
||||
return;
|
||||
}
|
||||
else{
|
||||
[pencilHandler touchesEnded:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
[_pencilHandler touchesEnded:touches withEvent:event];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1450,18 +1434,17 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
||||
}
|
||||
|
||||
- (void)switchPencilHover{
|
||||
[pencilHandler switchPencilHoever];
|
||||
[_pencilHandler switchPencilHoever];
|
||||
}
|
||||
|
||||
- (void)enablePencilHover{
|
||||
[pencilHandler enablePencilHover];
|
||||
[_pencilHandler enablePencilHover];
|
||||
}
|
||||
|
||||
- (void)disablePencilHover{
|
||||
[pencilHandler disablePencilHover];
|
||||
[_pencilHandler disablePencilHover];
|
||||
}
|
||||
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (BOOL)isMultipleTouchEnabled {
|
||||
return YES;
|
||||
|
||||
@@ -697,6 +697,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Adjust the pressure curve by adding or dragging the purple squares." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "通过添加或移动紫色方块点调整笔压曲线。"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "透過新增或移動紫色方塊點來調整筆壓曲線。"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "透過新增或移動紫色方塊點來調整筆壓曲線。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alias (optional)" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -2056,6 +2079,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Draw freely and vary pen pressure naturally." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "请随意绘制,笔压自然变化即可。"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "請隨意繪製,筆壓自然變化即可。"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "請隨意繪製,筆壓自然變化即可。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Drift Correction" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -4033,6 +4079,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"No" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "否"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "否"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "否"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No video received from host." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -4626,6 +4695,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pen Pressure Curve" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔压曲线"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pencil" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -5157,6 +5249,75 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pressure curve saved with current on-screen widget profile!" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔压曲线已随屏显控件配置保存!"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線已隨螢幕小工具設定儲存!"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線已隨螢幕小工具設定儲存!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pressure Range Test" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔压范围测试"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓範圍測試"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓範圍測試"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pressure-range-test" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔压范围测试"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓範圍測試"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓範圍測試"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Profile %@ duplicated from current layout" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -5687,6 +5848,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Reset" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "重置"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "重置"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "重置"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Reset the pen pressure curve by starting from the pen pressure test?" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是否从笔压范围测试开始重置笔压曲线?"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是否從筆壓範圍測試開始重置筆壓曲線?"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是否從筆壓範圍測試開始重置筆壓曲線?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Restart Required" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -7384,6 +7591,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Yes" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "是"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
@@ -240,6 +240,9 @@
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *rightStickMinOffsetStack;
|
||||
@property (strong, nonatomic) IBOutlet UISlider *rightStickMinOffsetSlider;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *pressureCurveStack;
|
||||
@property (strong, nonatomic) IBOutlet UISwitch *pressureCurveSwitch;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UIStackView *testStack;
|
||||
|
||||
|
||||
|
||||
@@ -391,6 +391,8 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[self rightStickMinOffsetSliderMoved:self.rightStickMinOffsetSlider];
|
||||
|
||||
[self.synthPhysicalInputSwitch setOn:oscProfile.synthesizePhysicalStick];
|
||||
|
||||
[self.pressureCurveSwitch setOn:oscProfile.pressureCurveEnabled];
|
||||
}
|
||||
|
||||
- (void)saveGameProfileConfigs{
|
||||
@@ -411,6 +413,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
&& oscProfile.reverseGyroHoldButton == self.reverseHoldButtonSwitch.isOn
|
||||
&& (int16_t)(oscProfile.physicalLeftStickMinOffset) == (int16_t)self.leftStickMinOffsetSlider.value
|
||||
&& (int16_t)(oscProfile.physicalRightStickMinOffset) == (int16_t)self.rightStickMinOffsetSlider.value
|
||||
&& oscProfile.pressureCurveEnabled == self.pressureCurveSwitch.isOn
|
||||
);
|
||||
|
||||
if(!configNotChanged){
|
||||
@@ -426,6 +429,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
oscProfile.reverseGyroHoldButton = self.reverseHoldButtonSwitch.isOn;
|
||||
oscProfile.physicalLeftStickMinOffset = (int16_t)self.leftStickMinOffsetSlider.value;
|
||||
oscProfile.physicalRightStickMinOffset = (int16_t)self.rightStickMinOffsetSlider.value;
|
||||
oscProfile.pressureCurveEnabled = self.pressureCurveSwitch.isOn;
|
||||
[oscProfileMan replaceSelectedProfileWith:oscProfile overwriteDefault:YES];
|
||||
}
|
||||
}
|
||||
@@ -850,12 +854,13 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
MenuSectionView* pencilSection = [[MenuSectionView alloc] init];
|
||||
pencilSection.delegate = self;
|
||||
pencilSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Pencil"];
|
||||
pencilSection.identifier = @"SettingsSectionMotionControl";
|
||||
pencilSection.identifier = @"SettingsSectionPencil";
|
||||
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];
|
||||
[self addSetting:self.pressureCurveStack ofId:@"pressureCurveStack" withInfoTag:NO withDynamicLabel:NO to:pencilSection];
|
||||
[pencilSection addToParentStack:_parentStack];
|
||||
}
|
||||
|
||||
@@ -3334,6 +3339,15 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[self findDynamicLabelFromStack:self.pencilTickIntervalStack].text = [NSString stringWithFormat:@" %d μs ", (uint16_t)sender.value];
|
||||
}
|
||||
|
||||
- (void)pressureCurveSwitchFlipped:(UISwitch* )sender{
|
||||
if(sender.isOn && !settingsViewJustLoaded){
|
||||
PressureCurveViewController* pressureCurveVC = [[PressureCurveViewController alloc] init];
|
||||
pressureCurveVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
||||
self.definesPresentationContext = true;
|
||||
[self presentViewController:pressureCurveVC animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadPencilSettings:(TemporarySettings*) tempSettings{
|
||||
if([self isIPad]){
|
||||
self.pencilTickSelector.selectedSegmentIndex = tempSettings.pencilTickMode.intValue;
|
||||
@@ -3343,6 +3357,9 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[self.pencilTickIntervalSlider setValue:tempSettings.pencilTickIntervalUs.floatValue];
|
||||
[self.pencilTickIntervalSlider addTarget:self action:@selector(pencilTickIntervalSliderMoved:) forControlEvents:UIControlEventValueChanged];
|
||||
[self pencilTickIntervalSliderMoved:self.pencilTickIntervalSlider];
|
||||
|
||||
[self.pressureCurveSwitch setOn:true];
|
||||
[self.pressureCurveSwitch addTarget:self action:@selector(pressureCurveSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -936,6 +936,7 @@
|
||||
- (void) returnToMainFrame {
|
||||
[_streamView clearOnScreenWidgets];
|
||||
if(micHandler) [micHandler clean];
|
||||
PencilHandler.sharedInstance = nil;
|
||||
|
||||
// Reset display mode back to default
|
||||
[self updatePreferredDisplayMode:NO];
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
#import "OSCProfilesManager.h"
|
||||
#import "opus.h"
|
||||
#import "opus_defines.h"
|
||||
#import "ThemeManager.h"
|
||||
// #import "RelativeTouchHandler.h"
|
||||
|
||||
@@ -1267,6 +1267,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"b1N-z8-12w.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Pressure Curve\"; ObjectID = \"b1N-z8-12w\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Pressure Curve"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔压曲线"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆壓曲線"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bfV-QT-eKw.text" : {
|
||||
"comment" : "Class = \"UILabel\"; text = \"Use Built-in Mic\"; ObjectID = \"bfV-QT-eKw\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
@@ -1628,7 +1658,37 @@
|
||||
}
|
||||
},
|
||||
"bVv-CW-kN8.segmentTitles[0]" : {
|
||||
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[0] = \"Manual\"; ObjectID = \"bVv-CW-kN8\";",
|
||||
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[0] = \"Off\"; ObjectID = \"bVv-CW-kN8\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Off"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "关闭"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "關閉"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "關閉"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bVv-CW-kN8.segmentTitles[1]" : {
|
||||
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[1] = \"Manual\"; ObjectID = \"bVv-CW-kN8\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@@ -1636,29 +1696,23 @@
|
||||
"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" : {
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Timestamp"
|
||||
"state" : "translated",
|
||||
"value" : "手动"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bVv-CW-kN8.segmentTitles[2]" : {
|
||||
"comment" : "Class = \"UISegmentedControl\"; bVv-CW-kN8.segmentTitles[2] = \"Off\"; ObjectID = \"bVv-CW-kN8\";",
|
||||
"extractionState" : "extracted_with_value",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Off"
|
||||
"state" : "translated",
|
||||
"value" : "手動"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "手動"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4570,6 +4624,24 @@
|
||||
"state" : "new",
|
||||
"value" : "Pencil Tick"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔触Tick"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆觸Tick"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "筆觸Tick"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5338,6 +5410,24 @@
|
||||
"state" : "new",
|
||||
"value" : "Tick Interval"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tick周期"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tick 週期"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Tick 週期"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user