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