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_node == accesskit_id
937                && request.target_tree == accesskit::TreeId::ROOT
938                && request.action == action
939            {
940                return Some(request);
941            }
942            None
943        })
944    }
945
946    #[cfg(feature = "accesskit")]
947    pub fn consume_accesskit_action_requests(
948        &mut self,
949        id: crate::Id,
950        mut consume: impl FnMut(&accesskit::ActionRequest) -> bool,
951    ) {
952        let accesskit_id = id.accesskit_id();
953        self.events.retain(|event| {
954            if let Event::AccessKitActionRequest(request) = event
955                && request.target_node == accesskit_id
956                && request.target_tree == accesskit::TreeId::ROOT
957            {
958                return !consume(request);
959            }
960            true
961        });
962    }
963
964    #[cfg(feature = "accesskit")]
965    pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool {
966        self.accesskit_action_requests(id, action).next().is_some()
967    }
968
969    #[cfg(feature = "accesskit")]
970    pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
971        self.accesskit_action_requests(id, action).count()
972    }
973
974    /// Get all events that matches the given filter.
975    pub fn filtered_events(&self, filter: &EventFilter) -> Vec<Event> {
976        self.events
977            .iter()
978            .filter(|event| filter.matches(event))
979            .cloned()
980            .collect()
981    }
982
983    /// A long press is something we detect on touch screens
984    /// to trigger a secondary click (context menu).
985    ///
986    /// Returns `true` only on one frame.
987    pub(crate) fn is_long_touch(&self) -> bool {
988        self.any_touches() && self.pointer.is_long_press()
989    }
990}
991
992// ----------------------------------------------------------------------------
993
994/// A pointer (mouse or touch) click.
995#[derive(Clone, Debug, PartialEq)]
996#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
997pub(crate) struct Click {
998    pub pos: Pos2,
999
1000    /// 1 or 2 (double-click) or 3 (triple-click)
1001    pub count: u32,
1002
1003    /// Allows you to check for e.g. shift-click
1004    pub modifiers: Modifiers,
1005}
1006
1007impl Click {
1008    pub fn is_double(&self) -> bool {
1009        self.count == 2
1010    }
1011
1012    pub fn is_triple(&self) -> bool {
1013        self.count == 3
1014    }
1015}
1016
1017#[derive(Clone, Debug, PartialEq)]
1018#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1019pub(crate) enum PointerEvent {
1020    Moved(Pos2),
1021    Pressed {
1022        position: Pos2,
1023        button: PointerButton,
1024    },
1025    Released {
1026        click: Option<Click>,
1027        button: PointerButton,
1028    },
1029}
1030
1031impl PointerEvent {
1032    pub fn is_press(&self) -> bool {
1033        matches!(self, Self::Pressed { .. })
1034    }
1035
1036    pub fn is_release(&self) -> bool {
1037        matches!(self, Self::Released { .. })
1038    }
1039
1040    pub fn is_click(&self) -> bool {
1041        matches!(self, Self::Released { click: Some(_), .. })
1042    }
1043}
1044
1045/// Mouse or touch state.
1046#[derive(Clone, Debug)]
1047#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1048pub struct PointerState {
1049    /// Latest known time
1050    time: f64,
1051
1052    // Consider a finger tapping a touch screen.
1053    // What position should we report?
1054    // The location of the touch, or `None`, because the finger is gone?
1055    //
1056    // For some cases we want the first: e.g. to check for interaction.
1057    // For showing tooltips, we want the latter (no tooltips, since there are no fingers).
1058    /// Latest reported pointer position.
1059    /// When tapping a touch screen, this will be `None`.
1060    latest_pos: Option<Pos2>,
1061
1062    /// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
1063    /// if there were interactions this frame.
1064    /// When tapping a touch screen, this will be the location of the touch.
1065    interact_pos: Option<Pos2>,
1066
1067    /// How much the pointer moved compared to last frame, in points.
1068    delta: Vec2,
1069
1070    /// How much the mouse moved since the last frame, in unspecified units.
1071    /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
1072    /// May be unavailable on some integrations.
1073    motion: Option<Vec2>,
1074
1075    /// Current velocity of pointer.
1076    velocity: Vec2,
1077
1078    /// Current direction of pointer.
1079    direction: Vec2,
1080
1081    /// Recent movement of the pointer.
1082    /// Used for calculating velocity of pointer.
1083    pos_history: History<Pos2>,
1084
1085    down: [bool; NUM_POINTER_BUTTONS],
1086
1087    /// Where did the current click/drag originate?
1088    /// `None` if no mouse button is down.
1089    press_origin: Option<Pos2>,
1090
1091    /// When did the current click/drag originate?
1092    /// `None` if no mouse button is down.
1093    press_start_time: Option<f64>,
1094
1095    /// Set to `true` if the pointer has moved too much (since being pressed)
1096    /// for it to be registered as a click.
1097    pub(crate) has_moved_too_much_for_a_click: bool,
1098
1099    /// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame?
1100    ///
1101    /// This could also be the trigger point for a long-touch.
1102    pub(crate) started_decidedly_dragging: bool,
1103
1104    /// When did the pointer get click last?
1105    /// Used to check for double-clicks.
1106    last_click_time: f64,
1107
1108    /// When did the pointer get click two clicks ago?
1109    /// Used to check for triple-clicks.
1110    last_last_click_time: f64,
1111
1112    /// When was the pointer last moved?
1113    /// Used for things like showing hover ui/tooltip with a delay.
1114    last_move_time: f64,
1115
1116    /// All button events that occurred this frame
1117    pub(crate) pointer_events: Vec<PointerEvent>,
1118
1119    /// Input state management configuration.
1120    ///
1121    /// This gets copied from `egui::Options` at the start of each frame for convenience.
1122    options: InputOptions,
1123}
1124
1125impl Default for PointerState {
1126    fn default() -> Self {
1127        Self {
1128            time: -f64::INFINITY,
1129            latest_pos: None,
1130            interact_pos: None,
1131            delta: Vec2::ZERO,
1132            motion: None,
1133            velocity: Vec2::ZERO,
1134            direction: Vec2::ZERO,
1135            pos_history: History::new(2..1000, 0.1),
1136            down: Default::default(),
1137            press_origin: None,
1138            press_start_time: None,
1139            has_moved_too_much_for_a_click: false,
1140            started_decidedly_dragging: false,
1141            last_click_time: f64::NEG_INFINITY,
1142            last_last_click_time: f64::NEG_INFINITY,
1143            last_move_time: f64::NEG_INFINITY,
1144            pointer_events: vec![],
1145            options: Default::default(),
1146        }
1147    }
1148}
1149
1150impl PointerState {
1151    #[must_use]
1152    pub(crate) fn begin_pass(mut self, time: f64, new: &RawInput, options: InputOptions) -> Self {
1153        let was_decidedly_dragging = self.is_decidedly_dragging();
1154
1155        self.time = time;
1156        self.options = options;
1157
1158        self.pointer_events.clear();
1159
1160        let old_pos = self.latest_pos;
1161        self.interact_pos = self.latest_pos;
1162        if self.motion.is_some() {
1163            self.motion = Some(Vec2::ZERO);
1164        }
1165
1166        let mut clear_history_after_velocity_calculation = false;
1167        for event in &new.events {
1168            match event {
1169                Event::PointerMoved(pos) => {
1170                    let pos = *pos;
1171
1172                    self.latest_pos = Some(pos);
1173                    self.interact_pos = Some(pos);
1174
1175                    if let Some(press_origin) = self.press_origin {
1176                        self.has_moved_too_much_for_a_click |=
1177                            press_origin.distance(pos) > self.options.max_click_dist;
1178                    }
1179
1180                    self.last_move_time = time;
1181                    self.pointer_events.push(PointerEvent::Moved(pos));
1182                }
1183                Event::PointerButton {
1184                    pos,
1185                    button,
1186                    pressed,
1187                    modifiers,
1188                } => {
1189                    let pos = *pos;
1190                    let button = *button;
1191                    let pressed = *pressed;
1192                    let modifiers = *modifiers;
1193
1194                    self.latest_pos = Some(pos);
1195                    self.interact_pos = Some(pos);
1196
1197                    if pressed {
1198                        // Start of a drag: we want to track the velocity for during the drag
1199                        // and ignore any incoming movement
1200                        self.pos_history.clear();
1201                    }
1202
1203                    if pressed {
1204                        self.press_origin = Some(pos);
1205                        self.press_start_time = Some(time);
1206                        self.has_moved_too_much_for_a_click = false;
1207                        self.pointer_events.push(PointerEvent::Pressed {
1208                            position: pos,
1209                            button,
1210                        });
1211                    } else {
1212                        // Released
1213                        let clicked = self.could_any_button_be_click();
1214
1215                        let click = if clicked {
1216                            let double_click =
1217                                (time - self.last_click_time) < self.options.max_double_click_delay;
1218                            let triple_click = (time - self.last_last_click_time)
1219                                < (self.options.max_double_click_delay * 2.0);
1220                            let count = if triple_click {
1221                                3
1222                            } else if double_click {
1223                                2
1224                            } else {
1225                                1
1226                            };
1227
1228                            self.last_last_click_time = self.last_click_time;
1229                            self.last_click_time = time;
1230
1231                            Some(Click {
1232                                pos,
1233                                count,
1234                                modifiers,
1235                            })
1236                        } else {
1237                            None
1238                        };
1239
1240                        self.pointer_events
1241                            .push(PointerEvent::Released { click, button });
1242
1243                        self.press_origin = None;
1244                        self.press_start_time = None;
1245                    }
1246
1247                    self.down[button as usize] = pressed; // must be done after the above call to `could_any_button_be_click`
1248                }
1249                Event::PointerGone => {
1250                    self.latest_pos = None;
1251                    // When dragging a slider and the mouse leaves the viewport, we still want the drag to work,
1252                    // so we don't treat this as a `PointerEvent::Released`.
1253                    // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame.
1254
1255                    // Delay the clearing until after the final velocity calculation, so we can
1256                    // get the final velocity when `drag_stopped` is true.
1257                    clear_history_after_velocity_calculation = true;
1258                }
1259                Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta,
1260                _ => {}
1261            }
1262        }
1263
1264        self.delta = if let (Some(old_pos), Some(new_pos)) = (old_pos, self.latest_pos) {
1265            new_pos - old_pos
1266        } else {
1267            Vec2::ZERO
1268        };
1269
1270        if let Some(pos) = self.latest_pos {
1271            self.pos_history.add(time, pos);
1272        } else {
1273            // we do not clear the `pos_history` here, because it is exactly when a finger has
1274            // released from the touch screen that we may want to assign a velocity to whatever
1275            // the user tried to throw.
1276        }
1277
1278        self.pos_history.flush(time);
1279
1280        self.velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
1281            self.pos_history.velocity().unwrap_or_default()
1282        } else {
1283            Vec2::default()
1284        };
1285        if self.velocity != Vec2::ZERO {
1286            self.last_move_time = time;
1287        }
1288        if clear_history_after_velocity_calculation {
1289            self.pos_history.clear();
1290        }
1291
1292        self.direction = self.pos_history.velocity().unwrap_or_default().normalized();
1293
1294        self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging;
1295
1296        self
1297    }
1298
1299    fn wants_repaint(&self) -> bool {
1300        !self.pointer_events.is_empty() || self.delta != Vec2::ZERO
1301    }
1302
1303    /// How much the pointer moved compared to last frame, in points.
1304    #[inline(always)]
1305    pub fn delta(&self) -> Vec2 {
1306        self.delta
1307    }
1308
1309    /// How much the mouse moved since the last frame, in unspecified units.
1310    /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
1311    /// May be unavailable on some integrations.
1312    #[inline(always)]
1313    pub fn motion(&self) -> Option<Vec2> {
1314        self.motion
1315    }
1316
1317    /// Current velocity of pointer.
1318    ///
1319    /// This is smoothed over a few frames,
1320    /// but can be ZERO when frame-rate is bad.
1321    #[inline(always)]
1322    pub fn velocity(&self) -> Vec2 {
1323        self.velocity
1324    }
1325
1326    /// Current direction of the pointer.
1327    ///
1328    /// This is less sensitive to bad framerate than [`Self::velocity`].
1329    #[inline(always)]
1330    pub fn direction(&self) -> Vec2 {
1331        self.direction
1332    }
1333
1334    /// Where did the current click/drag originate?
1335    /// `None` if no mouse button is down.
1336    #[inline(always)]
1337    pub fn press_origin(&self) -> Option<Pos2> {
1338        self.press_origin
1339    }
1340
1341    /// How far has the pointer moved since the start of the drag (if any)?
1342    pub fn total_drag_delta(&self) -> Option<Vec2> {
1343        Some(self.latest_pos? - self.press_origin?)
1344    }
1345
1346    /// When did the current click/drag originate?
1347    /// `None` if no mouse button is down.
1348    #[inline(always)]
1349    pub fn press_start_time(&self) -> Option<f64> {
1350        self.press_start_time
1351    }
1352
1353    /// Latest reported pointer position.
1354    /// When tapping a touch screen, this will be `None`.
1355    #[inline(always)]
1356    pub fn latest_pos(&self) -> Option<Pos2> {
1357        self.latest_pos
1358    }
1359
1360    /// If it is a good idea to show a tooltip, where is pointer?
1361    #[inline(always)]
1362    pub fn hover_pos(&self) -> Option<Pos2> {
1363        self.latest_pos
1364    }
1365
1366    /// If you detect a click or drag and wants to know where it happened, use this.
1367    ///
1368    /// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
1369    /// if there were interactions this frame.
1370    /// When tapping a touch screen, this will be the location of the touch.
1371    #[inline(always)]
1372    pub fn interact_pos(&self) -> Option<Pos2> {
1373        self.interact_pos
1374    }
1375
1376    /// Do we have a pointer?
1377    ///
1378    /// `false` if the mouse is not over the egui area, or if no touches are down on touch screens.
1379    #[inline(always)]
1380    pub fn has_pointer(&self) -> bool {
1381        self.latest_pos.is_some()
1382    }
1383
1384    /// Is the pointer currently still?
1385    /// This is smoothed so a few frames of stillness is required before this returns `true`.
1386    #[inline(always)]
1387    pub fn is_still(&self) -> bool {
1388        self.velocity == Vec2::ZERO
1389    }
1390
1391    /// Is the pointer currently moving?
1392    /// This is smoothed so a few frames of stillness is required before this returns `false`.
1393    #[inline]
1394    pub fn is_moving(&self) -> bool {
1395        self.velocity != Vec2::ZERO
1396    }
1397
1398    /// How long has it been (in seconds) since the pointer was last moved?
1399    #[inline(always)]
1400    pub fn time_since_last_movement(&self) -> f32 {
1401        (self.time - self.last_move_time) as f32
1402    }
1403
1404    /// How long has it been (in seconds) since the pointer was clicked?
1405    #[inline(always)]
1406    pub fn time_since_last_click(&self) -> f32 {
1407        (self.time - self.last_click_time) as f32
1408    }
1409
1410    /// Was any pointer button pressed (`!down -> down`) this frame?
1411    ///
1412    /// This can sometimes return `true` even if `any_down() == false`
1413    /// because a press can be shorted than one frame.
1414    pub fn any_pressed(&self) -> bool {
1415        self.pointer_events.iter().any(|event| event.is_press())
1416    }
1417
1418    /// Was any pointer button released (`down -> !down`) this frame?
1419    pub fn any_released(&self) -> bool {
1420        self.pointer_events.iter().any(|event| event.is_release())
1421    }
1422
1423    /// Was the button given pressed this frame?
1424    pub fn button_pressed(&self, button: PointerButton) -> bool {
1425        self.pointer_events
1426            .iter()
1427            .any(|event| matches!(event, &PointerEvent::Pressed{button: b, ..} if button == b))
1428    }
1429
1430    /// Was the button given released this frame?
1431    pub fn button_released(&self, button: PointerButton) -> bool {
1432        self.pointer_events
1433            .iter()
1434            .any(|event| matches!(event, &PointerEvent::Released{button: b, ..} if button == b))
1435    }
1436
1437    /// Was the primary button pressed this frame?
1438    pub fn primary_pressed(&self) -> bool {
1439        self.button_pressed(PointerButton::Primary)
1440    }
1441
1442    /// Was the secondary button pressed this frame?
1443    pub fn secondary_pressed(&self) -> bool {
1444        self.button_pressed(PointerButton::Secondary)
1445    }
1446
1447    /// Was the primary button released this frame?
1448    pub fn primary_released(&self) -> bool {
1449        self.button_released(PointerButton::Primary)
1450    }
1451
1452    /// Was the secondary button released this frame?
1453    pub fn secondary_released(&self) -> bool {
1454        self.button_released(PointerButton::Secondary)
1455    }
1456
1457    /// Is any pointer button currently down?
1458    pub fn any_down(&self) -> bool {
1459        self.down.iter().any(|&down| down)
1460    }
1461
1462    /// Were there any type of click this frame?
1463    pub fn any_click(&self) -> bool {
1464        self.pointer_events.iter().any(|event| event.is_click())
1465    }
1466
1467    /// Was the given pointer button given clicked this frame?
1468    ///
1469    /// Returns true on double- and triple- clicks too.
1470    pub fn button_clicked(&self, button: PointerButton) -> bool {
1471        self.pointer_events
1472            .iter()
1473            .any(|event| matches!(event, &PointerEvent::Released { button: b, click: Some(_) } if button == b))
1474    }
1475
1476    /// Was the button given double clicked this frame?
1477    pub fn button_double_clicked(&self, button: PointerButton) -> bool {
1478        self.pointer_events.iter().any(|event| {
1479            matches!(
1480                &event,
1481                PointerEvent::Released {
1482                    click: Some(click),
1483                    button: b,
1484                } if *b == button && click.is_double()
1485            )
1486        })
1487    }
1488
1489    /// Was the button given triple clicked this frame?
1490    pub fn button_triple_clicked(&self, button: PointerButton) -> bool {
1491        self.pointer_events.iter().any(|event| {
1492            matches!(
1493                &event,
1494                PointerEvent::Released {
1495                    click: Some(click),
1496                    button: b,
1497                } if *b == button && click.is_triple()
1498            )
1499        })
1500    }
1501
1502    /// Was the primary button clicked this frame?
1503    pub fn primary_clicked(&self) -> bool {
1504        self.button_clicked(PointerButton::Primary)
1505    }
1506
1507    /// Was the secondary button clicked this frame?
1508    pub fn secondary_clicked(&self) -> bool {
1509        self.button_clicked(PointerButton::Secondary)
1510    }
1511
1512    /// Is this button currently down?
1513    #[inline(always)]
1514    pub fn button_down(&self, button: PointerButton) -> bool {
1515        self.down[button as usize]
1516    }
1517
1518    /// If the pointer button is down, will it register as a click when released?
1519    ///
1520    /// See also [`Self::is_decidedly_dragging`].
1521    pub fn could_any_button_be_click(&self) -> bool {
1522        if self.any_down() || self.any_released() {
1523            if self.has_moved_too_much_for_a_click {
1524                return false;
1525            }
1526
1527            if let Some(press_start_time) = self.press_start_time
1528                && self.time - press_start_time > self.options.max_click_duration
1529            {
1530                return false;
1531            }
1532
1533            true
1534        } else {
1535            false
1536        }
1537    }
1538
1539    /// Just because the mouse is down doesn't mean we are dragging.
1540    /// We could be at the start of a click.
1541    /// But if the mouse is down long enough, or has moved far enough,
1542    /// then we consider it a drag.
1543    ///
1544    /// This function can return true on the same frame the drag is released,
1545    /// but NOT on the first frame it was started.
1546    ///
1547    /// See also [`Self::could_any_button_be_click`].
1548    pub fn is_decidedly_dragging(&self) -> bool {
1549        (self.any_down() || self.any_released())
1550            && !self.any_pressed()
1551            && !self.could_any_button_be_click()
1552            && !self.any_click()
1553    }
1554
1555    /// A long press is something we detect on touch screens
1556    /// to trigger a secondary click (context menu).
1557    ///
1558    /// Returns `true` only on one frame.
1559    pub(crate) fn is_long_press(&self) -> bool {
1560        self.started_decidedly_dragging
1561            && !self.has_moved_too_much_for_a_click
1562            && self.button_down(PointerButton::Primary)
1563            && self.press_start_time.is_some_and(|press_start_time| {
1564                self.time - press_start_time > self.options.max_click_duration
1565            })
1566    }
1567
1568    /// Is the primary button currently down?
1569    #[inline(always)]
1570    pub fn primary_down(&self) -> bool {
1571        self.button_down(PointerButton::Primary)
1572    }
1573
1574    /// Is the secondary button currently down?
1575    #[inline(always)]
1576    pub fn secondary_down(&self) -> bool {
1577        self.button_down(PointerButton::Secondary)
1578    }
1579
1580    /// Is the middle button currently down?
1581    #[inline(always)]
1582    pub fn middle_down(&self) -> bool {
1583        self.button_down(PointerButton::Middle)
1584    }
1585
1586    /// Is the mouse moving in the direction of the given rect?
1587    pub fn is_moving_towards_rect(&self, rect: &Rect) -> bool {
1588        if self.is_still() {
1589            return false;
1590        }
1591
1592        if let Some(pos) = self.hover_pos() {
1593            let dir = self.direction();
1594            if dir != Vec2::ZERO {
1595                return rect.intersects_ray(pos, self.direction());
1596            }
1597        }
1598        false
1599    }
1600}
1601
1602impl InputState {
1603    pub fn ui(&self, ui: &mut crate::Ui) {
1604        let Self {
1605            raw,
1606            pointer,
1607            touch_states,
1608
1609            last_scroll_time,
1610            unprocessed_scroll_delta,
1611            unprocessed_scroll_delta_for_zoom,
1612            raw_scroll_delta,
1613            smooth_scroll_delta,
1614            rotation_radians,
1615
1616            zoom_factor_delta,
1617            viewport_rect,
1618            safe_area_insets,
1619            pixels_per_point,
1620            max_texture_side,
1621            time,
1622            unstable_dt,
1623            predicted_dt,
1624            stable_dt,
1625            focused,
1626            modifiers,
1627            keys_down,
1628            events,
1629            options: _,
1630        } = self;
1631
1632        ui.style_mut()
1633            .text_styles
1634            .get_mut(&crate::TextStyle::Body)
1635            .unwrap()
1636            .family = crate::FontFamily::Monospace;
1637
1638        ui.collapsing("Raw Input", |ui| raw.ui(ui));
1639
1640        crate::containers::CollapsingHeader::new("🖱 Pointer")
1641            .default_open(false)
1642            .show(ui, |ui| {
1643                pointer.ui(ui);
1644            });
1645
1646        for (device_id, touch_state) in touch_states {
1647            ui.collapsing(format!("Touch State [device {}]", device_id.0), |ui| {
1648                touch_state.ui(ui);
1649            });
1650        }
1651
1652        ui.label(format!(
1653            "Time since last scroll: {:.1} s",
1654            time - last_scroll_time
1655        ));
1656        if cfg!(debug_assertions) {
1657            ui.label(format!(
1658                "unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points"
1659            ));
1660            ui.label(format!(
1661                "unprocessed_scroll_delta_for_zoom: {unprocessed_scroll_delta_for_zoom:?} points"
1662            ));
1663        }
1664        ui.label(format!("raw_scroll_delta: {raw_scroll_delta:?} points"));
1665        ui.label(format!(
1666            "smooth_scroll_delta: {smooth_scroll_delta:?} points"
1667        ));
1668        ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x"));
1669        ui.label(format!("rotation_radians: {rotation_radians:.3} radians"));
1670
1671        ui.label(format!("viewport_rect: {viewport_rect:?} points"));
1672        ui.label(format!("safe_area_insets: {safe_area_insets:?} points"));
1673        ui.label(format!(
1674            "{pixels_per_point} physical pixels for each logical point"
1675        ));
1676        ui.label(format!(
1677            "max texture size (on each side): {max_texture_side}"
1678        ));
1679        ui.label(format!("time: {time:.3} s"));
1680        ui.label(format!(
1681            "time since previous frame: {:.1} ms",
1682            1e3 * unstable_dt
1683        ));
1684        ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
1685        ui.label(format!("stable_dt:    {:.1} ms", 1e3 * stable_dt));
1686        ui.label(format!("focused:   {focused}"));
1687        ui.label(format!("modifiers: {modifiers:#?}"));
1688        ui.label(format!("keys_down: {keys_down:?}"));
1689        ui.scope(|ui| {
1690            ui.set_min_height(150.0);
1691            ui.label(format!("events: {events:#?}"))
1692                .on_hover_text("key presses etc");
1693        });
1694    }
1695}
1696
1697impl PointerState {
1698    pub fn ui(&self, ui: &mut crate::Ui) {
1699        let Self {
1700            time: _,
1701            latest_pos,
1702            interact_pos,
1703            delta,
1704            motion,
1705            velocity,
1706            direction,
1707            pos_history: _,
1708            down,
1709            press_origin,
1710            press_start_time,
1711            has_moved_too_much_for_a_click,
1712            started_decidedly_dragging,
1713            last_click_time,
1714            last_last_click_time,
1715            pointer_events,
1716            last_move_time,
1717            options: _,
1718        } = self;
1719
1720        ui.label(format!("latest_pos: {latest_pos:?}"));
1721        ui.label(format!("interact_pos: {interact_pos:?}"));
1722        ui.label(format!("delta: {delta:?}"));
1723        ui.label(format!("motion: {motion:?}"));
1724        ui.label(format!(
1725            "velocity: [{:3.0} {:3.0}] points/sec",
1726            velocity.x, velocity.y
1727        ));
1728        ui.label(format!("direction: {direction:?}"));
1729        ui.label(format!("down: {down:#?}"));
1730        ui.label(format!("press_origin: {press_origin:?}"));
1731        ui.label(format!("press_start_time: {press_start_time:?} s"));
1732        ui.label(format!(
1733            "has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}"
1734        ));
1735        ui.label(format!(
1736            "started_decidedly_dragging: {started_decidedly_dragging}"
1737        ));
1738        ui.label(format!("last_click_time: {last_click_time:#?}"));
1739        ui.label(format!("last_last_click_time: {last_last_click_time:#?}"));
1740        ui.label(format!("last_move_time: {last_move_time:#?}"));
1741        ui.label(format!("pointer_events: {pointer_events:?}"));
1742    }
1743}