9eead719b0
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>
73 lines
3.3 KiB
Rust
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;
|