viewport_lib/interaction/input/
mod.rs1pub mod action;
21pub mod binding;
23pub mod defaults;
25pub mod mode;
27pub mod query;
29
30pub mod action_frame;
33pub mod context;
35pub mod controller;
37pub mod event;
39pub mod first_person;
41mod look;
43pub mod movement;
45pub mod preset;
47pub mod third_person;
49pub mod viewport_binding;
51pub mod viewport_input;
53
54pub use action::Action;
56pub use binding::{ActivationMode, Binding, KeyCode, Modifiers, MouseButton, Trigger, TriggerKind};
57pub use defaults::default_bindings;
58pub use mode::{InputMode, NavigationMode};
59pub use query::{ActionState, FrameInput};
60
61pub use action_frame::{ActionFrame, NavigationActions, ResolvedActionState};
63pub use context::ViewportContext;
64pub use controller::OrbitCameraController;
65pub use event::{ButtonState, ScrollUnits, ViewportEvent};
66pub use first_person::FirstPersonCameraController;
67pub use movement::wish_xy_from_actions;
68pub use preset::{BindingPreset, viewport_all_bindings};
69pub use third_person::ThirdPersonCameraController;
70pub use viewport_binding::{ModifiersMatch, ViewportBinding, ViewportGesture};
71pub use viewport_input::ViewportInput;
72
73pub struct InputSystem {
76 bindings: Vec<Binding>,
77 mode: InputMode,
78}
79
80impl InputSystem {
81 pub fn new() -> Self {
83 Self {
84 bindings: default_bindings(),
85 mode: InputMode::Normal,
86 }
87 }
88
89 pub fn mode(&self) -> InputMode {
91 self.mode
92 }
93
94 pub fn set_mode(&mut self, mode: InputMode) {
96 self.mode = mode;
97 }
98
99 pub fn query(&self, action: Action, input: &FrameInput) -> ActionState {
104 for binding in &self.bindings {
105 if binding.action != action {
106 continue;
107 }
108 if !binding.active_modes.is_empty() && !binding.active_modes.contains(&self.mode) {
110 continue;
111 }
112 let state = query::evaluate_trigger(
113 &binding.trigger.kind,
114 &binding.trigger.activation,
115 &binding.trigger.modifiers,
116 binding.trigger.ignore_modifiers,
117 input,
118 );
119 if !matches!(state, ActionState::Inactive) {
120 return state;
121 }
122 }
123 ActionState::Inactive
124 }
125
126 pub fn bindings(&self) -> &[Binding] {
128 &self.bindings
129 }
130
131 pub fn set_bindings(&mut self, bindings: Vec<Binding>) {
133 self.bindings = bindings;
134 }
135}
136
137impl Default for InputSystem {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use binding::{KeyCode, Modifiers, MouseButton};
147 use query::FrameInput;
148
149 fn input_with_left_drag() -> FrameInput {
150 let mut input = FrameInput::default();
151 input.dragging.insert(MouseButton::Left);
152 input.drag_delta = glam::Vec2::new(10.0, 5.0);
153 input.hovered = true;
154 input
155 }
156
157 #[test]
158 fn test_query_orbit_active() {
159 let sys = InputSystem::new();
160 let mut input = input_with_left_drag();
161 input.modifiers = Modifiers::ALT;
162 let state = sys.query(Action::Orbit, &input);
163 assert!(
164 state.is_active(),
165 "orbit should be active on alt+left-drag in Normal mode"
166 );
167 }
168
169 #[test]
170 fn test_query_orbit_inactive_without_alt() {
171 let sys = InputSystem::new();
172 let input = input_with_left_drag();
173 let state = sys.query(Action::Orbit, &input);
174 assert!(
175 !state.is_active(),
176 "orbit should be inactive on plain left-drag in Normal mode"
177 );
178 }
179
180 #[test]
181 fn test_mode_filtering() {
182 let mut sys = InputSystem::new();
183 sys.set_mode(InputMode::FlyMode);
184 let input = input_with_left_drag();
185 let state = sys.query(Action::Orbit, &input);
187 assert!(!state.is_active(), "orbit should be inactive in FlyMode");
188 }
189
190 #[test]
191 fn test_modifier_matching() {
192 let sys = InputSystem::new();
193 let mut input = FrameInput::default();
195 input.dragging.insert(MouseButton::Left);
196 input.drag_delta = glam::Vec2::new(10.0, 5.0);
197 input.modifiers = Modifiers::SHIFT;
198 let state = sys.query(Action::Pan, &input);
199 assert!(
200 state.is_active(),
201 "pan should be active with shift+left drag"
202 );
203
204 let mut input2 = FrameInput::default();
206 input2.dragging.insert(MouseButton::Left);
207 input2.drag_delta = glam::Vec2::new(10.0, 5.0);
208 input2.modifiers = Modifiers::CTRL;
209 let state2 = sys.query(Action::Pan, &input2);
210 assert!(
211 !state2.is_active(),
212 "pan should be inactive with ctrl modifier"
213 );
214 }
215
216 #[test]
217 fn test_ignore_modifiers() {
218 let mut sys = InputSystem::new();
219 sys.set_mode(InputMode::FlyMode);
220 let mut input = FrameInput::default();
222 input.keys_held.insert(KeyCode::W);
223 input.modifiers = Modifiers::SHIFT;
224 let state = sys.query(Action::FlyForward, &input);
225 assert!(
226 state.is_active(),
227 "fly forward should be active with shift held (ignore_modifiers)"
228 );
229 }
230
231 #[test]
232 fn test_empty_input_inactive() {
233 let sys = InputSystem::new();
234 let input = FrameInput::default();
235 assert!(!sys.query(Action::Orbit, &input).is_active());
236 assert!(!sys.query(Action::Pan, &input).is_active());
237 assert!(!sys.query(Action::Zoom, &input).is_active());
238 assert!(!sys.query(Action::FocusObject, &input).is_active());
239 }
240}