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