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