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