first successful ipad build

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2025-12-14 22:50:35 +00:00
parent 70735a33a5
commit 82ba23bfa2
21 changed files with 1224 additions and 465 deletions

View File

@@ -1,217 +0,0 @@
//! Bridge Bevy's input to the engine's InputEvent system
//!
//! This temporarily reads Bevy's input and converts to InputEvents.
//! Later, we'll replace this with direct winit ownership.
use bevy::prelude::*;
use bevy::input::keyboard::KeyboardInput;
use bevy::input::mouse::{MouseButtonInput, MouseWheel};
use bevy::window::CursorMoved;
use libmarathon::platform::input::{InputEvent, InputEventBuffer, KeyCode as EngineKeyCode, MouseButton as EngineMouseButton, TouchPhase, Modifiers};
/// Convert Bevy's Vec2 to glam::Vec2
///
/// Bevy re-exports glam types, so they're the same layout.
/// We just construct a new one to be safe.
#[inline]
fn to_glam_vec2(v: bevy::math::Vec2) -> glam::Vec2 {
glam::Vec2::new(v.x, v.y)
}
/// Convert Bevy's KeyCode to engine's KeyCode (winit::keyboard::KeyCode)
///
/// Bevy re-exports winit's KeyCode but wraps it, so we need to extract it.
/// For now, we'll just match the common keys. TODO: Complete mapping.
fn bevy_to_engine_keycode(bevy_key: KeyCode) -> Option<EngineKeyCode> {
// In Bevy 0.17, KeyCode variants match winit directly
// We can use format matching as a temporary solution
use EngineKeyCode as E;
Some(match bevy_key {
KeyCode::KeyA => E::KeyA,
KeyCode::KeyB => E::KeyB,
KeyCode::KeyC => E::KeyC,
KeyCode::KeyD => E::KeyD,
KeyCode::KeyE => E::KeyE,
KeyCode::KeyF => E::KeyF,
KeyCode::KeyG => E::KeyG,
KeyCode::KeyH => E::KeyH,
KeyCode::KeyI => E::KeyI,
KeyCode::KeyJ => E::KeyJ,
KeyCode::KeyK => E::KeyK,
KeyCode::KeyL => E::KeyL,
KeyCode::KeyM => E::KeyM,
KeyCode::KeyN => E::KeyN,
KeyCode::KeyO => E::KeyO,
KeyCode::KeyP => E::KeyP,
KeyCode::KeyQ => E::KeyQ,
KeyCode::KeyR => E::KeyR,
KeyCode::KeyS => E::KeyS,
KeyCode::KeyT => E::KeyT,
KeyCode::KeyU => E::KeyU,
KeyCode::KeyV => E::KeyV,
KeyCode::KeyW => E::KeyW,
KeyCode::KeyX => E::KeyX,
KeyCode::KeyY => E::KeyY,
KeyCode::KeyZ => E::KeyZ,
KeyCode::Digit1 => E::Digit1,
KeyCode::Digit2 => E::Digit2,
KeyCode::Digit3 => E::Digit3,
KeyCode::Digit4 => E::Digit4,
KeyCode::Digit5 => E::Digit5,
KeyCode::Digit6 => E::Digit6,
KeyCode::Digit7 => E::Digit7,
KeyCode::Digit8 => E::Digit8,
KeyCode::Digit9 => E::Digit9,
KeyCode::Digit0 => E::Digit0,
KeyCode::Space => E::Space,
KeyCode::Enter => E::Enter,
KeyCode::Escape => E::Escape,
KeyCode::Backspace => E::Backspace,
KeyCode::Tab => E::Tab,
KeyCode::ShiftLeft => E::ShiftLeft,
KeyCode::ShiftRight => E::ShiftRight,
KeyCode::ControlLeft => E::ControlLeft,
KeyCode::ControlRight => E::ControlRight,
KeyCode::AltLeft => E::AltLeft,
KeyCode::AltRight => E::AltRight,
KeyCode::SuperLeft => E::SuperLeft,
KeyCode::SuperRight => E::SuperRight,
KeyCode::ArrowUp => E::ArrowUp,
KeyCode::ArrowDown => E::ArrowDown,
KeyCode::ArrowLeft => E::ArrowLeft,
KeyCode::ArrowRight => E::ArrowRight,
_ => return None, // Unmapped keys
})
}
pub struct DesktopInputBridgePlugin;
impl Plugin for DesktopInputBridgePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<InputEventBuffer>()
.add_systems(PreUpdate, (
clear_buffer,
collect_mouse_buttons,
collect_mouse_motion,
collect_mouse_wheel,
collect_keyboard,
).chain());
}
}
/// Clear the buffer at the start of each frame
fn clear_buffer(mut buffer: ResMut<InputEventBuffer>) {
buffer.events.clear();
}
/// Collect mouse button events
fn collect_mouse_buttons(
mut buffer: ResMut<InputEventBuffer>,
mut mouse_button_events: MessageReader<MouseButtonInput>,
windows: Query<&Window>,
) {
let cursor_pos = windows
.single()
.ok()
.and_then(|w| w.cursor_position())
.unwrap_or(Vec2::ZERO);
for event in mouse_button_events.read() {
let button = match event.button {
MouseButton::Left => EngineMouseButton::Left,
MouseButton::Right => EngineMouseButton::Right,
MouseButton::Middle => EngineMouseButton::Middle,
_ => continue,
};
let phase = if event.state.is_pressed() {
TouchPhase::Started
} else {
TouchPhase::Ended
};
buffer.events.push(InputEvent::Mouse {
pos: to_glam_vec2(cursor_pos),
button,
phase,
});
}
}
/// Collect mouse motion events (for hover and drag tracking)
fn collect_mouse_motion(
mut buffer: ResMut<InputEventBuffer>,
mut cursor_moved: MessageReader<CursorMoved>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
) {
for event in cursor_moved.read() {
let cursor_pos = event.position;
// ALWAYS send MouseMove for cursor tracking (hover, tooltips, etc.)
buffer.events.push(InputEvent::MouseMove {
pos: to_glam_vec2(cursor_pos),
});
// ALSO generate drag events for currently pressed buttons
if mouse_buttons.pressed(MouseButton::Left) {
buffer.events.push(InputEvent::Mouse {
pos: to_glam_vec2(cursor_pos),
button: EngineMouseButton::Left,
phase: TouchPhase::Moved,
});
}
if mouse_buttons.pressed(MouseButton::Right) {
buffer.events.push(InputEvent::Mouse {
pos: to_glam_vec2(cursor_pos),
button: EngineMouseButton::Right,
phase: TouchPhase::Moved,
});
}
}
}
/// Collect mouse wheel events
fn collect_mouse_wheel(
mut buffer: ResMut<InputEventBuffer>,
mut wheel_events: MessageReader<MouseWheel>,
windows: Query<&Window>,
) {
let cursor_pos = windows
.single()
.ok()
.and_then(|w| w.cursor_position())
.unwrap_or(Vec2::ZERO);
for event in wheel_events.read() {
buffer.events.push(InputEvent::MouseWheel {
delta: to_glam_vec2(Vec2::new(event.x, event.y)),
pos: to_glam_vec2(cursor_pos),
});
}
}
/// Collect keyboard events
fn collect_keyboard(
mut buffer: ResMut<InputEventBuffer>,
mut keyboard_events: MessageReader<KeyboardInput>,
keys: Res<ButtonInput<KeyCode>>,
) {
for event in keyboard_events.read() {
let modifiers = Modifiers {
shift: keys.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]),
ctrl: keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]),
alt: keys.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]),
meta: keys.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]),
};
// Convert Bevy's KeyCode to engine's KeyCode
if let Some(engine_key) = bevy_to_engine_keycode(event.key_code) {
buffer.events.push(InputEvent::Keyboard {
key: engine_key,
pressed: event.state.is_pressed(),
modifiers,
});
}
}
}

