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