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, rotate_gesture: f32, keys_pressed: HashSet<KeyCode>,
47 typed_chars: Vec<char>,
49
50 pointer_pos: Option<glam::Vec2>,
52 button_held: [bool; 3], button_press_pos: [Option<glam::Vec2>; 3],
56 modifiers: Modifiers,
57 keys_held: HashSet<KeyCode>,
59
60 ctx: ViewportContext,
61}
62
63fn button_index(b: MouseButton) -> usize {
64 match b {
65 MouseButton::Left => 0,
66 MouseButton::Right => 1,
67 MouseButton::Middle => 2,
68 }
69}
70
71impl ViewportInput {
72 pub fn new(bindings: Vec<ViewportBinding>) -> Self {
74 Self {
75 bindings,
76 drag_delta: glam::Vec2::ZERO,
77 wheel_delta: glam::Vec2::ZERO,
78 rotate_gesture: 0.0,
79 keys_pressed: HashSet::new(),
80 typed_chars: Vec::new(),
81 pointer_pos: None,
82 button_held: [false; 3],
83 button_press_pos: [None, None, None],
84 modifiers: Modifiers::NONE,
85 keys_held: HashSet::new(),
86 ctx: ViewportContext::default(),
87 }
88 }
89
90 pub fn from_preset(preset: BindingPreset) -> Self {
92 let bindings = match preset {
93 BindingPreset::ViewportPrimitives => viewport_primitives_bindings(),
94 BindingPreset::ViewportAll => viewport_all_bindings(),
95 };
96 Self::new(bindings)
97 }
98
99 pub fn begin_frame(&mut self, ctx: ViewportContext) {
105 self.ctx = ctx;
106 self.drag_delta = glam::Vec2::ZERO;
107 self.wheel_delta = glam::Vec2::ZERO;
108 self.rotate_gesture = 0.0;
109 self.keys_pressed.clear();
110 self.typed_chars.clear();
111 }
113
114 pub fn push_event(&mut self, event: ViewportEvent) {
116 match event {
117 ViewportEvent::PointerMoved { position } => {
118 if let Some(prev) = self.pointer_pos {
119 if self.button_held.iter().any(|&h| h) {
121 self.drag_delta += position - prev;
122 }
123 }
124 self.pointer_pos = Some(position);
125 }
126 ViewportEvent::MouseButton { button, state } => {
127 let idx = button_index(button);
128 match state {
129 ButtonState::Pressed => {
130 self.button_held[idx] = true;
131 self.button_press_pos[idx] = self.pointer_pos;
132 }
133 ButtonState::Released => {
134 self.button_held[idx] = false;
135 self.button_press_pos[idx] = None;
136 }
137 }
138 }
139 ViewportEvent::Wheel { delta, units } => {
140 let scale = match units {
141 ScrollUnits::Lines => PIXELS_PER_LINE,
142 ScrollUnits::Pixels => 1.0,
143 };
144 if self.ctx.hovered {
146 self.wheel_delta += delta * scale;
147 }
148 }
149 ViewportEvent::ModifiersChanged(mods) => {
150 self.modifiers = mods;
151 }
152 ViewportEvent::Key { key, state, repeat } => {
153 if !self.ctx.focused {
155 return;
156 }
157 match state {
158 ButtonState::Pressed => {
159 if !repeat {
160 self.keys_pressed.insert(key);
161 }
162 self.keys_held.insert(key);
163 }
164 ButtonState::Released => {
165 self.keys_held.remove(&key);
166 }
167 }
168 }
169 ViewportEvent::Character(c) => {
170 if c.is_ascii_digit() || c == '.' || c == '-' {
174 self.typed_chars.push(c);
175 }
176 }
177 ViewportEvent::PointerLeft => {
178 self.pointer_pos = None;
179 for held in &mut self.button_held {
181 *held = false;
182 }
183 for pos in &mut self.button_press_pos {
184 *pos = None;
185 }
186 }
187 ViewportEvent::FocusLost => {
188 for held in &mut self.button_held {
190 *held = false;
191 }
192 for pos in &mut self.button_press_pos {
193 *pos = None;
194 }
195 self.keys_held.clear();
196 self.keys_pressed.clear();
197 }
198 ViewportEvent::TrackpadRotate(angle) => {
199 if self.ctx.hovered {
200 self.rotate_gesture += angle;
201 }
202 }
203 }
204 }
205
206 pub fn resolve(&self) -> ActionFrame {
210 let mut orbit = glam::Vec2::ZERO;
211 let mut pan = glam::Vec2::ZERO;
212 let mut zoom = 0.0f32;
213 let mut actions = std::collections::HashMap::new();
214
215 let any_held_with_press = self
218 .button_held
219 .iter()
220 .enumerate()
221 .any(|(i, &held)| held && self.button_press_pos[i].is_some());
222 let pointer_active = self.ctx.hovered || any_held_with_press;
223
224 for binding in &self.bindings {
225 match &binding.gesture {
226 ViewportGesture::Drag { button, modifiers } => {
227 if !pointer_active {
228 continue;
229 }
230 let idx = button_index(*button);
231 let held = self.button_held[idx];
232 let press_started = self.button_press_pos[idx].is_some();
233 if held && press_started && modifiers.matches(self.modifiers) {
234 let delta = self.drag_delta;
235 match binding.action {
236 Action::Orbit => {
237 if orbit == glam::Vec2::ZERO {
238 orbit += delta;
239 actions
240 .entry(binding.action)
241 .or_insert(ResolvedActionState::Delta(delta));
242 }
243 }
244 Action::Pan => {
245 if pan == glam::Vec2::ZERO {
246 pan += delta;
247 actions
248 .entry(binding.action)
249 .or_insert(ResolvedActionState::Delta(delta));
250 }
251 }
252 Action::Zoom => {
253 if zoom == 0.0 {
254 zoom += delta.y;
255 actions
256 .entry(binding.action)
257 .or_insert(ResolvedActionState::Delta(delta));
258 }
259 }
260 _ => {
261 actions
262 .entry(binding.action)
263 .or_insert(ResolvedActionState::Delta(delta));
264 }
265 }
266 }
267 }
268 ViewportGesture::WheelY { modifiers } => {
269 if !pointer_active {
270 continue;
271 }
272 if modifiers.matches(self.modifiers) && self.wheel_delta.y != 0.0 {
273 let y = self.wheel_delta.y;
274 match binding.action {
275 Action::Zoom => zoom += y,
276 Action::Orbit => orbit.y += y,
277 Action::Pan => pan.y += y,
278 _ => {}
279 }
280 actions
281 .entry(binding.action)
282 .or_insert(ResolvedActionState::Delta(glam::Vec2::new(0.0, y)));
283 }
284 }
285 ViewportGesture::WheelXY { modifiers } => {
286 if !pointer_active {
287 continue;
288 }
289 if modifiers.matches(self.modifiers) && self.wheel_delta != glam::Vec2::ZERO {
290 let delta = self.wheel_delta;
291 match binding.action {
292 Action::Orbit => orbit += delta,
293 Action::Pan => pan += delta,
294 Action::Zoom => zoom += delta.y,
295 _ => {}
296 }
297 actions
298 .entry(binding.action)
299 .or_insert(ResolvedActionState::Delta(delta));
300 }
301 }
302 ViewportGesture::KeyPress { key, modifiers } => {
303 if self.keys_pressed.contains(key) && modifiers.matches(self.modifiers) {
304 actions
305 .entry(binding.action)
306 .or_insert(ResolvedActionState::Pressed);
307 }
308 }
309 ViewportGesture::KeyHold { key, modifiers } => {
310 if self.keys_held.contains(key) && modifiers.matches(self.modifiers) {
311 actions
312 .entry(binding.action)
313 .or_insert(ResolvedActionState::Held);
314 }
315 }
316 }
317 }
318
319 ActionFrame {
320 navigation: NavigationActions {
321 orbit,
322 pan,
323 zoom,
324 twist: self.rotate_gesture,
325 },
326 actions,
327 typed_chars: self.typed_chars.clone(),
328 }
329 }
330
331 pub fn modifiers(&self) -> Modifiers {
333 self.modifiers
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::interaction::input::event::ButtonState;
341 use crate::interaction::input::preset::viewport_all_bindings;
342
343 fn focused_ctx() -> ViewportContext {
344 ViewportContext {
345 hovered: true,
346 focused: true,
347 viewport_size: [800.0, 600.0],
348 }
349 }
350
351 #[test]
352 fn key_press_fires_once_then_clears() {
353 let mut input = ViewportInput::new(viewport_all_bindings());
354 input.begin_frame(focused_ctx());
355 input.push_event(ViewportEvent::Key {
356 key: KeyCode::F,
357 state: ButtonState::Pressed,
358 repeat: false,
359 });
360 let frame = input.resolve();
361 assert!(
362 frame.is_active(Action::FocusObject),
363 "FocusObject should be active on first frame"
364 );
365
366 input.begin_frame(focused_ctx());
368 let frame2 = input.resolve();
369 assert!(
370 !frame2.is_active(Action::FocusObject),
371 "FocusObject should not be active on second frame"
372 );
373 }
374
375 #[test]
376 fn key_ignored_when_not_focused() {
377 let mut input = ViewportInput::new(viewport_all_bindings());
378 input.begin_frame(ViewportContext {
379 hovered: true,
380 focused: false,
381 viewport_size: [800.0, 600.0],
382 });
383 input.push_event(ViewportEvent::Key {
384 key: KeyCode::F,
385 state: ButtonState::Pressed,
386 repeat: false,
387 });
388 let frame = input.resolve();
389 assert!(
390 !frame.is_active(Action::FocusObject),
391 "key should be ignored without focus"
392 );
393 }
394}