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