View File

@@ -1,28 +1,13 @@
//! Input handling modules
//! Input handling for Aspen
//!
//! This module contains platform-specific input adapters that bridge
//! native input (Bevy/winit, iOS pencil) to libmarathon's InputEvent system.
//! Input flow:
//! 1. Platform executor (desktop/iOS) captures native input
//! 2. Platform layer converts to InputEvents and populates InputEventBuffer
//! 3. InputHandler reads buffer and converts to GameActions
//! 4. GameActions are applied to entities
pub mod event_buffer;
pub mod input_handler;
#[cfg(target_os = "ios")]
pub mod pencil;
#[cfg(not(target_os = "ios"))]
pub mod desktop_bridge;
#[cfg(not(target_os = "ios"))]
pub mod mouse;
pub use event_buffer::InputEventBuffer;
pub use input_handler::InputHandlerPlugin;
#[cfg(target_os = "ios")]
pub use pencil::PencilInputPlugin;
#[cfg(not(target_os = "ios"))]
pub use desktop_bridge::DesktopInputBridgePlugin;
#[cfg(not(target_os = "ios"))]
pub use mouse::MouseInputPlugin;

View File

@@ -1,97 +0,0 @@
//! Mouse input handling for macOS
use bevy::prelude::*;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use libmarathon::networking::{EntityLockRegistry, NetworkedEntity, NodeVectorClock};
pub struct MouseInputPlugin;
impl Plugin for MouseInputPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, handle_mouse_input);
}
}
/// Mouse interaction state
#[derive(Resource, Default)]
struct MouseState {
/// Whether the left mouse button is currently pressed
left_pressed: bool,
/// Whether the right mouse button is currently pressed
right_pressed: bool,
}
/// Handle mouse input to move and rotate cubes that are locked by us
fn handle_mouse_input(
mouse_buttons: Res<ButtonInput<MouseButton>>,
mut mouse_motion: MessageReader<MouseMotion>,
mut mouse_wheel: MessageReader<MouseWheel>,
mut mouse_state: Local<Option<MouseState>>,
lock_registry: Res<EntityLockRegistry>,
node_clock: Res<NodeVectorClock>,
mut cube_query: Query<(&NetworkedEntity, &mut Transform), With<crate::cube::CubeMarker>>,
) {
// Initialize mouse state if needed
if mouse_state.is_none() {
*mouse_state = Some(MouseState::default());
}
let state = mouse_state.as_mut().unwrap();
// Update button states
state.left_pressed = mouse_buttons.pressed(MouseButton::Left);
state.right_pressed = mouse_buttons.pressed(MouseButton::Right);
let node_id = node_clock.node_id;
// Get total mouse delta this frame
let mut total_delta = Vec2::ZERO;
for motion in mouse_motion.read() {
total_delta += motion.delta;
}
// Process mouse motion - only for cubes locked by us
if total_delta != Vec2::ZERO {
for (networked, mut transform) in cube_query.iter_mut() {
// Only move cubes that we have locked
if !lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
continue;
}
if state.left_pressed {
// Left drag: Move cube in XY plane
// Scale factor for sensitivity
let sensitivity = 0.01;
transform.translation.x += total_delta.x * sensitivity;
transform.translation.y -= total_delta.y * sensitivity; // Invert Y
// Change detection will trigger clock tick automatically
} else if state.right_pressed {
// Right drag: Rotate cube
let sensitivity = 0.01;
let rotation_x = Quat::from_rotation_y(total_delta.x * sensitivity);
let rotation_y = Quat::from_rotation_x(-total_delta.y * sensitivity);
transform.rotation = rotation_x * transform.rotation * rotation_y;
// Change detection will trigger clock tick automatically
}
}
}
// Process mouse wheel for Z-axis movement - only for cubes locked by us
let mut total_scroll = 0.0;
for wheel in mouse_wheel.read() {
total_scroll += wheel.y;
}
if total_scroll != 0.0 {
for (networked, mut transform) in cube_query.iter_mut() {
// Only move cubes that we have locked
if !lock_registry.is_locked_by(networked.network_id, node_id, node_id) {
continue;
}
// Scroll: Move in Z axis
let sensitivity = 0.1;
transform.translation.z += total_scroll * sensitivity;
// Change detection will trigger clock tick automatically
}
}
}

