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 event;
37pub mod preset;
39pub mod viewport_binding;
41pub mod viewport_input;
43
44pub use action::Action;
46pub use binding::{ActivationMode, Binding, KeyCode, Modifiers, MouseButton, Trigger, TriggerKind};
47pub use defaults::default_bindings;
48pub use mode::{InputMode, NavigationMode};
49pub use query::{ActionState, FrameInput};
50
51pub use action_frame::{ActionFrame, NavigationActions, ResolvedActionState};
53pub use context::ViewportContext;
54pub use event::{ButtonState, ScrollUnits, ViewportEvent};
55pub use preset::{BindingPreset, viewport_all_bindings};
56pub use viewport_binding::{ModifiersMatch, ViewportBinding, ViewportGesture};
57pub use viewport_input::ViewportInput;
58
59pub struct InputSystem {
62 bindings: Vec<Binding>,
63 mode: InputMode,
64}
65
66impl InputSystem {
67 pub fn new() -> Self {
69 Self {
70 bindings: default_bindings(),
71 mode: InputMode::Normal,
72 }
73 }
74
75 pub fn mode(&self) -> InputMode {
77 self.mode
78 }
79
80 pub fn set_mode(&mut self, mode: InputMode) {
82 self.mode = mode;
83 }
84
85 pub fn query(&self, action: Action, input: &FrameInput) -> ActionState {
90 for binding in &self.bindings {
91 if binding.action != action {
92 continue;
93 }
94 if !binding.active_modes.is_empty() && !binding.active_modes.contains(&self.mode) {
96 continue;
97 }
98 let state = query::evaluate_trigger(
99 &binding.trigger.kind,
100 &binding.trigger.activation,
101 &binding.trigger.modifiers,
102 binding.trigger.ignore_modifiers,
103 input,
104 );
105 if !matches!(state, ActionState::Inactive) {
106 return state;
107 }
108 }
109 ActionState::Inactive
110 }
111
112 pub fn bindings(&self) -> &[Binding] {
114 &self.bindings
115 }
116
117 pub fn set_bindings(&mut self, bindings: Vec<Binding>) {
119 self.bindings = bindings;
120 }
121}
122
123impl Default for InputSystem {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use binding::{KeyCode, Modifiers, MouseButton};
133 use query::FrameInput;
134
135 fn input_with_left_drag() -> FrameInput {
136 let mut input = FrameInput::default();
137 input.dragging.insert(MouseButton::Left);
138 input.drag_delta = glam::Vec2::new(10.0, 5.0);
139 input.hovered = true;
140 input
141 }
142
143 #[test]
144 fn test_query_orbit_active() {
145 let sys = InputSystem::new();
146 let mut input = input_with_left_drag();
147 input.modifiers = Modifiers::ALT;
148 let state = sys.query(Action::Orbit, &input);
149 assert!(
150 state.is_active(),
151 "orbit should be active on alt+left-drag in Normal mode"
152 );
153 }
154
155 #[test]
156 fn test_query_orbit_inactive_without_alt() {
157 let sys = InputSystem::new();
158 let input = input_with_left_drag();
159 let state = sys.query(Action::Orbit, &input);
160 assert!(
161 !state.is_active(),
162 "orbit should be inactive on plain left-drag in Normal mode"
163 );
164 }
165
166 #[test]
167 fn test_mode_filtering() {
168 let mut sys = InputSystem::new();
169 sys.set_mode(InputMode::FlyMode);
170 let input = input_with_left_drag();
171 let state = sys.query(Action::Orbit, &input);
173 assert!(!state.is_active(), "orbit should be inactive in FlyMode");
174 }
175
176 #[test]
177 fn test_modifier_matching() {
178 let sys = InputSystem::new();
179 let mut input = FrameInput::default();
181 input.dragging.insert(MouseButton::Left);
182 input.drag_delta = glam::Vec2::new(10.0, 5.0);
183 input.modifiers = Modifiers::SHIFT;
184 let state = sys.query(Action::Pan, &input);
185 assert!(
186 state.is_active(),
187 "pan should be active with shift+left drag"
188 );
189
190 let mut input2 = FrameInput::default();
192 input2.dragging.insert(MouseButton::Left);
193 input2.drag_delta = glam::Vec2::new(10.0, 5.0);
194 input2.modifiers = Modifiers::CTRL;
195 let state2 = sys.query(Action::Pan, &input2);
196 assert!(
197 !state2.is_active(),
198 "pan should be inactive with ctrl modifier"
199 );
200 }
201
202 #[test]
203 fn test_ignore_modifiers() {
204 let mut sys = InputSystem::new();
205 sys.set_mode(InputMode::FlyMode);
206 let mut input = FrameInput::default();
208 input.keys_held.insert(KeyCode::W);
209 input.modifiers = Modifiers::SHIFT;
210 let state = sys.query(Action::FlyForward, &input);
211 assert!(
212 state.is_active(),
213 "fly forward should be active with shift held (ignore_modifiers)"
214 );
215 }
216
217 #[test]
218 fn test_empty_input_inactive() {
219 let sys = InputSystem::new();
220 let input = FrameInput::default();
221 assert!(!sys.query(Action::Orbit, &input).is_active());
222 assert!(!sys.query(Action::Pan, &input).is_active());
223 assert!(!sys.query(Action::Zoom, &input).is_active());
224 assert!(!sys.query(Action::FocusObject, &input).is_active());
225 }
226}