first successful ipad build
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user