diff --git a/VoidLink/Input/CustomOSC/OSCProfile.h b/VoidLink/Input/CustomOSC/OSCProfile.h index 2818ac20..9cb58e35 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.h +++ b/VoidLink/Input/CustomOSC/OSCProfile.h @@ -59,6 +59,10 @@ typedef NS_ENUM(NSInteger, PencilHoverMode) { @property (nonatomic, assign) int controllerGyroSwitchHold; @property (nonatomic, assign) int controllerGyroSwitchToggle; @property NSArray *pressureCurvePoints; +// @property NSArray *initialTouchPressureCurvePoints; +@property (nonatomic, assign) int phase1StrokeSampleIndexEnd; +@property (nonatomic, assign) int phase2StrokeSampleIndexEnd; +@property (nonatomic, assign) CGFloat strokeEqualizationStrength; @property (nonatomic, assign) bool pressureCurveEnabled; @property (nonatomic, assign) bool doubleTapShorcutEnabled; @property NSString *brushShortcut; diff --git a/VoidLink/Input/CustomOSC/OSCProfile.m b/VoidLink/Input/CustomOSC/OSCProfile.m index 2901a022..71e2cc11 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.m +++ b/VoidLink/Input/CustomOSC/OSCProfile.m @@ -52,6 +52,10 @@ [encoder encodeInt:self.controllerGyroSwitchHold forKey:@"controllerGyroSwitchHold"]; [encoder encodeInt:self.controllerGyroSwitchToggle forKey:@"controllerGyroSwitchToggle"]; [encoder encodeObject:self.pressureCurvePoints forKey:@"pressureCurvePoints"]; + // [encoder encodeObject:self.initialTouchPressureCurvePoints forKey:@"initialTouchPressureCurvePoints"]; + [encoder encodeInt:self.phase1StrokeSampleIndexEnd forKey:@"phase1StrokeSampleIndexEnd"]; + [encoder encodeInt:self.phase2StrokeSampleIndexEnd forKey:@"phase2StrokeSampleIndexEnd"]; + [encoder encodeDouble:self.strokeEqualizationStrength forKey:@"strokeEqualizationStrength"]; [encoder encodeBool:self.pressureCurveEnabled forKey:@"pressureCurveEnabled"]; [encoder encodeBool:self.doubleTapShorcutEnabled forKey:@"doubleTapShorcutEnabled"]; [encoder encodeObject:self.brushShortcut forKey:@"brushShortcut"]; @@ -108,6 +112,7 @@ 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: @@ -117,6 +122,21 @@ nil] forKey:@"pressureCurvePoints"] : @[@0.0, @0.0, @1.0, @1.0]; + + /* self.initialTouchPressureCurvePoints = + [decoder containsValueForKey:@"initialTouchPressureCurvePoints"] + ? [decoder decodeObjectOfClasses: + [NSSet setWithObjects: + [NSArray class], + [NSNumber class], + nil] + forKey:@"initialTouchPressureCurvePoints"] + : @[@0.0, @0.0, @1.0, @1.0]; */ + + self.phase1StrokeSampleIndexEnd = [decoder containsValueForKey:@"phase1StrokeSampleIndexEnd"] ? [decoder decodeIntForKey:@"phase1StrokeSampleIndexEnd"] : 10; + self.phase2StrokeSampleIndexEnd = [decoder containsValueForKey:@"phase2StrokeSampleIndexEnd"] ? [decoder decodeIntForKey:@"phase2StrokeSampleIndexEnd"] : 24; + self.strokeEqualizationStrength = [decoder containsValueForKey:@"strokeEqualizationStrength"] ? [decoder decodeFloatForKey:@"strokeEqualizationStrength"] : 1.0; + self.pressureCurveEnabled = [decoder containsValueForKey:@"pressureCurveEnabled"] ? [decoder decodeBoolForKey:@"pressureCurveEnabled"] : false; self.doubleTapShorcutEnabled = [decoder containsValueForKey:@"doubleTapShorcutEnabled"] ? [decoder decodeBoolForKey:@"doubleTapShorcutEnabled"] : false; @@ -190,6 +210,10 @@ copy.controllerGyroSwitchHold = self.controllerGyroSwitchHold; copy.controllerGyroSwitchToggle = self.controllerGyroSwitchToggle; copy.pressureCurvePoints = [[NSMutableArray alloc] initWithArray:self.pressureCurvePoints copyItems:YES]; + // copy.initialTouchPressureCurvePoints = [[NSMutableArray alloc] initWithArray:self.initialTouchPressureCurvePoints copyItems:YES]; + copy.phase1StrokeSampleIndexEnd = self.phase1StrokeSampleIndexEnd; + copy.phase2StrokeSampleIndexEnd = self.phase2StrokeSampleIndexEnd; + copy.strokeEqualizationStrength = self.strokeEqualizationStrength; copy.pressureCurveEnabled = self.pressureCurveEnabled; copy.doubleTapShorcutEnabled = self.doubleTapShorcutEnabled; copy.brushShortcut = [self.brushShortcut mutableCopy]; // NSString → NSMutableString diff --git a/VoidLink/Input/PencilHandler.swift b/VoidLink/Input/PencilHandler.swift index 5f0e67ee..693845ac 100644 --- a/VoidLink/Input/PencilHandler.swift +++ b/VoidLink/Input/PencilHandler.swift @@ -27,7 +27,7 @@ import UIKit @objc static private(set) var hoverMode: PencilHoverMode = .HoverPencil private(set) var pencilProEnabled: Bool = false private var isFirstMove: Bool = false - private var moveEventIndex: Int64 = 0 + private var strokeSampleIndex: Int32 = 0 private var initialMoveEventIndexLimit: Int64 private var touchBeganForce: Float = 0 @objc static private(set) var isDrawing: Bool = false @@ -36,7 +36,27 @@ import UIKit // static private let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero) static private var selectedProfile:OSCProfile? - private var pressureLUT = PressureCurveLUT(curve: PressureCurve()) + private var strokeLUT = PressureCurveLUT(curve: PressureCurve()) + // private var initialTouchLUT = PressureCurveLUT(curve: PressureCurve()) + private var phase1StrokeSampleIndexEnd:Int32 = 0 + private var phase2StrokeSampleIndexEnd:Int32 = 0 + private var phase2EqualizationStrength:Float = 0 + + enum StrokePhase: UInt8, CaseIterable { + case phase1 + case phase2 + case phase3 + } + + private func getStrokePhase(sampleIndex:Int32) -> StrokePhase { + if sampleIndex > phase2StrokeSampleIndexEnd { + return .phase3 + } + if sampleIndex > phase1StrokeSampleIndexEnd { + return .phase2 + } + return .phase1 + } @objc init(streamView: UIView, settings: TemporarySettings) { self.streamView = streamView @@ -96,12 +116,23 @@ import UIKit } } - let persistedCurvePoints = PressureCurve.importCurvePoints(selectedProfile.pressureCurvePoints) - let curve = PressureCurve() - curve.polylinePoints = persistedCurvePoints - curve.buildCurveSegments() + let strokePressureCurvePoints = PressureCurve.importCurvePoints(selectedProfile.pressureCurvePoints) + let strokePressureCurve = PressureCurve() + strokePressureCurve.polylinePoints = strokePressureCurvePoints + strokePressureCurve.buildCurveSegments() + + // let initialTouchPressureCurvePoints = PressureCurve.importCurvePoints(selectedProfile.initialTouchPressureCurvePoints) + // let initialTouchPressureCurve = PressureCurve() + // initialTouchPressureCurve.polylinePoints = initialTouchPressureCurvePoints + // initialTouchPressureCurve.buildCurveSegments() + + phase1StrokeSampleIndexEnd = selectedProfile.phase1StrokeSampleIndexEnd + phase2StrokeSampleIndexEnd = selectedProfile.phase2StrokeSampleIndexEnd + phase2EqualizationStrength = Float(selectedProfile.strokeEqualizationStrength) + DispatchQueue.main.asyncAfter(deadline: .now()) { - self.pressureLUT = PressureCurveLUT(curve: curve) + self.strokeLUT = PressureCurveLUT(curve: strokePressureCurve) + // self.initialTouchLUT = PressureCurveLUT(curve: initialTouchPressureCurve) } } @@ -109,9 +140,11 @@ import UIKit override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let event = event else { return } + previousForce = 0 + previousTargetForce = 0 PencilHandler.isDrawing = true // isFirstMove = true - moveEventIndex = 0 + strokeSampleIndex = 0 for touch in touches { let coalesced = event.coalescedTouches(for: touch) ?? [] _ = self.sendStylusEvent(touchBatch: pencilTickEnabled ? coalesced : [touch]) @@ -124,7 +157,6 @@ import UIKit let coalesced = event.coalescedTouches(for: touch) ?? [] _ = self.sendStylusEvent(touchBatch: pencilTickEnabled ? coalesced : [touch]) } - moveEventIndex += 1 } override func touchesEnded(_ touches: Set, with event: UIEvent?) { @@ -194,6 +226,9 @@ import UIKit return UInt8(90 - min(90, Int(altitudeDegs))) } + var previousForce: Float = 0 + var previousTargetForce: Float = 0 + var phase2IndexCount: Int32 = 0 private func sendStylusEvent(touchBatch: [UITouch], with event: UIEvent? = nil) -> DispatchTime { guard let streamView = streamView, touchBatch.count>0 else { return .now() } @@ -201,6 +236,8 @@ import UIKit var previousTimeStamp: TimeInterval = 0 var delay:TimeInterval = 0 var dispatchMoment: DispatchTime = .now() + + phase2IndexCount = phase2StrokeSampleIndexEnd - phase1StrokeSampleIndexEnd for touch in touchBatch { let point = touch.preciseLocation(in: streamView) @@ -209,22 +246,50 @@ import UIKit let location = self.adjustCoordinatesForVideoArea(point: point) 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)) - var targetForce = pressureCurveEnabled ? self.pressureLUT.value(at: force) : force + var force = Float(touch.force/touch.maximumPossibleForce)/sin(Float(altitude)) + force = (self.pencilTickEnabled && force == 0) ? previousForce : force + + self.strokeSampleIndex += 1 + var targetForce:Float + var equalizationStep:Float = 0 + var equalizedForce:Float = 0 + if(phase2IndexCount>0){ + equalizationStep = (phase2EqualizationStrength-1)/Float(phase2IndexCount-1) + } + + + let strokePhase = getStrokePhase(sampleIndex: self.strokeSampleIndex) + switch strokePhase { + case .phase1: + targetForce = pencilTickEnabled ? 0 : self.strokeLUT.value(at: force) + case .phase2: + targetForce = self.strokeLUT.value(at: force) + targetForce = max(targetForce,previousTargetForce) + equalizedForce = targetForce*(phase2EqualizationStrength - equalizationStep*Float(strokeSampleIndex-phase1StrokeSampleIndexEnd-1)) + case .phase3: + targetForce = self.strokeLUT.value(at: force) + } + targetForce = self.pressureCurveEnabled ? targetForce : force + + previousForce = force + previousTargetForce = targetForce + let eventType:UInt8 switch touch.phase { case .began: eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER : LI_TOUCH_EVENT_DOWN) - touchBeganForce = targetForce case .moved: eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER : LI_TOUCH_EVENT_MOVE) - targetForce = moveEventIndex < initialMoveEventIndexLimit ? touchBeganForce : targetForce case .ended: eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER_LEAVE : LI_TOUCH_EVENT_UP) + previousForce = 0 + previousTargetForce = 0 case .cancelled: eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER_LEAVE : LI_TOUCH_EVENT_UP) + previousForce = 0 + previousTargetForce = 0 default: eventType = UInt8(LI_TOUCH_EVENT_HOVER) } @@ -241,16 +306,19 @@ import UIKit tickMoment += (manualTick ? tickInterval : touch.timestamp - previousTimeStamp) } - let sendableForce = targetForce + let sendableForce = strokePhase == .phase2 ? equalizedForce : targetForce + DispatchQueue.global().asyncAfter(deadline: dispatchMoment + tickMoment + delay) { - /* if PencilHandler.autoHoverEnabled, eventType == UInt8(LI_TOUCH_EVENT_DOWN) { LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER_LEAVE), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 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), sendableForce, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude))) + if strokePhase != .phase1 || !self.pencilTickEnabled {LiSendPenEvent(eventType, UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), sendableForce, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude)))} + else { + LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude))) + } if eventType == UInt8(LI_TOUCH_EVENT_UP) { PencilHandler.isDrawing = false diff --git a/VoidLink/Input/PressureCurve.swift b/VoidLink/Input/PressureCurve.swift index f73608ec..ad44083a 100644 --- a/VoidLink/Input/PressureCurve.swift +++ b/VoidLink/Input/PressureCurve.swift @@ -476,16 +476,43 @@ extension UIView { } // MARK: - ViewController -class PressureCurveViewController: UIViewController { +class PressureCurveViewController: UIViewController, UIGestureRecognizerDelegate { private var navBar = UINavigationBar() + private var saveButton: UIBarButtonItem = UIBarButtonItem() private var container = UIView() private var curveView = PressureCurveView() + // private var initialTouchCurvePoints: [CGPoint] = [] + private var strokePressureCurvePoints: [CGPoint] = [] + + private var phase1StrokeSampleIndexEnd: Int32 = 0 + private var phase2StrokeSampleIndexEnd: Int32 = 0 private var minPressure: CGFloat = 0 private var maxPressure: CGFloat = 1 private var isFirstLaunch: Bool = GenericUtils.isFirstLaunchPressureCurveTool() + + // private var curvePhaseButton: UIBarButtonItem = UIBarButtonItem() + private let hookFilterLabel = UILabel() + private let hookFilterSlider = UISlider() + private let strokeEqualizationDepthLabel = UILabel() + private let strokeEqualizationDepthSlider = UISlider() + private let strokeEqualizationStrengthLabel = UILabel() + private let strokeEqualizationStrengthSlider = UISlider() + + enum PressureCurvePhase: UInt8, CaseIterable { + case initialTouch + case stroke + func next() -> PressureCurvePhase { + let all = Self.allCases + let index = all.firstIndex(of: self)! + let nextIndex = (index + 1) % all.count + return all[nextIndex] + } + } + + private(set) var curvePhase: PressureCurvePhase = .initialTouch override func viewDidLoad() { super.viewDidLoad() @@ -496,34 +523,39 @@ class PressureCurveViewController: UIViewController { let curveWidth: CGFloat = 530 let curveHeight: CGFloat = 530 curveView = PressureCurveView(frame: CGRect(x: 0, y: 0, width: curveWidth, height: curveHeight)) - curveView.center = self.view.center + curveView.center = CGPoint(x: self.view.center.x, y: self.view.center.y) 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 = UIView(frame: CGRect(x: 0, y: 0, width: curveWidth+10, height: curveHeight+148)) + container.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY+3) container.backgroundColor = .white container.layer.cornerRadius = 12 container.layer.masksToBounds = true - container.layer.borderWidth = 2 - container.layer.borderColor = ThemeManager.separatorColor.cgColor + container.layer.borderWidth = 0.5 + container.layer.borderColor = UIColor.black.withAlphaComponent(0.15).cgColor container.addSubview(curveView) + + self.setupNavigationBar() + self.setupBottomSliders() 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.centerYAnchor.constraint(equalTo: self.view.centerYAnchor,constant: -25), curveView.widthAnchor.constraint(equalToConstant: curveWidth), curveView.heightAnchor.constraint(equalToConstant: curveHeight) ]) + self.displayCurve() + + NotificationCenter.default.addObserver( self, selector: #selector(pencilProPurchaseAborted(_:)), @@ -536,6 +568,10 @@ class PressureCurveViewController: UIViewController { name: AddOnProduct.PencilProPack.purchaseSucceededNotification(), object: nil ) + + let tap = UITapGestureRecognizer(target: self, action: #selector(dismissSelf)) + tap.delegate = self + view.addGestureRecognizer(tap) } override func viewDidAppear(_ animated: Bool) { @@ -573,11 +609,123 @@ class PressureCurveViewController: UIViewController { private func loadPersistedCurve() { let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero) - let persistedCurvePoints = PressureCurve.importCurvePoints(oscProfileMan.getSelectedProfile().pressureCurvePoints) - curveView.curve.polylinePoints = persistedCurvePoints + let oscProfile = oscProfileMan.getSelectedProfile() + // initialTouchCurvePoints = PressureCurve.importCurvePoints(oscProfile.initialTouchPressureCurvePoints) + strokePressureCurvePoints = PressureCurve.importCurvePoints(oscProfile.pressureCurvePoints) + displayCurve() + + hookFilterSlider.value = Float(oscProfile.phase1StrokeSampleIndexEnd) + strokeEqualizationDepthSlider.value = Float(oscProfile.phase2StrokeSampleIndexEnd) + strokeEqualizationStrengthSlider.value = Float(oscProfile.strokeEqualizationStrength) + + strokeEqualizationDepthSliderMoved(strokeEqualizationDepthSlider) + hookFilterDepthSliderMoved(hookFilterSlider) + strokeEqualizationStrengthSliderMoved(strokeEqualizationStrengthSlider) + } + + private func displayCurve() { + switch curvePhase { + case .initialTouch: + break + // curveView.curve.polylinePoints = initialTouchCurvePoints + case .stroke: + curveView.curve.polylinePoints = strokePressureCurvePoints + } curveView.setNeedsDisplay() } + private func setupBottomSliders() { + + hookFilterLabel.translatesAutoresizingMaskIntoConstraints = false + hookFilterSlider.translatesAutoresizingMaskIntoConstraints = false + strokeEqualizationDepthLabel.translatesAutoresizingMaskIntoConstraints = false + strokeEqualizationDepthSlider.translatesAutoresizingMaskIntoConstraints = false + strokeEqualizationStrengthLabel.translatesAutoresizingMaskIntoConstraints = false + strokeEqualizationStrengthSlider.translatesAutoresizingMaskIntoConstraints = false + + let traits = UITraitCollection(userInterfaceStyle: .light) + + hookFilterLabel.font = UIFont.systemFont(ofSize: 14) + hookFilterLabel.textColor = .black + if GenericUtils.liquidGlassEnabled { + if #available(iOS 13.0, *) {hookFilterSlider.maximumTrackTintColor = .tertiarySystemFill.resolvedColor(with: traits)} + } + hookFilterSlider.minimumValue = 1 + hookFilterSlider.maximumValue = 20 + hookFilterSlider.addTarget(self, action: #selector(hookFilterDepthSliderMoved(_:)), for: .valueChanged) + + strokeEqualizationDepthLabel.font = UIFont.systemFont(ofSize: 14) + strokeEqualizationDepthLabel.textColor = .black + if GenericUtils.liquidGlassEnabled { + if #available(iOS 13.0, *) {strokeEqualizationDepthSlider.maximumTrackTintColor = .tertiarySystemFill.resolvedColor(with: traits)} + } + strokeEqualizationDepthSlider.minimumValue = 1 + strokeEqualizationDepthSlider.maximumValue = 80 + strokeEqualizationDepthSlider.addTarget(self, action: #selector(strokeEqualizationDepthSliderMoved(_:)), for: .valueChanged) + + strokeEqualizationStrengthLabel.font = UIFont.systemFont(ofSize: 14) + strokeEqualizationStrengthLabel.textColor = .black + if GenericUtils.liquidGlassEnabled { + if #available(iOS 13.0, *) {strokeEqualizationStrengthSlider.maximumTrackTintColor = .tertiarySystemFill.resolvedColor(with: traits)} + } + strokeEqualizationStrengthSlider.minimumValue = 0.1 + strokeEqualizationStrengthSlider.maximumValue = 3.5 + strokeEqualizationStrengthSlider.addTarget(self, action: #selector(strokeEqualizationStrengthSliderMoved(_:)), for: .valueChanged) + + container.addSubview(hookFilterLabel) + container.addSubview(hookFilterSlider) + container.addSubview(strokeEqualizationDepthLabel) + container.addSubview(strokeEqualizationDepthSlider) + container.addSubview(strokeEqualizationStrengthLabel) + container.addSubview(strokeEqualizationStrengthSlider) + + NSLayoutConstraint.activate([ + + // Slider + hookFilterLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + hookFilterLabel.widthAnchor.constraint(equalToConstant: 260), + hookFilterSlider.leadingAnchor.constraint(equalTo: hookFilterLabel.trailingAnchor, constant: 12), + hookFilterSlider.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16), + hookFilterSlider.topAnchor.constraint(equalTo: curveView.bottomAnchor, constant: 0), + hookFilterLabel.centerYAnchor.constraint(equalTo: hookFilterSlider.centerYAnchor, constant:0), + + strokeEqualizationDepthLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + strokeEqualizationDepthLabel.widthAnchor.constraint(equalToConstant: 260), + strokeEqualizationDepthSlider.leadingAnchor.constraint(equalTo: strokeEqualizationDepthLabel.trailingAnchor, constant: 12), + strokeEqualizationDepthSlider.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16), + strokeEqualizationDepthSlider.topAnchor.constraint(equalTo: hookFilterSlider.bottomAnchor, constant: 0), + strokeEqualizationDepthLabel.centerYAnchor.constraint(equalTo: strokeEqualizationDepthSlider.centerYAnchor, constant:0), + + strokeEqualizationStrengthLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + strokeEqualizationStrengthLabel.widthAnchor.constraint(equalToConstant: 260), + strokeEqualizationStrengthSlider.leadingAnchor.constraint(equalTo: strokeEqualizationStrengthLabel.trailingAnchor, constant: 12), + strokeEqualizationStrengthSlider.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16), + strokeEqualizationStrengthSlider.topAnchor.constraint(equalTo: strokeEqualizationDepthSlider.bottomAnchor, constant: 0), + strokeEqualizationStrengthLabel.centerYAnchor.constraint(equalTo: strokeEqualizationStrengthSlider.centerYAnchor, constant:0), + ]) + } + + @objc func hookFilterDepthSliderMoved(_ sender: UISlider) { + phase1StrokeSampleIndexEnd = Int32(sender.value.rounded()) + hookFilterLabel.text = "\(SwiftLocalizationHelper.localizedString(forKey: "Stroke start hook filtering depth: "))\(String(format: "%d", Int32(sender.value)))"; + if sender.value > strokeEqualizationDepthSlider.value { + strokeEqualizationDepthSlider.value = sender.value + strokeEqualizationDepthSliderMoved(strokeEqualizationDepthSlider) + } + } + + @objc func strokeEqualizationDepthSliderMoved(_ sender: UISlider) { + sender.value = max(sender.value, hookFilterSlider.value) + phase2StrokeSampleIndexEnd = Int32(sender.value.rounded()) + strokeEqualizationDepthLabel.text = "\(SwiftLocalizationHelper.localizedString(forKey: "Stroke start equalization depth: "))\(String(format: "%d", Int32(sender.value)))"; + } + + @objc func strokeEqualizationStrengthSliderMoved(_ sender: UISlider) { + // sender.value = sender.value + strokeEqualizationStrengthLabel.text = "\(SwiftLocalizationHelper.localizedString(forKey: "Stroke start force scaling: "))\(String(format: "%.2f", sender.value))"; + } + + public func setupNavigationBar() { // 1. 创建 UINavigationBar navBar.removeFromSuperview() @@ -589,9 +737,9 @@ class PressureCurveViewController: UIViewController { if #available(iOS 13.0, *) { let appearance = UINavigationBarAppearance() - appearance.configureWithTransparentBackground() // 透明背景 + // appearance.configureWithTransparentBackground() // 透明背景 appearance.shadowColor = nil // 去掉底部细线 - appearance.backgroundColor = .clear // 可以额外设置完全透明 + appearance.backgroundColor = .white // 可以额外设置完全透明 navBar.standardAppearance = appearance navBar.scrollEdgeAppearance = appearance @@ -602,30 +750,45 @@ class PressureCurveViewController: UIViewController { 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) // 导航栏标准高度 + navBar.heightAnchor.constraint(equalToConstant: 50) // 导航栏标准高度 ]) - 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)) + // curvePhaseButton = UIBarButtonItem(title: SwiftLocalizationHelper.localizedString(forKey: "Initial-touch-curve"), style: .plain, target: self, action: #selector(curvePhaseButtonTapped)) + curvePhase = .stroke + 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] + navItem.rightBarButtonItems = [saveButton] } + if curveView.testStage == .drawingStage { navItem = UINavigationItem(title: SwiftLocalizationHelper.localizedString(forKey: "Pressure Range Test")) } + + if #available(iOS 26.0, *) { + if let buttons = navItem.leftBarButtonItems { + for button in buttons { + button.hidesSharedBackground = true + button.tintColor = .tintColor + } + } + if let buttons = navItem.rightBarButtonItems { + for button in buttons { + button.hidesSharedBackground = true + button.tintColor = .tintColor + } + } + } - // 4. 将 UINavigationItem 添加到导航栏 navBar.items = [navItem] } @@ -681,10 +844,43 @@ class PressureCurveViewController: UIViewController { }) } + /* + @objc private func curvePhaseButtonTapped() { + switch curvePhase { + case .initialTouch: + // initialTouchCurvePoints = curveView.curve.polylinePoints + curvePhaseButton.title = SwiftLocalizationHelper.localizedString(forKey:"Initial-touch-curve") + case .stroke: + strokeCurvePoints = curveView.curve.polylinePoints + curvePhaseButton.title = SwiftLocalizationHelper.localizedString(forKey:"Stroke-curve") + } + curvePhase = curvePhase.next() + displayCurve() + } */ + @objc private func persistCurve() { let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero) let selectedProfile = oscProfileMan.getSelectedProfile() + + switch curvePhase { + case .initialTouch: + break + // initialTouchCurvePoints = curveView.curve.polylinePoints + case .stroke: + strokePressureCurvePoints = curveView.curve.polylinePoints + } + + curveView.curve.polylinePoints = strokePressureCurvePoints selectedProfile.pressureCurvePoints = PressureCurve.exportCurvePoints(curveView.curve) + // curveView.curve.polylinePoints = initialTouchCurvePoints + // selectedProfile.initialTouchPressureCurvePoints = PressureCurve.exportCurvePoints(curveView.curve) + + displayCurve() + + selectedProfile.phase1StrokeSampleIndexEnd = Int32(hookFilterSlider.value) + selectedProfile.phase2StrokeSampleIndexEnd = Int32(strokeEqualizationDepthSlider.value) + selectedProfile.strokeEqualizationStrength = CGFloat(strokeEqualizationStrengthSlider.value) + oscProfileMan.replaceSelectedProfile(with: selectedProfile, overwriteDefault: true) DispatchQueue.main.async { @@ -718,7 +914,9 @@ class PressureCurveViewController: UIViewController { } @objc private func saveTapped() { + saveButton.isEnabled = false IAPManager.checkPurchaseInfo(.PencilProPack) { info in + self.saveButton.isEnabled = true if info.valid { self.persistCurve() } @@ -731,4 +929,15 @@ class PressureCurveViewController: UIViewController { @objc private func exitTapped() { self.dismiss(animated: true) } + + public func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldReceive touch: UITouch + ) -> Bool { + return touch.view == view + } + + @objc private func dismissSelf(){ + dismiss(animated: true, completion: nil) + } } diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index a2aa0748..acfa5490 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -8088,6 +8088,75 @@ } } }, + "Stroke start equalization depth: " : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "笔迹起始端均衡深度: " + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端均衡深度: " + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端均衡深度: " + } + } + } + }, + "Stroke start force scaling: " : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "笔迹起始端力度系数: " + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端力度係數: " + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端力度係數: " + } + } + } + }, + "Stroke start hook filtering depth: " : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "笔迹起始端勾子过滤深度: " + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端勾子過濾深度: " + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆跡起始端勾子過濾深度: " + } + } + } + }, "Successfully sent wake-up request. It may take a few moments for the PC to wake. If it never wakes up, ensure it's properly configured for Wake-on-LAN." : { "extractionState" : "manual", "localizations" : {