Files
VoidLink/VoidLink/Input/PencilHandler.swift
T
2026-06-22 00:53:01 +08:00

669 lines
31 KiB
Swift

//
// PencilHandler.swift
// VoidLink
//
// Created by True砖家 on 2025/11/17.
// Copyright © 2025 True砖家 on Bilibili. All rights reserved.
//
import UIKit
@objc class PencilHandler: UIResponder, UIPencilInteractionDelegate {
@objc static var shared: PencilHandler?
/// 用来处理所有 touch 事件的宿主视图
weak var streamView: UIView?
// private var tickTimer: SafeTimer
private var pencilInteraction: Any?
@objc var streamAspectRatio: Float
private var tickInterval: TimeInterval
private var manualTick: Bool
private var pencilTickEnabled: Bool
private var pressureCurveEnabled: Bool = false
private var manualHoverFlag: Bool = false
@objc static var hoverSupported: Bool = false
// @objc static private(set) var autoHoverTermination: Bool = false
@objc static private(set) var hoverMode: PencilHoverMode = .HoverPencil
private(set) var pencilProEnabled: Bool = false
private var isFirstMove: Bool = false
private var strokeSampleIndex: Int32 = 0
private var initialMoveEventIndexLimit: Int64
private var touchBeganForce: Float = 0
@objc static private(set) var isDrawing: Bool = false
@objc static private(set) var pencilPausesNativeTouch: Bool = false
// static private let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
static private var selectedProfile:OSCProfile?
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
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
// initialMoveEventIndexLimit = UIScreen.main.maximumFramesPerSecond > 60 ? 4 : 2
initialMoveEventIndexLimit = 0
super.init()
setupPressureLUT()
PencilHandler.shared = self
}
@objc public func setupPressureLUT(profile:OSCProfile? = nil){
var selectedProfile:OSCProfile
if profile == nil {
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
selectedProfile = oscProfileMan.getSelectedProfile()
}
else{
selectedProfile = profile!
}
doubleTapShorcuts.removeAll()
if selectedProfile.doubleTapShorcutEnabled {
if selectedProfile.eraserShortcut != "" {
doubleTapShorcuts.append(selectedProfile.eraserShortcut)
}
if selectedProfile.brushShortcut != "" {
doubleTapShorcuts.append(selectedProfile.brushShortcut)
}
}
PencilHandler.squeezeStartShortcut = ""
PencilHandler.squeezeEndShortcut = ""
if selectedProfile.squeezeShorcutEnabled {
PencilHandler.squeezeStartShortcut = selectedProfile.squeezeStartShortcut
PencilHandler.squeezeEndShortcut = selectedProfile.squeezeEndShortcut
print("squeezeShorcutEnabled \(CACurrentMediaTime())")
}
// PencilHandler.autoHoverTermination = selectedProfile.autoPencilHoverTermination
PencilHandler.hoverMode = selectedProfile.pencilHoverMode
pressureCurveEnabled = selectedProfile.pressureCurveEnabled
if #available(iOS 15.0, *) {
IAPManager.checkPurchaseInfo(.PencilProPack) { info in
self.pressureCurveEnabled = self.pressureCurveEnabled && info.valid
self.pencilTickEnabled = self.pencilTickEnabled && info.valid
self.pencilProEnabled = info.valid
PencilHandler.pencilPausesNativeTouch = selectedProfile.pencilPausesNativeTouch && info.valid
if info.valid {
self.setupPencilInteraction(view: self.streamView)
}
}
}
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.strokeLUT = PressureCurveLUT(curve: strokePressureCurve)
// self.initialTouchLUT = PressureCurveLUT(curve: initialTouchPressureCurve)
}
}
// MARK: - Touch Events
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let event = event else { return }
previousForce = 0
previousTargetForce = 0
PencilHandler.isDrawing = true
// isFirstMove = true
strokeSampleIndex = 0
for touch in touches {
let coalesced = event.coalescedTouches(for: touch) ?? []
_ = 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: pencilTickEnabled ? coalesced : [touch])
}
}
override func touchesEnded(_ 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: pencilTickEnabled ? coalesced : [touch])
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchesEnded(touches, with: event)
}
// MARK: - Core Logic
func getVideoAreaSize()-> CGSize {
guard let streamView = streamView else { return CGSize(width: 0, height: 0) }
if streamView.bounds.size.width > streamView.bounds.size.height * CGFloat(streamAspectRatio) {
return CGSizeMake(streamView.bounds.size.height * CGFloat(streamAspectRatio), streamView.bounds.size.height);
} else {
return CGSizeMake(streamView.bounds.size.width, streamView.bounds.size.width / CGFloat(streamAspectRatio));
}
}
func adjustCoordinatesForVideoArea(point: CGPoint) -> CGPoint {
guard let streamView = streamView else { return CGPoint(x: 0, y: 0) }
var x = point.x - streamView.bounds.origin.x
var y = point.y - streamView.bounds.origin.y
if x < streamView.bounds.width / 2 {
x -= 1
} else {
x += 1
}
if y < streamView.bounds.height / 2 {
y -= 1
} else {
y += 1
}
let videoSize = getVideoAreaSize() // 返回 CGSize
let videoOrigin = CGPoint(
x: streamView.bounds.width / 2 - videoSize.width / 2,
y: streamView.bounds.height / 2 - videoSize.height / 2
)
let confinedX = min(max(x, videoOrigin.x), videoOrigin.x + videoSize.width) - videoOrigin.x
let confinedY = min(max(y, videoOrigin.y), videoOrigin.y + videoSize.height) - videoOrigin.y
return CGPoint(x: confinedX, y: confinedY)
}
func getRotation(fromAzimuthAngle azimuthAngle: Float) -> UInt16 {
var rotationAngle = (azimuthAngle - .pi / 2) * (180.0 / .pi) // 弧度转角度
if rotationAngle < 0 {
rotationAngle += 360
}
return UInt16(rotationAngle)
}
func getTilt(fromAltitudeAngle altitudeAngle: Float) -> UInt8 {
let altitudeDegs = abs(Int16(altitudeAngle * (180.0 / .pi)))
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() }
var tickMoment: TimeInterval = 0
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)
let azimuth = touch.azimuthAngle(in: streamView)
let altitude = touch.altitudeAngle
let location = self.adjustCoordinatesForVideoArea(point: point)
let videoSize = self.getVideoAreaSize()
let normalizedLocation = CGPoint(x: location.x/videoSize.width, y: location.y/videoSize.height)
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)
let forceMapping = pressureCurveEnabled ? self.strokeLUT.value(at: force) : force
switch strokePhase {
case .phase1:
targetForce = pencilTickEnabled ? 0 : forceMapping
case .phase2:
targetForce = forceMapping
targetForce = max(targetForce,previousTargetForce)
equalizedForce = targetForce*(phase2EqualizationStrength - equalizationStep*Float(strokeSampleIndex-phase1StrokeSampleIndexEnd-1))
case .phase3:
targetForce = forceMapping
}
targetForce = pencilTickEnabled ? 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)
case .moved:
eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER : LI_TOUCH_EVENT_MOVE)
case .ended:
eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER_LEAVE : LI_TOUCH_EVENT_UP)
targetForce = 0
previousForce = 0
previousTargetForce = 0
case .cancelled:
eventType = UInt8(manualHoverFlag ? LI_TOUCH_EVENT_HOVER_LEAVE : LI_TOUCH_EVENT_UP)
targetForce = 0
previousForce = 0
previousTargetForce = 0
default:
eventType = UInt8(LI_TOUCH_EVENT_HOVER)
}
delay = manualTick ? 0.0086 : 0.0086
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)
}
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)))
}
*/
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
if PencilHandler.hoverMode == .HoverDisabled || !PencilHandler.hoverSupported {
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)))
DispatchQueue.global().asyncAfter(deadline: .now() + 0.0086){
if !PencilHandler.isDrawing {
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)))
}
}
}
}
}
}
return dispatchMoment + tickMoment + delay
}
@objc public func switchPencilHover(){
manualHoverFlag = !manualHoverFlag
}
@objc public func enablePencilHover(){
manualHoverFlag = true
}
@objc public func disablePencilHover(){
manualHoverFlag = false
}
@available(iOS 12.1, *)
private func setupPencilInteraction(view:UIView?) {
let interaction = UIPencilInteraction()
guard let view else { return }
if pencilInteraction != nil {return}
interaction.delegate = self
view.addInteraction(interaction)
pencilInteraction = interaction
}
@available(iOS 12.1, *)
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
if !pencilProEnabled {return}
if doubleTapShorcuts.isEmpty {return}
let keyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: doubleTapShorcuts[shortcutIndex])
print("pencilInteractionDidTap \(CACurrentMediaTime())")
CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: keyStrings, delay: 0.1)
shortcutIndex = (shortcutIndex + 1) % doubleTapShorcuts.count
}
@available(iOS 17.5, *)
func pencilInteraction(
_ interaction: UIPencilInteraction,
didReceiveSqueeze squeeze: UIPencilInteraction.Squeeze
) {
if !pencilProEnabled {return}
if PencilHandler.squeezeStartShortcut == "", PencilHandler.squeezeEndShortcut == "" {return}
let keepPressedUntilRelease = PencilHandler.squeezeEndShortcut.uppercased() == "NULL"
let squeezePressKeyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: PencilHandler.squeezeStartShortcut)
let squeezeReleaseKeyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: PencilHandler.squeezeEndShortcut)
guard let squeezePressKeyStrings = squeezePressKeyStrings else {return}
guard squeezePressKeyStrings.count>0 else {return}
switch squeeze.phase {
case .began:
if keepPressedUntilRelease {CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: squeezePressKeyStrings, delay: 0.1, pressOnly: true)}
else {CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: squeezePressKeyStrings, delay: 0.1)}
break
case .ended, .cancelled:
if keepPressedUntilRelease {CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: squeezePressKeyStrings, releaseOnly: true)}
else {CommandManager.shared.sendAutoReleaseComboCommand(cmdStrings: squeezeReleaseKeyStrings, delay: 0.1)}
break
default:
break
}
}
@objc static private(set) var eraserShortcut:String = ""
@objc static private(set) var brushShortcut:String = ""
private var doubleTapShorcuts: Array<String> = []
private var shortcutIndex: Int = 0
@objc func replaceBrush(with shortcut:String){
if let i = doubleTapShorcuts.indices.last {
doubleTapShorcuts[i] = shortcut
}
}
@objc func replaceEraser(with shortcut:String){
if let i = doubleTapShorcuts.indices.first {
doubleTapShorcuts[i] = shortcut
}
}
@available(iOS 13.0, *)
func widgetPickerViewController(_ controller: WidgetPickerViewController, didCreateWidget payload: NSDictionary) {
let params = payload.mutableCopy() as? NSMutableDictionary ?? NSMutableDictionary()
let pickerAction = ((params["pickerAction"] as? String) ?? "").lowercased()
params.removeObject(forKey: "pickerAction")
if pickerAction == "create" {
}
}
@objc static public func enterDoubleTapShortcuts(in viewController: UIViewController){
if #available(iOS 13.0, *) {
let pickerViewController = WidgetPickerViewController()
pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate)
pickerViewController.keyboardPickerMode = .shortcutPicker
pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"]
pickerViewController.initialTabIdentifier = "keyboard"
pickerViewController.shortcutIdentifier = "eraser"
pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "Select eraser shortcut keys")
pickerViewController.presentOverFullScreen(from: viewController)
return
}
/*
let alert = UIAlertController(title: LocalizationHelper.localizedString(forKey: "Eraser Shortcut"),
message: LocalizationHelper.localizedString(forKey: "Enter eraser keyboard shortcut:"),
preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = LocalizationHelper.localizedString(forKey:"Example: e, ctrl+e, alt+e ...")
textField.keyboardType = .asciiCapable
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.text = selectedProfile.eraserShortcut
}
let okAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "OK"), style: .default) { _ in
let comboButtons = alert.textFields?[0].text ?? ""
let keyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: comboButtons)
if keyStrings?.count ?? 0 > 0 || comboButtons == "" {
eraserShortcut = comboButtons
}
enterBrushShortcut(in: viewController)
}
let learnMoreAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "Learn More"), style: .default) { _ in
if let url = URL(string: LocalizationHelper.localizedString(forKey: "pencilKeyboardCmdURL")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alert.addAction(learnMoreAction)
alert.addAction(okAction)
viewController.present(alert, animated: true, completion: {
})
*/
}
@available(iOS 13.0, *)
@objc static public func enterBrushShortcut(in viewController: UIViewController){
let pickerViewController = WidgetPickerViewController()
pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate)
pickerViewController.keyboardPickerMode = .shortcutPicker
pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"]
pickerViewController.initialTabIdentifier = "keyboard"
pickerViewController.shortcutIdentifier = "brush"
pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "Select brush shortcut keys")
pickerViewController.presentOverFullScreen(from: viewController)
return
/*
let alert = UIAlertController(title: LocalizationHelper.localizedString(forKey: "Brush Shortcut"),
message: LocalizationHelper.localizedString(forKey: "Enter brush keyboard shortcut:"),
preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = LocalizationHelper.localizedString(forKey:"Example: b, ctrl+b, alt+b ...")
textField.keyboardType = .asciiCapable
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.text = selectedProfile?.brushShortcut
}
let okAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "OK"), style: .default) { _ in
let comboButtons = alert.textFields?[0].text ?? ""
let keyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: comboButtons)
if keyStrings?.count ?? 0 > 0 || comboButtons == "" {
brushShortcut = comboButtons
}
if selectedProfile?.brushShortcut != brushShortcut
|| selectedProfile?.eraserShortcut != eraserShortcut {
guard let selectedProfile = selectedProfile else {return}
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
selectedProfile.brushShortcut = brushShortcut
selectedProfile.eraserShortcut = eraserShortcut
oscProfileMan.replaceSelectedProfile(with: selectedProfile, overwriteDefault: true)
}
}
let learnMoreAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "Learn More"), style: .default) { _ in
if let url = URL(string: LocalizationHelper.localizedString(forKey: "pencilKeyboardCmdURL")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alert.addAction(learnMoreAction)
alert.addAction(okAction)
viewController.present(alert, animated: true, completion: {
})
*/
}
@objc static private(set) var squeezeStartShortcut:String = ""
@objc static private(set) var squeezeEndShortcut:String = ""
@objc static public func enterSqueezeShortcuts(in viewController: UIViewController){
if #available(iOS 13.0, *) {
let pickerViewController = WidgetPickerViewController()
pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate)
pickerViewController.keyboardPickerMode = .shortcutPicker
pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"]
pickerViewController.initialTabIdentifier = "keyboard"
pickerViewController.shortcutIdentifier = "squeezePress"
pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "squeezePressShortcutPickerTip")
pickerViewController.presentOverFullScreen(from: viewController)
}
/*
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
selectedProfile = oscProfileMan.getSelectedProfile()
guard let selectedProfile = selectedProfile else {return}
let alert = UIAlertController(title: LocalizationHelper.localizedString(forKey: "Squeeze Shortcut"),
message: LocalizationHelper.localizedString(forKey: "enterSqueezePressShort"),
preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = LocalizationHelper.localizedString(forKey:"Example: e, ctrl+e, alt+e ...")
textField.keyboardType = .asciiCapable
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.text = selectedProfile.squeezeStartShortcut
}
let okAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "OK"), style: .default) { _ in
let comboButtons = alert.textFields?[0].text ?? ""
let keyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: comboButtons)
if keyStrings?.count ?? 0 > 0 || comboButtons == "" {
squeezeStartShortcut = comboButtons
}
enterSqueezeEndShortcut(in: viewController)
}
let learnMoreAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "Learn More"), style: .default) { _ in
if let url = URL(string: LocalizationHelper.localizedString(forKey: "pencilKeyboardCmdURL")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alert.addAction(learnMoreAction)
alert.addAction(okAction)
viewController.present(alert, animated: true, completion: {
})
*/
}
@objc static public func enterSqueezeEndShortcut(in viewController: UIViewController){
if #available(iOS 13.0, *) {
let pickerViewController = WidgetPickerViewController()
pickerViewController.delegate = (viewController as! any WidgetPickerViewControllerDelegate)
pickerViewController.keyboardPickerMode = .shortcutPicker
pickerViewController.tabIdentifiers = ["keyboard", "shortcuts"]
pickerViewController.initialTabIdentifier = "keyboard"
pickerViewController.shortcutIdentifier = "squeezeRelease"
pickerViewController.shortcutPickerTipText = LocalizationHelper.localizedString(forKey: "squeezeReleaseShortcutPickerTip")
pickerViewController.presentOverFullScreen(from: viewController)
}
/*
let alert = UIAlertController(title: LocalizationHelper.localizedString(forKey: "Squeeze Shortcut"),
message: LocalizationHelper.localizedString(forKey: "enterSqueezeReleaseShort"),
preferredStyle: .alert)
alert.addTextField { textField in
textField.placeholder = LocalizationHelper.localizedString(forKey:"Example: b, ctrl+b, alt+b ...")
textField.keyboardType = .asciiCapable
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.text = selectedProfile?.squeezeEndShortcut
}
let okAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "OK"), style: .default) { _ in
let comboButtons = alert.textFields?[0].text ?? ""
let keyStrings = CommandManager.shared.extractAutoReleaseButtonStrings(from: comboButtons)
if keyStrings?.count ?? 0 > 0 || comboButtons == "" {
squeezeEndShortcut = comboButtons
}
if selectedProfile?.squeezeEndShortcut != squeezeEndShortcut
|| selectedProfile?.squeezeStartShortcut != squeezeStartShortcut {
guard let selectedProfile = selectedProfile else {return}
let oscProfileMan = OSCProfilesManager.sharedManager(CGRectZero)
selectedProfile.squeezeEndShortcut = squeezeEndShortcut
selectedProfile.squeezeStartShortcut = squeezeStartShortcut
oscProfileMan.replaceSelectedProfile(with: selectedProfile, overwriteDefault: true)
}
}
let learnMoreAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "Learn More"), style: .default) { _ in
if let url = URL(string: LocalizationHelper.localizedString(forKey: "pencilKeyboardCmdURL")) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
alert.addAction(learnMoreAction)
alert.addAction(okAction)
viewController.present(alert, animated: true, completion: {
})
*/
}
private func attachHoverLeave(normalizedLocation:CGPoint){
DispatchQueue.global().asyncAfter(deadline: .now()+0.0086) {
LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, 0, 0)
DispatchQueue.global().asyncAfter(deadline: .now()+0.0086) {
// LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER_LEAVE), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, 0, 0)
}
}
}
private func preTouchHoverActionAt(normalizedLocation:CGPoint){
DispatchQueue.global().asyncAfter(deadline: .now()+0.0086) {
LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, 0, 0)
DispatchQueue.global().asyncAfter(deadline: .now()+0.0086) {
LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER_LEAVE), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, 0, 0)
}
}
}
}