Files
marathon/crates/libmarathon/src/platform/ios/pencil_bridge.rs
2026-02-07 18:18:57 +00:00

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
}