115 lines
3.4 KiB
Rust
115 lines
3.4 KiB
Rust
//! Apple Pencil input bridge for iOS
|
|
//!
|
|
//! This module captures raw Apple Pencil input via Swift/UIKit and converts
|
|
//! it to engine-agnostic InputEvents.
|
|
|
|
use crate::platform::input::{InputEvent, TouchPhase};
|
|
use glam::Vec2;
|
|
use std::sync::Mutex;
|
|
|
|
/// Raw pencil point data from Swift UITouch
|
|
///
|
|
/// This matches the C struct defined in PencilBridge.h
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
#[repr(C)] // Use C memory layout so Swift can interop
|
|
pub struct RawPencilPoint {
|
|
/// Screen X coordinate in points (not pixels)
|
|
pub x: f32,
|
|
/// Screen Y coordinate in points (not pixels)
|
|
pub y: f32,
|
|
/// Force/pressure (0.0 - 4.0 on Apple Pencil)
|
|
pub force: f32,
|
|
/// Altitude angle in radians (0 = flat, π/2 = perpendicular)
|
|
pub altitude: f32,
|
|
/// Azimuth angle in radians (rotation around vertical)
|
|
pub azimuth: f32,
|
|
/// iOS timestamp (seconds since system boot)
|
|
pub timestamp: f64,
|
|
/// Touch phase: 0=began, 1=moved, 2=ended
|
|
pub phase: u8,
|
|
}
|
|
|
|
/// Thread-safe buffer for pencil points
|
|
///
|
|
/// Swift's main thread pushes points here via C FFI.
|
|
/// Bevy's Update schedule drains them each frame.
|
|
static BUFFER: Mutex<Vec<RawPencilPoint>> = Mutex::new(Vec::new());
|
|
|
|
/// FFI function called from Swift when a pencil point is received
|
|
///
|
|
/// This is exposed as a C function so Swift can call it.
|
|
/// The `#[no_mangle]` prevents Rust from changing the function name.
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn rust_push_pencil_point(point: RawPencilPoint) {
|
|
if let Ok(mut buf) = BUFFER.lock() {
|
|
buf.push(point);
|
|
}
|
|
}
|
|
|
|
/// Legacy alias for compatibility
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn pencil_point_received(point: RawPencilPoint) {
|
|
rust_push_pencil_point(point);
|
|
}
|
|
|
|
/// Drain all buffered pencil points and convert to InputEvents
|
|
///
|
|
/// Call this from your Bevy Update system to consume input.
|
|
pub fn drain_as_input_events() -> Vec<InputEvent> {
|
|
BUFFER
|
|
.lock()
|
|
.ok()
|
|
.map(|mut b| {
|
|
std::mem::take(&mut *b)
|
|
.into_iter()
|
|
.map(raw_to_input_event)
|
|
.collect()
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Drain raw pencil points without conversion
|
|
///
|
|
/// Useful for debugging or custom processing.
|
|
pub fn drain_raw() -> Vec<RawPencilPoint> {
|
|
BUFFER
|
|
.lock()
|
|
.ok()
|
|
.map(|mut b| std::mem::take(&mut *b))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Convert a raw pencil point to an engine InputEvent
|
|
fn raw_to_input_event(p: RawPencilPoint) -> InputEvent {
|
|
InputEvent::Stylus {
|
|
pos: Vec2::new(p.x, p.y),
|
|
pressure: p.force,
|
|
tilt: Vec2::new(p.altitude, p.azimuth),
|
|
phase: match p.phase {
|
|
0 => TouchPhase::Started,
|
|
1 => TouchPhase::Moved,
|
|
2 => TouchPhase::Ended,
|
|
_ => TouchPhase::Cancelled,
|
|
},
|
|
timestamp: p.timestamp,
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "ios")]
|
|
unsafe extern "C" {
|
|
/// Attach the pencil capture system to a UIView
|
|
pub fn swift_attach_pencil_capture(view: *mut std::ffi::c_void);
|
|
/// Detach the pencil capture system from a UIView
|
|
pub fn swift_detach_pencil_capture(view: *mut std::ffi::c_void);
|
|
}
|
|
|
|
#[cfg(not(target_os = "ios"))]
|
|
pub unsafe fn swift_attach_pencil_capture(_: *mut std::ffi::c_void) {
|
|
// No-op on non-iOS platforms
|
|
}
|
|
|
|
#[cfg(not(target_os = "ios"))]
|
|
pub unsafe fn swift_detach_pencil_capture(_: *mut std::ffi::c_void) {
|
|
// No-op on non-iOS platforms
|
|
}
|