pub mod action;
pub mod binding;
pub mod defaults;
pub mod mode;
pub mod query;
pub mod action_frame;
pub mod controller;
pub mod context;
pub mod event;
pub mod preset;
pub mod viewport_binding;
pub mod viewport_input;
pub use action::Action;
pub use binding::{ActivationMode, Binding, KeyCode, Modifiers, MouseButton, Trigger, TriggerKind};
pub use defaults::default_bindings;
pub use mode::InputMode;
pub use query::{ActionState, FrameInput};
pub use action_frame::{ActionFrame, NavigationActions, ResolvedActionState};
pub use context::ViewportContext;
pub use controller::OrbitCameraController;
pub use event::{ButtonState, ScrollUnits, ViewportEvent};
pub use preset::{BindingPreset, viewport_all_bindings};
pub use viewport_binding::{ModifiersMatch, ViewportBinding, ViewportGesture};
pub use viewport_input::ViewportInput;
pub struct InputSystem {
bindings: Vec<Binding>,
mode: InputMode,
}
impl InputSystem {
pub fn new() -> Self {
Self {
bindings: default_bindings(),
mode: InputMode::Normal,
}
}
pub fn mode(&self) -> InputMode {
self.mode
}
pub fn set_mode(&mut self, mode: InputMode) {
self.mode = mode;
}
pub fn query(&self, action: Action, input: &FrameInput) -> ActionState {
for binding in &self.bindings {
if binding.action != action {
continue;
}
if !binding.active_modes.is_empty() && !binding.active_modes.contains(&self.mode) {
continue;
}
let state = query::evaluate_trigger(
&binding.trigger.kind,
&binding.trigger.activation,
&binding.trigger.modifiers,
binding.trigger.ignore_modifiers,
input,
);
if !matches!(state, ActionState::Inactive) {
return state;
}
}
ActionState::Inactive
}
pub fn bindings(&self) -> &[Binding] {
&self.bindings
}
pub fn set_bindings(&mut self, bindings: Vec<Binding>) {
self.bindings = bindings;
}
}
impl Default for InputSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use binding::{KeyCode, Modifiers, MouseButton};
use query::FrameInput;
fn input_with_left_drag() -> FrameInput {
let mut input = FrameInput::default();
input.dragging.insert(MouseButton::Left);
input.drag_delta = glam::Vec2::new(10.0, 5.0);
input.hovered = true;
input
}
#[test]
fn test_query_orbit_active() {
let sys = InputSystem::new();
let mut input = input_with_left_drag();
input.modifiers = Modifiers::ALT;
let state = sys.query(Action::Orbit, &input);
assert!(
state.is_active(),
"orbit should be active on alt+left-drag in Normal mode"
);
}
#[test]
fn test_query_orbit_inactive_without_alt() {
let sys = InputSystem::new();
let input = input_with_left_drag();
let state = sys.query(Action::Orbit, &input);
assert!(
!state.is_active(),
"orbit should be inactive on plain left-drag in Normal mode"
);
}
#[test]
fn test_mode_filtering() {
let mut sys = InputSystem::new();
sys.set_mode(InputMode::FlyMode);
let input = input_with_left_drag();
let state = sys.query(Action::Orbit, &input);
assert!(!state.is_active(), "orbit should be inactive in FlyMode");
}
#[test]
fn test_modifier_matching() {
let sys = InputSystem::new();
let mut input = FrameInput::default();
input.dragging.insert(MouseButton::Left);
input.drag_delta = glam::Vec2::new(10.0, 5.0);
input.modifiers = Modifiers::SHIFT;
let state = sys.query(Action::Pan, &input);
assert!(
state.is_active(),
"pan should be active with shift+left drag"
);
let mut input2 = FrameInput::default();
input2.dragging.insert(MouseButton::Left);
input2.drag_delta = glam::Vec2::new(10.0, 5.0);
input2.modifiers = Modifiers::CTRL;
let state2 = sys.query(Action::Pan, &input2);
assert!(
!state2.is_active(),
"pan should be inactive with ctrl modifier"
);
}
#[test]
fn test_ignore_modifiers() {
let mut sys = InputSystem::new();
sys.set_mode(InputMode::FlyMode);
let mut input = FrameInput::default();
input.keys_held.insert(KeyCode::W);
input.modifiers = Modifiers::SHIFT;
let state = sys.query(Action::FlyForward, &input);
assert!(
state.is_active(),
"fly forward should be active with shift held (ignore_modifiers)"
);
}
#[test]
fn test_empty_input_inactive() {
let sys = InputSystem::new();
let input = FrameInput::default();
assert!(!sys.query(Action::Orbit, &input).is_active());
assert!(!sys.query(Action::Pan, &input).is_active());
assert!(!sys.query(Action::Zoom, &input).is_active());
assert!(!sys.query(Action::FocusObject, &input).is_active());
}
}