pencil tip offset
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -175,6 +175,8 @@
|
||||
// Pencil settings:
|
||||
self.pencilTickMode = settings.pencilTickMode;
|
||||
self.pencilTickIntervalUs = settings.pencilTickIntervalUs;
|
||||
self.pencilTipOffsetX = settings.pencilTipOffsetX;
|
||||
self.pencilTipOffsetY = settings.pencilTipOffsetY;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
+1
-1
@@ -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];
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user