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