Skip to main content

egui/input_state/
mod.rs

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