egui/data/input.rs
1//! The input needed by egui.
2
3use epaint::ColorImage;
4
5use crate::{
6 emath::{Pos2, Rect, Vec2},
7 Key, Theme, ViewportId, ViewportIdMap,
8};
9
10/// What the integrations provides to egui at the start of each frame.
11///
12/// Set the values that make sense, leave the rest at their `Default::default()`.
13///
14/// You can check if `egui` is using the inputs using
15/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
16///
17/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left .corner.
18///
19/// Ii "points" can be calculated from native physical pixels
20/// using `pixels_per_point` = [`crate::Context::zoom_factor`] * `native_pixels_per_point`;
21#[derive(Clone, Debug, PartialEq)]
22#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
23pub struct RawInput {
24 /// The id of the active viewport.
25 pub viewport_id: ViewportId,
26
27 /// Information about all egui viewports.
28 pub viewports: ViewportIdMap<ViewportInfo>,
29
30 /// Position and size of the area that egui should use, in points.
31 /// Usually you would set this to
32 ///
33 /// `Some(Rect::from_min_size(Default::default(), screen_size_in_points))`.
34 ///
35 /// but you could also constrain egui to some smaller portion of your window if you like.
36 ///
37 /// `None` will be treated as "same as last frame", with the default being a very big area.
38 pub screen_rect: Option<Rect>,
39
40 /// Maximum size of one side of the font texture.
41 ///
42 /// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
43 ///
44 /// The default is a very small (but very portable) 2048.
45 pub max_texture_side: Option<usize>,
46
47 /// Monotonically increasing time, in seconds. Relative to whatever. Used for animations.
48 /// If `None` is provided, egui will assume a time delta of `predicted_dt` (default 1/60 seconds).
49 pub time: Option<f64>,
50
51 /// Should be set to the expected time between frames when painting at vsync speeds.
52 /// The default for this is 1/60.
53 /// Can safely be left at its default value.
54 pub predicted_dt: f32,
55
56 /// Which modifier keys are down at the start of the frame?
57 pub modifiers: Modifiers,
58
59 /// In-order events received this frame.
60 ///
61 /// There is currently no way to know if egui handles a particular event,
62 /// but you can check if egui is using the keyboard with [`crate::Context::wants_keyboard_input`]
63 /// and/or the pointer (mouse/touch) with [`crate::Context::is_using_pointer`].
64 pub events: Vec<Event>,
65
66 /// Dragged files hovering over egui.
67 pub hovered_files: Vec<HoveredFile>,
68
69 /// Dragged files dropped into egui.
70 ///
71 /// Note: when using `eframe` on Windows, this will always be empty if drag-and-drop support has
72 /// been disabled in [`crate::viewport::ViewportBuilder`].
73 pub dropped_files: Vec<DroppedFile>,
74
75 /// The native window has the keyboard focus (i.e. is receiving key presses).
76 ///
77 /// False when the user alt-tab away from the application, for instance.
78 pub focused: bool,
79
80 /// Does the OS use dark or light mode?
81 ///
82 /// `None` means "don't know".
83 pub system_theme: Option<Theme>,
84}
85
86impl Default for RawInput {
87 fn default() -> Self {
88 Self {
89 viewport_id: ViewportId::ROOT,
90 viewports: std::iter::once((ViewportId::ROOT, Default::default())).collect(),
91 screen_rect: None,
92 max_texture_side: None,
93 time: None,
94 predicted_dt: 1.0 / 60.0,
95 modifiers: Modifiers::default(),
96 events: vec![],
97 hovered_files: Default::default(),
98 dropped_files: Default::default(),
99 focused: true, // integrations opt into global focus tracking
100 system_theme: None,
101 }
102 }
103}
104
105impl RawInput {
106 /// Info about the active viewport
107 #[inline]
108 pub fn viewport(&self) -> &ViewportInfo {
109 self.viewports.get(&self.viewport_id).expect("Failed to find current viewport in egui RawInput. This is the fault of the egui backend")
110 }
111
112 /// Helper: move volatile (deltas and events), clone the rest.
113 ///
114 /// * [`Self::hovered_files`] is cloned.
115 /// * [`Self::dropped_files`] is moved.
116 pub fn take(&mut self) -> Self {
117 Self {
118 viewport_id: self.viewport_id,
119 viewports: self
120 .viewports
121 .iter_mut()
122 .map(|(id, info)| (*id, info.take()))
123 .collect(),
124 screen_rect: self.screen_rect.take(),
125 max_texture_side: self.max_texture_side.take(),
126 time: self.time,
127 predicted_dt: self.predicted_dt,
128 modifiers: self.modifiers,
129 events: std::mem::take(&mut self.events),
130 hovered_files: self.hovered_files.clone(),
131 dropped_files: std::mem::take(&mut self.dropped_files),
132 focused: self.focused,
133 system_theme: self.system_theme,
134 }
135 }
136
137 /// Add on new input.
138 pub fn append(&mut self, newer: Self) {
139 let Self {
140 viewport_id: viewport_ids,
141 viewports,
142 screen_rect,
143 max_texture_side,
144 time,
145 predicted_dt,
146 modifiers,
147 mut events,
148 mut hovered_files,
149 mut dropped_files,
150 focused,
151 system_theme,
152 } = newer;
153
154 self.viewport_id = viewport_ids;
155 self.viewports = viewports;
156 self.screen_rect = screen_rect.or(self.screen_rect);
157 self.max_texture_side = max_texture_side.or(self.max_texture_side);
158 self.time = time; // use latest time
159 self.predicted_dt = predicted_dt; // use latest dt
160 self.modifiers = modifiers; // use latest
161 self.events.append(&mut events);
162 self.hovered_files.append(&mut hovered_files);
163 self.dropped_files.append(&mut dropped_files);
164 self.focused = focused;
165 self.system_theme = system_theme;
166 }
167}
168
169/// An input event from the backend into egui, about a specific [viewport](crate::viewport).
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
172pub enum ViewportEvent {
173 /// The user clicked the close-button on the window, or similar.
174 ///
175 /// If this is the root viewport, the application will exit
176 /// after this frame unless you send a
177 /// [`crate::ViewportCommand::CancelClose`] command.
178 ///
179 /// If this is not the root viewport,
180 /// it is up to the user to hide this viewport the next frame.
181 ///
182 /// This even will wake up both the child and parent viewport.
183 Close,
184}
185
186/// Information about the current viewport, given as input each frame.
187///
188/// `None` means "unknown".
189///
190/// All units are in ui "points", which can be calculated from native physical pixels
191/// using `pixels_per_point` = [`crate::Context::zoom_factor`] * `[Self::native_pixels_per_point`];
192#[derive(Clone, Debug, Default, PartialEq)]
193#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
194pub struct ViewportInfo {
195 /// Parent viewport, if known.
196 pub parent: Option<crate::ViewportId>,
197
198 /// Name of the viewport, if known.
199 pub title: Option<String>,
200
201 pub events: Vec<ViewportEvent>,
202
203 /// The OS native pixels-per-point.
204 ///
205 /// This should always be set, if known.
206 ///
207 /// On web this takes browser scaling into account,
208 /// and corresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
209 pub native_pixels_per_point: Option<f32>,
210
211 /// Current monitor size in egui points.
212 pub monitor_size: Option<Vec2>,
213
214 /// The inner rectangle of the native window, in monitor space and ui points scale.
215 ///
216 /// This is the content rectangle of the viewport.
217 pub inner_rect: Option<Rect>,
218
219 /// The outer rectangle of the native window, in monitor space and ui points scale.
220 ///
221 /// This is the content rectangle plus decoration chrome.
222 pub outer_rect: Option<Rect>,
223
224 /// Are we minimized?
225 pub minimized: Option<bool>,
226
227 /// Are we maximized?
228 pub maximized: Option<bool>,
229
230 /// Are we in fullscreen mode?
231 pub fullscreen: Option<bool>,
232
233 /// Is the window focused and able to receive input?
234 ///
235 /// This should be the same as [`RawInput::focused`].
236 pub focused: Option<bool>,
237}
238
239impl ViewportInfo {
240 /// This viewport has been told to close.
241 ///
242 /// If this is the root viewport, the application will exit
243 /// after this frame unless you send a
244 /// [`crate::ViewportCommand::CancelClose`] command.
245 ///
246 /// If this is not the root viewport,
247 /// it is up to the user to hide this viewport the next frame.
248 pub fn close_requested(&self) -> bool {
249 self.events
250 .iter()
251 .any(|&event| event == ViewportEvent::Close)
252 }
253
254 /// Helper: move [`Self::events`], clone the other fields.
255 pub fn take(&mut self) -> Self {
256 Self {
257 parent: self.parent,
258 title: self.title.clone(),
259 events: std::mem::take(&mut self.events),
260 native_pixels_per_point: self.native_pixels_per_point,
261 monitor_size: self.monitor_size,
262 inner_rect: self.inner_rect,
263 outer_rect: self.outer_rect,
264 minimized: self.minimized,
265 maximized: self.maximized,
266 fullscreen: self.fullscreen,
267 focused: self.focused,
268 }
269 }
270
271 pub fn ui(&self, ui: &mut crate::Ui) {
272 let Self {
273 parent,
274 title,
275 events,
276 native_pixels_per_point,
277 monitor_size,
278 inner_rect,
279 outer_rect,
280 minimized,
281 maximized,
282 fullscreen,
283 focused,
284 } = self;
285
286 crate::Grid::new("viewport_info").show(ui, |ui| {
287 ui.label("Parent:");
288 ui.label(opt_as_str(parent));
289 ui.end_row();
290
291 ui.label("Title:");
292 ui.label(opt_as_str(title));
293 ui.end_row();
294
295 ui.label("Events:");
296 ui.label(format!("{events:?}"));
297 ui.end_row();
298
299 ui.label("Native pixels-per-point:");
300 ui.label(opt_as_str(native_pixels_per_point));
301 ui.end_row();
302
303 ui.label("Monitor size:");
304 ui.label(opt_as_str(monitor_size));
305 ui.end_row();
306
307 ui.label("Inner rect:");
308 ui.label(opt_rect_as_string(inner_rect));
309 ui.end_row();
310
311 ui.label("Outer rect:");
312 ui.label(opt_rect_as_string(outer_rect));
313 ui.end_row();
314
315 ui.label("Minimized:");
316 ui.label(opt_as_str(minimized));
317 ui.end_row();
318
319 ui.label("Maximized:");
320 ui.label(opt_as_str(maximized));
321 ui.end_row();
322
323 ui.label("Fullscreen:");
324 ui.label(opt_as_str(fullscreen));
325 ui.end_row();
326
327 ui.label("Focused:");
328 ui.label(opt_as_str(focused));
329 ui.end_row();
330
331 fn opt_rect_as_string(v: &Option<Rect>) -> String {
332 v.as_ref().map_or(String::new(), |r| {
333 format!("Pos: {:?}, size: {:?}", r.min, r.size())
334 })
335 }
336
337 fn opt_as_str<T: std::fmt::Debug>(v: &Option<T>) -> String {
338 v.as_ref().map_or(String::new(), |v| format!("{v:?}"))
339 }
340 });
341 }
342}
343
344/// A file about to be dropped into egui.
345#[derive(Clone, Debug, Default, PartialEq, Eq)]
346#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
347pub struct HoveredFile {
348 /// Set by the `egui-winit` backend.
349 pub path: Option<std::path::PathBuf>,
350
351 /// With the `eframe` web backend, this is set to the mime-type of the file (if available).
352 pub mime: String,
353}
354
355/// A file dropped into egui.
356#[derive(Clone, Debug, Default, PartialEq, Eq)]
357#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
358pub struct DroppedFile {
359 /// Set by the `egui-winit` backend.
360 pub path: Option<std::path::PathBuf>,
361
362 /// Name of the file. Set by the `eframe` web backend.
363 pub name: String,
364
365 /// With the `eframe` web backend, this is set to the mime-type of the file (if available).
366 pub mime: String,
367
368 /// Set by the `eframe` web backend.
369 pub last_modified: Option<std::time::SystemTime>,
370
371 /// Set by the `eframe` web backend.
372 pub bytes: Option<std::sync::Arc<[u8]>>,
373}
374
375/// An input event generated by the integration.
376///
377/// This only covers events that egui cares about.
378#[derive(Clone, Debug, PartialEq)]
379#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
380pub enum Event {
381 /// The integration detected a "copy" event (e.g. Cmd+C).
382 Copy,
383
384 /// The integration detected a "cut" event (e.g. Cmd+X).
385 Cut,
386
387 /// The integration detected a "paste" event (e.g. Cmd+V).
388 Paste(String),
389
390 /// Text input, e.g. via keyboard.
391 ///
392 /// When the user presses enter/return, do not send a [`Text`](Event::Text) (just [`Key::Enter`]).
393 Text(String),
394
395 /// A key was pressed or released.
396 Key {
397 /// Most of the time, it's the logical key, heeding the active keymap -- for instance, if the user has Dvorak
398 /// keyboard layout, it will be taken into account.
399 ///
400 /// If it's impossible to determine the logical key on desktop platforms (say, in case of non-Latin letters),
401 /// `key` falls back to the value of the corresponding physical key. This is necessary for proper work of
402 /// standard shortcuts that only respond to Latin-based bindings (such as `Ctrl` + `V`).
403 key: Key,
404
405 /// The physical key, corresponding to the actual position on the keyboard.
406 ///
407 /// This ignores keymaps, so it is not recommended to use this.
408 /// The only thing it makes sense for is things like games,
409 /// where e.g. the physical location of WSAD on QWERTY should always map to movement,
410 /// even if the user is using Dvorak or AZERTY.
411 ///
412 /// `eframe` does not (yet) implement this on web.
413 physical_key: Option<Key>,
414
415 /// Was it pressed or released?
416 pressed: bool,
417
418 /// If this is a `pressed` event, is it a key-repeat?
419 ///
420 /// On many platforms, holding down a key produces many repeated "pressed" events for it, so called key-repeats.
421 /// Sometimes you will want to ignore such events, and this lets you do that.
422 ///
423 /// egui will automatically detect such repeat events and mark them as such here.
424 /// Therefore, if you are writing an egui integration, you do not need to set this (just set it to `false`).
425 repeat: bool,
426
427 /// The state of the modifier keys at the time of the event.
428 modifiers: Modifiers,
429 },
430
431 /// The mouse or touch moved to a new place.
432 PointerMoved(Pos2),
433
434 /// The mouse moved, the units are unspecified.
435 /// Represents the actual movement of the mouse, without acceleration or clamped by screen edges.
436 /// `PointerMoved` and `MouseMoved` can be sent at the same time.
437 /// This event is optional. If the integration can not determine unfiltered motion it should not send this event.
438 MouseMoved(Vec2),
439
440 /// A mouse button was pressed or released (or a touch started or stopped).
441 PointerButton {
442 /// Where is the pointer?
443 pos: Pos2,
444
445 /// What mouse button? For touches, use [`PointerButton::Primary`].
446 button: PointerButton,
447
448 /// Was it the button/touch pressed this frame, or released?
449 pressed: bool,
450
451 /// The state of the modifier keys at the time of the event.
452 modifiers: Modifiers,
453 },
454
455 /// The mouse left the screen, or the last/primary touch input disappeared.
456 ///
457 /// This means there is no longer a cursor on the screen for hovering etc.
458 ///
459 /// On touch-up first send `PointerButton{pressed: false, …}` followed by `PointerLeft`.
460 PointerGone,
461
462 /// Zoom scale factor this frame (e.g. from a pinch gesture).
463 ///
464 /// * `zoom = 1`: no change.
465 /// * `zoom < 1`: pinch together
466 /// * `zoom > 1`: pinch spread
467 ///
468 /// Note that egui also implement zooming by holding `Ctrl` and scrolling the mouse wheel,
469 /// so integration need NOT emit this `Zoom` event in those cases, just [`Self::MouseWheel`].
470 ///
471 /// As a user, check [`crate::InputState::smooth_scroll_delta`] to see if the user did any zooming this frame.
472 Zoom(f32),
473
474 /// IME Event
475 Ime(ImeEvent),
476
477 /// On touch screens, report this *in addition to*
478 /// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
479 Touch {
480 /// Hashed device identifier (if available; may be zero).
481 /// Can be used to separate touches from different devices.
482 device_id: TouchDeviceId,
483
484 /// Unique identifier of a finger/pen. Value is stable from touch down
485 /// to lift-up
486 id: TouchId,
487
488 /// One of: start move end cancel.
489 phase: TouchPhase,
490
491 /// Position of the touch (or where the touch was last detected)
492 pos: Pos2,
493
494 /// Describes how hard the touch device was pressed. May always be `None` if the platform does
495 /// not support pressure sensitivity.
496 /// The value is in the range from 0.0 (no pressure) to 1.0 (maximum pressure).
497 force: Option<f32>,
498 },
499
500 /// A raw mouse wheel event as sent by the backend.
501 ///
502 /// Used for scrolling.
503 MouseWheel {
504 /// The unit of `delta`: points, lines, or pages.
505 unit: MouseWheelUnit,
506
507 /// The direction of the vector indicates how to move the _content_ that is being viewed.
508 /// So if you get positive values, the content being viewed should move to the right and down,
509 /// revealing new things to the left and up.
510 ///
511 /// A positive X-value indicates the content is being moved right,
512 /// as when swiping right on a touch-screen or track-pad with natural scrolling.
513 ///
514 /// A positive Y-value indicates the content is being moved down,
515 /// as when swiping down on a touch-screen or track-pad with natural scrolling.
516 delta: Vec2,
517
518 /// The state of the modifier keys at the time of the event.
519 modifiers: Modifiers,
520 },
521
522 /// The native window gained or lost focused (e.g. the user clicked alt-tab).
523 WindowFocused(bool),
524
525 /// An assistive technology (e.g. screen reader) requested an action.
526 #[cfg(feature = "accesskit")]
527 AccessKitActionRequest(accesskit::ActionRequest),
528
529 /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`].
530 Screenshot {
531 viewport_id: crate::ViewportId,
532
533 /// Whatever was passed to [`crate::ViewportCommand::Screenshot`].
534 user_data: crate::UserData,
535
536 image: std::sync::Arc<ColorImage>,
537 },
538}
539
540/// IME event.
541///
542/// See <https://docs.rs/winit/latest/winit/event/enum.Ime.html>
543#[derive(Clone, Debug, Eq, PartialEq)]
544#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
545pub enum ImeEvent {
546 /// Notifies when the IME was enabled.
547 Enabled,
548
549 /// A new IME candidate is being suggested.
550 Preedit(String),
551
552 /// IME composition ended with this final result.
553 Commit(String),
554
555 /// Notifies when the IME was disabled.
556 Disabled,
557}
558
559/// Mouse button (or similar for touch input)
560#[derive(Clone, Copy, Debug, Eq, PartialEq)]
561#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
562pub enum PointerButton {
563 /// The primary mouse button is usually the left one.
564 Primary = 0,
565
566 /// The secondary mouse button is usually the right one,
567 /// and most often used for context menus or other optional things.
568 Secondary = 1,
569
570 /// The tertiary mouse button is usually the middle mouse button (e.g. clicking the scroll wheel).
571 Middle = 2,
572
573 /// The first extra mouse button on some mice. In web typically corresponds to the Browser back button.
574 Extra1 = 3,
575
576 /// The second extra mouse button on some mice. In web typically corresponds to the Browser forward button.
577 Extra2 = 4,
578}
579
580/// Number of pointer buttons supported by egui, i.e. the number of possible states of [`PointerButton`].
581pub const NUM_POINTER_BUTTONS: usize = 5;
582
583/// State of the modifier keys. These must be fed to egui.
584///
585/// The best way to compare [`Modifiers`] is by using [`Modifiers::matches`].
586///
587/// NOTE: For cross-platform uses, ALT+SHIFT is a bad combination of modifiers
588/// as on mac that is how you type special characters,
589/// so those key presses are usually not reported to egui.
590#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
591#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
592pub struct Modifiers {
593 /// Either of the alt keys are down (option ⌥ on Mac).
594 pub alt: bool,
595
596 /// Either of the control keys are down.
597 /// When checking for keyboard shortcuts, consider using [`Self::command`] instead.
598 pub ctrl: bool,
599
600 /// Either of the shift keys are down.
601 pub shift: bool,
602
603 /// The Mac ⌘ Command key. Should always be set to `false` on other platforms.
604 pub mac_cmd: bool,
605
606 /// On Windows and Linux, set this to the same value as `ctrl`.
607 /// On Mac, this should be set whenever one of the ⌘ Command keys are down (same as `mac_cmd`).
608 /// This is so that egui can, for instance, select all text by checking for `command + A`
609 /// and it will work on both Mac and Windows.
610 pub command: bool,
611}
612
613impl std::fmt::Debug for Modifiers {
614 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
615 if self.is_none() {
616 return write!(f, "Modifiers::NONE");
617 }
618
619 let Self {
620 alt,
621 ctrl,
622 shift,
623 mac_cmd,
624 command,
625 } = *self;
626
627 let mut debug = f.debug_struct("Modifiers");
628 if alt {
629 debug.field("alt", &true);
630 }
631 if ctrl {
632 debug.field("ctrl", &true);
633 }
634 if shift {
635 debug.field("shift", &true);
636 }
637 if mac_cmd {
638 debug.field("mac_cmd", &true);
639 }
640 if command {
641 debug.field("command", &true);
642 }
643 debug.finish()
644 }
645}
646
647impl Modifiers {
648 pub const NONE: Self = Self {
649 alt: false,
650 ctrl: false,
651 shift: false,
652 mac_cmd: false,
653 command: false,
654 };
655
656 pub const ALT: Self = Self {
657 alt: true,
658 ctrl: false,
659 shift: false,
660 mac_cmd: false,
661 command: false,
662 };
663 pub const CTRL: Self = Self {
664 alt: false,
665 ctrl: true,
666 shift: false,
667 mac_cmd: false,
668 command: false,
669 };
670 pub const SHIFT: Self = Self {
671 alt: false,
672 ctrl: false,
673 shift: true,
674 mac_cmd: false,
675 command: false,
676 };
677
678 /// The Mac ⌘ Command key
679 pub const MAC_CMD: Self = Self {
680 alt: false,
681 ctrl: false,
682 shift: false,
683 mac_cmd: true,
684 command: false,
685 };
686
687 /// On Mac: ⌘ Command key, elsewhere: Ctrl key
688 pub const COMMAND: Self = Self {
689 alt: false,
690 ctrl: false,
691 shift: false,
692 mac_cmd: false,
693 command: true,
694 };
695
696 /// ```
697 /// # use egui::Modifiers;
698 /// assert_eq!(
699 /// Modifiers::CTRL | Modifiers::ALT,
700 /// Modifiers { ctrl: true, alt: true, ..Default::default() }
701 /// );
702 /// assert_eq!(
703 /// Modifiers::ALT.plus(Modifiers::CTRL),
704 /// Modifiers::CTRL.plus(Modifiers::ALT),
705 /// );
706 /// assert_eq!(
707 /// Modifiers::CTRL | Modifiers::ALT,
708 /// Modifiers::CTRL.plus(Modifiers::ALT),
709 /// );
710 /// ```
711 #[inline]
712 pub const fn plus(self, rhs: Self) -> Self {
713 Self {
714 alt: self.alt | rhs.alt,
715 ctrl: self.ctrl | rhs.ctrl,
716 shift: self.shift | rhs.shift,
717 mac_cmd: self.mac_cmd | rhs.mac_cmd,
718 command: self.command | rhs.command,
719 }
720 }
721
722 #[inline]
723 pub fn is_none(&self) -> bool {
724 self == &Self::default()
725 }
726
727 #[inline]
728 pub fn any(&self) -> bool {
729 !self.is_none()
730 }
731
732 #[inline]
733 pub fn all(&self) -> bool {
734 self.alt && self.ctrl && self.shift && self.command
735 }
736
737 /// Is shift the only pressed button?
738 #[inline]
739 pub fn shift_only(&self) -> bool {
740 self.shift && !(self.alt || self.command)
741 }
742
743 /// true if only [`Self::ctrl`] or only [`Self::mac_cmd`] is pressed.
744 #[inline]
745 pub fn command_only(&self) -> bool {
746 !self.alt && !self.shift && self.command
747 }
748
749 /// Checks that the `ctrl/cmd` matches, and that the `shift/alt` of the argument is a subset
750 /// of the pressed key (`self`).
751 ///
752 /// This means that if the pattern has not set `shift`, then `self` can have `shift` set or not.
753 ///
754 /// The reason is that many logical keys require `shift` or `alt` on some keyboard layouts.
755 /// For instance, in order to press `+` on an English keyboard, you need to press `shift` and `=`,
756 /// but a Swedish keyboard has dedicated `+` key.
757 /// So if you want to make a [`KeyboardShortcut`] looking for `Cmd` + `+`, it makes sense
758 /// to ignore the shift key.
759 /// Similarly, the `Alt` key is sometimes used to type special characters.
760 ///
761 /// However, if the pattern (the argument) explicitly requires the `shift` or `alt` keys
762 /// to be pressed, then they must be pressed.
763 ///
764 /// # Example:
765 /// ```
766 /// # use egui::Modifiers;
767 /// # let pressed_modifiers = Modifiers::default();
768 /// if pressed_modifiers.matches(Modifiers::ALT | Modifiers::SHIFT) {
769 /// // Alt and Shift are pressed, and nothing else
770 /// }
771 /// ```
772 ///
773 /// ## Behavior:
774 /// ```
775 /// # use egui::Modifiers;
776 /// assert!(Modifiers::CTRL.matches_logically(Modifiers::CTRL));
777 /// assert!(!Modifiers::CTRL.matches_logically(Modifiers::CTRL | Modifiers::SHIFT));
778 /// assert!((Modifiers::CTRL | Modifiers::SHIFT).matches_logically(Modifiers::CTRL));
779 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::CTRL));
780 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND));
781 /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND));
782 /// assert!(!Modifiers::COMMAND.matches_logically(Modifiers::MAC_CMD));
783 /// ```
784 pub fn matches_logically(&self, pattern: Self) -> bool {
785 if pattern.alt && !self.alt {
786 return false;
787 }
788 if pattern.shift && !self.shift {
789 return false;
790 }
791
792 self.cmd_ctrl_matches(pattern)
793 }
794
795 /// Check for equality but with proper handling of [`Self::command`].
796 ///
797 /// `self` here are the currently pressed modifiers,
798 /// and the argument the pattern we are testing for.
799 ///
800 /// Note that this will require the `shift` and `alt` keys match, even though
801 /// these modifiers are sometimes required to produce some logical keys.
802 /// For instance, to press `+` on an English keyboard, you need to press `shift` and `=`,
803 /// but on a Swedish keyboard you can press the dedicated `+` key.
804 /// Therefore, you often want to use [`Self::matches_logically`] instead.
805 ///
806 /// # Example:
807 /// ```
808 /// # use egui::Modifiers;
809 /// # let pressed_modifiers = Modifiers::default();
810 /// if pressed_modifiers.matches(Modifiers::ALT | Modifiers::SHIFT) {
811 /// // Alt and Shift are pressed, and nothing else
812 /// }
813 /// ```
814 ///
815 /// ## Behavior:
816 /// ```
817 /// # use egui::Modifiers;
818 /// assert!(Modifiers::CTRL.matches(Modifiers::CTRL));
819 /// assert!(!Modifiers::CTRL.matches(Modifiers::CTRL | Modifiers::SHIFT));
820 /// assert!(!(Modifiers::CTRL | Modifiers::SHIFT).matches(Modifiers::CTRL));
821 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches(Modifiers::CTRL));
822 /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches(Modifiers::COMMAND));
823 /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches(Modifiers::COMMAND));
824 /// assert!(!Modifiers::COMMAND.matches(Modifiers::MAC_CMD));
825 /// ```
826 pub fn matches_exact(&self, pattern: Self) -> bool {
827 // alt and shift must always match the pattern:
828 if pattern.alt != self.alt || pattern.shift != self.shift {
829 return false;
830 }
831
832 self.cmd_ctrl_matches(pattern)
833 }
834
835 #[deprecated = "Renamed `matches_exact`, but maybe you want to use `matches_logically` instead"]
836 pub fn matches(&self, pattern: Self) -> bool {
837 self.matches_exact(pattern)
838 }
839
840 /// Checks only cmd/ctrl, not alt/shift.
841 ///
842 /// `self` here are the currently pressed modifiers,
843 /// and the argument the pattern we are testing for.
844 ///
845 /// This takes care to properly handle the difference between
846 /// [`Self::ctrl`], [`Self::command`] and [`Self::mac_cmd`].
847 pub fn cmd_ctrl_matches(&self, pattern: Self) -> bool {
848 if pattern.mac_cmd {
849 // Mac-specific match:
850 if !self.mac_cmd {
851 return false;
852 }
853 if pattern.ctrl != self.ctrl {
854 return false;
855 }
856 return true;
857 }
858
859 if !pattern.ctrl && !pattern.command {
860 // the pattern explicitly doesn't want any ctrl/command:
861 return !self.ctrl && !self.command;
862 }
863
864 // if the pattern is looking for command, then `ctrl` may or may not be set depending on platform.
865 // if the pattern is looking for `ctrl`, then `command` may or may not be set depending on platform.
866
867 if pattern.ctrl && !self.ctrl {
868 return false;
869 }
870 if pattern.command && !self.command {
871 return false;
872 }
873
874 true
875 }
876
877 /// Whether another set of modifiers is contained in this set of modifiers with proper handling of [`Self::command`].
878 ///
879 /// ```
880 /// # use egui::Modifiers;
881 /// assert!(Modifiers::default().contains(Modifiers::default()));
882 /// assert!(Modifiers::CTRL.contains(Modifiers::default()));
883 /// assert!(Modifiers::CTRL.contains(Modifiers::CTRL));
884 /// assert!(Modifiers::CTRL.contains(Modifiers::COMMAND));
885 /// assert!(Modifiers::MAC_CMD.contains(Modifiers::COMMAND));
886 /// assert!(Modifiers::COMMAND.contains(Modifiers::MAC_CMD));
887 /// assert!(Modifiers::COMMAND.contains(Modifiers::CTRL));
888 /// assert!(!(Modifiers::ALT | Modifiers::CTRL).contains(Modifiers::SHIFT));
889 /// assert!((Modifiers::CTRL | Modifiers::SHIFT).contains(Modifiers::CTRL));
890 /// assert!(!Modifiers::CTRL.contains(Modifiers::CTRL | Modifiers::SHIFT));
891 /// ```
892 pub fn contains(&self, query: Self) -> bool {
893 if query == Self::default() {
894 return true;
895 }
896
897 let Self {
898 alt,
899 ctrl,
900 shift,
901 mac_cmd,
902 command,
903 } = *self;
904
905 if alt && query.alt {
906 return self.contains(Self {
907 alt: false,
908 ..query
909 });
910 }
911 if shift && query.shift {
912 return self.contains(Self {
913 shift: false,
914 ..query
915 });
916 }
917
918 if (ctrl || command) && (query.ctrl || query.command) {
919 return self.contains(Self {
920 command: false,
921 ctrl: false,
922 ..query
923 });
924 }
925 if (mac_cmd || command) && (query.mac_cmd || query.command) {
926 return self.contains(Self {
927 mac_cmd: false,
928 command: false,
929 ..query
930 });
931 }
932
933 false
934 }
935}
936
937impl std::ops::BitOr for Modifiers {
938 type Output = Self;
939
940 #[inline]
941 fn bitor(self, rhs: Self) -> Self {
942 self.plus(rhs)
943 }
944}
945
946// ----------------------------------------------------------------------------
947
948/// Names of different modifier keys.
949///
950/// Used to name modifiers.
951#[derive(Clone, Copy, Debug, Eq, PartialEq)]
952pub struct ModifierNames<'a> {
953 pub is_short: bool,
954
955 pub alt: &'a str,
956 pub ctrl: &'a str,
957 pub shift: &'a str,
958 pub mac_cmd: &'a str,
959 pub mac_alt: &'a str,
960
961 /// What goes between the names
962 pub concat: &'a str,
963}
964
965impl ModifierNames<'static> {
966 /// ⌥ ⌃ ⇧ ⌘ - NOTE: not supported by the default egui font.
967 pub const SYMBOLS: Self = Self {
968 is_short: true,
969 alt: "⌥",
970 ctrl: "⌃",
971 shift: "⇧",
972 mac_cmd: "⌘",
973 mac_alt: "⌥",
974 concat: "",
975 };
976
977 /// Alt, Ctrl, Shift, Cmd
978 pub const NAMES: Self = Self {
979 is_short: false,
980 alt: "Alt",
981 ctrl: "Ctrl",
982 shift: "Shift",
983 mac_cmd: "Cmd",
984 mac_alt: "Option",
985 concat: "+",
986 };
987}
988
989impl ModifierNames<'_> {
990 pub fn format(&self, modifiers: &Modifiers, is_mac: bool) -> String {
991 let mut s = String::new();
992
993 let mut append_if = |modifier_is_active, modifier_name| {
994 if modifier_is_active {
995 if !s.is_empty() {
996 s += self.concat;
997 }
998 s += modifier_name;
999 }
1000 };
1001
1002 if is_mac {
1003 append_if(modifiers.ctrl, self.ctrl);
1004 append_if(modifiers.shift, self.shift);
1005 append_if(modifiers.alt, self.mac_alt);
1006 append_if(modifiers.mac_cmd || modifiers.command, self.mac_cmd);
1007 } else {
1008 append_if(modifiers.ctrl || modifiers.command, self.ctrl);
1009 append_if(modifiers.alt, self.alt);
1010 append_if(modifiers.shift, self.shift);
1011 }
1012
1013 s
1014 }
1015}
1016
1017// ----------------------------------------------------------------------------
1018
1019/// A keyboard shortcut, e.g. `Ctrl+Alt+W`.
1020///
1021/// Can be used with [`crate::InputState::consume_shortcut`]
1022/// and [`crate::Context::format_shortcut`].
1023#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
1024#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1025pub struct KeyboardShortcut {
1026 pub modifiers: Modifiers,
1027
1028 pub logical_key: Key,
1029}
1030
1031impl KeyboardShortcut {
1032 pub const fn new(modifiers: Modifiers, logical_key: Key) -> Self {
1033 Self {
1034 modifiers,
1035 logical_key,
1036 }
1037 }
1038
1039 pub fn format(&self, names: &ModifierNames<'_>, is_mac: bool) -> String {
1040 let mut s = names.format(&self.modifiers, is_mac);
1041 if !s.is_empty() {
1042 s += names.concat;
1043 }
1044 if names.is_short {
1045 s += self.logical_key.symbol_or_name();
1046 } else {
1047 s += self.logical_key.name();
1048 }
1049 s
1050 }
1051}
1052
1053#[test]
1054fn format_kb_shortcut() {
1055 let cmd_shift_f = KeyboardShortcut::new(Modifiers::COMMAND | Modifiers::SHIFT, Key::F);
1056 assert_eq!(
1057 cmd_shift_f.format(&ModifierNames::NAMES, false),
1058 "Ctrl+Shift+F"
1059 );
1060 assert_eq!(
1061 cmd_shift_f.format(&ModifierNames::NAMES, true),
1062 "Shift+Cmd+F"
1063 );
1064 assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "⌃⇧F");
1065 assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F");
1066}
1067
1068// ----------------------------------------------------------------------------
1069
1070impl RawInput {
1071 pub fn ui(&self, ui: &mut crate::Ui) {
1072 let Self {
1073 viewport_id,
1074 viewports,
1075 screen_rect,
1076 max_texture_side,
1077 time,
1078 predicted_dt,
1079 modifiers,
1080 events,
1081 hovered_files,
1082 dropped_files,
1083 focused,
1084 system_theme,
1085 } = self;
1086
1087 ui.label(format!("Active viwport: {viewport_id:?}"));
1088 for (id, viewport) in viewports {
1089 ui.group(|ui| {
1090 ui.label(format!("Viewport {id:?}"));
1091 ui.push_id(id, |ui| {
1092 viewport.ui(ui);
1093 });
1094 });
1095 }
1096 ui.label(format!("screen_rect: {screen_rect:?} points"));
1097
1098 ui.label(format!("max_texture_side: {max_texture_side:?}"));
1099 if let Some(time) = time {
1100 ui.label(format!("time: {time:.3} s"));
1101 } else {
1102 ui.label("time: None");
1103 }
1104 ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
1105 ui.label(format!("modifiers: {modifiers:#?}"));
1106 ui.label(format!("hovered_files: {}", hovered_files.len()));
1107 ui.label(format!("dropped_files: {}", dropped_files.len()));
1108 ui.label(format!("focused: {focused}"));
1109 ui.label(format!("system_theme: {system_theme:?}"));
1110 ui.scope(|ui| {
1111 ui.set_min_height(150.0);
1112 ui.label(format!("events: {events:#?}"))
1113 .on_hover_text("key presses etc");
1114 });
1115 }
1116}
1117
1118/// this is a `u64` as values of this kind can always be obtained by hashing
1119#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
1120#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1121pub struct TouchDeviceId(pub u64);
1122
1123/// Unique identification of a touch occurrence (finger or pen or …).
1124/// A Touch ID is valid until the finger is lifted.
1125/// A new ID is used for the next touch.
1126#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
1127#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1128pub struct TouchId(pub u64);
1129
1130/// In what phase a touch event is in.
1131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1132#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1133pub enum TouchPhase {
1134 /// User just placed a touch point on the touch surface
1135 Start,
1136
1137 /// User moves a touch point along the surface. This event is also sent when
1138 /// any attributes (position, force, …) of the touch point change.
1139 Move,
1140
1141 /// User lifted the finger or pen from the surface, or slid off the edge of
1142 /// the surface
1143 End,
1144
1145 /// Touch operation has been disrupted by something (various reasons are possible,
1146 /// maybe a pop-up alert or any other kind of interruption which may not have
1147 /// been intended by the user)
1148 Cancel,
1149}
1150
1151/// The unit associated with the numeric value of a mouse wheel event
1152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1153#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
1154pub enum MouseWheelUnit {
1155 /// Number of ui points (logical pixels)
1156 Point,
1157
1158 /// Number of lines
1159 Line,
1160
1161 /// Number of pages
1162 Page,
1163}
1164
1165impl From<u64> for TouchId {
1166 fn from(id: u64) -> Self {
1167 Self(id)
1168 }
1169}
1170
1171impl From<i32> for TouchId {
1172 fn from(id: i32) -> Self {
1173 Self(id as u64)
1174 }
1175}
1176
1177impl From<u32> for TouchId {
1178 fn from(id: u32) -> Self {
1179 Self(id as u64)
1180 }
1181}
1182
1183// ----------------------------------------------------------------------------
1184
1185// TODO(emilk): generalize this to a proper event filter.
1186/// Controls which events that a focused widget will have exclusive access to.
1187///
1188/// Currently this only controls a few special keyboard events,
1189/// but in the future this `struct` should be extended into a full callback thing.
1190///
1191/// Any events not covered by the filter are given to the widget, but are not exclusive.
1192#[derive(Clone, Copy, Debug)]
1193pub struct EventFilter {
1194 /// If `true`, pressing tab will act on the widget,
1195 /// and NOT move focus away from the focused widget.
1196 ///
1197 /// Default: `false`
1198 pub tab: bool,
1199
1200 /// If `true`, pressing horizontal arrows will act on the
1201 /// widget, and NOT move focus away from the focused widget.
1202 ///
1203 /// Default: `false`
1204 pub horizontal_arrows: bool,
1205
1206 /// If `true`, pressing vertical arrows will act on the
1207 /// widget, and NOT move focus away from the focused widget.
1208 ///
1209 /// Default: `false`
1210 pub vertical_arrows: bool,
1211
1212 /// If `true`, pressing escape will act on the widget,
1213 /// and NOT surrender focus from the focused widget.
1214 ///
1215 /// Default: `false`
1216 pub escape: bool,
1217}
1218
1219#[allow(clippy::derivable_impls)] // let's be explicit
1220impl Default for EventFilter {
1221 fn default() -> Self {
1222 Self {
1223 tab: false,
1224 horizontal_arrows: false,
1225 vertical_arrows: false,
1226 escape: false,
1227 }
1228 }
1229}
1230
1231impl EventFilter {
1232 pub fn matches(&self, event: &Event) -> bool {
1233 if let Event::Key { key, .. } = event {
1234 match key {
1235 crate::Key::Tab => self.tab,
1236 crate::Key::ArrowUp | crate::Key::ArrowDown => self.vertical_arrows,
1237 crate::Key::ArrowRight | crate::Key::ArrowLeft => self.horizontal_arrows,
1238 crate::Key::Escape => self.escape,
1239 _ => true,
1240 }
1241 } else {
1242 true
1243 }
1244 }
1245}