//! 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> = 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 { 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 { 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 }