pencil tip offset

This commit is contained in:
True Zhuanjia
2026-07-30 23:22:50 +08:00
parent e0aff4b532
commit 51c13faa0c
19 changed files with 590 additions and 8 deletions
+4
View File
@@ -32,6 +32,7 @@
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 */; };
18D43D812F050240008C326F /* PencilTipOffsetCalibrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D43D802F050240008C326F /* PencilTipOffsetCalibrationViewController.swift */; };
18E4BCEA2E891895000A8657 /* MotionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18E4BCE92E891895000A8657 /* MotionHandler.swift */; };
18FCADAF2ECAC23200787A96 /* PencilHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FCADAE2ECAC23200787A96 /* PencilHandler.swift */; };
2A263D4A2FF3B3F800154DEA /* PublicExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A263D492FF3B3F800154DEA /* PublicExtensions.swift */; };
@@ -209,6 +210,7 @@
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>"; };
18D43D802F050240008C326F /* PencilTipOffsetCalibrationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PencilTipOffsetCalibrationViewController.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>"; };
2A039E562F72892B00546768 /* VoidLinkPreview.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VoidLinkPreview.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -773,6 +775,7 @@
988FCD3F293B091B003050E2 /* KeyboardInputField.h */,
988FCD40293B091B003050E2 /* KeyboardInputField.m */,
18D43D7E2F050240008C326F /* PressureCurve.swift */,
18D43D802F050240008C326F /* PencilTipOffsetCalibrationViewController.swift */,
18FCADAE2ECAC23200787A96 /* PencilHandler.swift */,
18416EA32EBB3DCE00AE0124 /* UITouchUtil.swift */,
18B317022EE67A39009B62C2 /* InertialScroller.swift */,
@@ -1202,6 +1205,7 @@
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */,
FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */,
18D43D7F2F050240008C326F /* PressureCurve.swift in Sources */,
18D43D812F050240008C326F /* PencilTipOffsetCalibrationViewController.swift in Sources */,
180E6E372E93BFDB0067C73A /* AlertControllerUtil.swift in Sources */,
98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */,
FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */,
+22
View File
@@ -1144,6 +1144,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="pTO-ff-set">
<rect key="frame" x="3145" y="191" width="293.5" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Pencil Tip Offset" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pTO-ff-lbl" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="143.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="pTO-ff-swi">
<rect key="frame" x="163.5" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="pTO-ff-wid"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="pTO-ff-hgt"/>
</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>
@@ -2342,6 +2362,8 @@
<outlet property="pencilTickIntervalStack" destination="O2s-9s-ABi" id="0kk-q4-ppy"/>
<outlet property="pencilTickSelector" destination="bVv-CW-kN8" id="Ymr-VI-Ilj"/>
<outlet property="pencilTickStack" destination="zuF-a5-GOI" id="Nn7-GA-yQM"/>
<outlet property="pencilTipOffsetStack" destination="pTO-ff-set" id="pTO-ff-oSt"/>
<outlet property="pencilTipOffsetSwitch" destination="pTO-ff-swi" id="pTO-ff-oSw"/>
<outlet property="performanceGraphStack" destination="H8p-G9-pnl" id="9RJ-uO-IpF"/>
<outlet property="pinchGestureStack" destination="uEq-Pc-Fum" id="68g-K6-P87"/>
<outlet property="pinchGestureSwitch" destination="TJy-Zp-DKM" id="Haj-ha-vNy"/>
+2
View File
@@ -118,6 +118,8 @@
// Pencil settings:
@property (nonatomic, retain) NSNumber * pencilTickMode;
@property (nonatomic, retain) NSNumber * pencilTickIntervalUs;
@property (nonatomic, retain) NSNumber * pencilTipOffsetX;
@property (nonatomic, retain) NSNumber * pencilTipOffsetY;
- (id) initFromSettings:(Settings*)settings;
+2
View File
@@ -175,6 +175,8 @@
// Pencil settings:
self.pencilTickMode = settings.pencilTickMode;
self.pencilTickIntervalUs = settings.pencilTickIntervalUs;
self.pencilTipOffsetX = settings.pencilTipOffsetX;
self.pencilTipOffsetY = settings.pencilTipOffsetY;
#endif
+2 -2
View File
@@ -154,8 +154,8 @@ import UIKit
@objc public static let stickWheels: [String] = ["LSWHEEL", "RSWHEEL"]
@objc public static let velocityBasedTouchPads: [String] = ["LSVPAD", "RSVPAD", "MOUSEPAD", "TRACKBALL", "LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL"]
@objc public static let inertialTouchPads: [String] = ["LSVPAD", "RSVPAD", "TRACKBALL", "LSPAD", "RSPAD"]
@objc public static let verticalTouchPads: [String] = ["LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let bidirectionalVerticalTouchPads: [String] = ["LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let verticalTouchPads: [String] = ["LTPAD", "RTPAD", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let singleDimensionActiveTouchpads: [String] = ["LTPAD", "RTPAD", "MOUSEWHEEL", "WHEEL", "DISCRETEWHEEL", "DSWHEEL"]
@objc public static let functionalButtonCmds: [String] = [
"SETTINGS",
"TOOLBOX",
@@ -244,7 +244,8 @@ class OSCProfilesManager: NSObject {
do {
let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedIfSafe)
if let profilesEncoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSMutableData.self, NSMutableArray.self], from: fileData) as? NSMutableArray {
let profilePayloadData = try ProfileFileContainer.unpackedData(from: fileData)
if let profilesEncoded = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSMutableData.self, NSMutableArray.self], from: profilePayloadData) as? NSMutableArray {
importEncodedProfiles(profilesEncoded)
if let selectedIndex = selectedIndex {
setProfileToSelected(selectedIndex)
+12 -2
View File
@@ -619,9 +619,11 @@ import ObjectiveC.runtime
self.hasSensitivityY = CommandManager.touchPadCmds.contains(self.touchPadString) && !CommandManager.stickWheels.contains(self.touchPadString)
self.hasSlideThreshold = CommandManager.mousePads.contains(self.touchPadString)
if CommandManager.bidirectionalVerticalTouchPads.contains(self.touchPadString){
if CommandManager.singleDimensionActiveTouchpads.contains(self.touchPadString){
self.sensitivityYMin = -4.0
self.sensitivityYMax = 4.0
self.sensitivityXMin = -4.0
self.sensitivityXMax = 4.0
}
if CommandManager.mousePads.contains(self.touchPadString){
@@ -2661,7 +2663,15 @@ import ObjectiveC.runtime
self.updateTouchLocation(touch: touch)
case "MOUSEWHEEL","WHEEL":
self.weightedDeltaY = Int(self.deltaY*7.5*self.sensitivityFactorY)
if firstTouchMoved {LiSendHighResScrollEvent(Int16(self.weightedDeltaY))}
self.weightedDeltaX = Int(self.deltaX*7.5*self.sensitivityFactorX)
if firstTouchMoved {
if abs(weightedDeltaY)>abs(weightedDeltaX) {
LiSendHighResScrollEvent(Int16(self.weightedDeltaY))
}
else {
LiSendHighResHScrollEvent(-Int16(self.weightedDeltaX))
}
}
self.updateTouchLocation(touch: touch)
case "DISCRETEWHEEL", "DSWHEEL":
let currentLocation = touch.location(in: self)
+9
View File
@@ -21,6 +21,7 @@ import UIKit
private var manualTick: Bool
private var pencilTickEnabled: Bool
private var pressureCurveEnabled: Bool = false
@objc private(set) var pencilTipOffset: CGPoint = .zero
private var manualHoverFlag: Bool = false
@objc static var hoverSupported: Bool = false
// @objc static private(set) var autoHoverTermination: Bool = false
@@ -66,6 +67,8 @@ import UIKit
tickInterval = TimeInterval(settings.pencilTickIntervalUs.floatValue/1000000)
manualTick = settings.pencilTickMode.intValue == PencilTickMode.ManualTick.rawValue
pencilTickEnabled = settings.pencilTickMode.intValue != PencilTickMode.PencilTickDisabled.rawValue
pencilTipOffset = CGPoint(x: CGFloat(settings.pencilTipOffsetX?.floatValue ?? 0),
y: CGFloat(settings.pencilTipOffsetY?.floatValue ?? 0))
// initialMoveEventIndexLimit = UIScreen.main.maximumFramesPerSecond > 60 ? 4 : 2
initialMoveEventIndexLimit = 0
super.init()
@@ -110,6 +113,7 @@ import UIKit
IAPManager.checkPurchaseInfo(.PencilProPack) { info in
self.pressureCurveEnabled = self.pressureCurveEnabled && info.valid
self.pencilTickEnabled = self.pencilTickEnabled && info.valid
self.pencilTipOffset = info.valid ? self.pencilTipOffset : .zero
self.pencilProEnabled = info.valid
PencilHandler.pencilPausesNativeTouch = selectedProfile.pencilPausesNativeTouch && info.valid
if info.valid {
@@ -243,6 +247,7 @@ import UIKit
for touch in touchBatch {
let point = touch.preciseLocation(in: streamView)
.applying(CGAffineTransform(translationX: pencilTipOffset.x, y: pencilTipOffset.y))
let azimuth = touch.azimuthAngle(in: streamView)
let altitude = touch.altitudeAngle
let location = self.adjustCoordinatesForVideoArea(point: point)
@@ -345,6 +350,10 @@ import UIKit
@objc public func switchPencilHover(){
manualHoverFlag = !manualHoverFlag
}
@objc public func updatePencilTipOffset(x: CGFloat, y: CGFloat) {
pencilTipOffset = CGPoint(x: x, y: y)
}
@objc public func enablePencilHover(){
manualHoverFlag = true
@@ -0,0 +1,361 @@
//
// PencilTipOffsetCalibrationViewController.swift
// VoidLink
//
// Created by True砖家 on 2026/7/30.
// Copyright © 2026 True砖家 on Bilibili. All rights reserved.
//
import UIKit
private enum PencilTipOffsetCalibrationLayout {
static let containerWidth: CGFloat = 430
static let containerHeight: CGFloat = 430
static let navBarHeight: CGFloat = 50
static let canvasTop: CGFloat = 66
static let canvasWidth: CGFloat = 320
static let canvasHeight: CGFloat = 260
static let sliderTopSpacing: CGFloat = 18
static let horizontalInset: CGFloat = 30
}
private final class PencilTipOffsetCanvasView: UIView {
var offset: CGPoint = .zero {
didSet { setNeedsDisplay() }
}
private var rawTouchPoint: CGPoint? {
didSet { setNeedsDisplay() }
}
override init(frame: CGRect) {
super.init(frame: frame)
isMultipleTouchEnabled = false
backgroundColor = UIColor(white: 0.97, alpha: 1)
layer.borderColor = UIColor.black.withAlphaComponent(0.12).cgColor
layer.borderWidth = 0.5
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let center = CGPoint(x: bounds.midX, y: bounds.midY)
context.setLineWidth(1)
context.setStrokeColor(UIColor.black.withAlphaComponent(0.14).cgColor)
context.move(to: CGPoint(x: 0, y: center.y))
context.addLine(to: CGPoint(x: bounds.maxX, y: center.y))
context.move(to: CGPoint(x: center.x, y: 0))
context.addLine(to: CGPoint(x: center.x, y: bounds.maxY))
context.strokePath()
context.setStrokeColor(UIColor.systemBlue.withAlphaComponent(0.45).cgColor)
context.setLineWidth(2)
context.move(to: center)
context.addLine(to: CGPoint(x: center.x + offset.x, y: center.y + offset.y))
context.strokePath()
if let rawTouchPoint {
drawLandingPreview(in: context, rawPoint: rawTouchPoint)
}
context.setFillColor(UIColor.black.withAlphaComponent(0.22).cgColor)
context.fillEllipse(in: CGRect(x: center.x - 5, y: center.y - 5, width: 10, height: 10))
let tipCenter = CGPoint(x: center.x + offset.x, y: center.y + offset.y)
context.setFillColor(UIColor.systemBlue.cgColor)
context.fillEllipse(in: CGRect(x: tipCenter.x - 5, y: tipCenter.y - 5, width: 10, height: 10))
}
private func drawLandingPreview(in context: CGContext, rawPoint: CGPoint) {
let correctedPoint = CGPoint(x: rawPoint.x + offset.x, y: rawPoint.y + offset.y)
context.setFillColor(UIColor.black.withAlphaComponent(0.28).cgColor)
context.fillEllipse(in: CGRect(x: rawPoint.x - 5, y: rawPoint.y - 5, width: 10, height: 10))
context.setFillColor(UIColor.systemBlue.cgColor)
context.fillEllipse(in: CGRect(x: correctedPoint.x - 5, y: correctedPoint.y - 5, width: 10, height: 10))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
updateTouchPoint(from: touches)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
updateTouchPoint(from: touches)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
updateTouchPoint(from: touches)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
rawTouchPoint = nil
}
private func updateTouchPoint(from touches: Set<UITouch>) {
guard let touch = touches.first(where: { $0.type == .pencil }) else { return }
rawTouchPoint = touch.preciseLocation(in: self)
}
}
@objc class PencilTipOffsetCalibrationViewController: UIViewController, UIGestureRecognizerDelegate {
private let container = UIView()
private let canvasView = PencilTipOffsetCanvasView()
private let xSlider = UISlider()
private let ySlider = UISlider()
private let xLabel = UILabel()
private let yLabel = UILabel()
private var saveButton = UIBarButtonItem()
private var persistedOffset: CGPoint = .zero
private var currentOffset: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
view.isOpaque = false
view.backgroundColor = UIColor.clear
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .light
}
loadPersistedOffset()
setupContainer()
setupNavigationBar()
setupCanvas()
setupSliders()
applyOffset(currentOffset)
NotificationCenter.default.addObserver(
self,
selector: #selector(pencilProPurchaseAborted(_:)),
name: AddOnProduct.PencilProPack.purchaseAbortedNotification(),
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(persistOffset),
name: AddOnProduct.PencilProPack.purchaseSucceededNotification(),
object: nil
)
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissSelf))
tap.delegate = self
view.addGestureRecognizer(tap)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
private func loadPersistedOffset() {
let dataMan = DataManager()
let settings = dataMan.retrieveSettings()
persistedOffset = CGPoint(
x: CGFloat(settings?.pencilTipOffsetX?.floatValue ?? 0),
y: CGFloat(settings?.pencilTipOffsetY?.floatValue ?? 0)
)
currentOffset = persistedOffset
}
private func setupContainer() {
container.translatesAutoresizingMaskIntoConstraints = false
container.backgroundColor = .white
container.layer.cornerRadius = 12
container.layer.masksToBounds = true
container.layer.borderWidth = 0.5
container.layer.borderColor = UIColor.black.withAlphaComponent(0.15).cgColor
view.addSubview(container)
NSLayoutConstraint.activate([
container.centerXAnchor.constraint(equalTo: view.centerXAnchor),
container.centerYAnchor.constraint(equalTo: view.centerYAnchor),
container.widthAnchor.constraint(equalToConstant: PencilTipOffsetCalibrationLayout.containerWidth),
container.heightAnchor.constraint(equalToConstant: PencilTipOffsetCalibrationLayout.containerHeight)
])
}
private func setupNavigationBar() {
let navBar = UINavigationBar()
navBar.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(navBar)
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.shadowColor = nil
appearance.backgroundColor = .white
navBar.standardAppearance = appearance
navBar.scrollEdgeAppearance = appearance
} else {
navBar.setBackgroundImage(UIImage(), for: .default)
navBar.shadowImage = UIImage()
navBar.isTranslucent = true
navBar.backgroundColor = .clear
}
saveButton = UIBarButtonItem(title: LocalizationHelper.localizedString(forKey: "Save"), style: .plain, target: self, action: #selector(saveTapped))
let resetButton = UIBarButtonItem(title: LocalizationHelper.localizedString(forKey: "Reset"), style: .plain, target: self, action: #selector(resetTapped))
let navItem = UINavigationItem(title: LocalizationHelper.localizedString(forKey: "Pencil Tip Offset"))
navItem.leftBarButtonItems = [resetButton]
navItem.rightBarButtonItems = [saveButton]
if #available(iOS 26.0, *) {
resetButton.hidesSharedBackground = true
saveButton.hidesSharedBackground = true
resetButton.tintColor = .tintColor
saveButton.tintColor = .tintColor
}
navBar.items = [navItem]
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: PencilTipOffsetCalibrationLayout.navBarHeight)
])
}
private func setupCanvas() {
canvasView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(canvasView)
NSLayoutConstraint.activate([
canvasView.topAnchor.constraint(equalTo: container.topAnchor, constant: PencilTipOffsetCalibrationLayout.canvasTop),
canvasView.centerXAnchor.constraint(equalTo: container.centerXAnchor),
canvasView.widthAnchor.constraint(equalToConstant: PencilTipOffsetCalibrationLayout.canvasWidth),
canvasView.heightAnchor.constraint(equalToConstant: PencilTipOffsetCalibrationLayout.canvasHeight)
])
}
private func setupSliders() {
let sliderStack = UIStackView()
sliderStack.axis = .vertical
sliderStack.spacing = 10
sliderStack.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(sliderStack)
configureSlider(xSlider, action: #selector(sliderMoved(_:)))
configureSlider(ySlider, action: #selector(sliderMoved(_:)))
configureLabel(xLabel)
configureLabel(yLabel)
sliderStack.addArrangedSubview(makeSliderRow(label: xLabel, slider: xSlider))
sliderStack.addArrangedSubview(makeSliderRow(label: yLabel, slider: ySlider))
NSLayoutConstraint.activate([
sliderStack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: PencilTipOffsetCalibrationLayout.horizontalInset),
sliderStack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -PencilTipOffsetCalibrationLayout.horizontalInset),
sliderStack.topAnchor.constraint(equalTo: canvasView.bottomAnchor, constant: PencilTipOffsetCalibrationLayout.sliderTopSpacing)
])
xSlider.value = Float(currentOffset.x)
ySlider.value = Float(currentOffset.y)
updateLabels()
}
private func configureSlider(_ slider: UISlider, action: Selector) {
slider.minimumValue = -15
slider.maximumValue = 15
slider.addTarget(self, action: action, for: .valueChanged)
if PublicUtils.liquidGlassEnabled, #available(iOS 13.0, *) {
slider.maximumTrackTintColor = .tertiarySystemFill
}
}
private func configureLabel(_ label: UILabel) {
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = .black
label.widthAnchor.constraint(equalToConstant: 110).isActive = true
}
private func makeSliderRow(label: UILabel, slider: UISlider) -> UIStackView {
let row = UIStackView(arrangedSubviews: [label, slider])
row.axis = .horizontal
row.alignment = .center
row.spacing = 12
return row
}
@objc private func sliderMoved(_ sender: UISlider) {
currentOffset = CGPoint(x: CGFloat(xSlider.value), y: CGFloat(ySlider.value))
applyOffset(currentOffset)
}
private func applyOffset(_ offset: CGPoint) {
canvasView.offset = offset
updateLabels()
}
private func updateLabels() {
xLabel.text = "\(LocalizationHelper.localizedString(forKey: "X Offset")): \(String(format: "%.1f", currentOffset.x))"
yLabel.text = "\(LocalizationHelper.localizedString(forKey: "Y Offset")): \(String(format: "%.1f", currentOffset.y))"
}
@objc private func resetTapped() {
xSlider.value = 0
ySlider.value = 0
currentOffset = .zero
applyOffset(currentOffset)
}
@objc private func persistOffset() {
let dataMan = DataManager()
guard let settings = dataMan.retrieveSettings() else { return }
settings.pencilTipOffsetX = NSNumber(value: Float(currentOffset.x))
settings.pencilTipOffsetY = NSNumber(value: Float(currentOffset.y))
dataMan.saveData()
persistedOffset = currentOffset
PencilHandler.shared?.updatePencilTipOffset(x: currentOffset.x, y: currentOffset.y)
DispatchQueue.main.async {
AlertControllerUtil.autoCompletion = true
AlertControllerUtil.showAlert(
in: self,
title: "",
message: LocalizationHelper.localizedString(forKey: "Pencil tip offset saved"),
withCancel: false,
buttonTitle: "",
countdown: 1
)
}
}
@objc private func saveTapped() {
saveButton.isEnabled = false
IAPManager.checkPurchaseInfo(.PencilProPack) { info in
self.saveButton.isEnabled = true
if info.valid {
self.persistOffset()
} else {
IAPManager.inAppPurchaseAction(viewController: self, product: .PencilProPack)
}
}
}
@objc private func pencilProPurchaseAborted(_ notification: Notification) {
guard let interruption = notification.object as? PurchaseInterruption else { return }
if interruption == .lowOSVersion {
AlertControllerUtil.showAlert(
in: self,
title: "",
message: LocalizationHelper.localizedString(forKey:"PencilProPackLowOSVersionTip"),
withCancel: false,
buttonTitle: LocalizationHelper.localizedString(forKey: "OK"),
countdown: 0
)
}
}
@objc private func dismissSelf() {
dismiss(animated: true)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == view
}
}
+3
View File
@@ -1000,6 +1000,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
}
CGPoint location = [self adjustCoordinatesForVideoArea:[gesture locationInView:self]];
PencilHandler* handler = PencilHandler.shared;
location = CGPointApplyAffineTransform(location, CGAffineTransformMakeTranslation(handler.pencilTipOffset.x, handler.pencilTipOffset.y));
CGSize videoSize = [self getVideoAreaSize];
float distance = 0.0f;
@@ -90,6 +90,8 @@
<attribute name="passthroughGestures" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="pencilTickIntervalUs" optional="YES" attributeType="Float" defaultValueString="2000" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pencilTickMode" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pencilTipOffsetX" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pencilTipOffsetY" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="pinchSensitivity" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
<attribute name="playAudioOnPC" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="pointerVelocityModeDivider" optional="YES" attributeType="Float" defaultValueString="0.5" usesScalarValueType="NO" syncable="YES"/>
@@ -125,4 +127,4 @@
<attribute name="useBuiltinMic" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" usesScalarValueType="NO" syncable="YES"/>
</entity>
</model>
</model>
+116
View File
@@ -13303,6 +13303,64 @@
}
}
},
"Pencil Tip Offset" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pencil Tip Offset"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "笔尖偏移"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移"
}
}
}
},
"Pencil tip offset saved" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pencil tip offset saved"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "笔尖偏移已保存"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移已儲存"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移已儲存"
}
}
}
},
"Pencil hover" : {
"extractionState" : "manual",
"localizations" : {
@@ -19111,6 +19169,35 @@
}
}
},
"X Offset" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "X Offset"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "X 偏移"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "X 偏移"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "X 偏移"
}
}
}
},
"Yaw factor: %.2f" : {
"extractionState" : "manual",
"localizations" : {
@@ -19134,6 +19221,35 @@
}
}
},
"Y Offset" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Y Offset"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "Y 偏移"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "Y 偏移"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "Y 偏移"
}
}
}
},
"Yes" : {
"extractionState" : "manual",
"localizations" : {
Binary file not shown.
Binary file not shown.
@@ -138,7 +138,7 @@ private enum ProfilePayloadCompression: UInt8 {
}
}
private enum ProfileFileContainer {
enum ProfileFileContainer {
private static let magic = Array("VLPR".utf8)
private static let version: UInt8 = 1
private static let headerSize = 14
@@ -1890,6 +1890,8 @@ static NSMutableSet* hostList;
// if([UIScreen mainScreen].maximumFramesPerSecond < 65) settings.touchMoveEventInterval = @(60);
settings.onscreenControls = @(OnScreenControlsLevelCustom);
settings.pencilTickIntervalUs = @(1750);
settings.pencilTipOffsetX = @(0);
settings.pencilTipOffsetY = @(0);
settings.oscLayoutToolFingers = @(99);
settings.keyboardToggleFingers = @(99);
@@ -291,6 +291,9 @@
@property (strong, nonatomic) IBOutlet UIStackView *pencilTickIntervalStack;
@property (strong, nonatomic) IBOutlet UISlider *pencilTickIntervalSlider;
@property (weak, nonatomic) IBOutlet UIStackView *pencilTipOffsetStack;
@property (weak, nonatomic) IBOutlet UISwitch *pencilTipOffsetSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *doubleTapShortcutStack;
@property (weak, nonatomic) IBOutlet UISwitch *doubleTapShortcutSwitch;
@@ -1074,6 +1074,8 @@ BOOL isCustomResolution(int resolutionSelected) {
self.pencilTickIntervalStack.hasDynamicLabel = YES;
[self addSetting:self.pencilTickIntervalStack ofId:@"pencilTickIntervalStack" to:pencilSection];
[self addSetting:self.pencilTipOffsetStack ofId:@"pencilTipOffsetStack" to:pencilSection];
self.pressureCurveStack.isGameProfileSetting = YES;
[self addSetting:self.pressureCurveStack ofId:@"pressureCurveStack" to:pencilSection];
@@ -4069,6 +4071,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.squeezeShortcutSwitch setOn:false];
[self.pencilPausesNativeTouchSwitch setOn:false];
[self.disablePencilSlideGestureSwitch setOn:false];
[self.pencilTipOffsetSwitch setOn:false];
NSNumber *value = notification.userInfo[@"interruption"];
@@ -4109,6 +4112,15 @@ BOOL isCustomResolution(int resolutionSelected) {
}
}
- (void)pencilTipOffsetSwitchFlipped:(UISwitch* )sender{
if(sender.isOn && !settingsViewJustLoaded){
PencilTipOffsetCalibrationViewController* calibrationVC = [[PencilTipOffsetCalibrationViewController alloc] init];
calibrationVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
self.definesPresentationContext = true;
[self presentViewController:calibrationVC animated:YES completion:nil];
}
}
- (void)doubleTapShortcutSwitchFlipped:(UISwitch* )sender{
if(sender.isOn && !settingsViewJustLoaded){
[IAPManager checkPurchaseInfo:AddOnProductPencilProPack completion:^(PurchaseInfo* info) {
@@ -4203,6 +4215,9 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.pencilTickIntervalSlider addTarget:self action:@selector(pencilTickIntervalSliderMoved:) forControlEvents:UIControlEventValueChanged];
[self.pencilTickIntervalSlider sendActionsForControlEvents:UIControlEventValueChanged];
[self.pencilTipOffsetSwitch setOn:(fabs(tempSettings.pencilTipOffsetX.floatValue) > 0.01 || fabs(tempSettings.pencilTipOffsetY.floatValue) > 0.01)];
[self.pencilTipOffsetSwitch addTarget:self action:@selector(pencilTipOffsetSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
[self.pressureCurveSwitch addTarget:self action:@selector(pressureCurveSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
[self.doubleTapShortcutSwitch addTarget:self action:@selector(doubleTapShortcutSwitchFlipped:) forControlEvents:UIControlEventValueChanged];
+31 -1
View File
@@ -5564,6 +5564,36 @@
}
}
},
"pTO-ff-lbl.text" : {
"comment" : "Class = \"UILabel\"; text = \"Pencil Tip Offset\"; ObjectID = \"pTO-ff-lbl\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Pencil Tip Offset"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "笔尖偏移"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "筆尖偏移"
}
}
}
},
"Pwa-YK-6Q2.text" : {
"comment" : "Class = \"UILabel\"; text = \"Opacity\"; ObjectID = \"Pwa-YK-6Q2\";",
"extractionState" : "extracted_with_value",
@@ -7254,4 +7284,4 @@
}
},
"version" : "1.0"
}
}