View File

@@ -1,69 +0,0 @@
//! Apple Pencil input system for iOS
//!
//! This module integrates the platform-agnostic pencil bridge with Bevy.
use bevy::prelude::*;
use libmarathon::{platform::input::InputEvent, platform::ios};
pub struct PencilInputPlugin;
impl Plugin for PencilInputPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, attach_pencil_capture)
.add_systems(PreUpdate, poll_pencil_input);
}
}
/// Resource to track the latest pencil state
#[derive(Resource, Default)]
pub struct PencilState {
pub latest: Option<InputEvent>,
pub points_this_frame: usize,
}
/// Attach the Swift pencil capture to Bevy's window
#[cfg(target_os = "ios")]
fn attach_pencil_capture(windows: Query<&bevy::window::RawHandleWrapper, With<bevy::window::PrimaryWindow>>) {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
let Ok(handle) = windows.get_single() else {
warn!("No primary window for pencil capture");
return;
};
unsafe {
if let Ok(raw) = handle.window_handle() {
if let RawWindowHandle::UiKit(h) = raw.as_ref() {
ios::swift_attach_pencil_capture(h.ui_view.as_ptr() as *mut _);
info!("✏️ Apple Pencil capture attached");
}
}
}
}
#[cfg(not(target_os = "ios"))]
fn attach_pencil_capture() {
// No-op on non-iOS platforms
}
/// Poll pencil input from the platform layer and update PencilState
fn poll_pencil_input(mut commands: Commands, state: Option<ResMut<PencilState>>) {
let events = ios::drain_as_input_events();
if events.is_empty() {
return;
}
// Insert resource if it doesn't exist
if state.is_none() {
commands.insert_resource(PencilState::default());
return;
}
if let Some(mut state) = state {
state.points_this_frame = events.len();
if let Some(latest) = events.last() {
state.latest = Some(*latest);
}
}
}