1#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
8#![expect(clippy::manual_range_contains)]
11
12#[cfg(target_os = "windows")]
13use std::collections::HashSet;
14
15#[cfg(feature = "accesskit")]
16pub use accesskit_winit;
17pub use egui;
18#[cfg(feature = "accesskit")]
19use egui::accesskit;
20use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
21pub use winit;
22
23pub mod clipboard;
24mod dropped_file;
25mod safe_area;
26mod window_settings;
27
28pub use window_settings::WindowSettings;
29
30use raw_window_handle::HasDisplayHandle;
31
32use dropped_file::NativeFile;
33
34use winit::{
35 dpi::{PhysicalPosition, PhysicalSize},
36 event::ElementState,
37 event_loop::ActiveEventLoop,
38 window::{CursorGrabMode, CustomCursor, Window, WindowButtons, WindowLevel},
39};
40
41pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
42 let size = if cfg!(target_os = "ios") {
43 window.outer_size()
47 } else {
48 window.inner_size()
49 };
50 egui::vec2(size.width as f32, size.height as f32)
51}
52
53pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
55 let native_pixels_per_point = window.scale_factor() as f32;
56 let egui_zoom_factor = egui_ctx.zoom_factor();
57 egui_zoom_factor * native_pixels_per_point
58}
59
60#[must_use]
63#[derive(Clone, Copy, Debug, Default)]
64pub struct EventResponse {
65 pub consumed: bool,
73
74 pub repaint: bool,
76}
77
78pub struct State {
84 egui_ctx: egui::Context,
86
87 viewport_id: ViewportId,
88 start_time: web_time::Instant,
89 egui_input: egui::RawInput,
90
91 modifiers: egui::Modifiers,
96
97 pointer_pos_in_points: Option<egui::Pos2>,
98 any_pointer_button_down: bool,
99 current_cursor_icon: Option<egui::CursorIcon>,
100
101 current_custom_cursor: Option<(usize, CustomCursor)>,
108
109 clipboard: clipboard::Clipboard,
110
111 simulate_touch_screen: bool,
116
117 pointer_touch_id: Option<u64>,
121
122 #[cfg(feature = "accesskit")]
123 pub accesskit: Option<accesskit_winit::Adapter>,
124
125 allow_ime: bool,
126 ime_rect_px: Option<egui::Rect>,
127 old_ime_purpose: egui::IMEPurpose,
128
129 #[cfg(target_os = "windows")]
133 pressed_processed_physical_keys: HashSet<winit::keyboard::PhysicalKey>,
134}
135
136impl State {
137 pub fn new(
139 egui_ctx: egui::Context,
140 viewport_id: ViewportId,
141 display_target: &dyn HasDisplayHandle,
142 native_pixels_per_point: Option<f32>,
143 theme: Option<winit::window::Theme>,
144 max_texture_side: Option<usize>,
145 ) -> Self {
146 profiling::function_scope!();
147
148 let egui_input = egui::RawInput {
149 focused: false, ..Default::default()
151 };
152
153 let mut slf = Self {
154 viewport_id,
155 start_time: web_time::Instant::now()
156 .checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time()))
157 .unwrap_or_else(web_time::Instant::now),
158 egui_ctx,
159 egui_input,
160 modifiers: egui::Modifiers::default(),
161 pointer_pos_in_points: None,
162 any_pointer_button_down: false,
163 current_cursor_icon: None,
164 current_custom_cursor: None,
165
166 clipboard: clipboard::Clipboard::new(
167 display_target.display_handle().ok().map(|h| h.as_raw()),
168 ),
169
170 simulate_touch_screen: false,
171 pointer_touch_id: None,
172
173 #[cfg(feature = "accesskit")]
174 accesskit: None,
175
176 allow_ime: false,
177 ime_rect_px: None,
178 old_ime_purpose: egui::IMEPurpose::Normal,
179 #[cfg(target_os = "windows")]
180 pressed_processed_physical_keys: HashSet::new(),
181 };
182
183 slf.egui_input
184 .viewports
185 .entry(ViewportId::ROOT)
186 .or_default()
187 .native_pixels_per_point = native_pixels_per_point;
188 slf.egui_input.system_theme = theme.map(to_egui_theme);
189
190 if let Some(max_texture_side) = max_texture_side {
191 slf.set_max_texture_side(max_texture_side);
192 }
193 slf
194 }
195
196 #[cfg(feature = "accesskit")]
197 pub fn init_accesskit<T: From<accesskit_winit::Event> + Send>(
198 &mut self,
199 event_loop: &ActiveEventLoop,
200 window: &Window,
201 event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
202 ) {
203 profiling::function_scope!();
204
205 self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
206 event_loop,
207 window,
208 event_loop_proxy,
209 ));
210 }
211
212 pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
215 self.egui_input.max_texture_side = Some(max_texture_side);
216 }
217
218 pub fn clipboard_text(&mut self) -> Option<String> {
220 self.clipboard.get()
221 }
222
223 pub fn set_clipboard_text(&mut self, text: String) {
225 self.clipboard.set_text(text);
226 }
227
228 pub fn allow_ime(&self) -> bool {
230 self.allow_ime
231 }
232
233 pub fn set_allow_ime(&mut self, allow: bool) {
235 self.allow_ime = allow;
236 }
237
238 #[inline]
239 pub fn egui_ctx(&self) -> &egui::Context {
240 &self.egui_ctx
241 }
242
243 #[inline]
246 pub fn egui_input(&self) -> &egui::RawInput {
247 &self.egui_input
248 }
249
250 #[inline]
253 pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
254 &mut self.egui_input
255 }
256
257 pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
265 profiling::function_scope!();
266
267 self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
268
269 let screen_size_in_pixels = screen_size_in_pixels(window);
273 let screen_size_in_points =
274 screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window);
275
276 self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
277 && screen_size_in_points.y > 0.0)
278 .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
279
280 self.egui_input.viewport_id = self.viewport_id;
282
283 self.egui_input
284 .viewports
285 .entry(self.viewport_id)
286 .or_default()
287 .native_pixels_per_point = Some(window.scale_factor() as f32);
288
289 self.egui_input.take()
290 }
291
292 pub fn on_window_event(
296 &mut self,
297 window: &Window,
298 event: &winit::event::WindowEvent,
299 ) -> EventResponse {
300 profiling::function_scope!(short_window_event_description(event));
301
302 #[cfg(feature = "accesskit")]
303 if let Some(accesskit) = self.accesskit.as_mut() {
304 accesskit.process_event(window, event);
305 }
306
307 use winit::event::WindowEvent;
308
309 #[cfg(target_os = "ios")]
310 match &event {
311 WindowEvent::Resized(_)
312 | WindowEvent::ScaleFactorChanged { .. }
313 | WindowEvent::Focused(true)
314 | WindowEvent::Occluded(false) => {
315 self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
319 }
320 _ => {}
321 }
322
323 match event {
324 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
325 let native_pixels_per_point = *scale_factor as f32;
326
327 self.egui_input
328 .viewports
329 .entry(self.viewport_id)
330 .or_default()
331 .native_pixels_per_point = Some(native_pixels_per_point);
332
333 EventResponse {
334 repaint: true,
335 consumed: false,
336 }
337 }
338 WindowEvent::MouseInput { state, button, .. } => {
339 self.on_mouse_button_input(*state, *button);
340 EventResponse {
341 repaint: true,
342 consumed: self.egui_ctx.egui_wants_pointer_input(),
343 }
344 }
345 WindowEvent::MouseWheel { delta, phase, .. } => {
346 self.on_mouse_wheel(window, *delta, *phase);
347 EventResponse {
348 repaint: true,
349 consumed: self.egui_ctx.egui_wants_pointer_input(),
350 }
351 }
352 WindowEvent::CursorMoved { position, .. } => {
353 self.on_cursor_moved(window, *position);
354 EventResponse {
355 repaint: true,
356 consumed: self.egui_ctx.egui_is_using_pointer(),
357 }
358 }
359 WindowEvent::CursorLeft { .. } => {
360 self.pointer_pos_in_points = None;
361 self.egui_input.events.push(egui::Event::PointerGone);
362 EventResponse {
363 repaint: true,
364 consumed: false,
365 }
366 }
367 WindowEvent::Touch(touch) => {
369 self.on_touch(window, touch);
370 let consumed = match touch.phase {
371 winit::event::TouchPhase::Started
372 | winit::event::TouchPhase::Ended
373 | winit::event::TouchPhase::Cancelled => {
374 self.egui_ctx.egui_wants_pointer_input()
375 }
376 winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
377 };
378 EventResponse {
379 repaint: true,
380 consumed,
381 }
382 }
383
384 WindowEvent::Ime(ime) => {
385 self.on_ime(ime);
386
387 EventResponse {
388 repaint: true,
389 consumed: self.egui_ctx.egui_wants_keyboard_input(),
390 }
391 }
392 WindowEvent::KeyboardInput {
393 event,
394 is_synthetic,
395 ..
396 } => {
397 if *is_synthetic && event.state == ElementState::Pressed {
398 EventResponse {
403 repaint: true,
404 consumed: false,
405 }
406 } else {
407 let egui_wants_keyboard_input = self.egui_ctx.egui_wants_keyboard_input();
408
409 if let Some(response) =
410 self.try_on_ime_processed_keyboard_input(event, egui_wants_keyboard_input)
411 {
412 response
413 } else {
414 self.on_keyboard_input(event);
415
416 let consumed = egui_wants_keyboard_input
418 || event.logical_key
419 == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
420 EventResponse {
421 repaint: true,
422 consumed,
423 }
424 }
425 }
426 }
427 WindowEvent::Focused(focused) => {
428 let focused = if cfg!(target_os = "macos") {
429 window.has_focus()
433 } else {
434 *focused
435 };
436
437 self.egui_input.focused = focused;
438 if !focused {
439 self.modifiers = egui::Modifiers::default();
441 }
442 self.egui_input
443 .events
444 .push(egui::Event::WindowFocused(focused));
445 EventResponse {
446 repaint: true,
447 consumed: false,
448 }
449 }
450 WindowEvent::ThemeChanged(winit_theme) => {
451 self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
452 EventResponse {
453 repaint: true,
454 consumed: false,
455 }
456 }
457 WindowEvent::HoveredFile(path) => {
458 self.egui_input.hovered_files.push(egui::HoveredFile {
459 path: Some(path.clone()),
460 ..Default::default()
461 });
462 EventResponse {
463 repaint: true,
464 consumed: false,
465 }
466 }
467 WindowEvent::HoveredFileCancelled => {
468 self.egui_input.hovered_files.clear();
469 EventResponse {
470 repaint: true,
471 consumed: false,
472 }
473 }
474 WindowEvent::DroppedFile(path) => {
475 self.egui_input.hovered_files.clear();
476 self.egui_input
477 .dropped_files
478 .push(std::sync::Arc::new(NativeFile::from(path.clone())));
479 EventResponse {
480 repaint: true,
481 consumed: false,
482 }
483 }
484 WindowEvent::ModifiersChanged(state) => {
485 let state = state.state();
486
487 let alt = state.alt_key();
488 let ctrl = state.control_key();
489 let shift = state.shift_key();
490 let super_ = state.super_key();
491
492 self.modifiers.alt = alt;
493 self.modifiers.ctrl = ctrl;
494 self.modifiers.shift = shift;
495 self.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
496 self.modifiers.command = if cfg!(target_os = "macos") {
497 super_
498 } else {
499 ctrl
500 };
501
502 self.egui_input
503 .events
504 .push(egui::Event::ModifiersChanged(self.modifiers));
505
506 EventResponse {
507 repaint: true,
508 consumed: false,
509 }
510 }
511
512 WindowEvent::RedrawRequested
514 | WindowEvent::CursorEntered { .. }
515 | WindowEvent::Destroyed
516 | WindowEvent::Occluded(_)
517 | WindowEvent::Resized(_)
518 | WindowEvent::Moved(_)
519 | WindowEvent::TouchpadPressure { .. }
520 | WindowEvent::CloseRequested => EventResponse {
521 repaint: true,
522 consumed: false,
523 },
524
525 WindowEvent::ActivationTokenDone { .. }
527 | WindowEvent::AxisMotion { .. }
528 | WindowEvent::DoubleTapGesture { .. } => EventResponse {
529 repaint: false,
530 consumed: false,
531 },
532
533 WindowEvent::PinchGesture { delta, .. } => {
534 let zoom_factor = (*delta as f32).exp();
537 self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
538 EventResponse {
539 repaint: true,
540 consumed: self.egui_ctx.egui_wants_pointer_input(),
541 }
542 }
543
544 WindowEvent::RotationGesture { delta, .. } => {
545 self.egui_input
549 .events
550 .push(egui::Event::Rotate(-delta.to_radians()));
551 EventResponse {
552 repaint: true,
553 consumed: self.egui_ctx.egui_wants_pointer_input(),
554 }
555 }
556
557 WindowEvent::PanGesture { delta, phase, .. } => {
558 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
559
560 self.egui_input.events.push(egui::Event::MouseWheel {
561 unit: egui::MouseWheelUnit::Point,
562 delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
563 phase: to_egui_touch_phase(*phase),
564 modifiers: self.modifiers,
565 });
566 EventResponse {
567 repaint: true,
568 consumed: self.egui_ctx.egui_wants_pointer_input(),
569 }
570 }
571 }
572 }
573
574 #[cfg(not(target_os = "windows"))]
575 #[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
576 #[inline(always)]
577 fn try_on_ime_processed_keyboard_input(
578 &mut self,
579 _event: &winit::event::KeyEvent,
580 _egui_wants_keyboard_input: bool,
581 ) -> Option<EventResponse> {
582 None
587 }
588
589 #[cfg(target_os = "windows")]
590 #[inline(always)]
591 fn try_on_ime_processed_keyboard_input(
592 &mut self,
593 event: &winit::event::KeyEvent,
594 egui_wants_keyboard_input: bool,
595 ) -> Option<EventResponse> {
596 if !self.allow_ime {
597 None
598 } else if event.logical_key == winit::keyboard::NamedKey::Process {
599 self.pressed_processed_physical_keys
628 .insert(event.physical_key);
629
630 Some(EventResponse {
631 repaint: false,
632 consumed: egui_wants_keyboard_input,
633 })
634 } else if event.state == ElementState::Released
635 && self
636 .pressed_processed_physical_keys
637 .remove(&event.physical_key)
638 {
639 Some(EventResponse {
651 repaint: false,
652 consumed: egui_wants_keyboard_input,
653 })
654 } else {
655 None
656 }
657 }
658
659 fn on_ime(&mut self, ime: &winit::event::Ime) {
700 match ime {
718 winit::event::Ime::Enabled | winit::event::Ime::Disabled => {}
722 winit::event::Ime::Preedit(text, active_range_bytes) => {
723 let active_range_chars = match *active_range_bytes {
724 Some((start_bytes, end_bytes)) => {
725 if let (Some(start_chars), Some(middle_chars)) = (
726 text.get(..start_bytes).map(|s| s.chars().count()),
727 text.get(start_bytes..end_bytes).map(|s| s.chars().count()),
728 ) {
729 if cfg!(target_os = "windows") && start_chars == 0 && middle_chars == 0
730 {
731 None
740 } else {
741 Some(start_chars..start_chars + middle_chars)
742 }
743 } else {
744 log::warn!("ignoring {ime:?}'s range because it is invalid");
745 None
746 }
747 }
748 None => None,
749 };
750
751 self.egui_input
752 .events
753 .push(egui::Event::Ime(egui::ImeEvent::Preedit {
754 text: text.clone(),
755 active_range_chars,
756 }));
757 }
758 winit::event::Ime::Commit(text) => {
759 self.egui_input
760 .events
761 .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
762 }
763 }
764 }
765
766 pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool {
768 if !self.is_pointer_in_window() && !self.any_pointer_button_down {
769 return false;
770 }
771
772 self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
773 x: delta.0 as f32,
774 y: delta.1 as f32,
775 }));
776 true
777 }
778
779 pub fn is_pointer_in_window(&self) -> bool {
781 self.pointer_pos_in_points.is_some()
782 }
783
784 pub fn is_any_pointer_button_down(&self) -> bool {
786 self.any_pointer_button_down
787 }
788
789 #[cfg(feature = "accesskit")]
793 pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
794 self.egui_input
795 .events
796 .push(egui::Event::AccessKitActionRequest(request));
797 }
798
799 fn on_mouse_button_input(
800 &mut self,
801 state: winit::event::ElementState,
802 button: winit::event::MouseButton,
803 ) {
804 if let Some(pos) = self.pointer_pos_in_points
805 && let Some(button) = translate_mouse_button(button)
806 {
807 let pressed = state == winit::event::ElementState::Pressed;
808
809 self.egui_input.events.push(egui::Event::PointerButton {
810 pos,
811 button,
812 pressed,
813 modifiers: self.modifiers,
814 });
815
816 if self.simulate_touch_screen {
817 if pressed {
818 self.any_pointer_button_down = true;
819
820 self.egui_input.events.push(egui::Event::Touch {
821 device_id: egui::TouchDeviceId(0),
822 id: egui::TouchId(0),
823 phase: egui::TouchPhase::Start,
824 pos,
825 force: None,
826 });
827 } else {
828 self.any_pointer_button_down = false;
829
830 self.egui_input.events.push(egui::Event::PointerGone);
831
832 self.egui_input.events.push(egui::Event::Touch {
833 device_id: egui::TouchDeviceId(0),
834 id: egui::TouchId(0),
835 phase: egui::TouchPhase::End,
836 pos,
837 force: None,
838 });
839 }
840 }
841 }
842 }
843
844 fn on_cursor_moved(
845 &mut self,
846 window: &Window,
847 pos_in_pixels: winit::dpi::PhysicalPosition<f64>,
848 ) {
849 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
850
851 let pos_in_points = egui::pos2(
852 pos_in_pixels.x as f32 / pixels_per_point,
853 pos_in_pixels.y as f32 / pixels_per_point,
854 );
855 self.pointer_pos_in_points = Some(pos_in_points);
856
857 if self.simulate_touch_screen {
858 if self.any_pointer_button_down {
859 self.egui_input
860 .events
861 .push(egui::Event::PointerMoved(pos_in_points));
862
863 self.egui_input.events.push(egui::Event::Touch {
864 device_id: egui::TouchDeviceId(0),
865 id: egui::TouchId(0),
866 phase: egui::TouchPhase::Move,
867 pos: pos_in_points,
868 force: None,
869 });
870 }
871 } else {
872 self.egui_input
873 .events
874 .push(egui::Event::PointerMoved(pos_in_points));
875 }
876 }
877
878 fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) {
879 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
880
881 self.egui_input.events.push(egui::Event::Touch {
883 device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
884 id: egui::TouchId::from(touch.id),
885 phase: to_egui_touch_phase(touch.phase),
886 pos: egui::pos2(
887 touch.location.x as f32 / pixels_per_point,
888 touch.location.y as f32 / pixels_per_point,
889 ),
890 force: match touch.force {
891 Some(winit::event::Force::Normalized(force)) => Some(force as f32),
892 Some(winit::event::Force::Calibrated {
893 force,
894 max_possible_force,
895 ..
896 }) => Some((force / max_possible_force) as f32),
897 None => None,
898 },
899 });
900 if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
903 {
904 match touch.phase {
906 winit::event::TouchPhase::Started => {
907 self.pointer_touch_id = Some(touch.id);
908 self.on_cursor_moved(window, touch.location);
910 self.on_mouse_button_input(
911 winit::event::ElementState::Pressed,
912 winit::event::MouseButton::Left,
913 );
914 }
915 winit::event::TouchPhase::Moved => {
916 self.on_cursor_moved(window, touch.location);
917 }
918 winit::event::TouchPhase::Ended => {
919 self.pointer_touch_id = None;
920 self.on_mouse_button_input(
921 winit::event::ElementState::Released,
922 winit::event::MouseButton::Left,
923 );
924 self.pointer_pos_in_points = None;
927 self.egui_input.events.push(egui::Event::PointerGone);
928 }
929 winit::event::TouchPhase::Cancelled => {
930 self.pointer_touch_id = None;
931 self.pointer_pos_in_points = None;
932 self.egui_input.events.push(egui::Event::PointerGone);
933 }
934 }
935 }
936 }
937
938 fn on_mouse_wheel(
939 &mut self,
940 window: &Window,
941 delta: winit::event::MouseScrollDelta,
942 phase: winit::event::TouchPhase,
943 ) {
944 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
945
946 {
947 let (unit, delta) = match delta {
948 winit::event::MouseScrollDelta::LineDelta(x, y) => {
949 (egui::MouseWheelUnit::Line, egui::vec2(x, y))
950 }
951 winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
952 x,
953 y,
954 }) => (
955 egui::MouseWheelUnit::Point,
956 egui::vec2(x as f32, y as f32) / pixels_per_point,
957 ),
958 };
959 let phase = to_egui_touch_phase(phase);
960 let modifiers = self.modifiers;
961 self.egui_input.events.push(egui::Event::MouseWheel {
962 unit,
963 delta,
964 phase,
965 modifiers,
966 });
967 }
968 }
969
970 fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
971 let winit::event::KeyEvent {
972 physical_key,
980
981 logical_key: winit_logical_key,
986
987 text,
988
989 state,
990
991 location: _, repeat: _, ..
994 } = event;
995
996 let pressed = *state == winit::event::ElementState::Pressed;
997
998 let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
999 key_from_key_code(keycode)
1000 } else {
1001 None
1002 };
1003
1004 let logical_key = key_from_winit_key(winit_logical_key);
1005
1006 log::trace!(
1008 "logical {:?} -> {:?}, physical {:?} -> {:?}",
1009 event.logical_key,
1010 logical_key,
1011 event.physical_key,
1012 physical_key
1013 );
1014
1015 if let Some(active_key) = logical_key.or(physical_key) {
1020 if pressed {
1021 if is_cut_command(self.modifiers, active_key) {
1022 self.egui_input.events.push(egui::Event::Cut);
1023 return;
1024 } else if is_copy_command(self.modifiers, active_key) {
1025 self.egui_input.events.push(egui::Event::Copy);
1026 return;
1027 } else if is_paste_command(self.modifiers, active_key) {
1028 if let Some(contents) = self.clipboard.get() {
1029 let contents = contents.replace("\r\n", "\n");
1030 if !contents.is_empty() {
1031 self.egui_input.events.push(egui::Event::Paste(contents));
1032 }
1033 }
1034 return;
1035 }
1036 }
1037
1038 self.egui_input.events.push(egui::Event::Key {
1039 key: active_key,
1040 physical_key,
1041 pressed,
1042 repeat: false, modifiers: self.modifiers,
1044 });
1045 }
1046
1047 if let Some(text) = text
1048 .as_ref()
1049 .map(|t| t.as_str())
1050 .or_else(|| winit_logical_key.to_text())
1051 {
1052 if !text.is_empty() && text.chars().all(is_printable_char) {
1055 let is_cmd =
1060 self.modifiers.ctrl || self.modifiers.command || self.modifiers.mac_cmd;
1061 if pressed && !is_cmd {
1062 self.egui_input
1063 .events
1064 .push(egui::Event::Text(text.to_owned()));
1065 }
1066 }
1067 }
1068 }
1069
1070 pub fn handle_platform_output(
1079 &mut self,
1080 window: &Window,
1081 platform_output: egui::PlatformOutput,
1082 ) {
1083 self.handle_platform_output_inner(window, None, platform_output);
1084 }
1085
1086 pub fn handle_platform_output_with_event_loop(
1094 &mut self,
1095 window: &Window,
1096 event_loop: &ActiveEventLoop,
1097 platform_output: egui::PlatformOutput,
1098 ) {
1099 self.handle_platform_output_inner(window, Some(event_loop), platform_output);
1100 }
1101
1102 fn handle_platform_output_inner(
1103 &mut self,
1104 window: &Window,
1105 event_loop: Option<&ActiveEventLoop>,
1106 platform_output: egui::PlatformOutput,
1107 ) {
1108 profiling::function_scope!();
1109
1110 let egui::PlatformOutput {
1111 commands,
1112 cursor_icon,
1113 cursor_image,
1114 events: _, mutable_text_under_cursor: _, ime,
1117 accesskit_update,
1118 num_completed_passes: _, request_discard_reasons: _, } = platform_output;
1121
1122 for command in commands {
1123 match command {
1124 egui::OutputCommand::CopyText(text) => {
1125 self.clipboard.set_text(text);
1126 }
1127 egui::OutputCommand::CopyImage(image) => {
1128 self.clipboard.set_image(&image);
1129 }
1130 egui::OutputCommand::OpenUrl(open_url) => {
1131 open_url_in_browser(&open_url.url);
1132 }
1133 }
1134 }
1135
1136 self.apply_cursor(window, event_loop, cursor_icon, cursor_image.as_ref());
1137
1138 let allow_ime = ime.is_some();
1139 let is_toggling_ime = self.allow_ime != allow_ime;
1140 if is_toggling_ime {
1141 self.allow_ime = allow_ime;
1142 #[cfg(target_os = "windows")]
1143 if !self.allow_ime {
1144 self.pressed_processed_physical_keys.clear();
1150 }
1151
1152 profiling::scope!("set_ime_allowed");
1153 window.set_ime_allowed(allow_ime);
1154 }
1155
1156 if let Some(ime) = ime {
1157 if !is_toggling_ime && ime.should_interrupt_composition {
1158 window.set_ime_allowed(false);
1162 window.set_ime_allowed(true);
1163 }
1164
1165 if ime.purpose != self.old_ime_purpose {
1166 self.old_ime_purpose = ime.purpose;
1167 window.set_ime_purpose(to_winit_ime_purpose(ime.purpose));
1168 }
1169
1170 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
1171 let ime_rect_px = pixels_per_point * ime.rect;
1172 if self.ime_rect_px != Some(ime_rect_px)
1173 || self.egui_ctx.input(|i| !i.events.is_empty())
1174 {
1175 self.ime_rect_px = Some(ime_rect_px);
1176 profiling::scope!("set_ime_cursor_area");
1177 window.set_ime_cursor_area(
1178 winit::dpi::PhysicalPosition {
1179 x: ime_rect_px.min.x,
1180 y: ime_rect_px.min.y,
1181 },
1182 winit::dpi::PhysicalSize {
1183 width: ime_rect_px.width(),
1184 height: ime_rect_px.height(),
1185 },
1186 );
1187 }
1188 } else {
1189 self.ime_rect_px = None;
1190 }
1191
1192 #[cfg(feature = "accesskit")]
1193 if let Some(accesskit) = self.accesskit.as_mut()
1194 && let Some(update) = accesskit_update
1195 {
1196 profiling::scope!("accesskit");
1197 accesskit.update_if_active(|| update);
1198 }
1199
1200 #[cfg(not(feature = "accesskit"))]
1201 let _ = accesskit_update;
1202 }
1203
1204 fn apply_cursor(
1209 &mut self,
1210 window: &Window,
1211 event_loop: Option<&ActiveEventLoop>,
1212 cursor_icon: egui::CursorIcon,
1213 cursor_image: Option<&egui::CustomCursorImage>,
1214 ) {
1215 let is_pointer_in_window = self.pointer_pos_in_points.is_some();
1216 if !is_pointer_in_window {
1217 self.current_cursor_icon = None;
1221 self.current_custom_cursor = None;
1222 return;
1223 }
1224
1225 if let (Some(image), Some(event_loop)) = (cursor_image, event_loop) {
1230 let key = std::sync::Arc::as_ptr(&image.rgba).cast::<u8>() as usize;
1231 let cached = self
1232 .current_custom_cursor
1233 .as_ref()
1234 .filter(|(k, _)| *k == key)
1235 .map(|(_, c)| c.clone());
1236
1237 let custom = match cached {
1238 Some(c) => c,
1239 None => match winit::window::CustomCursor::from_rgba(
1240 image.rgba.to_vec(),
1241 image.size[0],
1242 image.size[1],
1243 image.hotspot[0],
1244 image.hotspot[1],
1245 ) {
1246 Ok(source) => {
1247 let c = event_loop.create_custom_cursor(source);
1248 self.current_custom_cursor = Some((key, c.clone()));
1249 c
1250 }
1251 Err(err) => {
1252 log::warn!(
1253 "egui-winit: invalid cursor bitmap, falling back to cursor_icon: {err:?}"
1254 );
1255 self.current_custom_cursor = None;
1256 self.set_cursor_icon_inner(window, cursor_icon);
1257 return;
1258 }
1259 },
1260 };
1261
1262 window.set_cursor_visible(true);
1263 window.set_cursor(custom);
1264 self.current_cursor_icon = None;
1267 return;
1268 }
1269
1270 self.current_custom_cursor = None;
1271 self.set_cursor_icon_inner(window, cursor_icon);
1272 }
1273
1274 fn set_cursor_icon_inner(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
1277 if self.current_cursor_icon == Some(cursor_icon) {
1278 return;
1281 }
1282
1283 self.current_cursor_icon = Some(cursor_icon);
1284
1285 if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
1286 window.set_cursor_visible(true);
1287 window.set_cursor(winit_cursor_icon);
1288 } else {
1289 window.set_cursor_visible(false);
1290 }
1291 }
1292}
1293
1294fn to_egui_touch_phase(phase: winit::event::TouchPhase) -> egui::TouchPhase {
1295 match phase {
1296 winit::event::TouchPhase::Started => egui::TouchPhase::Start,
1297 winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
1298 winit::event::TouchPhase::Ended => egui::TouchPhase::End,
1299 winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
1300 }
1301}
1302
1303fn to_egui_theme(theme: winit::window::Theme) -> Theme {
1304 match theme {
1305 winit::window::Theme::Dark => Theme::Dark,
1306 winit::window::Theme::Light => Theme::Light,
1307 }
1308}
1309
1310pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1311 let inner_pos_px = window.inner_position().ok()?;
1312 let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
1313
1314 let inner_size_px = window.inner_size();
1315 let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);
1316
1317 let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);
1318
1319 Some(inner_rect_px / pixels_per_point)
1320}
1321
1322pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1323 let outer_pos_px = window.outer_position().ok()?;
1324 let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);
1325
1326 let outer_size_px = window.outer_size();
1327 let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);
1328
1329 let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);
1330
1331 Some(outer_rect_px / pixels_per_point)
1332}
1333
1334pub fn update_viewport_info(
1340 viewport_info: &mut ViewportInfo,
1341 egui_ctx: &egui::Context,
1342 window: &Window,
1343 is_init: bool,
1344) {
1345 profiling::function_scope!();
1346 let pixels_per_point = pixels_per_point(egui_ctx, window);
1347
1348 let has_a_position = match window.is_minimized() {
1349 Some(true) => false,
1350 Some(false) | None => true,
1351 };
1352
1353 let inner_rect = if has_a_position {
1354 inner_rect_in_points(window, pixels_per_point)
1355 } else {
1356 None
1357 };
1358
1359 let outer_rect = if has_a_position {
1360 outer_rect_in_points(window, pixels_per_point)
1361 } else {
1362 None
1363 };
1364
1365 let monitor_size = {
1366 profiling::scope!("monitor_size");
1367 if let Some(monitor) = window.current_monitor() {
1368 let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
1369 Some(egui::vec2(size.width, size.height))
1370 } else {
1371 None
1372 }
1373 };
1374
1375 viewport_info.title = Some(window.title());
1376 viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
1377
1378 viewport_info.monitor_size = monitor_size;
1379 viewport_info.inner_rect = inner_rect;
1380 viewport_info.outer_rect = outer_rect;
1381
1382 if is_init || !cfg!(target_os = "macos") {
1383 viewport_info.maximized = Some(window.is_maximized());
1387 viewport_info.minimized = Some(window.is_minimized().unwrap_or(false));
1388 }
1389
1390 viewport_info.fullscreen = Some(window.fullscreen().is_some());
1391 viewport_info.focused = Some(window.has_focus());
1392}
1393
1394fn open_url_in_browser(_url: &str) {
1395 #[cfg(feature = "webbrowser")]
1396 if let Err(err) = webbrowser::open(_url) {
1397 log::warn!("Failed to open url: {err}");
1398 }
1399
1400 #[cfg(not(feature = "webbrowser"))]
1401 {
1402 log::warn!("Cannot open url - feature \"links\" not enabled.");
1403 }
1404}
1405
1406fn is_printable_char(chr: char) -> bool {
1411 let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
1412 || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
1413 || '\u{100000}' <= chr && chr <= '\u{10fffd}';
1414
1415 !is_in_private_use_area && !chr.is_ascii_control()
1416}
1417
1418fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1419 keycode == egui::Key::Cut
1420 || (modifiers.command && keycode == egui::Key::X)
1421 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
1422}
1423
1424fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1425 keycode == egui::Key::Copy
1426 || (modifiers.command && keycode == egui::Key::C)
1427 || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
1428}
1429
1430fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1431 keycode == egui::Key::Paste
1432 || (modifiers.command && keycode == egui::Key::V)
1433 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
1434}
1435
1436fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
1437 match button {
1438 winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
1439 winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
1440 winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
1441 winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
1442 winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
1443 winit::event::MouseButton::Other(_) => None,
1444 }
1445}
1446
1447fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
1448 match key {
1449 winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
1450 winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
1451 winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
1452 }
1453}
1454
1455fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
1456 use egui::Key;
1457 use winit::keyboard::NamedKey;
1458
1459 Some(match named_key {
1460 NamedKey::Enter => Key::Enter,
1461 NamedKey::Tab => Key::Tab,
1462 NamedKey::ArrowDown => Key::ArrowDown,
1463 NamedKey::ArrowLeft => Key::ArrowLeft,
1464 NamedKey::ArrowRight => Key::ArrowRight,
1465 NamedKey::ArrowUp => Key::ArrowUp,
1466 NamedKey::End => Key::End,
1467 NamedKey::Home => Key::Home,
1468 NamedKey::PageDown => Key::PageDown,
1469 NamedKey::PageUp => Key::PageUp,
1470 NamedKey::Backspace => Key::Backspace,
1471 NamedKey::Delete => Key::Delete,
1472 NamedKey::Insert => Key::Insert,
1473 NamedKey::Escape => Key::Escape,
1474 NamedKey::Cut => Key::Cut,
1475 NamedKey::Copy => Key::Copy,
1476 NamedKey::Paste => Key::Paste,
1477
1478 NamedKey::Space => Key::Space,
1479
1480 NamedKey::F1 => Key::F1,
1481 NamedKey::F2 => Key::F2,
1482 NamedKey::F3 => Key::F3,
1483 NamedKey::F4 => Key::F4,
1484 NamedKey::F5 => Key::F5,
1485 NamedKey::F6 => Key::F6,
1486 NamedKey::F7 => Key::F7,
1487 NamedKey::F8 => Key::F8,
1488 NamedKey::F9 => Key::F9,
1489 NamedKey::F10 => Key::F10,
1490 NamedKey::F11 => Key::F11,
1491 NamedKey::F12 => Key::F12,
1492 NamedKey::F13 => Key::F13,
1493 NamedKey::F14 => Key::F14,
1494 NamedKey::F15 => Key::F15,
1495 NamedKey::F16 => Key::F16,
1496 NamedKey::F17 => Key::F17,
1497 NamedKey::F18 => Key::F18,
1498 NamedKey::F19 => Key::F19,
1499 NamedKey::F20 => Key::F20,
1500 NamedKey::F21 => Key::F21,
1501 NamedKey::F22 => Key::F22,
1502 NamedKey::F23 => Key::F23,
1503 NamedKey::F24 => Key::F24,
1504 NamedKey::F25 => Key::F25,
1505 NamedKey::F26 => Key::F26,
1506 NamedKey::F27 => Key::F27,
1507 NamedKey::F28 => Key::F28,
1508 NamedKey::F29 => Key::F29,
1509 NamedKey::F30 => Key::F30,
1510 NamedKey::F31 => Key::F31,
1511 NamedKey::F32 => Key::F32,
1512 NamedKey::F33 => Key::F33,
1513 NamedKey::F34 => Key::F34,
1514 NamedKey::F35 => Key::F35,
1515
1516 NamedKey::BrowserBack => Key::BrowserBack,
1517 _ => {
1518 log::trace!("Unknown key: {named_key:?}");
1519 return None;
1520 }
1521 })
1522}
1523
1524fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
1525 use egui::Key;
1526 use winit::keyboard::KeyCode;
1527
1528 Some(match key {
1529 KeyCode::ArrowDown => Key::ArrowDown,
1530 KeyCode::ArrowLeft => Key::ArrowLeft,
1531 KeyCode::ArrowRight => Key::ArrowRight,
1532 KeyCode::ArrowUp => Key::ArrowUp,
1533
1534 KeyCode::Escape => Key::Escape,
1535 KeyCode::Tab => Key::Tab,
1536 KeyCode::Backspace => Key::Backspace,
1537 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
1538
1539 KeyCode::Insert => Key::Insert,
1540 KeyCode::Delete => Key::Delete,
1541 KeyCode::Home => Key::Home,
1542 KeyCode::End => Key::End,
1543 KeyCode::PageUp => Key::PageUp,
1544 KeyCode::PageDown => Key::PageDown,
1545
1546 KeyCode::Space => Key::Space,
1548 KeyCode::Comma => Key::Comma,
1549 KeyCode::Period => Key::Period,
1550 KeyCode::Semicolon => Key::Semicolon,
1552 KeyCode::Backslash => Key::Backslash,
1553 KeyCode::Slash | KeyCode::NumpadDivide => Key::Slash,
1554 KeyCode::BracketLeft => Key::OpenBracket,
1555 KeyCode::BracketRight => Key::CloseBracket,
1556 KeyCode::Backquote => Key::Backtick,
1557 KeyCode::Quote => Key::Quote,
1558
1559 KeyCode::Cut => Key::Cut,
1560 KeyCode::Copy => Key::Copy,
1561 KeyCode::Paste => Key::Paste,
1562 KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
1563 KeyCode::NumpadAdd => Key::Plus,
1564 KeyCode::Equal => Key::Equals,
1565
1566 KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
1567 KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
1568 KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
1569 KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
1570 KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
1571 KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
1572 KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
1573 KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
1574 KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
1575 KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
1576
1577 KeyCode::KeyA => Key::A,
1578 KeyCode::KeyB => Key::B,
1579 KeyCode::KeyC => Key::C,
1580 KeyCode::KeyD => Key::D,
1581 KeyCode::KeyE => Key::E,
1582 KeyCode::KeyF => Key::F,
1583 KeyCode::KeyG => Key::G,
1584 KeyCode::KeyH => Key::H,
1585 KeyCode::KeyI => Key::I,
1586 KeyCode::KeyJ => Key::J,
1587 KeyCode::KeyK => Key::K,
1588 KeyCode::KeyL => Key::L,
1589 KeyCode::KeyM => Key::M,
1590 KeyCode::KeyN => Key::N,
1591 KeyCode::KeyO => Key::O,
1592 KeyCode::KeyP => Key::P,
1593 KeyCode::KeyQ => Key::Q,
1594 KeyCode::KeyR => Key::R,
1595 KeyCode::KeyS => Key::S,
1596 KeyCode::KeyT => Key::T,
1597 KeyCode::KeyU => Key::U,
1598 KeyCode::KeyV => Key::V,
1599 KeyCode::KeyW => Key::W,
1600 KeyCode::KeyX => Key::X,
1601 KeyCode::KeyY => Key::Y,
1602 KeyCode::KeyZ => Key::Z,
1603
1604 KeyCode::F1 => Key::F1,
1605 KeyCode::F2 => Key::F2,
1606 KeyCode::F3 => Key::F3,
1607 KeyCode::F4 => Key::F4,
1608 KeyCode::F5 => Key::F5,
1609 KeyCode::F6 => Key::F6,
1610 KeyCode::F7 => Key::F7,
1611 KeyCode::F8 => Key::F8,
1612 KeyCode::F9 => Key::F9,
1613 KeyCode::F10 => Key::F10,
1614 KeyCode::F11 => Key::F11,
1615 KeyCode::F12 => Key::F12,
1616 KeyCode::F13 => Key::F13,
1617 KeyCode::F14 => Key::F14,
1618 KeyCode::F15 => Key::F15,
1619 KeyCode::F16 => Key::F16,
1620 KeyCode::F17 => Key::F17,
1621 KeyCode::F18 => Key::F18,
1622 KeyCode::F19 => Key::F19,
1623 KeyCode::F20 => Key::F20,
1624 KeyCode::F21 => Key::F21,
1625 KeyCode::F22 => Key::F22,
1626 KeyCode::F23 => Key::F23,
1627 KeyCode::F24 => Key::F24,
1628 KeyCode::F25 => Key::F25,
1629 KeyCode::F26 => Key::F26,
1630 KeyCode::F27 => Key::F27,
1631 KeyCode::F28 => Key::F28,
1632 KeyCode::F29 => Key::F29,
1633 KeyCode::F30 => Key::F30,
1634 KeyCode::F31 => Key::F31,
1635 KeyCode::F32 => Key::F32,
1636 KeyCode::F33 => Key::F33,
1637 KeyCode::F34 => Key::F34,
1638 KeyCode::F35 => Key::F35,
1639
1640 KeyCode::ShiftLeft => Key::ShiftLeft,
1645 KeyCode::ShiftRight => Key::ShiftRight,
1646 KeyCode::ControlLeft => Key::ControlLeft,
1647 KeyCode::ControlRight => Key::ControlRight,
1648 KeyCode::AltLeft => Key::AltLeft,
1649 KeyCode::AltRight => Key::AltRight,
1650 KeyCode::SuperLeft => Key::SuperLeft,
1651 KeyCode::SuperRight => Key::SuperRight,
1652
1653 KeyCode::IntlBackslash => Key::IntlBackslash,
1655
1656 _ => {
1657 return None;
1658 }
1659 })
1660}
1661
1662fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
1663 match cursor_icon {
1664 egui::CursorIcon::None => None,
1665
1666 egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
1667 egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
1668 egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
1669 egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
1670 egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
1671 egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1672 egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
1673 egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
1674 egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1675 egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
1676 egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
1677 egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1678 egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1679 egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
1680 egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
1681
1682 egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
1683 egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
1684 egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
1685 egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
1686
1687 egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
1688 egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
1689 egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
1690 egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
1691 egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
1692 egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
1693 egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
1694 egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
1695 egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
1696 egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
1697
1698 egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
1699 egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
1700 egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
1701 egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
1702 egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
1703 }
1704}
1705
1706#[derive(PartialEq, Eq, Hash, Debug)]
1709pub enum ActionRequested {
1710 Screenshot(egui::UserData),
1711 Cut,
1712 Copy,
1713 Paste,
1714}
1715
1716pub fn process_viewport_commands(
1717 egui_ctx: &egui::Context,
1718 info: &mut ViewportInfo,
1719 commands: impl IntoIterator<Item = ViewportCommand>,
1720 window: &Window,
1721 actions_requested: &mut Vec<ActionRequested>,
1722) {
1723 for command in commands {
1724 process_viewport_command(egui_ctx, window, command, info, actions_requested);
1725 }
1726}
1727
1728fn process_viewport_command(
1729 egui_ctx: &egui::Context,
1730 window: &Window,
1731 command: ViewportCommand,
1732 info: &mut ViewportInfo,
1733 actions_requested: &mut Vec<ActionRequested>,
1734) {
1735 profiling::function_scope!(&format!("{command:?}"));
1736
1737 use winit::window::ResizeDirection;
1738
1739 log::trace!("Processing ViewportCommand::{command:?}");
1740
1741 let pixels_per_point = pixels_per_point(egui_ctx, window);
1742
1743 match command {
1744 ViewportCommand::Close => {
1745 info.events.push(egui::ViewportEvent::Close);
1746 }
1747 ViewportCommand::CancelClose => {
1748 }
1750 ViewportCommand::StartDrag => {
1751 if window.has_focus()
1753 && let Err(err) = window.drag_window()
1754 {
1755 log::warn!("{command:?}: {err}");
1756 }
1757 }
1758 ViewportCommand::InnerSize(size) => {
1759 let width_px = pixels_per_point * size.x.max(1.0);
1760 let height_px = pixels_per_point * size.y.max(1.0);
1761 let requested_size = PhysicalSize::new(width_px, height_px);
1762 if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {
1763 info.inner_rect = inner_rect_in_points(window, pixels_per_point);
1777 info.outer_rect = outer_rect_in_points(window, pixels_per_point);
1778 } else {
1779 }
1783 }
1784 ViewportCommand::BeginResize(direction) => {
1785 if let Err(err) = window.drag_resize_window(match direction {
1786 egui::viewport::ResizeDirection::North => ResizeDirection::North,
1787 egui::viewport::ResizeDirection::South => ResizeDirection::South,
1788 egui::viewport::ResizeDirection::East => ResizeDirection::East,
1789 egui::viewport::ResizeDirection::West => ResizeDirection::West,
1790 egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
1791 egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
1792 egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
1793 egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
1794 }) {
1795 log::warn!("{command:?}: {err}");
1796 }
1797 }
1798 ViewportCommand::Title(title) => {
1799 window.set_title(&title);
1800 }
1801 ViewportCommand::Transparent(v) => window.set_transparent(v),
1802 ViewportCommand::Visible(v) => window.set_visible(v),
1803 ViewportCommand::OuterPosition(pos) => {
1804 window.set_outer_position(PhysicalPosition::new(
1805 pixels_per_point * pos.x,
1806 pixels_per_point * pos.y,
1807 ));
1808 }
1809 ViewportCommand::MinInnerSize(s) => {
1810 window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
1811 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1812 ));
1813 }
1814 ViewportCommand::MaxInnerSize(s) => {
1815 window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
1816 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1817 ));
1818 }
1819 ViewportCommand::ResizeIncrements(s) => {
1820 window.set_resize_increments(
1821 s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
1822 );
1823 }
1824 ViewportCommand::Resizable(v) => window.set_resizable(v),
1825 ViewportCommand::EnableButtons {
1826 close,
1827 minimized,
1828 maximize,
1829 } => window.set_enabled_buttons(
1830 if close {
1831 WindowButtons::CLOSE
1832 } else {
1833 WindowButtons::empty()
1834 } | if minimized {
1835 WindowButtons::MINIMIZE
1836 } else {
1837 WindowButtons::empty()
1838 } | if maximize {
1839 WindowButtons::MAXIMIZE
1840 } else {
1841 WindowButtons::empty()
1842 },
1843 ),
1844 ViewportCommand::Minimized(v) => {
1845 window.set_minimized(v);
1846 info.minimized = Some(v);
1847 }
1848 ViewportCommand::Maximized(v) => {
1849 window.set_maximized(v);
1850 info.maximized = Some(v);
1851 }
1852 ViewportCommand::Fullscreen(v) => {
1853 window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
1854 }
1855 ViewportCommand::SetMonitor(idx) => {
1856 if let Some(monitor) = window.available_monitors().nth(idx) {
1857 window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor))));
1858 } else {
1859 log::warn!(
1860 "ViewportCommand::SetMonitor({idx}): index out of range ({} monitors available)",
1861 window.available_monitors().count()
1862 );
1863 }
1864 }
1865 ViewportCommand::Decorations(v) => {
1866 window.set_decorations(v);
1867 #[cfg(target_os = "windows")]
1868 {
1869 use winit::platform::windows::WindowExtWindows as _;
1870 window.set_undecorated_shadow(!v);
1871 }
1872 }
1873 ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
1874 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1875 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1876 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1877 }),
1878 ViewportCommand::Icon(icon) => {
1879 let winit_icon = icon.and_then(|icon| to_winit_icon(&icon));
1880 window.set_window_icon(winit_icon);
1881 }
1882 ViewportCommand::IMERect(rect) => {
1883 window.set_ime_cursor_area(
1884 PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
1885 PhysicalSize::new(
1886 pixels_per_point * rect.size().x,
1887 pixels_per_point * rect.size().y,
1888 ),
1889 );
1890 }
1891 ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
1892 ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(to_winit_ime_purpose(p)),
1893 ViewportCommand::Focus => {
1894 if !window.has_focus() {
1895 window.focus_window();
1896 }
1897 }
1898 ViewportCommand::RequestUserAttention(a) => {
1899 window.request_user_attention(match a {
1900 egui::UserAttentionType::Reset => None,
1901 egui::UserAttentionType::Critical => {
1902 Some(winit::window::UserAttentionType::Critical)
1903 }
1904 egui::UserAttentionType::Informational => {
1905 Some(winit::window::UserAttentionType::Informational)
1906 }
1907 });
1908 }
1909 ViewportCommand::SetTheme(t) => window.set_theme(match t {
1910 egui::SystemTheme::Light => Some(winit::window::Theme::Light),
1911 egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
1912 egui::SystemTheme::SystemDefault => None,
1913 }),
1914 ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
1915 ViewportCommand::CursorPosition(pos) => {
1916 if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
1917 pixels_per_point * pos.x,
1918 pixels_per_point * pos.y,
1919 )) {
1920 log::warn!("{command:?}: {err}");
1921 }
1922 }
1923 ViewportCommand::CursorGrab(o) => {
1924 if let Err(err) = window.set_cursor_grab(match o {
1925 egui::viewport::CursorGrab::None => CursorGrabMode::None,
1926 egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
1927 egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
1928 }) {
1929 log::warn!("{command:?}: {err}");
1930 }
1931 }
1932 ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
1933 ViewportCommand::MousePassthrough(passthrough) => {
1934 if let Err(err) = window.set_cursor_hittest(!passthrough) {
1935 log::warn!("{command:?}: {err}");
1936 }
1937 }
1938 ViewportCommand::Screenshot(user_data) => {
1939 actions_requested.push(ActionRequested::Screenshot(user_data));
1940 }
1941 ViewportCommand::RequestCut => {
1942 actions_requested.push(ActionRequested::Cut);
1943 }
1944 ViewportCommand::RequestCopy => {
1945 actions_requested.push(ActionRequested::Copy);
1946 }
1947 ViewportCommand::RequestPaste => {
1948 actions_requested.push(ActionRequested::Paste);
1949 }
1950 }
1951}
1952
1953fn to_winit_ime_purpose(purpose: egui::IMEPurpose) -> winit::window::ImePurpose {
1954 match purpose {
1955 egui::IMEPurpose::Password => winit::window::ImePurpose::Password,
1956 egui::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
1957 egui::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
1958 }
1959}
1960
1961pub fn create_window(
1968 egui_ctx: &egui::Context,
1969 event_loop: &ActiveEventLoop,
1970 viewport_builder: &ViewportBuilder,
1971) -> Result<Window, winit::error::OsError> {
1972 profiling::function_scope!();
1973
1974 let mut window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone());
1975
1976 if let Some(idx) = viewport_builder.monitor {
1981 if let Some(monitor) = event_loop.available_monitors().nth(idx) {
1982 window_attributes = window_attributes
1983 .with_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor))));
1984 } else {
1985 log::warn!(
1986 "ViewportBuilder::with_monitor({idx}): index out of range ({} monitors available)",
1987 event_loop.available_monitors().count()
1988 );
1989 }
1990 }
1991
1992 let window = event_loop.create_window(window_attributes)?;
1993 apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
1994 Ok(window)
1995}
1996
1997pub fn create_winit_window_attributes(
1998 egui_ctx: &egui::Context,
1999 viewport_builder: ViewportBuilder,
2000) -> winit::window::WindowAttributes {
2001 profiling::function_scope!();
2002
2003 let ViewportBuilder {
2004 title,
2005 position,
2006 inner_size,
2007 min_inner_size,
2008 max_inner_size,
2009 fullscreen,
2010 maximized,
2011 resizable,
2012 transparent,
2013 decorations,
2014 icon,
2015 active,
2016 visible,
2017 close_button,
2018 minimize_button,
2019 maximize_button,
2020 window_level,
2021
2022 fullsize_content_view: _fullsize_content_view,
2024 movable_by_window_background: _movable_by_window_background,
2025 title_shown: _title_shown,
2026 titlebar_buttons_shown: _titlebar_buttons_shown,
2027 titlebar_shown: _titlebar_shown,
2028 has_shadow: _has_shadow,
2029
2030 drag_and_drop: _drag_and_drop,
2032 taskbar: _taskbar,
2033
2034 app_id: _app_id,
2036
2037 window_type: _window_type,
2039 override_redirect: _override_redirect,
2040
2041 mouse_passthrough: _, clamp_size_to_monitor_size: _, monitor: _, } = viewport_builder;
2045
2046 let mut window_attributes = winit::window::WindowAttributes::default()
2047 .with_title(title.unwrap_or_else(|| "egui window".to_owned()))
2048 .with_transparent(transparent.unwrap_or(false))
2049 .with_decorations(decorations.unwrap_or(true))
2050 .with_resizable(resizable.unwrap_or(true))
2051 .with_visible(visible.unwrap_or(true))
2052 .with_maximized(if cfg!(target_os = "ios") {
2053 true
2054 } else {
2055 maximized.unwrap_or(false)
2056 })
2057 .with_window_level(match window_level.unwrap_or_default() {
2058 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
2059 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
2060 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
2061 })
2062 .with_fullscreen(
2063 fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),
2064 )
2065 .with_enabled_buttons({
2066 let mut buttons = WindowButtons::empty();
2067 if minimize_button.unwrap_or(true) {
2068 buttons |= WindowButtons::MINIMIZE;
2069 }
2070 if maximize_button.unwrap_or(true) {
2071 buttons |= WindowButtons::MAXIMIZE;
2072 }
2073 if close_button.unwrap_or(true) {
2074 buttons |= WindowButtons::CLOSE;
2075 }
2076 buttons
2077 })
2078 .with_active(active.unwrap_or(true));
2079
2080 #[expect(
2086 clippy::disallowed_types,
2087 reason = "zoom factor is manually accounted for"
2088 )]
2089 #[cfg(not(target_os = "ios"))]
2090 {
2091 use winit::dpi::{LogicalPosition, LogicalSize};
2092 let zoom_factor = egui_ctx.zoom_factor();
2093
2094 if let Some(size) = inner_size {
2095 window_attributes = window_attributes
2096 .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2097 }
2098
2099 if let Some(size) = min_inner_size {
2100 window_attributes = window_attributes
2101 .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2102 }
2103
2104 if let Some(size) = max_inner_size {
2105 window_attributes = window_attributes
2106 .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2107 }
2108
2109 if let Some(pos) = position {
2110 window_attributes = window_attributes.with_position(LogicalPosition::new(
2111 zoom_factor * pos.x,
2112 zoom_factor * pos.y,
2113 ));
2114 }
2115 }
2116 #[cfg(target_os = "ios")]
2117 {
2118 _ = egui_ctx;
2120 _ = pixels_per_point;
2121 _ = position;
2122 _ = inner_size;
2123 _ = min_inner_size;
2124 _ = max_inner_size;
2125 }
2126
2127 if let Some(icon) = icon {
2128 let winit_icon = to_winit_icon(&icon);
2129 window_attributes = window_attributes.with_window_icon(winit_icon);
2130 }
2131
2132 #[cfg(all(feature = "wayland", target_os = "linux"))]
2133 if let Some(app_id) = _app_id {
2134 use winit::platform::wayland::WindowAttributesExtWayland as _;
2135 window_attributes = window_attributes.with_name(app_id, "");
2136 }
2137
2138 #[cfg(all(feature = "x11", target_os = "linux"))]
2139 {
2140 use winit::platform::x11::WindowAttributesExtX11 as _;
2141 if let Some(window_type) = _window_type {
2142 use winit::platform::x11::WindowType;
2143 window_attributes = window_attributes.with_x11_window_type(vec![match window_type {
2144 egui::X11WindowType::Normal => WindowType::Normal,
2145 egui::X11WindowType::Utility => WindowType::Utility,
2146 egui::X11WindowType::Dock => WindowType::Dock,
2147 egui::X11WindowType::Desktop => WindowType::Desktop,
2148 egui::X11WindowType::Toolbar => WindowType::Toolbar,
2149 egui::X11WindowType::Menu => WindowType::Menu,
2150 egui::X11WindowType::Splash => WindowType::Splash,
2151 egui::X11WindowType::Dialog => WindowType::Dialog,
2152 egui::X11WindowType::DropdownMenu => WindowType::DropdownMenu,
2153 egui::X11WindowType::PopupMenu => WindowType::PopupMenu,
2154 egui::X11WindowType::Tooltip => WindowType::Tooltip,
2155 egui::X11WindowType::Notification => WindowType::Notification,
2156 egui::X11WindowType::Combo => WindowType::Combo,
2157 egui::X11WindowType::Dnd => WindowType::Dnd,
2158 }]);
2159 }
2160 if let Some(override_redirect) = _override_redirect {
2161 window_attributes = window_attributes.with_override_redirect(override_redirect);
2162 }
2163 }
2164
2165 #[cfg(target_os = "windows")]
2166 {
2167 use winit::platform::windows::WindowAttributesExtWindows as _;
2168 if let Some(enable) = _drag_and_drop {
2169 window_attributes = window_attributes.with_drag_and_drop(enable);
2170 }
2171 if let Some(show) = _taskbar {
2172 window_attributes = window_attributes.with_skip_taskbar(!show);
2173 }
2174 window_attributes = window_attributes.with_undecorated_shadow(!decorations.unwrap_or(true));
2175 }
2176
2177 #[cfg(target_os = "macos")]
2178 {
2179 use winit::platform::macos::WindowAttributesExtMacOS as _;
2180 window_attributes = window_attributes
2181 .with_title_hidden(!_title_shown.unwrap_or(true))
2182 .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true))
2183 .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true))
2184 .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false))
2185 .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false))
2186 .with_has_shadow(_has_shadow.unwrap_or(true));
2187 }
2188
2189 window_attributes
2190}
2191
2192fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
2193 if icon.is_empty() {
2194 None
2195 } else {
2196 profiling::function_scope!();
2197 match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
2198 Ok(winit_icon) => Some(winit_icon),
2199 Err(err) => {
2200 log::warn!("Invalid IconData: {err}");
2201 None
2202 }
2203 }
2204 }
2205}
2206
2207pub fn apply_viewport_builder_to_window(
2209 egui_ctx: &egui::Context,
2210 window: &Window,
2211 builder: &ViewportBuilder,
2212) {
2213 if let Some(mouse_passthrough) = builder.mouse_passthrough
2214 && let Err(err) = window.set_cursor_hittest(!mouse_passthrough)
2215 {
2216 log::warn!("set_cursor_hittest failed: {err}");
2217 }
2218
2219 {
2220 let pixels_per_point = pixels_per_point(egui_ctx, window);
2226
2227 if let Some(size) = builder.inner_size
2228 && window
2229 .request_inner_size(PhysicalSize::new(
2230 pixels_per_point * size.x,
2231 pixels_per_point * size.y,
2232 ))
2233 .is_some()
2234 {
2235 log::debug!("Failed to set window size");
2236 }
2237 if let Some(size) = builder.min_inner_size {
2238 window.set_min_inner_size(Some(PhysicalSize::new(
2239 pixels_per_point * size.x,
2240 pixels_per_point * size.y,
2241 )));
2242 }
2243 if let Some(size) = builder.max_inner_size {
2244 window.set_max_inner_size(Some(PhysicalSize::new(
2245 pixels_per_point * size.x,
2246 pixels_per_point * size.y,
2247 )));
2248 }
2249 if let Some(pos) = builder.position {
2250 let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
2251 window.set_outer_position(pos);
2252 }
2253 if let Some(maximized) = builder.maximized {
2254 window.set_maximized(maximized);
2255 }
2256 }
2257}
2258
2259pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'static str {
2264 use winit::event::DeviceEvent;
2265
2266 match event {
2267 DeviceEvent::Added => "DeviceEvent::Added",
2268 DeviceEvent::Removed => "DeviceEvent::Removed",
2269 DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion",
2270 DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel",
2271 DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
2272 DeviceEvent::Button { .. } => "DeviceEvent::Button",
2273 DeviceEvent::Key { .. } => "DeviceEvent::Key",
2274 }
2275}
2276
2277pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
2280 use winit::event::WindowEvent;
2281
2282 match event {
2283 WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
2284 WindowEvent::Resized { .. } => "WindowEvent::Resized",
2285 WindowEvent::Moved { .. } => "WindowEvent::Moved",
2286 WindowEvent::CloseRequested => "WindowEvent::CloseRequested",
2287 WindowEvent::Destroyed => "WindowEvent::Destroyed",
2288 WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
2289 WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
2290 WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled",
2291 WindowEvent::Focused { .. } => "WindowEvent::Focused",
2292 WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
2293 WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
2294 WindowEvent::Ime { .. } => "WindowEvent::Ime",
2295 WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
2296 WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
2297 WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
2298 WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
2299 WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
2300 WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture",
2301 WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested",
2302 WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture",
2303 WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture",
2304 WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",
2305 WindowEvent::AxisMotion { .. } => "WindowEvent::AxisMotion",
2306 WindowEvent::Touch { .. } => "WindowEvent::Touch",
2307 WindowEvent::ScaleFactorChanged { .. } => "WindowEvent::ScaleFactorChanged",
2308 WindowEvent::ThemeChanged { .. } => "WindowEvent::ThemeChanged",
2309 WindowEvent::Occluded { .. } => "WindowEvent::Occluded",
2310 WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
2311 }
2312}