viewport_lib/interaction/input/
viewport_input.rs1use std::collections::HashSet;
7
8use super::action::Action;
9use super::action_frame::{ActionFrame, NavigationActions, ResolvedActionState};
10use super::binding::{KeyCode, Modifiers, MouseButton};
11use super::context::ViewportContext;
12use super::event::{ButtonState, ScrollUnits, ViewportEvent};
13use super::preset::{BindingPreset, viewport_all_bindings, viewport_primitives_bindings};
14use super::viewport_binding::{ViewportBinding, ViewportGesture};
15
16const PIXELS_PER_LINE: f32 = 28.0;
18
19pub struct ViewportInput {
38 bindings: Vec<ViewportBinding>,
39
40 drag_delta: glam::Vec2,
42 wheel_delta: glam::Vec2, keys_pressed: HashSet<KeyCode>,
46
47 pointer_pos: Option<glam::Vec2>,
49 button_held: [bool; 3], button_press_pos: [Option<glam::Vec2>; 3],
53 modifiers: Modifiers,
54 keys_held: HashSet<KeyCode>,
56
57 ctx: ViewportContext,
58}
59
60fn button_index(b: MouseButton) -> usize {
61 match b {
62 MouseButton::Left => 0,
63 MouseButton::Right => 1,
64 MouseButton::Middle => 2,
65 }
66}
67
68impl ViewportInput {
69 pub fn new(bindings: Vec<ViewportBinding>) -> Self {
71 Self {
72 bindings,
73 drag_delta: glam::Vec2::ZERO,
74 wheel_delta: glam::Vec2::ZERO,
75 keys_pressed: HashSet::new(),
76 pointer_pos: None,
77 button_held: [false; 3],
78 button_press_pos: [None, None, None],
79 modifiers: Modifiers::NONE,
80 keys_held: HashSet::new(),
81 ctx: ViewportContext::default(),
82 }
83 }
84
85 pub fn from_preset(preset: BindingPreset) -> Self {
87 let bindings = match preset {
88 BindingPreset::ViewportPrimitives => viewport_primitives_bindings(),
89 BindingPreset::ViewportAll => viewport_all_bindings(),
90 };
91 Self::new(bindings)
92 }
93
94 pub fn begin_frame(&mut self, ctx: ViewportContext) {
100 self.ctx = ctx;
101 self.drag_delta = glam::Vec2::ZERO;
102 self.wheel_delta = glam::Vec2::ZERO;
103 self.keys_pressed.clear();
104 }
106
107 pub fn push_event(&mut self, event: ViewportEvent) {
109 match event {
110 ViewportEvent::PointerMoved { position } => {
111 if let Some(prev) = self.pointer_pos {
112 if self.button_held.iter().any(|&h| h) {
114 self.drag_delta += position - prev;
115 }
116 }
117 self.pointer_pos = Some(position);
118 }
119 ViewportEvent::MouseButton { button, state } => {
120 let idx = button_index(button);
121 match state {
122 ButtonState::Pressed => {
123 self.button_held[idx] = true;
124 self.button_press_pos[idx] = self.pointer_pos;
125 }
126 ButtonState::Released => {
127 self.button_held[idx] = false;
128 self.button_press_pos[idx] = None;
129 }
130 }
131 }
132 ViewportEvent::Wheel { delta, units } => {
133 let scale = match units {
134 ScrollUnits::Lines => PIXELS_PER_LINE,
135 ScrollUnits::Pixels => 1.0,
136 };
137 if self.ctx.hovered {
139 self.wheel_delta += delta * scale;
140 }
141 }
142 ViewportEvent::ModifiersChanged(mods) => {
143 self.modifiers = mods;
144 }
145 ViewportEvent::Key { key, state, repeat } => {
146 if !self.ctx.focused {
148 return;
149 }
150 match state {
151 ButtonState::Pressed => {
152 if !repeat {
153 self.keys_pressed.insert(key);
154 }
155 self.keys_held.insert(key);
156 }
157 ButtonState::Released => {
158 self.keys_held.remove(&key);
159 }
160 }
161 }
162 ViewportEvent::PointerLeft => {
163 self.pointer_pos = None;
164 for held in &mut self.button_held {
166 *held = false;
167 }
168 for pos in &mut self.button_press_pos {
169 *pos = None;
170 }
171 }
172 ViewportEvent::FocusLost => {
173 for held in &mut self.button_held {
175 *held = false;
176 }
177 for pos in &mut self.button_press_pos {
178 *pos = None;
179 }
180 self.keys_held.clear();
181 self.keys_pressed.clear();
182 }
183 }
184 }
185
186 pub fn resolve(&self) -> ActionFrame {
190 let mut orbit = glam::Vec2::ZERO;
191 let mut pan = glam::Vec2::ZERO;
192 let mut zoom = 0.0f32;
193 let mut actions = std::collections::HashMap::new();
194
195 let any_held_with_press = self.button_held.iter().enumerate().any(|(i, &held)| {
198 held && self.button_press_pos[i].is_some()
199 });
200 let pointer_active = self.ctx.hovered || any_held_with_press;
201
202 for binding in &self.bindings {
203 match &binding.gesture {
204 ViewportGesture::Drag { button, modifiers } => {
205 if !pointer_active {
206 continue;
207 }
208 let idx = button_index(*button);
209 let held = self.button_held[idx];
210 let press_started = self.button_press_pos[idx].is_some();
211 if held && press_started && modifiers.matches(self.modifiers) {
212 let delta = self.drag_delta;
213 match binding.action {
214 Action::Orbit => {
215 if orbit == glam::Vec2::ZERO {
216 orbit += delta;
217 actions.entry(binding.action).or_insert(
218 ResolvedActionState::Delta(delta),
219 );
220 }
221 }
222 Action::Pan => {
223 if pan == glam::Vec2::ZERO {
224 pan += delta;
225 actions.entry(binding.action).or_insert(
226 ResolvedActionState::Delta(delta),
227 );
228 }
229 }
230 Action::Zoom => {
231 if zoom == 0.0 {
232 zoom += delta.y;
233 actions.entry(binding.action).or_insert(
234 ResolvedActionState::Delta(delta),
235 );
236 }
237 }
238 _ => {
239 actions.entry(binding.action).or_insert(
240 ResolvedActionState::Delta(delta),
241 );
242 }
243 }
244 }
245 }
246 ViewportGesture::WheelY { modifiers } => {
247 if !pointer_active {
248 continue;
249 }
250 if modifiers.matches(self.modifiers) && self.wheel_delta.y != 0.0 {
251 let y = self.wheel_delta.y;
252 match binding.action {
253 Action::Zoom => zoom += y,
254 Action::Orbit => orbit.y += y,
255 Action::Pan => pan.y += y,
256 _ => {}
257 }
258 actions.entry(binding.action).or_insert(
259 ResolvedActionState::Delta(glam::Vec2::new(0.0, y)),
260 );
261 }
262 }
263 ViewportGesture::WheelXY { modifiers } => {
264 if !pointer_active {
265 continue;
266 }
267 if modifiers.matches(self.modifiers) && self.wheel_delta != glam::Vec2::ZERO {
268 let delta = self.wheel_delta;
269 match binding.action {
270 Action::Orbit => orbit += delta,
271 Action::Pan => pan += delta,
272 Action::Zoom => zoom += delta.y,
273 _ => {}
274 }
275 actions.entry(binding.action).or_insert(
276 ResolvedActionState::Delta(delta),
277 );
278 }
279 }
280 ViewportGesture::KeyPress { key, modifiers } => {
281 if self.keys_pressed.contains(key) && modifiers.matches(self.modifiers) {
282 actions.entry(binding.action).or_insert(ResolvedActionState::Pressed);
283 }
284 }
285 ViewportGesture::KeyHold { key, modifiers } => {
286 if self.keys_held.contains(key) && modifiers.matches(self.modifiers) {
287 actions.entry(binding.action).or_insert(ResolvedActionState::Held);
288 }
289 }
290 }
291 }
292
293 ActionFrame {
294 navigation: NavigationActions { orbit, pan, zoom },
295 actions,
296 }
297 }
298
299 pub fn modifiers(&self) -> Modifiers {
301 self.modifiers
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::interaction::input::preset::viewport_all_bindings;
309 use crate::interaction::input::event::ButtonState;
310
311 fn focused_ctx() -> ViewportContext {
312 ViewportContext {
313 hovered: true,
314 focused: true,
315 viewport_size: [800.0, 600.0],
316 }
317 }
318
319 #[test]
320 fn key_press_fires_once_then_clears() {
321 let mut input = ViewportInput::new(viewport_all_bindings());
322 input.begin_frame(focused_ctx());
323 input.push_event(ViewportEvent::Key {
324 key: KeyCode::F,
325 state: ButtonState::Pressed,
326 repeat: false,
327 });
328 let frame = input.resolve();
329 assert!(frame.is_active(Action::FocusObject), "FocusObject should be active on first frame");
330
331 input.begin_frame(focused_ctx());
333 let frame2 = input.resolve();
334 assert!(!frame2.is_active(Action::FocusObject), "FocusObject should not be active on second frame");
335 }
336
337 #[test]
338 fn key_ignored_when_not_focused() {
339 let mut input = ViewportInput::new(viewport_all_bindings());
340 input.begin_frame(ViewportContext {
341 hovered: true,
342 focused: false,
343 viewport_size: [800.0, 600.0],
344 });
345 input.push_event(ViewportEvent::Key {
346 key: KeyCode::F,
347 state: ButtonState::Pressed,
348 repeat: false,
349 });
350 let frame = input.resolve();
351 assert!(!frame.is_active(Action::FocusObject), "key should be ignored without focus");
352 }
353}