egui_winit/
lib.rs

1//! [`egui`] bindings for [`winit`](https://github.com/rust-windowing/winit).
2//!
3//! The library translates winit events to egui, handled copy/paste,
4//! updates the cursor, open links clicked in egui, etc.
5//!
6//! ## Feature flags
7#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
8//!
9
10#![allow(clippy::manual_range_contains)]
11
12#[cfg(feature = "accesskit")]
13pub use accesskit_winit;
14pub use egui;
15#[cfg(feature = "accesskit")]
16use egui::accesskit;
17use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
18pub use winit;
19
20pub mod clipboard;
21mod safe_area;
22mod window_settings;
23
24pub use window_settings::WindowSettings;
25
26use raw_window_handle::HasDisplayHandle;
27
28use winit::{
29    dpi::{PhysicalPosition, PhysicalSize},
30    event::ElementState,
31    event_loop::ActiveEventLoop,
32    window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
33};
34
35pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
36    let size = if cfg!(target_os = "ios") {
37        // `outer_size` Includes the area behind the "dynamic island".
38        // It is up to the eframe user to make sure the dynamic island doesn't cover anything important.
39        // That will be easier once https://github.com/rust-windowing/winit/pull/3890 lands
40        window.outer_size()
41    } else {
42        window.inner_size()
43    };
44    egui::vec2(size.width as f32, size.height as f32)
45}
46
47/// Calculate the `pixels_per_point` for a given window, given the current egui zoom factor
48pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
49    let native_pixels_per_point = window.scale_factor() as f32;
50    let egui_zoom_factor = egui_ctx.zoom_factor();
51    egui_zoom_factor * native_pixels_per_point
52}
53
54// ----------------------------------------------------------------------------
55
56#[must_use]
57#[derive(Clone, Copy, Debug, Default)]
58pub struct EventResponse {
59    /// If true, egui consumed this event, i.e. wants exclusive use of this event
60    /// (e.g. a mouse click on an egui window, or entering text into a text field).
61    ///
62    /// For instance, if you use egui for a game, you should only
63    /// pass on the events to your game when [`Self::consumed`] is `false`.
64    ///
65    /// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs.
66    pub consumed: bool,
67
68    /// Do we need an egui refresh because of this event?
69    pub repaint: bool,
70}
71
72// ----------------------------------------------------------------------------
73
74/// Handles the integration between egui and a winit Window.
75///
76/// Instantiate one of these per viewport/window.
77pub struct State {
78    /// Shared clone.
79    egui_ctx: egui::Context,
80
81    viewport_id: ViewportId,
82    start_time: web_time::Instant,
83    egui_input: egui::RawInput,
84    pointer_pos_in_points: Option<egui::Pos2>,
85    any_pointer_button_down: bool,
86    current_cursor_icon: Option<egui::CursorIcon>,
87
88    clipboard: clipboard::Clipboard,
89
90    /// If `true`, mouse inputs will be treated as touches.
91    /// Useful for debugging touch support in egui.
92    ///
93    /// Creates duplicate touches, if real touch inputs are coming.
94    simulate_touch_screen: bool,
95
96    /// Is Some(…) when a touch is being translated to a pointer.
97    ///
98    /// Only one touch will be interpreted as pointer at any time.
99    pointer_touch_id: Option<u64>,
100
101    /// track ime state
102    has_sent_ime_enabled: bool,
103
104    #[cfg(feature = "accesskit")]
105    accesskit: Option<accesskit_winit::Adapter>,
106
107    allow_ime: bool,
108    ime_rect_px: Option<egui::Rect>,
109}
110
111impl State {
112    /// Construct a new instance
113    pub fn new(
114        egui_ctx: egui::Context,
115        viewport_id: ViewportId,
116        display_target: &dyn HasDisplayHandle,
117        native_pixels_per_point: Option<f32>,
118        theme: Option<winit::window::Theme>,
119        max_texture_side: Option<usize>,
120    ) -> Self {
121        profiling::function_scope!();
122
123        let egui_input = egui::RawInput {
124            focused: false, // winit will tell us when we have focus
125            ..Default::default()
126        };
127
128        let mut slf = Self {
129            egui_ctx,
130            viewport_id,
131            start_time: web_time::Instant::now(),
132            egui_input,
133            pointer_pos_in_points: None,
134            any_pointer_button_down: false,
135            current_cursor_icon: None,
136
137            clipboard: clipboard::Clipboard::new(
138                display_target.display_handle().ok().map(|h| h.as_raw()),
139            ),
140
141            simulate_touch_screen: false,
142            pointer_touch_id: None,
143
144            has_sent_ime_enabled: false,
145
146            #[cfg(feature = "accesskit")]
147            accesskit: None,
148
149            allow_ime: false,
150            ime_rect_px: None,
151        };
152
153        slf.egui_input
154            .viewports
155            .entry(ViewportId::ROOT)
156            .or_default()
157            .native_pixels_per_point = native_pixels_per_point;
158        slf.egui_input.system_theme = theme.map(to_egui_theme);
159
160        if let Some(max_texture_side) = max_texture_side {
161            slf.set_max_texture_side(max_texture_side);
162        }
163        slf
164    }
165
166    #[cfg(feature = "accesskit")]
167    pub fn init_accesskit<T: From<accesskit_winit::Event> + Send>(
168        &mut self,
169        event_loop: &ActiveEventLoop,
170        window: &Window,
171        event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
172    ) {
173        profiling::function_scope!();
174
175        self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
176            event_loop,
177            window,
178            event_loop_proxy,
179        ));
180    }
181
182    /// Call this once a graphics context has been created to update the maximum texture dimensions
183    /// that egui will use.
184    pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
185        self.egui_input.max_texture_side = Some(max_texture_side);
186    }
187
188    /// Fetches text from the clipboard and returns it.
189    pub fn clipboard_text(&mut self) -> Option<String> {
190        self.clipboard.get()
191    }
192
193    /// Places the text onto the clipboard.
194    pub fn set_clipboard_text(&mut self, text: String) {
195        self.clipboard.set_text(text);
196    }
197
198    /// Returns [`false`] or the last value that [`Window::set_ime_allowed()`] was called with, used for debouncing.
199    pub fn allow_ime(&self) -> bool {
200        self.allow_ime
201    }
202
203    /// Set the last value that [`Window::set_ime_allowed()`] was called with.
204    pub fn set_allow_ime(&mut self, allow: bool) {
205        self.allow_ime = allow;
206    }
207
208    #[inline]
209    pub fn egui_ctx(&self) -> &egui::Context {
210        &self.egui_ctx
211    }
212
213    /// The current input state.
214    /// This is changed by [`Self::on_window_event`] and cleared by [`Self::take_egui_input`].
215    #[inline]
216    pub fn egui_input(&self) -> &egui::RawInput {
217        &self.egui_input
218    }
219
220    /// The current input state.
221    /// This is changed by [`Self::on_window_event`] and cleared by [`Self::take_egui_input`].
222    #[inline]
223    pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
224        &mut self.egui_input
225    }
226
227    /// Prepare for a new frame by extracting the accumulated input,
228    ///
229    /// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect).
230    ///
231    /// You need to set [`egui::RawInput::viewports`] yourself though.
232    /// Use [`update_viewport_info`] to update the info for each
233    /// viewport.
234    pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
235        profiling::function_scope!();
236
237        self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
238
239        // On Windows, a minimized window will have 0 width and height.
240        // See: https://github.com/rust-windowing/winit/issues/208
241        // This solves an issue where egui window positions would be changed when minimizing on Windows.
242        let screen_size_in_pixels = screen_size_in_pixels(window);
243        let screen_size_in_points =
244            screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window);
245
246        self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
247            && screen_size_in_points.y > 0.0)
248            .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
249
250        // Tell egui which viewport is now active:
251        self.egui_input.viewport_id = self.viewport_id;
252
253        self.egui_input
254            .viewports
255            .entry(self.viewport_id)
256            .or_default()
257            .native_pixels_per_point = Some(window.scale_factor() as f32);
258
259        self.egui_input.take()
260    }
261
262    /// Call this when there is a new event.
263    ///
264    /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
265    pub fn on_window_event(
266        &mut self,
267        window: &Window,
268        event: &winit::event::WindowEvent,
269    ) -> EventResponse {
270        profiling::function_scope!(short_window_event_description(event));
271
272        #[cfg(feature = "accesskit")]
273        if let Some(accesskit) = self.accesskit.as_mut() {
274            accesskit.process_event(window, event);
275        }
276
277        use winit::event::WindowEvent;
278
279        #[cfg(target_os = "ios")]
280        match &event {
281            WindowEvent::Resized(_)
282            | WindowEvent::ScaleFactorChanged { .. }
283            | WindowEvent::Focused(true)
284            | WindowEvent::Occluded(false) => {
285                // Once winit v0.31 has been released this can be reworked to get the safe area from
286                // `Window::safe_area`, and updated from a new event which is being discussed in
287                // https://github.com/rust-windowing/winit/issues/3911.
288                self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
289            }
290            _ => {}
291        }
292
293        match event {
294            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
295                let native_pixels_per_point = *scale_factor as f32;
296
297                self.egui_input
298                    .viewports
299                    .entry(self.viewport_id)
300                    .or_default()
301                    .native_pixels_per_point = Some(native_pixels_per_point);
302
303                EventResponse {
304                    repaint: true,
305                    consumed: false,
306                }
307            }
308            WindowEvent::MouseInput { state, button, .. } => {
309                self.on_mouse_button_input(*state, *button);
310                EventResponse {
311                    repaint: true,
312                    consumed: self.egui_ctx.wants_pointer_input(),
313                }
314            }
315            WindowEvent::MouseWheel { delta, .. } => {
316                self.on_mouse_wheel(window, *delta);
317                EventResponse {
318                    repaint: true,
319                    consumed: self.egui_ctx.wants_pointer_input(),
320                }
321            }
322            WindowEvent::CursorMoved { position, .. } => {
323                self.on_cursor_moved(window, *position);
324                EventResponse {
325                    repaint: true,
326                    consumed: self.egui_ctx.is_using_pointer(),
327                }
328            }
329            WindowEvent::CursorLeft { .. } => {
330                self.pointer_pos_in_points = None;
331                self.egui_input.events.push(egui::Event::PointerGone);
332                EventResponse {
333                    repaint: true,
334                    consumed: false,
335                }
336            }
337            // WindowEvent::TouchpadPressure {device_id, pressure, stage, ..  } => {} // TODO(emilk)
338            WindowEvent::Touch(touch) => {
339                self.on_touch(window, touch);
340                let consumed = match touch.phase {
341                    winit::event::TouchPhase::Started
342                    | winit::event::TouchPhase::Ended
343                    | winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(),
344                    winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(),
345                };
346                EventResponse {
347                    repaint: true,
348                    consumed,
349                }
350            }
351
352            WindowEvent::Ime(ime) => {
353                // on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit.
354                // So no need to check is_mac_cmd.
355                //
356                // How winit produce `Ime::Enabled` and `Ime::Disabled` differs in MacOS
357                // and Windows.
358                //
359                // - On Windows, before and after each Commit will produce an Enable/Disabled
360                // event.
361                // - On MacOS, only when user explicit enable/disable ime. No Disabled
362                // after Commit.
363                //
364                // We use input_method_editor_started to manually insert CompositionStart
365                // between Commits.
366                match ime {
367                    winit::event::Ime::Enabled => {
368                        if cfg!(target_os = "linux") {
369                            // This event means different things in X11 and Wayland, but we can just
370                            // ignore it and enable IME on the preedit event.
371                            // See <https://github.com/rust-windowing/winit/issues/2498>
372                        } else {
373                            self.ime_event_enable();
374                        }
375                    }
376                    winit::event::Ime::Preedit(text, Some(_cursor)) => {
377                        self.ime_event_enable();
378                        self.egui_input
379                            .events
380                            .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
381                    }
382                    winit::event::Ime::Commit(text) => {
383                        self.egui_input
384                            .events
385                            .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
386                        self.ime_event_disable();
387                    }
388                    winit::event::Ime::Disabled | winit::event::Ime::Preedit(_, None) => {
389                        self.ime_event_disable();
390                    }
391                }
392
393                EventResponse {
394                    repaint: true,
395                    consumed: self.egui_ctx.wants_keyboard_input(),
396                }
397            }
398            WindowEvent::KeyboardInput {
399                event,
400                is_synthetic,
401                ..
402            } => {
403                // Winit generates fake "synthetic" KeyboardInput events when the focus
404                // is changed to the window, or away from it. Synthetic key presses
405                // represent no real key presses and should be ignored.
406                // See https://github.com/rust-windowing/winit/issues/3543
407                if *is_synthetic && event.state == ElementState::Pressed {
408                    EventResponse {
409                        repaint: true,
410                        consumed: false,
411                    }
412                } else {
413                    self.on_keyboard_input(event);
414
415                    // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
416                    let consumed = self.egui_ctx.wants_keyboard_input()
417                        || event.logical_key
418                            == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
419                    EventResponse {
420                        repaint: true,
421                        consumed,
422                    }
423                }
424            }
425            WindowEvent::Focused(focused) => {
426                let focused = if cfg!(target_os = "macos") {
427                    // TODO(emilk): remove this work-around once we update winit
428                    // https://github.com/rust-windowing/winit/issues/4371
429                    // https://github.com/emilk/egui/issues/7588
430                    window.has_focus()
431                } else {
432                    *focused
433                };
434
435                self.egui_input.focused = focused;
436                self.egui_input
437                    .events
438                    .push(egui::Event::WindowFocused(focused));
439                EventResponse {
440                    repaint: true,
441                    consumed: false,
442                }
443            }
444            WindowEvent::ThemeChanged(winit_theme) => {
445                self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
446                EventResponse {
447                    repaint: true,
448                    consumed: false,
449                }
450            }
451            WindowEvent::HoveredFile(path) => {
452                self.egui_input.hovered_files.push(egui::HoveredFile {
453                    path: Some(path.clone()),
454                    ..Default::default()
455                });
456                EventResponse {
457                    repaint: true,
458                    consumed: false,
459                }
460            }
461            WindowEvent::HoveredFileCancelled => {
462                self.egui_input.hovered_files.clear();
463                EventResponse {
464                    repaint: true,
465                    consumed: false,
466                }
467            }
468            WindowEvent::DroppedFile(path) => {
469                self.egui_input.hovered_files.clear();
470                self.egui_input.dropped_files.push(egui::DroppedFile {
471                    path: Some(path.clone()),
472                    ..Default::default()
473                });
474                EventResponse {
475                    repaint: true,
476                    consumed: false,
477                }
478            }
479            WindowEvent::ModifiersChanged(state) => {
480                let state = state.state();
481
482                let alt = state.alt_key();
483                let ctrl = state.control_key();
484                let shift = state.shift_key();
485                let super_ = state.super_key();
486
487                self.egui_input.modifiers.alt = alt;
488                self.egui_input.modifiers.ctrl = ctrl;
489                self.egui_input.modifiers.shift = shift;
490                self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
491                self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
492                    super_
493                } else {
494                    ctrl
495                };
496
497                EventResponse {
498                    repaint: true,
499                    consumed: false,
500                }
501            }
502
503            // Things that may require repaint:
504            WindowEvent::RedrawRequested
505            | WindowEvent::CursorEntered { .. }
506            | WindowEvent::Destroyed
507            | WindowEvent::Occluded(_)
508            | WindowEvent::Resized(_)
509            | WindowEvent::Moved(_)
510            | WindowEvent::TouchpadPressure { .. }
511            | WindowEvent::CloseRequested => EventResponse {
512                repaint: true,
513                consumed: false,
514            },
515
516            // Things we completely ignore:
517            WindowEvent::ActivationTokenDone { .. }
518            | WindowEvent::AxisMotion { .. }
519            | WindowEvent::DoubleTapGesture { .. } => EventResponse {
520                repaint: false,
521                consumed: false,
522            },
523
524            WindowEvent::PinchGesture { delta, .. } => {
525                // Positive delta values indicate magnification (zooming in).
526                // Negative delta values indicate shrinking (zooming out).
527                let zoom_factor = (*delta as f32).exp();
528                self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
529                EventResponse {
530                    repaint: true,
531                    consumed: self.egui_ctx.wants_pointer_input(),
532                }
533            }
534
535            WindowEvent::RotationGesture { delta, .. } => {
536                // Positive delta values indicate counterclockwise rotation
537                // Negative delta values indicate clockwise rotation
538                // This is opposite of egui's sign convention for angles
539                self.egui_input
540                    .events
541                    .push(egui::Event::Rotate(-delta.to_radians()));
542                EventResponse {
543                    repaint: true,
544                    consumed: self.egui_ctx.wants_pointer_input(),
545                }
546            }
547
548            WindowEvent::PanGesture { delta, .. } => {
549                let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
550
551                self.egui_input.events.push(egui::Event::MouseWheel {
552                    unit: egui::MouseWheelUnit::Point,
553                    delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
554                    modifiers: self.egui_input.modifiers,
555                });
556                EventResponse {
557                    repaint: true,
558                    consumed: self.egui_ctx.wants_pointer_input(),
559                }
560            }
561        }
562    }
563
564    pub fn ime_event_enable(&mut self) {
565        if !self.has_sent_ime_enabled {
566            self.egui_input
567                .events
568                .push(egui::Event::Ime(egui::ImeEvent::Enabled));
569            self.has_sent_ime_enabled = true;
570        }
571    }
572
573    pub fn ime_event_disable(&mut self) {
574        self.egui_input
575            .events
576            .push(egui::Event::Ime(egui::ImeEvent::Disabled));
577        self.has_sent_ime_enabled = false;
578    }
579
580    pub fn on_mouse_motion(&mut self, delta: (f64, f64)) {
581        self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
582            x: delta.0 as f32,
583            y: delta.1 as f32,
584        }));
585    }
586
587    /// Call this when there is a new [`accesskit::ActionRequest`].
588    ///
589    /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
590    #[cfg(feature = "accesskit")]
591    pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
592        self.egui_input
593            .events
594            .push(egui::Event::AccessKitActionRequest(request));
595    }
596
597    fn on_mouse_button_input(
598        &mut self,
599        state: winit::event::ElementState,
600        button: winit::event::MouseButton,
601    ) {
602        if let Some(pos) = self.pointer_pos_in_points
603            && let Some(button) = translate_mouse_button(button)
604        {
605            let pressed = state == winit::event::ElementState::Pressed;
606
607            self.egui_input.events.push(egui::Event::PointerButton {
608                pos,
609                button,
610                pressed,
611                modifiers: self.egui_input.modifiers,
612            });
613
614            if self.simulate_touch_screen {
615                if pressed {
616                    self.any_pointer_button_down = true;
617
618                    self.egui_input.events.push(egui::Event::Touch {
619                        device_id: egui::TouchDeviceId(0),
620                        id: egui::TouchId(0),
621                        phase: egui::TouchPhase::Start,
622                        pos,
623                        force: None,
624                    });
625                } else {
626                    self.any_pointer_button_down = false;
627
628                    self.egui_input.events.push(egui::Event::PointerGone);
629
630                    self.egui_input.events.push(egui::Event::Touch {
631                        device_id: egui::TouchDeviceId(0),
632                        id: egui::TouchId(0),
633                        phase: egui::TouchPhase::End,
634                        pos,
635                        force: None,
636                    });
637                }
638            }
639        }
640    }
641
642    fn on_cursor_moved(
643        &mut self,
644        window: &Window,
645        pos_in_pixels: winit::dpi::PhysicalPosition<f64>,
646    ) {
647        let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
648
649        let pos_in_points = egui::pos2(
650            pos_in_pixels.x as f32 / pixels_per_point,
651            pos_in_pixels.y as f32 / pixels_per_point,
652        );
653        self.pointer_pos_in_points = Some(pos_in_points);
654
655        if self.simulate_touch_screen {
656            if self.any_pointer_button_down {
657                self.egui_input
658                    .events
659                    .push(egui::Event::PointerMoved(pos_in_points));
660
661                self.egui_input.events.push(egui::Event::Touch {
662                    device_id: egui::TouchDeviceId(0),
663                    id: egui::TouchId(0),
664                    phase: egui::TouchPhase::Move,
665                    pos: pos_in_points,
666                    force: None,
667                });
668            }
669        } else {
670            self.egui_input
671                .events
672                .push(egui::Event::PointerMoved(pos_in_points));
673        }
674    }
675
676    fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) {
677        let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
678
679        // Emit touch event
680        self.egui_input.events.push(egui::Event::Touch {
681            device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
682            id: egui::TouchId::from(touch.id),
683            phase: match touch.phase {
684                winit::event::TouchPhase::Started => egui::TouchPhase::Start,
685                winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
686                winit::event::TouchPhase::Ended => egui::TouchPhase::End,
687                winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
688            },
689            pos: egui::pos2(
690                touch.location.x as f32 / pixels_per_point,
691                touch.location.y as f32 / pixels_per_point,
692            ),
693            force: match touch.force {
694                Some(winit::event::Force::Normalized(force)) => Some(force as f32),
695                Some(winit::event::Force::Calibrated {
696                    force,
697                    max_possible_force,
698                    ..
699                }) => Some((force / max_possible_force) as f32),
700                None => None,
701            },
702        });
703        // If we're not yet translating a touch or we're translating this very
704        // touch …
705        if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
706        {
707            // … emit PointerButton resp. PointerMoved events to emulate mouse
708            match touch.phase {
709                winit::event::TouchPhase::Started => {
710                    self.pointer_touch_id = Some(touch.id);
711                    // First move the pointer to the right location
712                    self.on_cursor_moved(window, touch.location);
713                    self.on_mouse_button_input(
714                        winit::event::ElementState::Pressed,
715                        winit::event::MouseButton::Left,
716                    );
717                }
718                winit::event::TouchPhase::Moved => {
719                    self.on_cursor_moved(window, touch.location);
720                }
721                winit::event::TouchPhase::Ended => {
722                    self.pointer_touch_id = None;
723                    self.on_mouse_button_input(
724                        winit::event::ElementState::Released,
725                        winit::event::MouseButton::Left,
726                    );
727                    // The pointer should vanish completely to not get any
728                    // hover effects
729                    self.pointer_pos_in_points = None;
730                    self.egui_input.events.push(egui::Event::PointerGone);
731                }
732                winit::event::TouchPhase::Cancelled => {
733                    self.pointer_touch_id = None;
734                    self.pointer_pos_in_points = None;
735                    self.egui_input.events.push(egui::Event::PointerGone);
736                }
737            }
738        }
739    }
740
741    fn on_mouse_wheel(&mut self, window: &Window, delta: winit::event::MouseScrollDelta) {
742        let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
743
744        {
745            let (unit, delta) = match delta {
746                winit::event::MouseScrollDelta::LineDelta(x, y) => {
747                    (egui::MouseWheelUnit::Line, egui::vec2(x, y))
748                }
749                winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
750                    x,
751                    y,
752                }) => (
753                    egui::MouseWheelUnit::Point,
754                    egui::vec2(x as f32, y as f32) / pixels_per_point,
755                ),
756            };
757            let modifiers = self.egui_input.modifiers;
758            self.egui_input.events.push(egui::Event::MouseWheel {
759                unit,
760                delta,
761                modifiers,
762            });
763        }
764    }
765
766    fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
767        let winit::event::KeyEvent {
768            // Represents the position of a key independent of the currently active layout.
769            //
770            // It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
771            // The most prevalent use case for this is games. For example the default keys for the player
772            // to move around might be the W, A, S, and D keys on a US layout. The position of these keys
773            // is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY"
774            // layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)
775            physical_key,
776
777            // Represents the results of a keymap, i.e. what character a certain key press represents.
778            // When telling users "Press Ctrl-F to find", this is where we should
779            // look for the "F" key, because they may have a dvorak layout on
780            // a qwerty keyboard, and so the logical "F" character may not be located on the physical `KeyCode::KeyF` position.
781            logical_key: winit_logical_key,
782
783            text,
784
785            state,
786
787            location: _, // e.g. is it on the numpad?
788            repeat: _,   // egui will figure this out for us
789            ..
790        } = event;
791
792        let pressed = *state == winit::event::ElementState::Pressed;
793
794        let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
795            key_from_key_code(keycode)
796        } else {
797            None
798        };
799
800        let logical_key = key_from_winit_key(winit_logical_key);
801
802        // Helpful logging to enable when adding new key support
803        log::trace!(
804            "logical {:?} -> {:?},  physical {:?} -> {:?}",
805            event.logical_key,
806            logical_key,
807            event.physical_key,
808            physical_key
809        );
810
811        // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters: it lets them
812        // emit events as if the corresponding keys from the Latin layout were pressed. In this case, clipboard shortcuts
813        // are mapped to the physical keys that normally contain C, X, V, etc.
814        // See also: https://github.com/emilk/egui/issues/3653
815        if let Some(active_key) = logical_key.or(physical_key) {
816            if pressed {
817                if is_cut_command(self.egui_input.modifiers, active_key) {
818                    self.egui_input.events.push(egui::Event::Cut);
819                    return;
820                } else if is_copy_command(self.egui_input.modifiers, active_key) {
821                    self.egui_input.events.push(egui::Event::Copy);
822                    return;
823                } else if is_paste_command(self.egui_input.modifiers, active_key) {
824                    if let Some(contents) = self.clipboard.get() {
825                        let contents = contents.replace("\r\n", "\n");
826                        if !contents.is_empty() {
827                            self.egui_input.events.push(egui::Event::Paste(contents));
828                        }
829                    }
830                    return;
831                }
832            }
833
834            self.egui_input.events.push(egui::Event::Key {
835                key: active_key,
836                physical_key,
837                pressed,
838                repeat: false, // egui will fill this in for us!
839                modifiers: self.egui_input.modifiers,
840            });
841        }
842
843        if let Some(text) = text
844            .as_ref()
845            .map(|t| t.as_str())
846            .or_else(|| winit_logical_key.to_text())
847        {
848            // Make sure there is text, and that it is not control characters
849            // (e.g. delete is sent as "\u{f728}" on macOS).
850            if !text.is_empty() && text.chars().all(is_printable_char) {
851                // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.
852                // We need to ignore these characters that are side-effects of commands.
853                // Also make sure the key is pressed (not released). On Linux, text might
854                // contain some data even when the key is released.
855                let is_cmd = self.egui_input.modifiers.ctrl
856                    || self.egui_input.modifiers.command
857                    || self.egui_input.modifiers.mac_cmd;
858                if pressed && !is_cmd {
859                    self.egui_input
860                        .events
861                        .push(egui::Event::Text(text.to_owned()));
862                }
863            }
864        }
865    }
866
867    /// Call with the output given by `egui`.
868    ///
869    /// This will, if needed:
870    /// * update the cursor
871    /// * copy text to the clipboard
872    /// * open any clicked urls
873    /// * update the IME
874    /// *
875    pub fn handle_platform_output(
876        &mut self,
877        window: &Window,
878        platform_output: egui::PlatformOutput,
879    ) {
880        profiling::function_scope!();
881
882        let egui::PlatformOutput {
883            commands,
884            cursor_icon,
885            events: _,                    // handled elsewhere
886            mutable_text_under_cursor: _, // only used in eframe web
887            ime,
888            #[cfg(feature = "accesskit")]
889            accesskit_update,
890            num_completed_passes: _,    // `egui::Context::run` handles this
891            request_discard_reasons: _, // `egui::Context::run` handles this
892        } = platform_output;
893
894        for command in commands {
895            match command {
896                egui::OutputCommand::CopyText(text) => {
897                    self.clipboard.set_text(text);
898                }
899                egui::OutputCommand::CopyImage(image) => {
900                    self.clipboard.set_image(&image);
901                }
902                egui::OutputCommand::OpenUrl(open_url) => {
903                    open_url_in_browser(&open_url.url);
904                }
905            }
906        }
907
908        self.set_cursor_icon(window, cursor_icon);
909
910        let allow_ime = ime.is_some();
911        if self.allow_ime != allow_ime {
912            self.allow_ime = allow_ime;
913            profiling::scope!("set_ime_allowed");
914            window.set_ime_allowed(allow_ime);
915        }
916
917        if let Some(ime) = ime {
918            let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
919            let ime_rect_px = pixels_per_point * ime.rect;
920            if self.ime_rect_px != Some(ime_rect_px)
921                || self.egui_ctx.input(|i| !i.events.is_empty())
922            {
923                self.ime_rect_px = Some(ime_rect_px);
924                profiling::scope!("set_ime_cursor_area");
925                window.set_ime_cursor_area(
926                    winit::dpi::PhysicalPosition {
927                        x: ime_rect_px.min.x,
928                        y: ime_rect_px.min.y,
929                    },
930                    winit::dpi::PhysicalSize {
931                        width: ime_rect_px.width(),
932                        height: ime_rect_px.height(),
933                    },
934                );
935            }
936        } else {
937            self.ime_rect_px = None;
938        }
939
940        #[cfg(feature = "accesskit")]
941        if let Some(accesskit) = self.accesskit.as_mut()
942            && let Some(update) = accesskit_update
943        {
944            profiling::scope!("accesskit");
945            accesskit.update_if_active(|| update);
946        }
947    }
948
949    fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
950        if self.current_cursor_icon == Some(cursor_icon) {
951            // Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
952            // On other platforms: just early-out to save CPU.
953            return;
954        }
955
956        let is_pointer_in_window = self.pointer_pos_in_points.is_some();
957        if is_pointer_in_window {
958            self.current_cursor_icon = Some(cursor_icon);
959
960            if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
961                window.set_cursor_visible(true);
962                window.set_cursor(winit_cursor_icon);
963            } else {
964                window.set_cursor_visible(false);
965            }
966        } else {
967            // Remember to set the cursor again once the cursor returns to the screen:
968            self.current_cursor_icon = None;
969        }
970    }
971}
972
973fn to_egui_theme(theme: winit::window::Theme) -> Theme {
974    match theme {
975        winit::window::Theme::Dark => Theme::Dark,
976        winit::window::Theme::Light => Theme::Light,
977    }
978}
979
980pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
981    let inner_pos_px = window.inner_position().ok()?;
982    let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
983
984    let inner_size_px = window.inner_size();
985    let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);
986
987    let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);
988
989    Some(inner_rect_px / pixels_per_point)
990}
991
992pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
993    let outer_pos_px = window.outer_position().ok()?;
994    let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);
995
996    let outer_size_px = window.outer_size();
997    let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);
998
999    let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);
1000
1001    Some(outer_rect_px / pixels_per_point)
1002}
1003
1004/// Update the given viewport info with the current state of the window.
1005///
1006/// Call before [`State::take_egui_input`].
1007///
1008/// If this is called right after window creation, `is_init` should be `true`, otherwise `false`.
1009pub fn update_viewport_info(
1010    viewport_info: &mut ViewportInfo,
1011    egui_ctx: &egui::Context,
1012    window: &Window,
1013    is_init: bool,
1014) {
1015    profiling::function_scope!();
1016    let pixels_per_point = pixels_per_point(egui_ctx, window);
1017
1018    let has_a_position = match window.is_minimized() {
1019        Some(true) => false,
1020        Some(false) | None => true,
1021    };
1022
1023    let inner_rect = if has_a_position {
1024        inner_rect_in_points(window, pixels_per_point)
1025    } else {
1026        None
1027    };
1028
1029    let outer_rect = if has_a_position {
1030        outer_rect_in_points(window, pixels_per_point)
1031    } else {
1032        None
1033    };
1034
1035    let monitor_size = {
1036        profiling::scope!("monitor_size");
1037        if let Some(monitor) = window.current_monitor() {
1038            let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
1039            Some(egui::vec2(size.width, size.height))
1040        } else {
1041            None
1042        }
1043    };
1044
1045    viewport_info.title = Some(window.title());
1046    viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
1047
1048    viewport_info.monitor_size = monitor_size;
1049    viewport_info.inner_rect = inner_rect;
1050    viewport_info.outer_rect = outer_rect;
1051
1052    if is_init || !cfg!(target_os = "macos") {
1053        // Asking for minimized/maximized state at runtime leads to a deadlock on Mac when running
1054        // `cargo run -p custom_window_frame`.
1055        // See https://github.com/emilk/egui/issues/3494
1056        viewport_info.maximized = Some(window.is_maximized());
1057        viewport_info.minimized = Some(window.is_minimized().unwrap_or(false));
1058    }
1059
1060    viewport_info.fullscreen = Some(window.fullscreen().is_some());
1061    viewport_info.focused = Some(window.has_focus());
1062}
1063
1064fn open_url_in_browser(_url: &str) {
1065    #[cfg(feature = "webbrowser")]
1066    if let Err(err) = webbrowser::open(_url) {
1067        log::warn!("Failed to open url: {err}");
1068    }
1069
1070    #[cfg(not(feature = "webbrowser"))]
1071    {
1072        log::warn!("Cannot open url - feature \"links\" not enabled.");
1073    }
1074}
1075
1076/// Winit sends special keys (backspace, delete, F1, …) as characters.
1077/// Ignore those.
1078/// We also ignore '\r', '\n', '\t'.
1079/// Newlines are handled by the `Key::Enter` event.
1080fn is_printable_char(chr: char) -> bool {
1081    let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
1082        || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
1083        || '\u{100000}' <= chr && chr <= '\u{10fffd}';
1084
1085    !is_in_private_use_area && !chr.is_ascii_control()
1086}
1087
1088fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1089    keycode == egui::Key::Cut
1090        || (modifiers.command && keycode == egui::Key::X)
1091        || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
1092}
1093
1094fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1095    keycode == egui::Key::Copy
1096        || (modifiers.command && keycode == egui::Key::C)
1097        || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
1098}
1099
1100fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1101    keycode == egui::Key::Paste
1102        || (modifiers.command && keycode == egui::Key::V)
1103        || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
1104}
1105
1106fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
1107    match button {
1108        winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
1109        winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
1110        winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
1111        winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
1112        winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
1113        winit::event::MouseButton::Other(_) => None,
1114    }
1115}
1116
1117fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
1118    match key {
1119        winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
1120        winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
1121        winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
1122    }
1123}
1124
1125fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
1126    use egui::Key;
1127    use winit::keyboard::NamedKey;
1128
1129    Some(match named_key {
1130        NamedKey::Enter => Key::Enter,
1131        NamedKey::Tab => Key::Tab,
1132        NamedKey::ArrowDown => Key::ArrowDown,
1133        NamedKey::ArrowLeft => Key::ArrowLeft,
1134        NamedKey::ArrowRight => Key::ArrowRight,
1135        NamedKey::ArrowUp => Key::ArrowUp,
1136        NamedKey::End => Key::End,
1137        NamedKey::Home => Key::Home,
1138        NamedKey::PageDown => Key::PageDown,
1139        NamedKey::PageUp => Key::PageUp,
1140        NamedKey::Backspace => Key::Backspace,
1141        NamedKey::Delete => Key::Delete,
1142        NamedKey::Insert => Key::Insert,
1143        NamedKey::Escape => Key::Escape,
1144        NamedKey::Cut => Key::Cut,
1145        NamedKey::Copy => Key::Copy,
1146        NamedKey::Paste => Key::Paste,
1147
1148        NamedKey::Space => Key::Space,
1149
1150        NamedKey::F1 => Key::F1,
1151        NamedKey::F2 => Key::F2,
1152        NamedKey::F3 => Key::F3,
1153        NamedKey::F4 => Key::F4,
1154        NamedKey::F5 => Key::F5,
1155        NamedKey::F6 => Key::F6,
1156        NamedKey::F7 => Key::F7,
1157        NamedKey::F8 => Key::F8,
1158        NamedKey::F9 => Key::F9,
1159        NamedKey::F10 => Key::F10,
1160        NamedKey::F11 => Key::F11,
1161        NamedKey::F12 => Key::F12,
1162        NamedKey::F13 => Key::F13,
1163        NamedKey::F14 => Key::F14,
1164        NamedKey::F15 => Key::F15,
1165        NamedKey::F16 => Key::F16,
1166        NamedKey::F17 => Key::F17,
1167        NamedKey::F18 => Key::F18,
1168        NamedKey::F19 => Key::F19,
1169        NamedKey::F20 => Key::F20,
1170        NamedKey::F21 => Key::F21,
1171        NamedKey::F22 => Key::F22,
1172        NamedKey::F23 => Key::F23,
1173        NamedKey::F24 => Key::F24,
1174        NamedKey::F25 => Key::F25,
1175        NamedKey::F26 => Key::F26,
1176        NamedKey::F27 => Key::F27,
1177        NamedKey::F28 => Key::F28,
1178        NamedKey::F29 => Key::F29,
1179        NamedKey::F30 => Key::F30,
1180        NamedKey::F31 => Key::F31,
1181        NamedKey::F32 => Key::F32,
1182        NamedKey::F33 => Key::F33,
1183        NamedKey::F34 => Key::F34,
1184        NamedKey::F35 => Key::F35,
1185
1186        NamedKey::BrowserBack => Key::BrowserBack,
1187        _ => {
1188            log::trace!("Unknown key: {named_key:?}");
1189            return None;
1190        }
1191    })
1192}
1193
1194fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
1195    use egui::Key;
1196    use winit::keyboard::KeyCode;
1197
1198    Some(match key {
1199        KeyCode::ArrowDown => Key::ArrowDown,
1200        KeyCode::ArrowLeft => Key::ArrowLeft,
1201        KeyCode::ArrowRight => Key::ArrowRight,
1202        KeyCode::ArrowUp => Key::ArrowUp,
1203
1204        KeyCode::Escape => Key::Escape,
1205        KeyCode::Tab => Key::Tab,
1206        KeyCode::Backspace => Key::Backspace,
1207        KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
1208
1209        KeyCode::Insert => Key::Insert,
1210        KeyCode::Delete => Key::Delete,
1211        KeyCode::Home => Key::Home,
1212        KeyCode::End => Key::End,
1213        KeyCode::PageUp => Key::PageUp,
1214        KeyCode::PageDown => Key::PageDown,
1215
1216        // Punctuation
1217        KeyCode::Space => Key::Space,
1218        KeyCode::Comma => Key::Comma,
1219        KeyCode::Period => Key::Period,
1220        // KeyCode::Colon => Key::Colon, // NOTE: there is no physical colon key on an american keyboard
1221        KeyCode::Semicolon => Key::Semicolon,
1222        KeyCode::Backslash => Key::Backslash,
1223        KeyCode::Slash | KeyCode::NumpadDivide => Key::Slash,
1224        KeyCode::BracketLeft => Key::OpenBracket,
1225        KeyCode::BracketRight => Key::CloseBracket,
1226        KeyCode::Backquote => Key::Backtick,
1227        KeyCode::Quote => Key::Quote,
1228
1229        KeyCode::Cut => Key::Cut,
1230        KeyCode::Copy => Key::Copy,
1231        KeyCode::Paste => Key::Paste,
1232        KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
1233        KeyCode::NumpadAdd => Key::Plus,
1234        KeyCode::Equal => Key::Equals,
1235
1236        KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
1237        KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
1238        KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
1239        KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
1240        KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
1241        KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
1242        KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
1243        KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
1244        KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
1245        KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
1246
1247        KeyCode::KeyA => Key::A,
1248        KeyCode::KeyB => Key::B,
1249        KeyCode::KeyC => Key::C,
1250        KeyCode::KeyD => Key::D,
1251        KeyCode::KeyE => Key::E,
1252        KeyCode::KeyF => Key::F,
1253        KeyCode::KeyG => Key::G,
1254        KeyCode::KeyH => Key::H,
1255        KeyCode::KeyI => Key::I,
1256        KeyCode::KeyJ => Key::J,
1257        KeyCode::KeyK => Key::K,
1258        KeyCode::KeyL => Key::L,
1259        KeyCode::KeyM => Key::M,
1260        KeyCode::KeyN => Key::N,
1261        KeyCode::KeyO => Key::O,
1262        KeyCode::KeyP => Key::P,
1263        KeyCode::KeyQ => Key::Q,
1264        KeyCode::KeyR => Key::R,
1265        KeyCode::KeyS => Key::S,
1266        KeyCode::KeyT => Key::T,
1267        KeyCode::KeyU => Key::U,
1268        KeyCode::KeyV => Key::V,
1269        KeyCode::KeyW => Key::W,
1270        KeyCode::KeyX => Key::X,
1271        KeyCode::KeyY => Key::Y,
1272        KeyCode::KeyZ => Key::Z,
1273
1274        KeyCode::F1 => Key::F1,
1275        KeyCode::F2 => Key::F2,
1276        KeyCode::F3 => Key::F3,
1277        KeyCode::F4 => Key::F4,
1278        KeyCode::F5 => Key::F5,
1279        KeyCode::F6 => Key::F6,
1280        KeyCode::F7 => Key::F7,
1281        KeyCode::F8 => Key::F8,
1282        KeyCode::F9 => Key::F9,
1283        KeyCode::F10 => Key::F10,
1284        KeyCode::F11 => Key::F11,
1285        KeyCode::F12 => Key::F12,
1286        KeyCode::F13 => Key::F13,
1287        KeyCode::F14 => Key::F14,
1288        KeyCode::F15 => Key::F15,
1289        KeyCode::F16 => Key::F16,
1290        KeyCode::F17 => Key::F17,
1291        KeyCode::F18 => Key::F18,
1292        KeyCode::F19 => Key::F19,
1293        KeyCode::F20 => Key::F20,
1294        KeyCode::F21 => Key::F21,
1295        KeyCode::F22 => Key::F22,
1296        KeyCode::F23 => Key::F23,
1297        KeyCode::F24 => Key::F24,
1298        KeyCode::F25 => Key::F25,
1299        KeyCode::F26 => Key::F26,
1300        KeyCode::F27 => Key::F27,
1301        KeyCode::F28 => Key::F28,
1302        KeyCode::F29 => Key::F29,
1303        KeyCode::F30 => Key::F30,
1304        KeyCode::F31 => Key::F31,
1305        KeyCode::F32 => Key::F32,
1306        KeyCode::F33 => Key::F33,
1307        KeyCode::F34 => Key::F34,
1308        KeyCode::F35 => Key::F35,
1309
1310        _ => {
1311            return None;
1312        }
1313    })
1314}
1315
1316fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
1317    match cursor_icon {
1318        egui::CursorIcon::None => None,
1319
1320        egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
1321        egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
1322        egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
1323        egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
1324        egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
1325        egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1326        egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
1327        egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
1328        egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1329        egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
1330        egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
1331        egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1332        egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1333        egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
1334        egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
1335
1336        egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
1337        egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
1338        egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
1339        egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
1340
1341        egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
1342        egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
1343        egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
1344        egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
1345        egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
1346        egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
1347        egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
1348        egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
1349        egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
1350        egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
1351
1352        egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
1353        egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
1354        egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
1355        egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
1356        egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
1357    }
1358}
1359
1360// Helpers for egui Viewports
1361// ---------------------------------------------------------------------------
1362#[derive(PartialEq, Eq, Hash, Debug)]
1363pub enum ActionRequested {
1364    Screenshot(egui::UserData),
1365    Cut,
1366    Copy,
1367    Paste,
1368}
1369
1370pub fn process_viewport_commands(
1371    egui_ctx: &egui::Context,
1372    info: &mut ViewportInfo,
1373    commands: impl IntoIterator<Item = ViewportCommand>,
1374    window: &Window,
1375    actions_requested: &mut Vec<ActionRequested>,
1376) {
1377    for command in commands {
1378        process_viewport_command(egui_ctx, window, command, info, actions_requested);
1379    }
1380}
1381
1382fn process_viewport_command(
1383    egui_ctx: &egui::Context,
1384    window: &Window,
1385    command: ViewportCommand,
1386    info: &mut ViewportInfo,
1387    actions_requested: &mut Vec<ActionRequested>,
1388) {
1389    profiling::function_scope!(&format!("{command:?}"));
1390
1391    use winit::window::ResizeDirection;
1392
1393    log::trace!("Processing ViewportCommand::{command:?}");
1394
1395    let pixels_per_point = pixels_per_point(egui_ctx, window);
1396
1397    match command {
1398        ViewportCommand::Close => {
1399            info.events.push(egui::ViewportEvent::Close);
1400        }
1401        ViewportCommand::CancelClose => {
1402            // Need to be handled elsewhere
1403        }
1404        ViewportCommand::StartDrag => {
1405            // If `.has_focus()` is not checked on x11 the input will be permanently taken until the app is killed!
1406            if window.has_focus()
1407                && let Err(err) = window.drag_window()
1408            {
1409                log::warn!("{command:?}: {err}");
1410            }
1411        }
1412        ViewportCommand::InnerSize(size) => {
1413            let width_px = pixels_per_point * size.x.max(1.0);
1414            let height_px = pixels_per_point * size.y.max(1.0);
1415            let requested_size = PhysicalSize::new(width_px, height_px);
1416            if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {
1417                // On platforms where the size is entirely controlled by the user the
1418                // applied size will be returned immediately, resize event in such case
1419                // may not be generated.
1420                // e.g. Linux
1421
1422                // On platforms where resizing is disallowed by the windowing system, the current
1423                // inner size is returned immediately, and the user one is ignored.
1424                // e.g. Android, iOS, …
1425
1426                // However, comparing the results is prone to numerical errors
1427                // because the linux backend converts physical to logical and back again.
1428                // So let's just assume it worked:
1429
1430                info.inner_rect = inner_rect_in_points(window, pixels_per_point);
1431                info.outer_rect = outer_rect_in_points(window, pixels_per_point);
1432            } else {
1433                // e.g. macOS, Windows
1434                // The request went to the display system,
1435                // and the actual size will be delivered later with the [`WindowEvent::Resized`].
1436            }
1437        }
1438        ViewportCommand::BeginResize(direction) => {
1439            if let Err(err) = window.drag_resize_window(match direction {
1440                egui::viewport::ResizeDirection::North => ResizeDirection::North,
1441                egui::viewport::ResizeDirection::South => ResizeDirection::South,
1442                egui::viewport::ResizeDirection::East => ResizeDirection::East,
1443                egui::viewport::ResizeDirection::West => ResizeDirection::West,
1444                egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
1445                egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
1446                egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
1447                egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
1448            }) {
1449                log::warn!("{command:?}: {err}");
1450            }
1451        }
1452        ViewportCommand::Title(title) => {
1453            window.set_title(&title);
1454        }
1455        ViewportCommand::Transparent(v) => window.set_transparent(v),
1456        ViewportCommand::Visible(v) => window.set_visible(v),
1457        ViewportCommand::OuterPosition(pos) => {
1458            window.set_outer_position(PhysicalPosition::new(
1459                pixels_per_point * pos.x,
1460                pixels_per_point * pos.y,
1461            ));
1462        }
1463        ViewportCommand::MinInnerSize(s) => {
1464            window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
1465                PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1466            ));
1467        }
1468        ViewportCommand::MaxInnerSize(s) => {
1469            window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
1470                PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1471            ));
1472        }
1473        ViewportCommand::ResizeIncrements(s) => {
1474            window.set_resize_increments(
1475                s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
1476            );
1477        }
1478        ViewportCommand::Resizable(v) => window.set_resizable(v),
1479        ViewportCommand::EnableButtons {
1480            close,
1481            minimized,
1482            maximize,
1483        } => window.set_enabled_buttons(
1484            if close {
1485                WindowButtons::CLOSE
1486            } else {
1487                WindowButtons::empty()
1488            } | if minimized {
1489                WindowButtons::MINIMIZE
1490            } else {
1491                WindowButtons::empty()
1492            } | if maximize {
1493                WindowButtons::MAXIMIZE
1494            } else {
1495                WindowButtons::empty()
1496            },
1497        ),
1498        ViewportCommand::Minimized(v) => {
1499            window.set_minimized(v);
1500            info.minimized = Some(v);
1501        }
1502        ViewportCommand::Maximized(v) => {
1503            window.set_maximized(v);
1504            info.maximized = Some(v);
1505        }
1506        ViewportCommand::Fullscreen(v) => {
1507            window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
1508        }
1509        ViewportCommand::Decorations(v) => window.set_decorations(v),
1510        ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
1511            egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1512            egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1513            egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1514        }),
1515        ViewportCommand::Icon(icon) => {
1516            let winit_icon = icon.and_then(|icon| to_winit_icon(&icon));
1517            window.set_window_icon(winit_icon);
1518        }
1519        ViewportCommand::IMERect(rect) => {
1520            window.set_ime_cursor_area(
1521                PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
1522                PhysicalSize::new(
1523                    pixels_per_point * rect.size().x,
1524                    pixels_per_point * rect.size().y,
1525                ),
1526            );
1527        }
1528        ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
1529        ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
1530            egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,
1531            egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
1532            egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
1533        }),
1534        ViewportCommand::Focus => {
1535            if !window.has_focus() {
1536                window.focus_window();
1537            }
1538        }
1539        ViewportCommand::RequestUserAttention(a) => {
1540            window.request_user_attention(match a {
1541                egui::UserAttentionType::Reset => None,
1542                egui::UserAttentionType::Critical => {
1543                    Some(winit::window::UserAttentionType::Critical)
1544                }
1545                egui::UserAttentionType::Informational => {
1546                    Some(winit::window::UserAttentionType::Informational)
1547                }
1548            });
1549        }
1550        ViewportCommand::SetTheme(t) => window.set_theme(match t {
1551            egui::SystemTheme::Light => Some(winit::window::Theme::Light),
1552            egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
1553            egui::SystemTheme::SystemDefault => None,
1554        }),
1555        ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
1556        ViewportCommand::CursorPosition(pos) => {
1557            if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
1558                pixels_per_point * pos.x,
1559                pixels_per_point * pos.y,
1560            )) {
1561                log::warn!("{command:?}: {err}");
1562            }
1563        }
1564        ViewportCommand::CursorGrab(o) => {
1565            if let Err(err) = window.set_cursor_grab(match o {
1566                egui::viewport::CursorGrab::None => CursorGrabMode::None,
1567                egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
1568                egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
1569            }) {
1570                log::warn!("{command:?}: {err}");
1571            }
1572        }
1573        ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
1574        ViewportCommand::MousePassthrough(passthrough) => {
1575            if let Err(err) = window.set_cursor_hittest(!passthrough) {
1576                log::warn!("{command:?}: {err}");
1577            }
1578        }
1579        ViewportCommand::Screenshot(user_data) => {
1580            actions_requested.push(ActionRequested::Screenshot(user_data));
1581        }
1582        ViewportCommand::RequestCut => {
1583            actions_requested.push(ActionRequested::Cut);
1584        }
1585        ViewportCommand::RequestCopy => {
1586            actions_requested.push(ActionRequested::Copy);
1587        }
1588        ViewportCommand::RequestPaste => {
1589            actions_requested.push(ActionRequested::Paste);
1590        }
1591    }
1592}
1593
1594/// Build and intitlaize a window.
1595///
1596/// Wrapper around `create_winit_window_builder` and `apply_viewport_builder_to_window`.
1597///
1598/// # Errors
1599/// Possible causes of error include denied permission, incompatible system, and lack of memory.
1600pub fn create_window(
1601    egui_ctx: &egui::Context,
1602    event_loop: &ActiveEventLoop,
1603    viewport_builder: &ViewportBuilder,
1604) -> Result<Window, winit::error::OsError> {
1605    profiling::function_scope!();
1606
1607    let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone());
1608    let window = event_loop.create_window(window_attributes)?;
1609    apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
1610    Ok(window)
1611}
1612
1613pub fn create_winit_window_attributes(
1614    egui_ctx: &egui::Context,
1615    viewport_builder: ViewportBuilder,
1616) -> winit::window::WindowAttributes {
1617    profiling::function_scope!();
1618
1619    let ViewportBuilder {
1620        title,
1621        position,
1622        inner_size,
1623        min_inner_size,
1624        max_inner_size,
1625        fullscreen,
1626        maximized,
1627        resizable,
1628        transparent,
1629        decorations,
1630        icon,
1631        active,
1632        visible,
1633        close_button,
1634        minimize_button,
1635        maximize_button,
1636        window_level,
1637
1638        // macOS:
1639        fullsize_content_view: _fullsize_content_view,
1640        movable_by_window_background: _movable_by_window_background,
1641        title_shown: _title_shown,
1642        titlebar_buttons_shown: _titlebar_buttons_shown,
1643        titlebar_shown: _titlebar_shown,
1644        has_shadow: _has_shadow,
1645
1646        // Windows:
1647        drag_and_drop: _drag_and_drop,
1648        taskbar: _taskbar,
1649
1650        // wayland:
1651        app_id: _app_id,
1652
1653        // x11
1654        window_type: _window_type,
1655
1656        mouse_passthrough: _, // handled in `apply_viewport_builder_to_window`
1657        clamp_size_to_monitor_size: _, // Handled in `viewport_builder` in `epi_integration.rs`
1658    } = viewport_builder;
1659
1660    let mut window_attributes = winit::window::WindowAttributes::default()
1661        .with_title(title.unwrap_or_else(|| "egui window".to_owned()))
1662        .with_transparent(transparent.unwrap_or(false))
1663        .with_decorations(decorations.unwrap_or(true))
1664        .with_resizable(resizable.unwrap_or(true))
1665        .with_visible(visible.unwrap_or(true))
1666        .with_maximized(if cfg!(target_os = "ios") {
1667            true
1668        } else {
1669            maximized.unwrap_or(false)
1670        })
1671        .with_window_level(match window_level.unwrap_or_default() {
1672            egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1673            egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1674            egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1675        })
1676        .with_fullscreen(
1677            fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),
1678        )
1679        .with_enabled_buttons({
1680            let mut buttons = WindowButtons::empty();
1681            if minimize_button.unwrap_or(true) {
1682                buttons |= WindowButtons::MINIMIZE;
1683            }
1684            if maximize_button.unwrap_or(true) {
1685                buttons |= WindowButtons::MAXIMIZE;
1686            }
1687            if close_button.unwrap_or(true) {
1688                buttons |= WindowButtons::CLOSE;
1689            }
1690            buttons
1691        })
1692        .with_active(active.unwrap_or(true));
1693
1694    // Here and below: we create `LogicalSize` / `LogicalPosition` taking
1695    // zoom factor into account. We don't have a good way to get physical size here,
1696    // and trying to do it anyway leads to weird bugs on Wayland, see:
1697    // https://github.com/emilk/egui/issues/7095#issuecomment-2920545377
1698    // https://github.com/rust-windowing/winit/issues/4266
1699    #[expect(
1700        clippy::disallowed_types,
1701        reason = "zoom factor is manually accounted for"
1702    )]
1703    #[cfg(not(target_os = "ios"))]
1704    {
1705        use winit::dpi::{LogicalPosition, LogicalSize};
1706        let zoom_factor = egui_ctx.zoom_factor();
1707
1708        if let Some(size) = inner_size {
1709            window_attributes = window_attributes
1710                .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1711        }
1712
1713        if let Some(size) = min_inner_size {
1714            window_attributes = window_attributes
1715                .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1716        }
1717
1718        if let Some(size) = max_inner_size {
1719            window_attributes = window_attributes
1720                .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1721        }
1722
1723        if let Some(pos) = position {
1724            window_attributes = window_attributes.with_position(LogicalPosition::new(
1725                zoom_factor * pos.x,
1726                zoom_factor * pos.y,
1727            ));
1728        }
1729    }
1730    #[cfg(target_os = "ios")]
1731    {
1732        // Unused:
1733        _ = egui_ctx;
1734        _ = pixels_per_point;
1735        _ = position;
1736        _ = inner_size;
1737        _ = min_inner_size;
1738        _ = max_inner_size;
1739    }
1740
1741    if let Some(icon) = icon {
1742        let winit_icon = to_winit_icon(&icon);
1743        window_attributes = window_attributes.with_window_icon(winit_icon);
1744    }
1745
1746    #[cfg(all(feature = "wayland", target_os = "linux"))]
1747    if let Some(app_id) = _app_id {
1748        use winit::platform::wayland::WindowAttributesExtWayland as _;
1749        window_attributes = window_attributes.with_name(app_id, "");
1750    }
1751
1752    #[cfg(all(feature = "x11", target_os = "linux"))]
1753    {
1754        if let Some(window_type) = _window_type {
1755            use winit::platform::x11::WindowAttributesExtX11 as _;
1756            use winit::platform::x11::WindowType;
1757            window_attributes = window_attributes.with_x11_window_type(vec![match window_type {
1758                egui::X11WindowType::Normal => WindowType::Normal,
1759                egui::X11WindowType::Utility => WindowType::Utility,
1760                egui::X11WindowType::Dock => WindowType::Dock,
1761                egui::X11WindowType::Desktop => WindowType::Desktop,
1762                egui::X11WindowType::Toolbar => WindowType::Toolbar,
1763                egui::X11WindowType::Menu => WindowType::Menu,
1764                egui::X11WindowType::Splash => WindowType::Splash,
1765                egui::X11WindowType::Dialog => WindowType::Dialog,
1766                egui::X11WindowType::DropdownMenu => WindowType::DropdownMenu,
1767                egui::X11WindowType::PopupMenu => WindowType::PopupMenu,
1768                egui::X11WindowType::Tooltip => WindowType::Tooltip,
1769                egui::X11WindowType::Notification => WindowType::Notification,
1770                egui::X11WindowType::Combo => WindowType::Combo,
1771                egui::X11WindowType::Dnd => WindowType::Dnd,
1772            }]);
1773        }
1774    }
1775
1776    #[cfg(target_os = "windows")]
1777    {
1778        use winit::platform::windows::WindowAttributesExtWindows as _;
1779        if let Some(enable) = _drag_and_drop {
1780            window_attributes = window_attributes.with_drag_and_drop(enable);
1781        }
1782        if let Some(show) = _taskbar {
1783            window_attributes = window_attributes.with_skip_taskbar(!show);
1784        }
1785    }
1786
1787    #[cfg(target_os = "macos")]
1788    {
1789        use winit::platform::macos::WindowAttributesExtMacOS as _;
1790        window_attributes = window_attributes
1791            .with_title_hidden(!_title_shown.unwrap_or(true))
1792            .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true))
1793            .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true))
1794            .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false))
1795            .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false))
1796            .with_has_shadow(_has_shadow.unwrap_or(true));
1797    }
1798
1799    window_attributes
1800}
1801
1802fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
1803    if icon.is_empty() {
1804        None
1805    } else {
1806        profiling::function_scope!();
1807        match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
1808            Ok(winit_icon) => Some(winit_icon),
1809            Err(err) => {
1810                log::warn!("Invalid IconData: {err}");
1811                None
1812            }
1813        }
1814    }
1815}
1816
1817/// Applies what `create_winit_window_builder` couldn't
1818pub fn apply_viewport_builder_to_window(
1819    egui_ctx: &egui::Context,
1820    window: &Window,
1821    builder: &ViewportBuilder,
1822) {
1823    if let Some(mouse_passthrough) = builder.mouse_passthrough
1824        && let Err(err) = window.set_cursor_hittest(!mouse_passthrough)
1825    {
1826        log::warn!("set_cursor_hittest failed: {err}");
1827    }
1828
1829    {
1830        // In `create_winit_window_builder` we didn't know
1831        // on what monitor the window would appear, so we didn't know
1832        // how to translate egui ui point to native physical pixels.
1833        // Now we do know:
1834
1835        let pixels_per_point = pixels_per_point(egui_ctx, window);
1836
1837        if let Some(size) = builder.inner_size
1838            && window
1839                .request_inner_size(PhysicalSize::new(
1840                    pixels_per_point * size.x,
1841                    pixels_per_point * size.y,
1842                ))
1843                .is_some()
1844        {
1845            log::debug!("Failed to set window size");
1846        }
1847        if let Some(size) = builder.min_inner_size {
1848            window.set_min_inner_size(Some(PhysicalSize::new(
1849                pixels_per_point * size.x,
1850                pixels_per_point * size.y,
1851            )));
1852        }
1853        if let Some(size) = builder.max_inner_size {
1854            window.set_max_inner_size(Some(PhysicalSize::new(
1855                pixels_per_point * size.x,
1856                pixels_per_point * size.y,
1857            )));
1858        }
1859        if let Some(pos) = builder.position {
1860            let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
1861            window.set_outer_position(pos);
1862        }
1863        if let Some(maximized) = builder.maximized {
1864            window.set_maximized(maximized);
1865        }
1866    }
1867}
1868
1869// ---------------------------------------------------------------------------
1870
1871/// Short and fast description of a device event.
1872/// Useful for logging and profiling.
1873pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'static str {
1874    use winit::event::DeviceEvent;
1875
1876    match event {
1877        DeviceEvent::Added => "DeviceEvent::Added",
1878        DeviceEvent::Removed => "DeviceEvent::Removed",
1879        DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion",
1880        DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel",
1881        DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
1882        DeviceEvent::Button { .. } => "DeviceEvent::Button",
1883        DeviceEvent::Key { .. } => "DeviceEvent::Key",
1884    }
1885}
1886
1887/// Short and fast description of a window event.
1888/// Useful for logging and profiling.
1889pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
1890    use winit::event::WindowEvent;
1891
1892    match event {
1893        WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
1894        WindowEvent::Resized { .. } => "WindowEvent::Resized",
1895        WindowEvent::Moved { .. } => "WindowEvent::Moved",
1896        WindowEvent::CloseRequested => "WindowEvent::CloseRequested",
1897        WindowEvent::Destroyed => "WindowEvent::Destroyed",
1898        WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
1899        WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
1900        WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled",
1901        WindowEvent::Focused { .. } => "WindowEvent::Focused",
1902        WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
1903        WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
1904        WindowEvent::Ime { .. } => "WindowEvent::Ime",
1905        WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
1906        WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
1907        WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
1908        WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
1909        WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
1910        WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture",
1911        WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested",
1912        WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture",
1913        WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture",
1914        WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",
1915        WindowEvent::AxisMotion { .. } => "WindowEvent::AxisMotion",
1916        WindowEvent::Touch { .. } => "WindowEvent::Touch",
1917        WindowEvent::ScaleFactorChanged { .. } => "WindowEvent::ScaleFactorChanged",
1918        WindowEvent::ThemeChanged { .. } => "WindowEvent::ThemeChanged",
1919        WindowEvent::Occluded { .. } => "WindowEvent::Occluded",
1920        WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
1921    }
1922}