egui/input_state/
mod.rs

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