Blender4iOS/Sources/GreasePencil/PencilCaptureView.swift

74 lines
2.5 KiB
Swift

// PencilCaptureView.swift
// UIViewRepresentable overlay that captures Apple Pencil touches
// with full pressure/tilt/azimuth fidelity and forwards them
// as PencilSample values to a callback.
import SwiftUI
import UIKit
struct PencilCaptureOverlay: UIViewRepresentable {
let onStrokeBegan: () -> Void
let onSample: (PencilSample) -> Void
let onStrokeEnded: () -> Void
func makeUIView(context: Context) -> PencilTouchView {
let view = PencilTouchView()
view.onStrokeBegan = onStrokeBegan
view.onSample = onSample
view.onStrokeEnded = onStrokeEnded
view.backgroundColor = .clear
view.isMultipleTouchEnabled = false
return view
}
func updateUIView(_ uiView: PencilTouchView, context: Context) {}
}
/// Raw UIView that intercepts pencil touches.
final class PencilTouchView: UIView {
var onStrokeBegan: (() -> Void)?
var onSample: ((PencilSample) -> Void)?
var onStrokeEnded: (() -> Void)?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, touch.type == .pencil else { return }
onStrokeBegan?()
emitSamples(touch: touch, event: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, touch.type == .pencil else { return }
emitSamples(touch: touch, event: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, touch.type == .pencil else { return }
emitSamples(touch: touch, event: event)
onStrokeEnded?()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
onStrokeEnded?()
}
private func emitSamples(touch: UITouch, event: UIEvent?) {
// Coalesced touches give us sub-frame samples for smooth strokes
let coalescedTouches = event?.coalescedTouches(for: touch) ?? [touch]
for ct in coalescedTouches {
let loc = ct.preciseLocation(in: self)
let pressure = ct.maximumPossibleForce > 0
? Float(ct.force / ct.maximumPossibleForce)
: 0.5
let sample = PencilSample(
screenPosition: SIMD2<Float>(Float(loc.x), Float(loc.y)),
pressure: pressure,
altitude: Float(ct.altitudeAngle),
azimuth: Float(ct.azimuthAngle(in: self)),
timestamp: ct.timestamp
)
onSample?(sample)
}
}
}