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