viewport_lib/interaction/input/
mod.rs1pub mod action;
9pub mod binding;
11pub mod defaults;
13pub mod mode;
15pub mod query;
17
18pub use action::Action;
19pub use binding::{ActivationMode, Binding, KeyCode, Modifiers, MouseButton, Trigger, TriggerKind};
20pub use defaults::default_bindings;
21pub use mode::InputMode;
22pub use query::{ActionState, FrameInput};
23
24pub struct InputSystem {
27 bindings: Vec<Binding>,
28 mode: InputMode,
29}
30
31impl InputSystem {
32 pub fn new() -> Self {
34 Self {
35 bindings: default_bindings(),
36 mode: InputMode::Normal,
37 }
38 }
39
40 pub fn mode(&self) -> InputMode {
42 self.mode
43 }
44
45 pub fn set_mode(&mut self, mode: InputMode) {
47 self.mode = mode;
48 }
49
50 pub fn query(&self, action: Action, input: &FrameInput) -> ActionState {
55 for binding in &self.bindings {
56 if binding.action != action {
57 continue;
58 }
59 if !binding.active_modes.is_empty() && !binding.active_modes.contains(&self.mode) {
61 continue;
62 }
63 let state = query::evaluate_trigger(
64 &binding.trigger.kind,
65 &binding.trigger.activation,
66 &binding.trigger.modifiers,
67 binding.trigger.ignore_modifiers,
68 input,
69 );
70 if !matches!(state, ActionState::Inactive) {
71 return state;
72 }
73 }
74 ActionState::Inactive
75 }
76
77 pub fn bindings(&self) -> &[Binding] {
79 &self.bindings
80 }
81
82 pub fn set_bindings(&mut self, bindings: Vec<Binding>) {
84 self.bindings = bindings;
85 }
86}
87
88impl Default for InputSystem {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use binding::{KeyCode, Modifiers, MouseButton};
98 use query::FrameInput;
99
100 fn input_with_left_drag() -> FrameInput {
101 let mut input = FrameInput::default();
102 input.dragging.insert(MouseButton::Left);
103 input.drag_delta = glam::Vec2::new(10.0, 5.0);
104 input.hovered = true;
105 input
106 }
107
108 #[test]
109 fn test_query_orbit_active() {
110 let sys = InputSystem::new();
111 let mut input = input_with_left_drag();
112 input.modifiers = Modifiers::ALT;
113 let state = sys.query(Action::Orbit, &input);
114 assert!(
115 state.is_active(),
116 "orbit should be active on alt+left-drag in Normal mode"
117 );
118 }
119
120 #[test]
121 fn test_query_orbit_inactive_without_alt() {
122 let sys = InputSystem::new();
123 let input = input_with_left_drag();
124 let state = sys.query(Action::Orbit, &input);
125 assert!(
126 !state.is_active(),
127 "orbit should be inactive on plain left-drag in Normal mode"
128 );
129 }
130
131 #[test]
132 fn test_mode_filtering() {
133 let mut sys = InputSystem::new();
134 sys.set_mode(InputMode::FlyMode);
135 let input = input_with_left_drag();
136 let state = sys.query(Action::Orbit, &input);
138 assert!(!state.is_active(), "orbit should be inactive in FlyMode");
139 }
140
141 #[test]
142 fn test_modifier_matching() {
143 let sys = InputSystem::new();
144 let mut input = FrameInput::default();
146 input.dragging.insert(MouseButton::Left);
147 input.drag_delta = glam::Vec2::new(10.0, 5.0);
148 input.modifiers = Modifiers::SHIFT;
149 let state = sys.query(Action::Pan, &input);
150 assert!(
151 state.is_active(),
152 "pan should be active with shift+left drag"
153 );
154
155 let mut input2 = FrameInput::default();
157 input2.dragging.insert(MouseButton::Left);
158 input2.drag_delta = glam::Vec2::new(10.0, 5.0);
159 input2.modifiers = Modifiers::CTRL;
160 let state2 = sys.query(Action::Pan, &input2);
161 assert!(
162 !state2.is_active(),
163 "pan should be inactive with ctrl modifier"
164 );
165 }
166
167 #[test]
168 fn test_ignore_modifiers() {
169 let mut sys = InputSystem::new();
170 sys.set_mode(InputMode::FlyMode);
171 let mut input = FrameInput::default();
173 input.keys_held.insert(KeyCode::W);
174 input.modifiers = Modifiers::SHIFT;
175 let state = sys.query(Action::FlyForward, &input);
176 assert!(
177 state.is_active(),
178 "fly forward should be active with shift held (ignore_modifiers)"
179 );
180 }
181
182 #[test]
183 fn test_empty_input_inactive() {
184 let sys = InputSystem::new();
185 let input = FrameInput::default();
186 assert!(!sys.query(Action::Orbit, &input).is_active());
187 assert!(!sys.query(Action::Pan, &input).is_active());
188 assert!(!sys.query(Action::Zoom, &input).is_active());
189 assert!(!sys.query(Action::FocusObject, &input).is_active());
190 }
191}