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