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