egui/input_state.rs
1mod touch_state;
2
3use crate::data::input::*;
4use crate::{emath::*, util::History};
5use std::{
6 collections::{BTreeMap, HashSet},
7 time::Duration,
8};
9
10pub use crate::Key;
11pub use touch_state::MultiTouchInfo;
12use touch_state::TouchState;
13
14/// If the pointer moves more than this, it won't become a click (but it is still a drag)
15const MAX_CLICK_DIST: f32 = 6.0; // TODO(emilk): move to settings
16
17/// If the pointer is down for longer than this it will no longer register as a click.
18///
19/// If a touch is held for this many seconds while still,
20/// then it will register as a "long-touch" which is equivalent to a secondary click.
21///
22/// This is to support "press and hold for context menu" on touch screens.
23const MAX_CLICK_DURATION: f64 = 0.8; // TODO(emilk): move to settings
24
25/// The new pointer press must come within this many seconds from previous pointer release
26const MAX_DOUBLE_CLICK_DELAY: f64 = 0.3; // TODO(emilk): move to settings
27
28/// Input state that egui updates each frame.
29///
30/// You can access this with [`crate::Context::input`].
31///
32/// You can check if `egui` is using the inputs using
33/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
34#[derive(Clone, Debug)]
35#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
36pub struct InputState {
37 /// The raw input we got this frame from the backend.
38 pub raw: RawInput,
39
40 /// State of the mouse or simple touch gestures which can be mapped to mouse operations.
41 pub pointer: PointerState,
42
43 /// State of touches, except those covered by `PointerState` (like clicks and drags).
44 /// (We keep a separate [`TouchState`] for each encountered touch device.)
45 touch_states: BTreeMap<TouchDeviceId, TouchState>,
46
47 // ----------------------------------------------
48 // Scrolling:
49 //
50 /// Time of the last scroll event.
51 last_scroll_time: f64,
52
53 /// Used for smoothing the scroll delta.
54 unprocessed_scroll_delta: Vec2,
55
56 /// Used for smoothing the scroll delta when zooming.
57 unprocessed_scroll_delta_for_zoom: f32,
58
59 /// You probably want to use [`Self::smooth_scroll_delta`] instead.
60 ///
61 /// The raw input of how many points the user scrolled.
62 ///
63 /// The delta dictates how the _content_ should move.
64 ///
65 /// A positive X-value indicates the content is being moved right,
66 /// as when swiping right on a touch-screen or track-pad with natural scrolling.
67 ///
68 /// A positive Y-value indicates the content is being moved down,
69 /// as when swiping down on a touch-screen or track-pad with natural scrolling.
70 ///
71 /// When using a notched scroll-wheel this will spike very large for one frame,
72 /// then drop to zero. For a smoother experience, use [`Self::smooth_scroll_delta`].
73 pub raw_scroll_delta: Vec2,
74
75 /// How many points the user scrolled, smoothed over a few frames.
76 ///
77 /// The delta dictates how the _content_ should move.
78 ///
79 /// A positive X-value indicates the content is being moved right,
80 /// as when swiping right on a touch-screen or track-pad with natural scrolling.
81 ///
82 /// A positive Y-value indicates the content is being moved down,
83 /// as when swiping down on a touch-screen or track-pad with natural scrolling.
84 ///
85 /// [`crate::ScrollArea`] will both read and write to this field, so that
86 /// at the end of the frame this will be zero if a scroll-area consumed the delta.
87 pub smooth_scroll_delta: Vec2,
88
89 /// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
90 ///
91 /// * `zoom = 1`: no change.
92 /// * `zoom < 1`: pinch together
93 /// * `zoom > 1`: pinch spread
94 zoom_factor_delta: f32,
95
96 // ----------------------------------------------
97 /// Position and size of the egui area.
98 pub screen_rect: Rect,
99
100 /// Also known as device pixel ratio, > 1 for high resolution screens.
101 pub pixels_per_point: f32,
102
103 /// Maximum size of one side of a texture.
104 ///
105 /// This depends on the backend.
106 pub max_texture_side: usize,
107
108 /// Time in seconds. Relative to whatever. Used for animation.
109 pub time: f64,
110
111 /// Time since last frame, in seconds.
112 ///
113 /// This can be very unstable in reactive mode (when we don't paint each frame).
114 /// For animations it is therefore better to use [`Self::stable_dt`].
115 pub unstable_dt: f32,
116
117 /// Estimated time until next frame (provided we repaint right away).
118 ///
119 /// Used for animations to get instant feedback (avoid frame delay).
120 /// Should be set to the expected time between frames when painting at vsync speeds.
121 ///
122 /// On most integrations this has a fixed value of `1.0 / 60.0`, so it is not a very accurate estimate.
123 pub predicted_dt: f32,
124
125 /// Time since last frame (in seconds), but gracefully handles the first frame after sleeping in reactive mode.
126 ///
127 /// In reactive mode (available in e.g. `eframe`), `egui` only updates when there is new input
128 /// or something is animating.
129 /// This can lead to large gaps of time (sleep), leading to large [`Self::unstable_dt`].
130 ///
131 /// If `egui` requested a repaint the previous frame, then `egui` will use
132 /// `stable_dt = unstable_dt;`, but if `egui` did not not request a repaint last frame,
133 /// then `egui` will assume `unstable_dt` is too large, and will use
134 /// `stable_dt = predicted_dt;`.
135 ///
136 /// This means that for the first frame after a sleep,
137 /// `stable_dt` will be a prediction of the delta-time until the next frame,
138 /// and in all other situations this will be an accurate measurement of time passed
139 /// since the previous frame.
140 ///
141 /// Note that a frame can still stall for various reasons, so `stable_dt` can
142 /// still be unusually large in some situations.
143 ///
144 /// When animating something, it is recommended that you use something like
145 /// `stable_dt.min(0.1)` - this will give you smooth animations when the framerate is good
146 /// (even in reactive mode), but will avoid large jumps when framerate is bad,
147 /// and will effectively slow down the animation when FPS drops below 10.
148 pub stable_dt: f32,
149
150 /// The native window has the keyboard focus (i.e. is receiving key presses).
151 ///
152 /// False when the user alt-tab away from the application, for instance.
153 pub focused: bool,
154
155 /// Which modifier keys are down at the start of the frame?
156 pub modifiers: Modifiers,
157
158 // The keys that are currently being held down.
159 pub keys_down: HashSet<Key>,
160
161 /// In-order events received this frame
162 pub events: Vec<Event>,
163}
164
165impl Default for InputState {
166 fn default() -> Self {
167 Self {
168 raw: Default::default(),
169 pointer: Default::default(),
170 touch_states: Default::default(),
171
172 last_scroll_time: f64::NEG_INFINITY,
173 unprocessed_scroll_delta: Vec2::ZERO,
174 unprocessed_scroll_delta_for_zoom: 0.0,
175 raw_scroll_delta: Vec2::ZERO,
176 smooth_scroll_delta: Vec2::ZERO,
177 zoom_factor_delta: 1.0,
178
179 screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
180 pixels_per_point: 1.0,
181 max_texture_side: 2048,
182 time: 0.0,
183 unstable_dt: 1.0 / 60.0,
184 predicted_dt: 1.0 / 60.0,
185 stable_dt: 1.0 / 60.0,
186 focused: false,
187 modifiers: Default::default(),
188 keys_down: Default::default(),
189 events: Default::default(),
190 }
191 }
192}
193
194impl InputState {
195 #[must_use]
196 pub fn begin_frame(
197 mut self,
198 mut new: RawInput,
199 requested_immediate_repaint_prev_frame: bool,
200 pixels_per_point: f32,
201 options: &crate::Options,
202 ) -> Self {
203 crate::profile_function!();
204
205 let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
206 let unstable_dt = (time - self.time) as f32;
207
208 let stable_dt = if requested_immediate_repaint_prev_frame {
209 // we should have had a repaint straight away,
210 // so this should be trustable.
211 unstable_dt
212 } else {
213 new.predicted_dt
214 };
215
216 let screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
217 self.create_touch_states_for_new_devices(&new.events);
218 for touch_state in self.touch_states.values_mut() {
219 touch_state.begin_frame(time, &new, self.pointer.interact_pos);
220 }
221 let pointer = self.pointer.begin_frame(time, &new);
222
223 let mut keys_down = self.keys_down;
224 let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor
225 let mut raw_scroll_delta = Vec2::ZERO;
226
227 let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta;
228 let mut unprocessed_scroll_delta_for_zoom = self.unprocessed_scroll_delta_for_zoom;
229 let mut smooth_scroll_delta = Vec2::ZERO;
230 let mut smooth_scroll_delta_for_zoom = 0.0;
231
232 for event in &mut new.events {
233 match event {
234 Event::Key {
235 key,
236 pressed,
237 repeat,
238 ..
239 } => {
240 if *pressed {
241 let first_press = keys_down.insert(*key);
242 *repeat = !first_press;
243 } else {
244 keys_down.remove(key);
245 }
246 }
247 Event::MouseWheel {
248 unit,
249 delta,
250 modifiers,
251 } => {
252 let mut delta = match unit {
253 MouseWheelUnit::Point => *delta,
254 MouseWheelUnit::Line => options.line_scroll_speed * *delta,
255 MouseWheelUnit::Page => screen_rect.height() * *delta,
256 };
257
258 if modifiers.shift {
259 // Treat as horizontal scrolling.
260 // Note: one Mac we already get horizontal scroll events when shift is down.
261 delta = vec2(delta.x + delta.y, 0.0);
262 }
263
264 raw_scroll_delta += delta;
265
266 // Mouse wheels often go very large steps.
267 // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta.
268 // So we smooth it out over several frames for a nicer user experience when scrolling in egui.
269 // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing,
270 // because it adds latency.
271 let is_smooth = match unit {
272 MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here
273 MouseWheelUnit::Line | MouseWheelUnit::Page => false,
274 };
275
276 let is_zoom = modifiers.ctrl || modifiers.mac_cmd || modifiers.command;
277
278 #[allow(clippy::collapsible_else_if)]
279 if is_zoom {
280 if is_smooth {
281 smooth_scroll_delta_for_zoom += delta.y;
282 } else {
283 unprocessed_scroll_delta_for_zoom += delta.y;
284 }
285 } else {
286 if is_smooth {
287 smooth_scroll_delta += delta;
288 } else {
289 unprocessed_scroll_delta += delta;
290 }
291 }
292 }
293 Event::Zoom(factor) => {
294 zoom_factor_delta *= *factor;
295 }
296 _ => {}
297 }
298 }
299
300 {
301 let dt = stable_dt.at_most(0.1);
302 let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize
303
304 if unprocessed_scroll_delta != Vec2::ZERO {
305 for d in 0..2 {
306 if unprocessed_scroll_delta[d].abs() < 1.0 {
307 smooth_scroll_delta[d] += unprocessed_scroll_delta[d];
308 unprocessed_scroll_delta[d] = 0.0;
309 } else {
310 let applied = t * unprocessed_scroll_delta[d];
311 smooth_scroll_delta[d] += applied;
312 unprocessed_scroll_delta[d] -= applied;
313 }
314 }
315 }
316
317 {
318 // Smooth scroll-to-zoom:
319 if unprocessed_scroll_delta_for_zoom.abs() < 1.0 {
320 smooth_scroll_delta_for_zoom += unprocessed_scroll_delta_for_zoom;
321 unprocessed_scroll_delta_for_zoom = 0.0;
322 } else {
323 let applied = t * unprocessed_scroll_delta_for_zoom;
324 smooth_scroll_delta_for_zoom += applied;
325 unprocessed_scroll_delta_for_zoom -= applied;
326 }
327
328 zoom_factor_delta *=
329 (options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp();
330 }
331 }
332
333 let is_scrolling = raw_scroll_delta != Vec2::ZERO || smooth_scroll_delta != Vec2::ZERO;
334 let last_scroll_time = if is_scrolling {
335 time
336 } else {
337 self.last_scroll_time
338 };
339
340 Self {
341 pointer,
342 touch_states: self.touch_states,
343
344 last_scroll_time,
345 unprocessed_scroll_delta,
346 unprocessed_scroll_delta_for_zoom,
347 raw_scroll_delta,
348 smooth_scroll_delta,
349 zoom_factor_delta,
350
351 screen_rect,
352 pixels_per_point,
353 max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side),
354 time,
355 unstable_dt,
356 predicted_dt: new.predicted_dt,
357 stable_dt,
358 focused: new.focused,
359 modifiers: new.modifiers,
360 keys_down,
361 events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events
362 raw: new,
363 }
364 }
365
366 /// Info about the active viewport
367 #[inline]
368 pub fn viewport(&self) -> &ViewportInfo {
369 self.raw.viewport()
370 }
371
372 #[inline(always)]
373 pub fn screen_rect(&self) -> Rect {
374 self.screen_rect
375 }
376
377 /// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
378 /// * `zoom = 1`: no change
379 /// * `zoom < 1`: pinch together
380 /// * `zoom > 1`: pinch spread
381 #[inline(always)]
382 pub fn zoom_delta(&self) -> f32 {
383 // If a multi touch gesture is detected, it measures the exact and linear proportions of
384 // the distances of the finger tips. It is therefore potentially more accurate than
385 // `zoom_factor_delta` which is based on the `ctrl-scroll` event which, in turn, may be
386 // synthesized from an original touch gesture.
387 self.multi_touch()
388 .map_or(self.zoom_factor_delta, |touch| touch.zoom_delta)
389 }
390
391 /// 2D non-proportional zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
392 ///
393 /// For multitouch devices the user can do a horizontal or vertical pinch gesture.
394 /// In these cases a non-proportional zoom factor is a available.
395 /// In other cases, this reverts to `Vec2::splat(self.zoom_delta())`.
396 ///
397 /// For horizontal pinches, this will return `[z, 1]`,
398 /// for vertical pinches this will return `[1, z]`,
399 /// and otherwise this will return `[z, z]`,
400 /// where `z` is the zoom factor:
401 /// * `zoom = 1`: no change
402 /// * `zoom < 1`: pinch together
403 /// * `zoom > 1`: pinch spread
404 #[inline(always)]
405 pub fn zoom_delta_2d(&self) -> Vec2 {
406 // If a multi touch gesture is detected, it measures the exact and linear proportions of
407 // the distances of the finger tips. It is therefore potentially more accurate than
408 // `zoom_factor_delta` which is based on the `ctrl-scroll` event which, in turn, may be
409 // synthesized from an original touch gesture.
410 self.multi_touch().map_or_else(
411 || Vec2::splat(self.zoom_factor_delta),
412 |touch| touch.zoom_delta_2d,
413 )
414 }
415
416 /// How long has it been (in seconds) since the use last scrolled?
417 #[inline(always)]
418 pub fn time_since_last_scroll(&self) -> f32 {
419 (self.time - self.last_scroll_time) as f32
420 }
421
422 /// The [`crate::Context`] will call this at the end of each frame to see if we need a repaint.
423 ///
424 /// Returns how long to wait for a repaint.
425 pub fn wants_repaint_after(&self) -> Option<Duration> {
426 if self.pointer.wants_repaint()
427 || self.unprocessed_scroll_delta.abs().max_elem() > 0.2
428 || self.unprocessed_scroll_delta_for_zoom.abs() > 0.2
429 || !self.events.is_empty()
430 {
431 // Immediate repaint
432 return Some(Duration::ZERO);
433 }
434
435 if self.any_touches() && !self.pointer.is_decidedly_dragging() {
436 // We need to wake up and check for press-and-hold for the context menu.
437 if let Some(press_start_time) = self.pointer.press_start_time {
438 let press_duration = self.time - press_start_time;
439 if press_duration < MAX_CLICK_DURATION {
440 let secs_until_menu = MAX_CLICK_DURATION - press_duration;
441 return Some(Duration::from_secs_f64(secs_until_menu));
442 }
443 }
444 }
445
446 None
447 }
448
449 /// Count presses of a key. If non-zero, the presses are consumed, so that this will only return non-zero once.
450 ///
451 /// Includes key-repeat events.
452 ///
453 /// This uses [`Modifiers::matches_logically`] to match modifiers,
454 /// meaning extra Shift and Alt modifiers are ignored.
455 /// Therefore, you should match most specific shortcuts first,
456 /// i.e. check for `Cmd-Shift-S` ("Save as…") before `Cmd-S` ("Save"),
457 /// so that a user pressing `Cmd-Shift-S` won't trigger the wrong command!
458 pub fn count_and_consume_key(&mut self, modifiers: Modifiers, logical_key: Key) -> usize {
459 let mut count = 0usize;
460
461 self.events.retain(|event| {
462 let is_match = matches!(
463 event,
464 Event::Key {
465 key: ev_key,
466 modifiers: ev_mods,
467 pressed: true,
468 ..
469 } if *ev_key == logical_key && ev_mods.matches_logically(modifiers)
470 );
471
472 count += is_match as usize;
473
474 !is_match
475 });
476
477 count
478 }
479
480 /// Check for a key press. If found, `true` is returned and the key pressed is consumed, so that this will only return `true` once.
481 ///
482 /// Includes key-repeat events.
483 ///
484 /// This uses [`Modifiers::matches_logically`] to match modifiers,
485 /// meaning extra Shift and Alt modifiers are ignored.
486 /// Therefore, you should match most specific shortcuts first,
487 /// i.e. check for `Cmd-Shift-S` ("Save as…") before `Cmd-S` ("Save"),
488 /// so that a user pressing `Cmd-Shift-S` won't trigger the wrong command!
489 pub fn consume_key(&mut self, modifiers: Modifiers, logical_key: Key) -> bool {
490 self.count_and_consume_key(modifiers, logical_key) > 0
491 }
492
493 /// Check if the given shortcut has been pressed.
494 ///
495 /// If so, `true` is returned and the key pressed is consumed, so that this will only return `true` once.
496 ///
497 /// This uses [`Modifiers::matches_logically`] to match modifiers,
498 /// meaning extra Shift and Alt modifiers are ignored.
499 /// Therefore, you should match most specific shortcuts first,
500 /// i.e. check for `Cmd-Shift-S` ("Save as…") before `Cmd-S` ("Save"),
501 /// so that a user pressing `Cmd-Shift-S` won't trigger the wrong command!
502 pub fn consume_shortcut(&mut self, shortcut: &KeyboardShortcut) -> bool {
503 let KeyboardShortcut {
504 modifiers,
505 logical_key,
506 } = *shortcut;
507 self.consume_key(modifiers, logical_key)
508 }
509
510 /// Was the given key pressed this frame?
511 ///
512 /// Includes key-repeat events.
513 pub fn key_pressed(&self, desired_key: Key) -> bool {
514 self.num_presses(desired_key) > 0
515 }
516
517 /// How many times was the given key pressed this frame?
518 ///
519 /// Includes key-repeat events.
520 pub fn num_presses(&self, desired_key: Key) -> usize {
521 self.events
522 .iter()
523 .filter(|event| {
524 matches!(
525 event,
526 Event::Key { key, pressed: true, .. }
527 if *key == desired_key
528 )
529 })
530 .count()
531 }
532
533 /// Is the given key currently held down?
534 pub fn key_down(&self, desired_key: Key) -> bool {
535 self.keys_down.contains(&desired_key)
536 }
537
538 /// Was the given key released this frame?
539 pub fn key_released(&self, desired_key: Key) -> bool {
540 self.events.iter().any(|event| {
541 matches!(
542 event,
543 Event::Key {
544 key,
545 pressed: false,
546 ..
547 } if *key == desired_key
548 )
549 })
550 }
551
552 /// Also known as device pixel ratio, > 1 for high resolution screens.
553 #[inline(always)]
554 pub fn pixels_per_point(&self) -> f32 {
555 self.pixels_per_point
556 }
557
558 /// Size of a physical pixel in logical gui coordinates (points).
559 #[inline(always)]
560 pub fn physical_pixel_size(&self) -> f32 {
561 1.0 / self.pixels_per_point()
562 }
563
564 /// How imprecise do we expect the mouse/touch input to be?
565 /// Returns imprecision in points.
566 #[inline(always)]
567 pub fn aim_radius(&self) -> f32 {
568 // TODO(emilk): multiply by ~3 for touch inputs because fingers are fat
569 self.physical_pixel_size()
570 }
571
572 /// Returns details about the currently ongoing multi-touch gesture, if any. Note that this
573 /// method returns `None` for single-touch gestures (click, drag, …).
574 ///
575 /// ```
576 /// # use egui::emath::Rot2;
577 /// # egui::__run_test_ui(|ui| {
578 /// let mut zoom = 1.0; // no zoom
579 /// let mut rotation = 0.0; // no rotation
580 /// let multi_touch = ui.input(|i| i.multi_touch());
581 /// if let Some(multi_touch) = multi_touch {
582 /// zoom *= multi_touch.zoom_delta;
583 /// rotation += multi_touch.rotation_delta;
584 /// }
585 /// let transform = zoom * Rot2::from_angle(rotation);
586 /// # });
587 /// ```
588 ///
589 /// By far not all touch devices are supported, and the details depend on the `egui`
590 /// integration backend you are using. `eframe` web supports multi touch for most mobile
591 /// devices, but not for a `Trackpad` on `MacOS`, for example. The backend has to be able to
592 /// capture native touch events, but many browsers seem to pass such events only for touch
593 /// _screens_, but not touch _pads._
594 ///
595 /// Refer to [`MultiTouchInfo`] for details about the touch information available.
596 ///
597 /// Consider using `zoom_delta()` instead of `MultiTouchInfo::zoom_delta` as the former
598 /// delivers a synthetic zoom factor based on ctrl-scroll events, as a fallback.
599 pub fn multi_touch(&self) -> Option<MultiTouchInfo> {
600 // In case of multiple touch devices simply pick the touch_state of the first active device
601 self.touch_states.values().find_map(|t| t.info())
602 }
603
604 /// True if there currently are any fingers touching egui.
605 pub fn any_touches(&self) -> bool {
606 self.touch_states.values().any(|t| t.any_touches())
607 }
608
609 /// True if we have ever received a touch event.
610 pub fn has_touch_screen(&self) -> bool {
611 !self.touch_states.is_empty()
612 }
613
614 /// Scans `events` for device IDs of touch devices we have not seen before,
615 /// and creates a new [`TouchState`] for each such device.
616 fn create_touch_states_for_new_devices(&mut self, events: &[Event]) {
617 for event in events {
618 if let Event::Touch { device_id, .. } = event {
619 self.touch_states
620 .entry(*device_id)
621 .or_insert_with(|| TouchState::new(*device_id));
622 }
623 }
624 }
625
626 #[cfg(feature = "accesskit")]
627 pub fn accesskit_action_requests(
628 &self,
629 id: crate::Id,
630 action: accesskit::Action,
631 ) -> impl Iterator<Item = &accesskit::ActionRequest> {
632 let accesskit_id = id.accesskit_id();
633 self.events.iter().filter_map(move |event| {
634 if let Event::AccessKitActionRequest(request) = event {
635 if request.target == accesskit_id && request.action == action {
636 return Some(request);
637 }
638 }
639 None
640 })
641 }
642
643 #[cfg(feature = "accesskit")]
644 pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool {
645 self.accesskit_action_requests(id, action).next().is_some()
646 }
647
648 #[cfg(feature = "accesskit")]
649 pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
650 self.accesskit_action_requests(id, action).count()
651 }
652
653 /// Get all events that matches the given filter.
654 pub fn filtered_events(&self, filter: &EventFilter) -> Vec<Event> {
655 self.events
656 .iter()
657 .filter(|event| filter.matches(event))
658 .cloned()
659 .collect()
660 }
661
662 /// A long press is something we detect on touch screens
663 /// to trigger a secondary click (context menu).
664 ///
665 /// Returns `true` only on one frame.
666 pub(crate) fn is_long_touch(&self) -> bool {
667 self.any_touches() && self.pointer.is_long_press()
668 }
669}
670
671// ----------------------------------------------------------------------------
672
673/// A pointer (mouse or touch) click.
674#[derive(Clone, Debug, PartialEq)]
675#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
676pub(crate) struct Click {
677 pub pos: Pos2,
678
679 /// 1 or 2 (double-click) or 3 (triple-click)
680 pub count: u32,
681
682 /// Allows you to check for e.g. shift-click
683 pub modifiers: Modifiers,
684}
685
686impl Click {
687 pub fn is_double(&self) -> bool {
688 self.count == 2
689 }
690
691 pub fn is_triple(&self) -> bool {
692 self.count == 3
693 }
694}
695
696#[derive(Clone, Debug, PartialEq)]
697#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
698pub(crate) enum PointerEvent {
699 Moved(Pos2),
700 Pressed {
701 position: Pos2,
702 button: PointerButton,
703 },
704 Released {
705 click: Option<Click>,
706 button: PointerButton,
707 },
708}
709
710impl PointerEvent {
711 pub fn is_press(&self) -> bool {
712 matches!(self, Self::Pressed { .. })
713 }
714
715 pub fn is_release(&self) -> bool {
716 matches!(self, Self::Released { .. })
717 }
718
719 pub fn is_click(&self) -> bool {
720 matches!(self, Self::Released { click: Some(_), .. })
721 }
722}
723
724/// Mouse or touch state.
725#[derive(Clone, Debug)]
726#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
727pub struct PointerState {
728 /// Latest known time
729 time: f64,
730
731 // Consider a finger tapping a touch screen.
732 // What position should we report?
733 // The location of the touch, or `None`, because the finger is gone?
734 //
735 // For some cases we want the first: e.g. to check for interaction.
736 // For showing tooltips, we want the latter (no tooltips, since there are no fingers).
737 /// Latest reported pointer position.
738 /// When tapping a touch screen, this will be `None`.
739 latest_pos: Option<Pos2>,
740
741 /// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
742 /// if there were interactions this frame.
743 /// When tapping a touch screen, this will be the location of the touch.
744 interact_pos: Option<Pos2>,
745
746 /// How much the pointer moved compared to last frame, in points.
747 delta: Vec2,
748
749 /// How much the mouse moved since the last frame, in unspecified units.
750 /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
751 /// May be unavailable on some integrations.
752 motion: Option<Vec2>,
753
754 /// Current velocity of pointer.
755 velocity: Vec2,
756
757 /// Current direction of pointer.
758 direction: Vec2,
759
760 /// Recent movement of the pointer.
761 /// Used for calculating velocity of pointer.
762 pos_history: History<Pos2>,
763
764 down: [bool; NUM_POINTER_BUTTONS],
765
766 /// Where did the current click/drag originate?
767 /// `None` if no mouse button is down.
768 press_origin: Option<Pos2>,
769
770 /// When did the current click/drag originate?
771 /// `None` if no mouse button is down.
772 press_start_time: Option<f64>,
773
774 /// Set to `true` if the pointer has moved too much (since being pressed)
775 /// for it to be registered as a click.
776 pub(crate) has_moved_too_much_for_a_click: bool,
777
778 /// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame?
779 ///
780 /// This could also be the trigger point for a long-touch.
781 pub(crate) started_decidedly_dragging: bool,
782
783 /// When did the pointer get click last?
784 /// Used to check for double-clicks.
785 last_click_time: f64,
786
787 /// When did the pointer get click two clicks ago?
788 /// Used to check for triple-clicks.
789 last_last_click_time: f64,
790
791 /// When was the pointer last moved?
792 /// Used for things like showing hover ui/tooltip with a delay.
793 last_move_time: f64,
794
795 /// All button events that occurred this frame
796 pub(crate) pointer_events: Vec<PointerEvent>,
797}
798
799impl Default for PointerState {
800 fn default() -> Self {
801 Self {
802 time: -f64::INFINITY,
803 latest_pos: None,
804 interact_pos: None,
805 delta: Vec2::ZERO,
806 motion: None,
807 velocity: Vec2::ZERO,
808 direction: Vec2::ZERO,
809 pos_history: History::new(2..1000, 0.1),
810 down: Default::default(),
811 press_origin: None,
812 press_start_time: None,
813 has_moved_too_much_for_a_click: false,
814 started_decidedly_dragging: false,
815 last_click_time: std::f64::NEG_INFINITY,
816 last_last_click_time: std::f64::NEG_INFINITY,
817 last_move_time: std::f64::NEG_INFINITY,
818 pointer_events: vec![],
819 }
820 }
821}
822
823impl PointerState {
824 #[must_use]
825 pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> Self {
826 let was_decidedly_dragging = self.is_decidedly_dragging();
827
828 self.time = time;
829
830 self.pointer_events.clear();
831
832 let old_pos = self.latest_pos;
833 self.interact_pos = self.latest_pos;
834 if self.motion.is_some() {
835 self.motion = Some(Vec2::ZERO);
836 }
837
838 for event in &new.events {
839 match event {
840 Event::PointerMoved(pos) => {
841 let pos = *pos;
842
843 self.latest_pos = Some(pos);
844 self.interact_pos = Some(pos);
845
846 if let Some(press_origin) = self.press_origin {
847 self.has_moved_too_much_for_a_click |=
848 press_origin.distance(pos) > MAX_CLICK_DIST;
849 }
850
851 self.pointer_events.push(PointerEvent::Moved(pos));
852 }
853 Event::PointerButton {
854 pos,
855 button,
856 pressed,
857 modifiers,
858 } => {
859 let pos = *pos;
860 let button = *button;
861 let pressed = *pressed;
862 let modifiers = *modifiers;
863
864 self.latest_pos = Some(pos);
865 self.interact_pos = Some(pos);
866
867 if pressed {
868 // Start of a drag: we want to track the velocity for during the drag
869 // and ignore any incoming movement
870 self.pos_history.clear();
871 }
872
873 if pressed {
874 self.press_origin = Some(pos);
875 self.press_start_time = Some(time);
876 self.has_moved_too_much_for_a_click = false;
877 self.pointer_events.push(PointerEvent::Pressed {
878 position: pos,
879 button,
880 });
881 } else {
882 // Released
883 let clicked = self.could_any_button_be_click();
884
885 let click = if clicked {
886 let double_click =
887 (time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY;
888 let triple_click =
889 (time - self.last_last_click_time) < (MAX_DOUBLE_CLICK_DELAY * 2.0);
890 let count = if triple_click {
891 3
892 } else if double_click {
893 2
894 } else {
895 1
896 };
897
898 self.last_last_click_time = self.last_click_time;
899 self.last_click_time = time;
900
901 Some(Click {
902 pos,
903 count,
904 modifiers,
905 })
906 } else {
907 None
908 };
909
910 self.pointer_events
911 .push(PointerEvent::Released { click, button });
912
913 self.press_origin = None;
914 self.press_start_time = None;
915 }
916
917 self.down[button as usize] = pressed; // must be done after the above call to `could_any_button_be_click`
918 }
919 Event::PointerGone => {
920 self.latest_pos = None;
921 // When dragging a slider and the mouse leaves the viewport, we still want the drag to work,
922 // so we don't treat this as a `PointerEvent::Released`.
923 // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame.
924 self.pos_history.clear();
925 }
926 Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta,
927 _ => {}
928 }
929 }
930
931 self.delta = if let (Some(old_pos), Some(new_pos)) = (old_pos, self.latest_pos) {
932 new_pos - old_pos
933 } else {
934 Vec2::ZERO
935 };
936
937 if let Some(pos) = self.latest_pos {
938 self.pos_history.add(time, pos);
939 } else {
940 // we do not clear the `pos_history` here, because it is exactly when a finger has
941 // released from the touch screen that we may want to assign a velocity to whatever
942 // the user tried to throw.
943 }
944
945 self.pos_history.flush(time);
946
947 self.velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
948 self.pos_history.velocity().unwrap_or_default()
949 } else {
950 Vec2::default()
951 };
952 if self.velocity != Vec2::ZERO {
953 self.last_move_time = time;
954 }
955
956 self.direction = self.pos_history.velocity().unwrap_or_default().normalized();
957
958 self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging;
959
960 self
961 }
962
963 fn wants_repaint(&self) -> bool {
964 !self.pointer_events.is_empty() || self.delta != Vec2::ZERO
965 }
966
967 /// How much the pointer moved compared to last frame, in points.
968 #[inline(always)]
969 pub fn delta(&self) -> Vec2 {
970 self.delta
971 }
972
973 /// How much the mouse moved since the last frame, in unspecified units.
974 /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
975 /// May be unavailable on some integrations.
976 #[inline(always)]
977 pub fn motion(&self) -> Option<Vec2> {
978 self.motion
979 }
980
981 /// Current velocity of pointer.
982 ///
983 /// This is smoothed over a few frames,
984 /// but can be ZERO when frame-rate is bad.
985 #[inline(always)]
986 pub fn velocity(&self) -> Vec2 {
987 self.velocity
988 }
989
990 /// Current direction of the pointer.
991 ///
992 /// This is less sensitive to bad framerate than [`Self::velocity`].
993 #[inline(always)]
994 pub fn direction(&self) -> Vec2 {
995 self.direction
996 }
997
998 /// Where did the current click/drag originate?
999 /// `None` if no mouse button is down.
1000 #[inline(always)]
1001 pub fn press_origin(&self) -> Option<Pos2> {
1002 self.press_origin
1003 }
1004
1005 /// When did the current click/drag originate?
1006 /// `None` if no mouse button is down.
1007 #[inline(always)]
1008 pub fn press_start_time(&self) -> Option<f64> {
1009 self.press_start_time
1010 }
1011
1012 /// Latest reported pointer position.
1013 /// When tapping a touch screen, this will be `None`.
1014 #[inline(always)]
1015 pub fn latest_pos(&self) -> Option<Pos2> {
1016 self.latest_pos
1017 }
1018
1019 /// If it is a good idea to show a tooltip, where is pointer?
1020 #[inline(always)]
1021 pub fn hover_pos(&self) -> Option<Pos2> {
1022 self.latest_pos
1023 }
1024
1025 /// If you detect a click or drag and wants to know where it happened, use this.
1026 ///
1027 /// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
1028 /// if there were interactions this frame.
1029 /// When tapping a touch screen, this will be the location of the touch.
1030 #[inline(always)]
1031 pub fn interact_pos(&self) -> Option<Pos2> {
1032 self.interact_pos
1033 }
1034
1035 /// Do we have a pointer?
1036 ///
1037 /// `false` if the mouse is not over the egui area, or if no touches are down on touch screens.
1038 #[inline(always)]
1039 pub fn has_pointer(&self) -> bool {
1040 self.latest_pos.is_some()
1041 }
1042
1043 /// Is the pointer currently still?
1044 /// This is smoothed so a few frames of stillness is required before this returns `true`.
1045 #[inline(always)]
1046 pub fn is_still(&self) -> bool {
1047 self.velocity == Vec2::ZERO
1048 }
1049
1050 /// Is the pointer currently moving?
1051 /// This is smoothed so a few frames of stillness is required before this returns `false`.
1052 #[inline]
1053 pub fn is_moving(&self) -> bool {
1054 self.velocity != Vec2::ZERO
1055 }
1056
1057 /// How long has it been (in seconds) since the pointer was last moved?
1058 #[inline(always)]
1059 pub fn time_since_last_movement(&self) -> f32 {
1060 (self.time - self.last_move_time) as f32
1061 }
1062
1063 /// How long has it been (in seconds) since the pointer was clicked?
1064 #[inline(always)]
1065 pub fn time_since_last_click(&self) -> f32 {
1066 (self.time - self.last_click_time) as f32
1067 }
1068
1069 /// Was any pointer button pressed (`!down -> down`) this frame?
1070 ///
1071 /// This can sometimes return `true` even if `any_down() == false`
1072 /// because a press can be shorted than one frame.
1073 pub fn any_pressed(&self) -> bool {
1074 self.pointer_events.iter().any(|event| event.is_press())
1075 }
1076
1077 /// Was any pointer button released (`down -> !down`) this frame?
1078 pub fn any_released(&self) -> bool {
1079 self.pointer_events.iter().any(|event| event.is_release())
1080 }
1081
1082 /// Was the button given pressed this frame?
1083 pub fn button_pressed(&self, button: PointerButton) -> bool {
1084 self.pointer_events
1085 .iter()
1086 .any(|event| matches!(event, &PointerEvent::Pressed{button: b, ..} if button == b))
1087 }
1088
1089 /// Was the button given released this frame?
1090 pub fn button_released(&self, button: PointerButton) -> bool {
1091 self.pointer_events
1092 .iter()
1093 .any(|event| matches!(event, &PointerEvent::Released{button: b, ..} if button == b))
1094 }
1095
1096 /// Was the primary button pressed this frame?
1097 pub fn primary_pressed(&self) -> bool {
1098 self.button_pressed(PointerButton::Primary)
1099 }
1100
1101 /// Was the secondary button pressed this frame?
1102 pub fn secondary_pressed(&self) -> bool {
1103 self.button_pressed(PointerButton::Secondary)
1104 }
1105
1106 /// Was the primary button released this frame?
1107 pub fn primary_released(&self) -> bool {
1108 self.button_released(PointerButton::Primary)
1109 }
1110
1111 /// Was the secondary button released this frame?
1112 pub fn secondary_released(&self) -> bool {
1113 self.button_released(PointerButton::Secondary)
1114 }
1115
1116 /// Is any pointer button currently down?
1117 pub fn any_down(&self) -> bool {
1118 self.down.iter().any(|&down| down)
1119 }
1120
1121 /// Were there any type of click this frame?
1122 pub fn any_click(&self) -> bool {
1123 self.pointer_events.iter().any(|event| event.is_click())
1124 }
1125
1126 /// Was the given pointer button given clicked this frame?
1127 ///
1128 /// Returns true on double- and triple- clicks too.
1129 pub fn button_clicked(&self, button: PointerButton) -> bool {
1130 self.pointer_events
1131 .iter()
1132 .any(|event| matches!(event, &PointerEvent::Released { button: b, click: Some(_) } if button == b))
1133 }
1134
1135 /// Was the button given double clicked this frame?
1136 pub fn button_double_clicked(&self, button: PointerButton) -> bool {
1137 self.pointer_events.iter().any(|event| {
1138 matches!(
1139 &event,
1140 PointerEvent::Released {
1141 click: Some(click),
1142 button: b,
1143 } if *b == button && click.is_double()
1144 )
1145 })
1146 }
1147
1148 /// Was the button given triple clicked this frame?
1149 pub fn button_triple_clicked(&self, button: PointerButton) -> bool {
1150 self.pointer_events.iter().any(|event| {
1151 matches!(
1152 &event,
1153 PointerEvent::Released {
1154 click: Some(click),
1155 button: b,
1156 } if *b == button && click.is_triple()
1157 )
1158 })
1159 }
1160
1161 /// Was the primary button clicked this frame?
1162 pub fn primary_clicked(&self) -> bool {
1163 self.button_clicked(PointerButton::Primary)
1164 }
1165
1166 /// Was the secondary button clicked this frame?
1167 pub fn secondary_clicked(&self) -> bool {
1168 self.button_clicked(PointerButton::Secondary)
1169 }
1170
1171 /// Is this button currently down?
1172 #[inline(always)]
1173 pub fn button_down(&self, button: PointerButton) -> bool {
1174 self.down[button as usize]
1175 }
1176
1177 /// If the pointer button is down, will it register as a click when released?
1178 ///
1179 /// See also [`Self::is_decidedly_dragging`].
1180 pub fn could_any_button_be_click(&self) -> bool {
1181 if self.any_down() || self.any_released() {
1182 if self.has_moved_too_much_for_a_click {
1183 return false;
1184 }
1185
1186 if let Some(press_start_time) = self.press_start_time {
1187 if self.time - press_start_time > MAX_CLICK_DURATION {
1188 return false;
1189 }
1190 }
1191
1192 true
1193 } else {
1194 false
1195 }
1196 }
1197
1198 /// Just because the mouse is down doesn't mean we are dragging.
1199 /// We could be at the start of a click.
1200 /// But if the mouse is down long enough, or has moved far enough,
1201 /// then we consider it a drag.
1202 ///
1203 /// This function can return true on the same frame the drag is released,
1204 /// but NOT on the first frame it was started.
1205 ///
1206 /// See also [`Self::could_any_button_be_click`].
1207 pub fn is_decidedly_dragging(&self) -> bool {
1208 (self.any_down() || self.any_released())
1209 && !self.any_pressed()
1210 && !self.could_any_button_be_click()
1211 && !self.any_click()
1212 }
1213
1214 /// A long press is something we detect on touch screens
1215 /// to trigger a secondary click (context menu).
1216 ///
1217 /// Returns `true` only on one frame.
1218 pub(crate) fn is_long_press(&self) -> bool {
1219 self.started_decidedly_dragging
1220 && !self.has_moved_too_much_for_a_click
1221 && self.button_down(PointerButton::Primary)
1222 && self.press_start_time.map_or(false, |press_start_time| {
1223 self.time - press_start_time > MAX_CLICK_DURATION
1224 })
1225 }
1226
1227 /// Is the primary button currently down?
1228 #[inline(always)]
1229 pub fn primary_down(&self) -> bool {
1230 self.button_down(PointerButton::Primary)
1231 }
1232
1233 /// Is the secondary button currently down?
1234 #[inline(always)]
1235 pub fn secondary_down(&self) -> bool {
1236 self.button_down(PointerButton::Secondary)
1237 }
1238
1239 /// Is the middle button currently down?
1240 #[inline(always)]
1241 pub fn middle_down(&self) -> bool {
1242 self.button_down(PointerButton::Middle)
1243 }
1244}
1245
1246impl InputState {
1247 pub fn ui(&self, ui: &mut crate::Ui) {
1248 let Self {
1249 raw,
1250 pointer,
1251 touch_states,
1252
1253 last_scroll_time,
1254 unprocessed_scroll_delta,
1255 unprocessed_scroll_delta_for_zoom,
1256 raw_scroll_delta,
1257 smooth_scroll_delta,
1258
1259 zoom_factor_delta,
1260 screen_rect,
1261 pixels_per_point,
1262 max_texture_side,
1263 time,
1264 unstable_dt,
1265 predicted_dt,
1266 stable_dt,
1267 focused,
1268 modifiers,
1269 keys_down,
1270 events,
1271 } = self;
1272
1273 ui.style_mut()
1274 .text_styles
1275 .get_mut(&crate::TextStyle::Body)
1276 .unwrap()
1277 .family = crate::FontFamily::Monospace;
1278
1279 ui.collapsing("Raw Input", |ui| raw.ui(ui));
1280
1281 crate::containers::CollapsingHeader::new("🖱 Pointer")
1282 .default_open(false)
1283 .show(ui, |ui| {
1284 pointer.ui(ui);
1285 });
1286
1287 for (device_id, touch_state) in touch_states {
1288 ui.collapsing(format!("Touch State [device {}]", device_id.0), |ui| {
1289 touch_state.ui(ui);
1290 });
1291 }
1292
1293 ui.label(format!(
1294 "Time since last scroll: {:.1} s",
1295 time - last_scroll_time
1296 ));
1297 if cfg!(debug_assertions) {
1298 ui.label(format!(
1299 "unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points"
1300 ));
1301 ui.label(format!(
1302 "unprocessed_scroll_delta_for_zoom: {unprocessed_scroll_delta_for_zoom:?} points"
1303 ));
1304 }
1305 ui.label(format!("raw_scroll_delta: {raw_scroll_delta:?} points"));
1306 ui.label(format!(
1307 "smooth_scroll_delta: {smooth_scroll_delta:?} points"
1308 ));
1309 ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x"));
1310
1311 ui.label(format!("screen_rect: {screen_rect:?} points"));
1312 ui.label(format!(
1313 "{pixels_per_point} physical pixels for each logical point"
1314 ));
1315 ui.label(format!(
1316 "max texture size (on each side): {max_texture_side}"
1317 ));
1318 ui.label(format!("time: {time:.3} s"));
1319 ui.label(format!(
1320 "time since previous frame: {:.1} ms",
1321 1e3 * unstable_dt
1322 ));
1323 ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
1324 ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt));
1325 ui.label(format!("focused: {focused}"));
1326 ui.label(format!("modifiers: {modifiers:#?}"));
1327 ui.label(format!("keys_down: {keys_down:?}"));
1328 ui.scope(|ui| {
1329 ui.set_min_height(150.0);
1330 ui.label(format!("events: {events:#?}"))
1331 .on_hover_text("key presses etc");
1332 });
1333 }
1334}
1335
1336impl PointerState {
1337 pub fn ui(&self, ui: &mut crate::Ui) {
1338 let Self {
1339 time: _,
1340 latest_pos,
1341 interact_pos,
1342 delta,
1343 motion,
1344 velocity,
1345 direction,
1346 pos_history: _,
1347 down,
1348 press_origin,
1349 press_start_time,
1350 has_moved_too_much_for_a_click,
1351 started_decidedly_dragging,
1352 last_click_time,
1353 last_last_click_time,
1354 pointer_events,
1355 last_move_time,
1356 } = self;
1357
1358 ui.label(format!("latest_pos: {latest_pos:?}"));
1359 ui.label(format!("interact_pos: {interact_pos:?}"));
1360 ui.label(format!("delta: {delta:?}"));
1361 ui.label(format!("motion: {motion:?}"));
1362 ui.label(format!(
1363 "velocity: [{:3.0} {:3.0}] points/sec",
1364 velocity.x, velocity.y
1365 ));
1366 ui.label(format!("direction: {direction:?}"));
1367 ui.label(format!("down: {down:#?}"));
1368 ui.label(format!("press_origin: {press_origin:?}"));
1369 ui.label(format!("press_start_time: {press_start_time:?} s"));
1370 ui.label(format!(
1371 "has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}"
1372 ));
1373 ui.label(format!(
1374 "started_decidedly_dragging: {started_decidedly_dragging}"
1375 ));
1376 ui.label(format!("last_click_time: {last_click_time:#?}"));
1377 ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
1378 ui.label(format!("last_move_time: {last_move_time:#?}"));
1379 ui.label(format!("pointer_events: {pointer_events:?}"));
1380 }
1381}