Files
Oxide/engine/src/input/mod.rs
T
Homer Simpson 9eead719b0 Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history:
engine, editor, physics, script, examples, tests, docs, and assets.
Relicensed from GPLv3 to MIT and updated repo URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 20:41:02 +02:00

73 lines
3.3 KiB
Rust

//! Per-frame input — raw state, edges, and remappable named actions.
//!
//! Stage 7 builds the engine's input abstraction in three layers:
//!
//! 1. [`InputState`] (piece 1) — the per-frame snapshot of keyboard, mouse,
//! cursor, and scroll, with `pressed` / `released` edge detection and a
//! persistent `held` state. The windowing runner pumps raw `WindowEvent`s
//! into it and clears edges between frames; game/editor code reads it via
//! [`AppCtx::input`](crate::window::AppCtx::input).
//! 2. [`Binding`] + [`ActionMap`] (piece 2) — named actions like `"Jump"`
//! bound to one or more physical inputs, each carrying a **default**
//! binding and a (possibly remapped) **current** binding. Game code
//! queries actions by name, so a user-facing remap never touches game
//! code. Current bindings round-trip through RON for persistence
//! (typically via the [`Settings`](crate::settings::Settings) framework).
//! 3. [`AxisBinding`] + [`Axis2DBinding`] (piece 3) — directional inputs
//! composed from `Binding` direction sets (e.g. `WASD` → `Vec2 "Move"`),
//! stored alongside button actions in the same [`ActionMap`] and
//! persisted through the same [`ActionOverrides`] payload.
//!
//! # Why edges and state are tracked separately
//!
//! Game logic typically wants three distinct things from a physical input:
//! the moment it became pressed (a jump fires once on key-down, never on
//! subsequent frames while held), the moment it was released (a charged
//! shot fires on key-up), and whether it is currently down (a sprint key
//! accelerates while held). Tracking all three explicitly makes the
//! semantics robust against OS key auto-repeat — a held key produces a
//! single `pressed` edge no matter how many times the OS re-sends the
//! event — and avoids the per-callsite bookkeeping every action would
//! otherwise need.
//!
//! # Quick reference
//!
//! ```
//! use oxide_engine::input::{ActionMap, Binding, InputState};
//! use oxide_engine::winit::keyboard::KeyCode;
//!
//! let mut input = InputState::new();
//! input.press_key(KeyCode::Space);
//! assert!(input.pressed(KeyCode::Space)); // edge — true only this frame
//! assert!(input.held(KeyCode::Space)); // state — true while held
//!
//! // Layer named actions on top — game code never names the physical key.
//! let mut actions = ActionMap::new();
//! actions.register("Jump", [Binding::Key(KeyCode::Space)]);
//! assert!(actions.action_pressed("Jump", &input));
//!
//! input.end_frame();
//! assert!(!input.pressed(KeyCode::Space)); // edge cleared
//! assert!(input.held(KeyCode::Space)); // held persists
//! ```
//!
//! # Synthesized-event API
//!
//! The mutators on [`InputState`] (`press_key`, `release_mouse`,
//! `set_cursor`, `add_mouse_delta`, `add_scroll`, `forget_cursor`,
//! `release_all_held`) are the same path `handle_event` uses, and are
//! intentionally public so tests can drive input directly without
//! constructing `winit` events (winit 0.30's `DeviceId` cannot be
//! fabricated outside an event loop, so most `WindowEvent` variants are
//! unreachable from synthesized events).
mod action;
mod axis;
mod binding;
mod state;
pub use action::{ActionMap, ActionOverrides};
pub use axis::{Axis2DBinding, AxisBinding};
pub use binding::Binding;
pub use state::InputState;