moved some things around.

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2025-12-14 21:25:52 +00:00
parent 5493faa1f1
commit 70735a33a5
16 changed files with 861 additions and 298 deletions

View File

@@ -1,342 +0,0 @@
//! Input controller - maps raw InputEvents to semantic GameActions
//!
//! This layer provides:
//! - Input remapping (change key bindings)
//! - Accessibility (alternative input methods)
//! - Context-aware bindings (different actions in different modes)
use super::game_actions::GameAction;
use super::input_events::{InputEvent, KeyCode, MouseButton, TouchPhase};
use glam::Vec2;
use std::collections::HashMap;
/// Input binding - maps an input trigger to a game action
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputBinding {
/// Mouse button press/release
MouseButton(MouseButton),
/// Mouse drag with a specific button
MouseDrag(MouseButton),
/// Mouse wheel scroll
MouseWheel,
/// Keyboard key press
Key(KeyCode),
/// Keyboard key with modifiers
KeyWithModifiers {
key: KeyCode,
shift: bool,
ctrl: bool,
alt: bool,
meta: bool,
},
/// Stylus input (Apple Pencil, etc.)
StylusDrag,
/// Touch input
TouchDrag,
}
/// Input context - different binding sets for different game modes
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputContext {
/// Manipulating 3D entities
EntityManipulation,
/// Camera control
CameraControl,
/// UI interaction
UI,
/// Text input
TextInput,
}
/// Accessibility settings for input processing
#[derive(Debug, Clone)]
pub struct AccessibilitySettings {
/// Mouse sensitivity multiplier (1.0 = normal)
pub mouse_sensitivity: f32,
/// Scroll sensitivity multiplier (1.0 = normal)
pub scroll_sensitivity: f32,
/// Stylus pressure sensitivity (1.0 = normal)
pub stylus_sensitivity: f32,
/// Enable one-handed mode (use keyboard for rotation)
pub one_handed_mode: bool,
/// Invert Y axis for rotation
pub invert_y: bool,
/// Minimum drag distance before registering as drag (in pixels)
pub drag_threshold: f32,
}
impl Default for AccessibilitySettings {
fn default() -> Self {
Self {
mouse_sensitivity: 1.0,
scroll_sensitivity: 1.0,
stylus_sensitivity: 1.0,
one_handed_mode: false,
invert_y: false,
drag_threshold: 2.0,
}
}
}
/// Input controller - converts InputEvents to GameActions
pub struct InputController {
/// Current input context
current_context: InputContext,
/// Bindings for each context
bindings: HashMap<InputContext, HashMap<InputBinding, GameAction>>,
/// Accessibility settings
accessibility: AccessibilitySettings,
/// Drag state tracking
drag_state: DragState,
}
#[derive(Default)]
struct DragState {
/// Is currently dragging
active: bool,
/// Which button/input is dragging
source: Option<DragSource>,
/// Start position
start_pos: Vec2,
/// Last position
last_pos: Vec2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DragSource {
MouseLeft,
MouseRight,
Stylus,
Touch,
}
impl InputController {
/// Create a new input controller with default bindings
pub fn new() -> Self {
let mut controller = Self {
current_context: InputContext::EntityManipulation,
bindings: HashMap::new(),
accessibility: AccessibilitySettings::default(),
drag_state: DragState::default(),
};
controller.setup_default_bindings();
controller
}
/// Set the current input context
pub fn set_context(&mut self, context: InputContext) {
self.current_context = context;
}
/// Get the current context
pub fn context(&self) -> InputContext {
self.current_context
}
/// Update accessibility settings
pub fn set_accessibility(&mut self, settings: AccessibilitySettings) {
self.accessibility = settings;
}
/// Get current accessibility settings
pub fn accessibility(&self) -> &AccessibilitySettings {
&self.accessibility
}
/// Process an input event and produce game actions
pub fn process_event(&mut self, event: &InputEvent) -> Vec<GameAction> {
let mut actions = Vec::new();
match event {
InputEvent::MouseMove { pos: _ } => {
// Mouse hover - no game actions, just UI tracking
// This is handled by egui's custom_input_system
}
InputEvent::Mouse { pos, button, phase } => {
self.process_mouse(*pos, *button, *phase, &mut actions);
}
InputEvent::MouseWheel { delta, pos: _ } => {
let adjusted_delta = delta.y * self.accessibility.scroll_sensitivity;
actions.push(GameAction::MoveEntityDepth { delta: adjusted_delta });
}
InputEvent::Keyboard { key, pressed, modifiers: _ } => {
if *pressed {
self.process_key(*key, &mut actions);
}
}
InputEvent::Stylus { pos, pressure: _, tilt: _, phase, timestamp: _ } => {
self.process_stylus(*pos, *phase, &mut actions);
}
InputEvent::Touch { pos, phase, id: _ } => {
self.process_touch(*pos, *phase, &mut actions);
}
}
actions
}
/// Process mouse input
fn process_mouse(&mut self, pos: Vec2, button: MouseButton, phase: TouchPhase, actions: &mut Vec<GameAction>) {
match phase {
TouchPhase::Started => {
// Single click = select
actions.push(GameAction::SelectEntity { position: pos });
// Start drag tracking
self.drag_state.active = true;
self.drag_state.source = Some(match button {
MouseButton::Left => DragSource::MouseLeft,
MouseButton::Right => DragSource::MouseRight,
MouseButton::Middle => return, // Don't handle middle button
});
self.drag_state.start_pos = pos;
self.drag_state.last_pos = pos;
actions.push(GameAction::BeginDrag { position: pos });
}
TouchPhase::Moved => {
if self.drag_state.active {
let delta = (pos - self.drag_state.last_pos) * self.accessibility.mouse_sensitivity;
self.drag_state.last_pos = pos;
// Check if we've exceeded drag threshold
let total_delta = pos - self.drag_state.start_pos;
if total_delta.length() < self.accessibility.drag_threshold {
return; // Too small to count as drag
}
actions.push(GameAction::ContinueDrag { position: pos, delta });
// Context-specific drag actions
match self.current_context {
InputContext::EntityManipulation => {
match self.drag_state.source {
Some(DragSource::MouseLeft) => {
actions.push(GameAction::MoveEntity { delta });
}
Some(DragSource::MouseRight) => {
let adjusted_delta = if self.accessibility.invert_y {
Vec2::new(delta.x, -delta.y)
} else {
delta
};
actions.push(GameAction::RotateEntity { delta: adjusted_delta });
}
_ => {}
}
}
InputContext::CameraControl => {
actions.push(GameAction::MoveCamera { delta });
}
_ => {}
}
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
if self.drag_state.active {
actions.push(GameAction::EndDrag { position: pos });
self.drag_state.active = false;
self.drag_state.source = None;
}
}
}
}
/// Process keyboard input
fn process_key(&mut self, key: KeyCode, actions: &mut Vec<GameAction>) {
match key {
KeyCode::KeyR => actions.push(GameAction::ResetEntity),
KeyCode::Delete | KeyCode::Backspace => actions.push(GameAction::DeleteEntity),
KeyCode::KeyZ if self.accessibility.one_handed_mode => {
// In one-handed mode, Z key can trigger actions
actions.push(GameAction::Undo);
}
KeyCode::Escape => actions.push(GameAction::Cancel),
KeyCode::Enter => actions.push(GameAction::Confirm),
KeyCode::Tab => actions.push(GameAction::ToggleUI),
_ => {}
}
}
/// Process stylus input (Apple Pencil, etc.)
fn process_stylus(&mut self, pos: Vec2, phase: TouchPhase, actions: &mut Vec<GameAction>) {
match phase {
TouchPhase::Started => {
actions.push(GameAction::SelectEntity { position: pos });
actions.push(GameAction::BeginDrag { position: pos });
self.drag_state.active = true;
self.drag_state.source = Some(DragSource::Stylus);
self.drag_state.start_pos = pos;
self.drag_state.last_pos = pos;
}
TouchPhase::Moved => {
if self.drag_state.active {
let delta = (pos - self.drag_state.last_pos) * self.accessibility.stylus_sensitivity;
self.drag_state.last_pos = pos;
actions.push(GameAction::ContinueDrag { position: pos, delta });
actions.push(GameAction::MoveEntity { delta });
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
if self.drag_state.active {
actions.push(GameAction::EndDrag { position: pos });
self.drag_state.active = false;
self.drag_state.source = None;
}
}
}
}
/// Process touch input
fn process_touch(&mut self, pos: Vec2, phase: TouchPhase, actions: &mut Vec<GameAction>) {
// For now, treat touch like stylus
self.process_stylus(pos, phase, actions);
}
/// Set up default input bindings
fn setup_default_bindings(&mut self) {
// For now, bindings are hardcoded in process_event
// Later, we can make this fully data-driven
}
}
impl Default for InputController {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "input_controller_tests.rs"]
mod tests;

View File

@@ -1,150 +0,0 @@
//! Abstract input event types for the engine
//!
//! These types are platform-agnostic and represent all forms of input
//! (stylus, mouse, touch) in a unified way. Platform-specific code
//! (iOS pencil bridge, desktop mouse) converts to these types.
use glam::Vec2;
/// Phase of a touch/stylus/mouse input
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchPhase {
/// Input just started
Started,
/// Input moved
Moved,
/// Input ended normally
Ended,
/// Input was cancelled (e.g., system gesture)
Cancelled,
}
/// Mouse button types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MouseButton {
Left,
Right,
Middle,
}
/// Keyboard key (using winit's KeyCode for now - can abstract later)
pub use winit::keyboard::KeyCode;
/// Keyboard modifiers
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub meta: bool, // Command on macOS, Windows key on Windows
}
/// Input event buffer for Bevy ECS integration
///
/// The executor fills this buffer each frame with input events from winit,
/// and Bevy systems (like egui) consume these events.
#[derive(bevy::prelude::Resource, Default, Clone)]
pub struct InputEventBuffer {
pub events: Vec<InputEvent>,
}
/// Abstract input event that the engine processes
///
/// Platform-specific code converts native input (UITouch, winit events)
/// into these engine-agnostic events.
#[derive(Debug, Clone, Copy)]
pub enum InputEvent {
/// Stylus input (Apple Pencil, Surface Pen, etc.)
Stylus {
/// Screen position in pixels
pos: Vec2,
/// Pressure (0.0 = no pressure, 1.0+ = max pressure)
/// Note: Apple Pencil reports 0.0-4.0 range
pressure: f32,
/// Tilt vector:
/// - x: altitude angle (0 = flat on screen, π/2 = perpendicular)
/// - y: azimuth angle (rotation around vertical axis)
tilt: Vec2,
/// Touch phase
phase: TouchPhase,
/// Platform timestamp (for input prediction)
timestamp: f64,
},
/// Mouse input (desktop)
Mouse {
/// Screen position in pixels
pos: Vec2,
/// Which button
button: MouseButton,
/// Touch phase
phase: TouchPhase,
},
/// Mouse cursor movement (no button pressed)
/// This is separate from Mouse to distinguish hover from drag
MouseMove {
/// Screen position in pixels
pos: Vec2,
},
/// Touch input (fingers on touchscreen)
Touch {
/// Screen position in pixels
pos: Vec2,
/// Touch phase
phase: TouchPhase,
/// Touch ID (for multi-touch tracking)
id: u64,
},
/// Keyboard input
Keyboard {
/// Physical key code
key: KeyCode,
/// Whether the key was pressed or released
pressed: bool,
/// Modifier keys held during the event
modifiers: Modifiers,
},
/// Mouse wheel scroll
MouseWheel {
/// Scroll delta (pixels or lines depending on device)
delta: Vec2,
/// Current mouse position
pos: Vec2,
},
}
impl InputEvent {
/// Get the position for positional input types
pub fn position(&self) -> Option<Vec2> {
match self {
InputEvent::Stylus { pos, .. } => Some(*pos),
InputEvent::Mouse { pos, .. } => Some(*pos),
InputEvent::MouseMove { pos } => Some(*pos),
InputEvent::Touch { pos, .. } => Some(*pos),
InputEvent::MouseWheel { pos, .. } => Some(*pos),
InputEvent::Keyboard { .. } => None,
}
}
/// Get the phase for input types that have phases
pub fn phase(&self) -> Option<TouchPhase> {
match self {
InputEvent::Stylus { phase, .. } => Some(*phase),
InputEvent::Mouse { phase, .. } => Some(*phase),
InputEvent::Touch { phase, .. } => Some(*phase),
InputEvent::Keyboard { .. } | InputEvent::MouseWheel { .. } | InputEvent::MouseMove { .. } => None,
}
}
/// Check if this is an active input (not ended/cancelled)
pub fn is_active(&self) -> bool {
match self.phase() {
Some(phase) => !matches!(phase, TouchPhase::Ended | TouchPhase::Cancelled),
None => true, // Keyboard and wheel events are considered instantaneous
}
}
}

View File

@@ -1,12 +1,18 @@
//! Core Engine module - networking and persistence outside Bevy
//! Core Engine module - application logic and coordination
//!
//! This module handles the core application logic that sits between the
//! platform layer and the game systems:
//! - **bridge**: Communication bridge between async EngineCore and Bevy ECS
//! - **core**: Async EngineCore running on tokio (CRDT sync, networking, persistence)
//! - **commands**: Commands that can be sent to EngineCore
//! - **events**: Events emitted by EngineCore
//! - **game_actions**: High-level game actions (SelectEntity, MoveEntity, etc.)
mod bridge;
mod commands;
mod core;
mod events;
mod game_actions;
mod input_controller;
mod input_events;
mod networking;
mod persistence;
@@ -15,7 +21,5 @@ pub use commands::EngineCommand;
pub use core::EngineCore;
pub use events::EngineEvent;
pub use game_actions::GameAction;
pub use input_controller::{AccessibilitySettings, InputContext, InputController};
pub use input_events::{InputEvent, InputEventBuffer, KeyCode, Modifiers, MouseButton, TouchPhase};
pub use networking::NetworkingManager;
pub use persistence::PersistenceManager;