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