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