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
23#[cfg(target_os = "macos")]
25mod macos_scroll_momentum;
26
27pub mod clipboard;
28#[cfg(not(target_arch = "wasm32"))]
29mod dropped_file;
30mod safe_area;
31mod window_settings;
32
33pub use window_settings::{WindowGeometry, WindowSettings};
34
35use raw_window_handle::HasDisplayHandle;
36
37#[cfg(not(target_arch = "wasm32"))]
38use dropped_file::NativeFile;
39
40use winit::{
41 dpi::{PhysicalPosition, PhysicalSize},
42 event::ElementState,
43 event_loop::ActiveEventLoop,
44 window::{CursorGrabMode, CustomCursor, Window, WindowButtons, WindowLevel},
45};
46
47pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
48 let size = if cfg!(target_os = "ios") {
49 window.outer_size()
53 } else {
54 window.inner_size()
55 };
56 egui::vec2(size.width as f32, size.height as f32)
57}
58
59pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
61 let native_pixels_per_point = window.scale_factor() as f32;
62 let egui_zoom_factor = egui_ctx.zoom_factor();
63 egui_zoom_factor * native_pixels_per_point
64}
65
66#[must_use]
69#[derive(Clone, Copy, Debug, Default)]
70pub struct EventResponse {
71 pub consumed: bool,
79
80 pub repaint: bool,
82}
83
84pub struct State {
90 egui_ctx: egui::Context,
92
93 viewport_id: ViewportId,
94 start_time: web_time::Instant,
95 egui_input: egui::RawInput,
96
97 modifiers: egui::Modifiers,
102
103 pointer_pos_in_points: Option<egui::Pos2>,
104 any_pointer_button_down: bool,
105 current_cursor_icon: Option<egui::CursorIcon>,
106
107 current_custom_cursor: Option<(usize, CustomCursor)>,
114
115 clipboard: clipboard::Clipboard,
116
117 simulate_touch_screen: bool,
122
123 pointer_touch_id: Option<u64>,
127
128 #[cfg(feature = "accesskit")]
129 pub accesskit: Option<accesskit_winit::Adapter>,
130
131 allow_ime: bool,
132
133 clipboard_shortcuts: bool,
135
136 ime_cursor_rect_px: Option<egui::Rect>,
137 old_ime_purpose: egui::IMEPurpose,
138
139 #[cfg(target_os = "macos")]
141 scroll_momentum_monitor: Option<macos_scroll_momentum::ScrollMomentumMonitor>,
142
143 #[cfg(target_os = "windows")]
147 pressed_processed_physical_keys: HashSet<winit::keyboard::PhysicalKey>,
148}
149
150impl State {
151 pub fn new(
153 egui_ctx: egui::Context,
154 viewport_id: ViewportId,
155 display_target: &dyn HasDisplayHandle,
156 native_pixels_per_point: Option<f32>,
157 theme: Option<winit::window::Theme>,
158 max_texture_side: Option<usize>,
159 ) -> Self {
160 profiling::function_scope!();
161
162 let egui_input = egui::RawInput {
163 focused: false, ..Default::default()
165 };
166
167 #[expect(unsafe_code)]
171 let clipboard = unsafe {
172 clipboard::Clipboard::new(display_target.display_handle().ok().map(|h| h.as_raw()))
173 };
174
175 let mut slf = Self {
176 viewport_id,
177 start_time: web_time::Instant::now()
178 .checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time()))
179 .unwrap_or_else(web_time::Instant::now),
180 egui_ctx,
181 egui_input,
182 modifiers: egui::Modifiers::default(),
183 pointer_pos_in_points: None,
184 any_pointer_button_down: false,
185 current_cursor_icon: None,
186 current_custom_cursor: None,
187
188 clipboard,
189
190 simulate_touch_screen: false,
191 pointer_touch_id: None,
192
193 #[cfg(feature = "accesskit")]
194 accesskit: None,
195
196 allow_ime: false,
197 clipboard_shortcuts: true,
198 ime_cursor_rect_px: None,
199 old_ime_purpose: egui::IMEPurpose::Normal,
200 #[cfg(target_os = "macos")]
201 scroll_momentum_monitor: macos_scroll_momentum::ScrollMomentumMonitor::install(),
202 #[cfg(target_os = "windows")]
203 pressed_processed_physical_keys: HashSet::new(),
204 };
205
206 slf.egui_input
207 .viewports
208 .entry(ViewportId::ROOT)
209 .or_default()
210 .native_pixels_per_point = native_pixels_per_point;
211 slf.egui_input.system_theme = theme.map(to_egui_theme);
212
213 if let Some(max_texture_side) = max_texture_side {
214 slf.set_max_texture_side(max_texture_side);
215 }
216 slf
217 }
218
219 #[cfg(feature = "accesskit")]
220 pub fn init_accesskit<T: From<accesskit_winit::Event> + Send>(
221 &mut self,
222 event_loop: &ActiveEventLoop,
223 window: &Window,
224 event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
225 ) {
226 profiling::function_scope!();
227
228 self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
229 event_loop,
230 window,
231 event_loop_proxy,
232 ));
233 }
234
235 pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
238 self.egui_input.max_texture_side = Some(max_texture_side);
239 }
240
241 pub fn clipboard_text(&mut self) -> Option<String> {
243 self.clipboard.get()
244 }
245
246 pub fn clipboard_image(&mut self) -> Option<egui::ColorImage> {
249 self.clipboard.get_image()
250 }
251
252 pub fn set_clipboard_text(&mut self, text: String) {
254 self.clipboard.set_text(text);
255 }
256
257 pub fn allow_ime(&self) -> bool {
259 self.allow_ime
260 }
261
262 pub fn set_allow_ime(&mut self, allow: bool) {
264 self.allow_ime = allow;
265 }
266
267 #[inline]
271 pub fn clipboard_shortcuts(&self) -> bool {
272 self.clipboard_shortcuts
273 }
274
275 #[inline]
290 pub fn set_clipboard_shortcuts(&mut self, enabled: bool) {
291 self.clipboard_shortcuts = enabled;
292 }
293
294 #[inline]
295 pub fn egui_ctx(&self) -> &egui::Context {
296 &self.egui_ctx
297 }
298
299 #[inline]
302 pub fn egui_input(&self) -> &egui::RawInput {
303 &self.egui_input
304 }
305
306 #[inline]
309 pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
310 &mut self.egui_input
311 }
312
313 pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
321 profiling::function_scope!();
322
323 self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
324
325 let screen_size_in_pixels = screen_size_in_pixels(window);
329 let screen_size_in_points =
330 screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window);
331
332 self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
333 && screen_size_in_points.y > 0.0)
334 .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
335
336 self.egui_input.viewport_id = self.viewport_id;
338
339 self.egui_input
340 .viewports
341 .entry(self.viewport_id)
342 .or_default()
343 .native_pixels_per_point = Some(window.scale_factor() as f32);
344
345 self.egui_input.take()
346 }
347
348 pub fn on_window_event(
352 &mut self,
353 window: &Window,
354 event: &winit::event::WindowEvent,
355 ) -> EventResponse {
356 profiling::function_scope!(short_window_event_description(event));
357
358 #[cfg(feature = "accesskit")]
359 if let Some(accesskit) = self.accesskit.as_mut() {
360 accesskit.process_event(window, event);
361 }
362
363 use winit::event::WindowEvent;
364
365 #[cfg(target_os = "ios")]
366 match &event {
367 WindowEvent::Resized(_)
368 | WindowEvent::ScaleFactorChanged { .. }
369 | WindowEvent::Focused(true)
370 | WindowEvent::Occluded(false) => {
371 self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
375 }
376 _ => {}
377 }
378
379 match event {
380 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
381 let native_pixels_per_point = *scale_factor as f32;
382
383 self.egui_input
384 .viewports
385 .entry(self.viewport_id)
386 .or_default()
387 .native_pixels_per_point = Some(native_pixels_per_point);
388
389 EventResponse {
390 repaint: true,
391 consumed: false,
392 }
393 }
394 WindowEvent::MouseInput { state, button, .. } => {
395 self.on_mouse_button_input(*state, *button);
396 EventResponse {
397 repaint: true,
398 consumed: self.egui_ctx.egui_wants_pointer_input(),
399 }
400 }
401 WindowEvent::MouseWheel { delta, phase, .. } => {
402 self.on_mouse_wheel(window, *delta, *phase);
403 EventResponse {
404 repaint: true,
405 consumed: self.egui_ctx.egui_wants_pointer_input(),
406 }
407 }
408 WindowEvent::CursorMoved { position, .. } => {
409 self.on_cursor_moved(window, *position);
410 EventResponse {
411 repaint: true,
412 consumed: self.egui_ctx.egui_is_using_pointer(),
413 }
414 }
415 WindowEvent::CursorLeft { .. } => {
416 self.pointer_pos_in_points = None;
417 self.egui_input.events.push(egui::Event::PointerGone);
418 EventResponse {
419 repaint: true,
420 consumed: false,
421 }
422 }
423 WindowEvent::Touch(touch) => {
425 self.on_touch(window, touch);
426 let consumed = match touch.phase {
427 winit::event::TouchPhase::Started
428 | winit::event::TouchPhase::Ended
429 | winit::event::TouchPhase::Cancelled => {
430 self.egui_ctx.egui_wants_pointer_input()
431 }
432 winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
433 };
434 EventResponse {
435 repaint: true,
436 consumed,
437 }
438 }
439
440 WindowEvent::Ime(ime) => {
441 self.on_ime(ime);
442
443 EventResponse {
444 repaint: true,
445 consumed: self.egui_ctx.egui_wants_keyboard_input(),
446 }
447 }
448 WindowEvent::KeyboardInput {
449 event,
450 is_synthetic,
451 ..
452 } => {
453 if *is_synthetic && event.state == ElementState::Pressed {
454 EventResponse {
459 repaint: true,
460 consumed: false,
461 }
462 } else {
463 let egui_wants_keyboard_input = self.egui_ctx.egui_wants_keyboard_input();
464
465 if let Some(response) =
466 self.try_on_ime_processed_keyboard_input(event, egui_wants_keyboard_input)
467 {
468 response
469 } else {
470 self.on_keyboard_input(event);
471
472 let consumed = egui_wants_keyboard_input
474 || event.logical_key
475 == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
476 EventResponse {
477 repaint: true,
478 consumed,
479 }
480 }
481 }
482 }
483 WindowEvent::Focused(focused) => {
484 let focused = if cfg!(target_os = "macos") {
485 window.has_focus()
489 } else {
490 *focused
491 };
492
493 self.egui_input.focused = focused;
494 if !focused {
495 self.modifiers = egui::Modifiers::default();
497 }
498 self.egui_input
499 .events
500 .push(egui::Event::WindowFocused(focused));
501 EventResponse {
502 repaint: true,
503 consumed: false,
504 }
505 }
506 WindowEvent::ThemeChanged(winit_theme) => {
507 self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
508 EventResponse {
509 repaint: true,
510 consumed: false,
511 }
512 }
513 WindowEvent::HoveredFile(path) => {
514 self.egui_input.hovered_files.push(egui::HoveredFile {
515 path: Some(path.clone()),
516 ..Default::default()
517 });
518 EventResponse {
519 repaint: true,
520 consumed: false,
521 }
522 }
523 WindowEvent::HoveredFileCancelled => {
524 self.egui_input.hovered_files.clear();
525 EventResponse {
526 repaint: true,
527 consumed: false,
528 }
529 }
530 #[cfg(not(target_arch = "wasm32"))]
531 WindowEvent::DroppedFile(path) => {
532 self.egui_input.hovered_files.clear();
533 self.egui_input
534 .dropped_files
535 .push(std::sync::Arc::new(NativeFile::from(path.clone())));
536 EventResponse {
537 repaint: true,
538 consumed: false,
539 }
540 }
541 #[cfg(target_arch = "wasm32")]
544 WindowEvent::DroppedFile(_) => EventResponse {
545 repaint: false,
546 consumed: false,
547 },
548 WindowEvent::ModifiersChanged(state) => {
549 let state = state.state();
550
551 let alt = state.alt_key();
552 let ctrl = state.control_key();
553 let shift = state.shift_key();
554 let super_ = state.super_key();
555
556 self.modifiers.alt = alt;
557 self.modifiers.ctrl = ctrl;
558 self.modifiers.shift = shift;
559 let is_mac = self.egui_ctx.os().is_mac();
563 self.modifiers.mac_cmd = is_mac && super_;
564 self.modifiers.command = if is_mac { super_ } else { ctrl };
565
566 self.egui_input
567 .events
568 .push(egui::Event::ModifiersChanged(self.modifiers));
569
570 EventResponse {
571 repaint: true,
572 consumed: false,
573 }
574 }
575
576 WindowEvent::RedrawRequested
578 | WindowEvent::CursorEntered { .. }
579 | WindowEvent::Destroyed
580 | WindowEvent::Occluded(_)
581 | WindowEvent::Resized(_)
582 | WindowEvent::Moved(_)
583 | WindowEvent::TouchpadPressure { .. }
584 | WindowEvent::CloseRequested => EventResponse {
585 repaint: true,
586 consumed: false,
587 },
588
589 WindowEvent::ActivationTokenDone { .. }
591 | WindowEvent::AxisMotion { .. }
592 | WindowEvent::DoubleTapGesture { .. } => EventResponse {
593 repaint: false,
594 consumed: false,
595 },
596
597 WindowEvent::PinchGesture { delta, .. } => {
598 let zoom_factor = (*delta as f32).exp();
601 self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
602 EventResponse {
603 repaint: true,
604 consumed: self.egui_ctx.egui_wants_pointer_input(),
605 }
606 }
607
608 WindowEvent::RotationGesture { delta, .. } => {
609 self.egui_input
613 .events
614 .push(egui::Event::Rotate(-delta.to_radians()));
615 EventResponse {
616 repaint: true,
617 consumed: self.egui_ctx.egui_wants_pointer_input(),
618 }
619 }
620
621 WindowEvent::PanGesture { delta, phase, .. } => {
622 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
623
624 self.egui_input.events.push(egui::Event::MouseWheel {
625 unit: egui::MouseWheelUnit::Point,
626 delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
627 phase: to_egui_touch_phase(*phase),
628 source: egui::MouseWheelSource::Trackpad,
629 modifiers: self.modifiers,
630 });
631 EventResponse {
632 repaint: true,
633 consumed: self.egui_ctx.egui_wants_pointer_input(),
634 }
635 }
636 }
637 }
638
639 #[cfg(not(target_os = "windows"))]
640 #[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
641 #[inline(always)]
642 fn try_on_ime_processed_keyboard_input(
643 &mut self,
644 _event: &winit::event::KeyEvent,
645 _egui_wants_keyboard_input: bool,
646 ) -> Option<EventResponse> {
647 None
652 }
653
654 #[cfg(target_os = "windows")]
655 #[inline(always)]
656 fn try_on_ime_processed_keyboard_input(
657 &mut self,
658 event: &winit::event::KeyEvent,
659 egui_wants_keyboard_input: bool,
660 ) -> Option<EventResponse> {
661 if !self.allow_ime {
662 None
663 } else if event.logical_key == winit::keyboard::NamedKey::Process {
664 self.pressed_processed_physical_keys
693 .insert(event.physical_key);
694
695 Some(EventResponse {
696 repaint: false,
697 consumed: egui_wants_keyboard_input,
698 })
699 } else if event.state == ElementState::Released
700 && self
701 .pressed_processed_physical_keys
702 .remove(&event.physical_key)
703 {
704 Some(EventResponse {
716 repaint: false,
717 consumed: egui_wants_keyboard_input,
718 })
719 } else {
720 None
721 }
722 }
723
724 fn on_ime(&mut self, ime: &winit::event::Ime) {
765 match ime {
783 winit::event::Ime::Enabled | winit::event::Ime::Disabled => {}
787 winit::event::Ime::Preedit(text, active_range_bytes) => {
788 let active_range_chars = match *active_range_bytes {
789 Some((start_bytes, end_bytes)) => {
790 if let (Some(start_chars), Some(middle_chars)) = (
791 text.get(..start_bytes).map(|s| s.chars().count()),
792 text.get(start_bytes..end_bytes).map(|s| s.chars().count()),
793 ) {
794 if cfg!(target_os = "windows") && start_chars == 0 && middle_chars == 0
795 {
796 None
805 } else {
806 Some(start_chars..start_chars + middle_chars)
807 }
808 } else {
809 log::warn!("ignoring {ime:?}'s range because it is invalid");
810 None
811 }
812 }
813 None => None,
814 };
815
816 self.egui_input
817 .events
818 .push(egui::Event::Ime(egui::ImeEvent::Preedit {
819 text: text.clone(),
820 active_range_chars,
821 }));
822 }
823 winit::event::Ime::Commit(text) => {
824 self.egui_input
825 .events
826 .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
827 }
828 }
829 }
830
831 pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool {
839 if !self.is_pointer_in_window() && !self.any_pointer_button_down {
840 return false;
841 }
842
843 self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
844 x: delta.0 as f32,
845 y: delta.1 as f32,
846 }));
847 true
848 }
849
850 pub fn is_pointer_in_window(&self) -> bool {
852 self.pointer_pos_in_points.is_some()
853 }
854
855 pub fn is_any_pointer_button_down(&self) -> bool {
857 self.any_pointer_button_down
858 }
859
860 #[cfg(feature = "accesskit")]
864 pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
865 self.egui_input
866 .events
867 .push(egui::Event::AccessKitActionRequest(request));
868 }
869
870 fn on_mouse_button_input(
871 &mut self,
872 state: winit::event::ElementState,
873 button: winit::event::MouseButton,
874 ) {
875 if let Some(pos) = self.pointer_pos_in_points
876 && let Some(button) = translate_mouse_button(button)
877 {
878 let pressed = state == winit::event::ElementState::Pressed;
879
880 self.egui_input.events.push(egui::Event::PointerButton {
881 pos,
882 button,
883 pressed,
884 modifiers: self.modifiers,
885 });
886
887 if pressed
889 && button == egui::PointerButton::Middle
890 && let Some(text) = self.clipboard.get_primary_text()
891 && !text.is_empty()
892 {
893 self.egui_input
894 .events
895 .push(egui::Event::MiddleClickPaste { pos, text });
896 }
897
898 if self.simulate_touch_screen {
899 if pressed {
900 self.any_pointer_button_down = true;
901
902 self.egui_input.events.push(egui::Event::Touch {
903 device_id: egui::TouchDeviceId(0),
904 id: egui::TouchId(0),
905 phase: egui::TouchPhase::Start,
906 pos,
907 force: None,
908 });
909 } else {
910 self.any_pointer_button_down = false;
911
912 self.egui_input.events.push(egui::Event::PointerGone);
913
914 self.egui_input.events.push(egui::Event::Touch {
915 device_id: egui::TouchDeviceId(0),
916 id: egui::TouchId(0),
917 phase: egui::TouchPhase::End,
918 pos,
919 force: None,
920 });
921 }
922 }
923 }
924 }
925
926 fn on_cursor_moved(
927 &mut self,
928 window: &Window,
929 pos_in_pixels: winit::dpi::PhysicalPosition<f64>,
930 ) {
931 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
932
933 let pos_in_points = egui::pos2(
934 pos_in_pixels.x as f32 / pixels_per_point,
935 pos_in_pixels.y as f32 / pixels_per_point,
936 );
937 self.pointer_pos_in_points = Some(pos_in_points);
938
939 if self.simulate_touch_screen {
940 if self.any_pointer_button_down {
941 self.egui_input
942 .events
943 .push(egui::Event::PointerMoved(pos_in_points));
944
945 self.egui_input.events.push(egui::Event::Touch {
946 device_id: egui::TouchDeviceId(0),
947 id: egui::TouchId(0),
948 phase: egui::TouchPhase::Move,
949 pos: pos_in_points,
950 force: None,
951 });
952 }
953 } else {
954 self.egui_input
955 .events
956 .push(egui::Event::PointerMoved(pos_in_points));
957 }
958 }
959
960 fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) {
961 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
962
963 self.egui_input.events.push(egui::Event::Touch {
965 device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
966 id: egui::TouchId::from(touch.id),
967 phase: to_egui_touch_phase(touch.phase),
968 pos: egui::pos2(
969 touch.location.x as f32 / pixels_per_point,
970 touch.location.y as f32 / pixels_per_point,
971 ),
972 force: match touch.force {
973 Some(winit::event::Force::Normalized(force)) => Some(force as f32),
974 Some(winit::event::Force::Calibrated {
975 force,
976 max_possible_force,
977 ..
978 }) => Some((force / max_possible_force) as f32),
979 None => None,
980 },
981 });
982 if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
985 {
986 match touch.phase {
988 winit::event::TouchPhase::Started => {
989 self.pointer_touch_id = Some(touch.id);
990 self.on_cursor_moved(window, touch.location);
992 self.on_mouse_button_input(
993 winit::event::ElementState::Pressed,
994 winit::event::MouseButton::Left,
995 );
996 }
997 winit::event::TouchPhase::Moved => {
998 self.on_cursor_moved(window, touch.location);
999 }
1000 winit::event::TouchPhase::Ended => {
1001 self.pointer_touch_id = None;
1002 self.on_mouse_button_input(
1003 winit::event::ElementState::Released,
1004 winit::event::MouseButton::Left,
1005 );
1006 self.pointer_pos_in_points = None;
1009 self.egui_input.events.push(egui::Event::PointerGone);
1010 }
1011 winit::event::TouchPhase::Cancelled => {
1012 self.pointer_touch_id = None;
1013 self.pointer_pos_in_points = None;
1014 self.egui_input.events.push(egui::Event::PointerGone);
1015 }
1016 }
1017 }
1018 }
1019
1020 fn on_mouse_wheel(
1021 &mut self,
1022 window: &Window,
1023 delta: winit::event::MouseScrollDelta,
1024 phase: winit::event::TouchPhase,
1025 ) {
1026 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
1027
1028 {
1029 let (unit, delta) = match delta {
1030 winit::event::MouseScrollDelta::LineDelta(x, y) => {
1031 (egui::MouseWheelUnit::Line, egui::vec2(x, y))
1032 }
1033 winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
1034 x,
1035 y,
1036 }) => (
1037 egui::MouseWheelUnit::Point,
1038 egui::vec2(x as f32, y as f32) / pixels_per_point,
1039 ),
1040 };
1041 let phase = to_egui_touch_phase(phase);
1042 let modifiers = self.modifiers;
1043
1044 let source = cfg_select! {
1049 target_os = "macos" => {
1050 if self
1051 .scroll_momentum_monitor
1052 .as_ref()
1053 .is_some_and(|monitor| monitor.latest_scroll_event_is_momentum())
1054 {
1055 egui::MouseWheelSource::Momentum
1056 } else {
1057 match unit {
1059 egui::MouseWheelUnit::Point => egui::MouseWheelSource::Trackpad,
1060 egui::MouseWheelUnit::Line | egui::MouseWheelUnit::Page => {
1061 egui::MouseWheelSource::Wheel
1062 }
1063 }
1064 }
1065 }
1066 _ => egui::MouseWheelSource::Unknown,
1067 };
1068
1069 self.egui_input.events.push(egui::Event::MouseWheel {
1070 unit,
1071 delta,
1072 phase,
1073 source,
1074 modifiers,
1075 });
1076 }
1077 }
1078
1079 fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
1080 let winit::event::KeyEvent {
1081 physical_key,
1089
1090 logical_key: winit_logical_key,
1095
1096 text,
1097
1098 state,
1099
1100 location: _, repeat: _, ..
1103 } = event;
1104
1105 let pressed = *state == winit::event::ElementState::Pressed;
1106
1107 let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
1108 key_from_key_code(keycode)
1109 } else {
1110 None
1111 };
1112
1113 let logical_key = key_from_winit_key(winit_logical_key);
1114
1115 log::trace!(
1117 "logical {:?} -> {:?}, physical {:?} -> {:?}",
1118 event.logical_key,
1119 logical_key,
1120 event.physical_key,
1121 physical_key
1122 );
1123
1124 if let Some(active_key) = logical_key.or(physical_key) {
1129 if pressed && self.clipboard_shortcuts {
1130 if is_cut_command(self.modifiers, active_key) {
1131 self.egui_input.events.push(egui::Event::Cut);
1132 return;
1133 } else if is_copy_command(self.modifiers, active_key) {
1134 self.egui_input.events.push(egui::Event::Copy);
1135 return;
1136 } else if is_paste_command(self.modifiers, active_key) {
1137 if let Some(contents) = self.clipboard.get() {
1138 let contents = contents.replace("\r\n", "\n");
1139 if !contents.is_empty() {
1140 self.egui_input.events.push(egui::Event::Paste(contents));
1141 }
1142 } else if let Some(image) = self.clipboard.get_image() {
1143 self.egui_input
1148 .events
1149 .push(egui::Event::PasteImage(std::sync::Arc::new(image)));
1150 }
1151 return;
1152 }
1153 }
1154
1155 self.egui_input.events.push(egui::Event::Key {
1156 key: active_key,
1157 physical_key,
1158 pressed,
1159 repeat: false, modifiers: self.modifiers,
1161 });
1162 }
1163
1164 if let Some(text) = text
1165 .as_ref()
1166 .map(|t| t.as_str())
1167 .or_else(|| winit_logical_key.to_text())
1168 {
1169 if !text.is_empty() && text.chars().all(is_printable_char) {
1172 let is_cmd =
1177 self.modifiers.ctrl || self.modifiers.command || self.modifiers.mac_cmd;
1178 if pressed && !is_cmd {
1179 self.egui_input
1180 .events
1181 .push(egui::Event::Text(text.to_owned()));
1182 }
1183 }
1184 }
1185 }
1186
1187 pub fn handle_platform_output(
1196 &mut self,
1197 window: &Window,
1198 platform_output: egui::PlatformOutput,
1199 ) {
1200 self.handle_platform_output_inner(window, None, platform_output);
1201 }
1202
1203 pub fn handle_platform_output_with_event_loop(
1211 &mut self,
1212 window: &Window,
1213 event_loop: &ActiveEventLoop,
1214 platform_output: egui::PlatformOutput,
1215 ) {
1216 self.handle_platform_output_inner(window, Some(event_loop), platform_output);
1217 }
1218
1219 fn handle_platform_output_inner(
1220 &mut self,
1221 window: &Window,
1222 event_loop: Option<&ActiveEventLoop>,
1223 platform_output: egui::PlatformOutput,
1224 ) {
1225 profiling::function_scope!();
1226
1227 let egui::PlatformOutput {
1228 commands,
1229 cursor_icon,
1230 cursor_image,
1231 events: _, mutable_text_under_cursor: _, ime,
1234 accesskit_update,
1235 num_completed_passes: _, request_discard_reasons: _, } = platform_output;
1238
1239 for command in commands {
1240 match command {
1241 egui::OutputCommand::CopyText(text) => {
1242 self.clipboard.set_text(text);
1243 }
1244 egui::OutputCommand::TextSelectionSettled(text) => {
1245 self.clipboard.set_primary_text(text);
1246 }
1247 egui::OutputCommand::CopyImage(image) => {
1248 self.clipboard.set_image(&image);
1249 }
1250 egui::OutputCommand::OpenUrl(open_url) => {
1251 open_url_in_browser(&open_url.url);
1252 }
1253 }
1254 }
1255
1256 self.apply_cursor(window, event_loop, cursor_icon, cursor_image.as_ref());
1257
1258 let allow_ime = ime.is_some();
1259 let is_toggling_ime = self.allow_ime != allow_ime;
1260 if is_toggling_ime {
1261 self.allow_ime = allow_ime;
1262 #[cfg(target_os = "windows")]
1263 if !self.allow_ime {
1264 self.pressed_processed_physical_keys.clear();
1270 }
1271
1272 profiling::scope!("set_ime_allowed");
1273 window.set_ime_allowed(allow_ime);
1274 }
1275
1276 if let Some(ime) = ime {
1277 if !is_toggling_ime && ime.should_interrupt_composition {
1278 window.set_ime_allowed(false);
1282 window.set_ime_allowed(true);
1283 }
1284
1285 if ime.purpose != self.old_ime_purpose {
1286 self.old_ime_purpose = ime.purpose;
1287 window.set_ime_purpose(to_winit_ime_purpose(ime.purpose));
1288 }
1289
1290 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
1291 let ime_cursor_rect_px = pixels_per_point * ime.cursor_rect;
1292 if self.ime_cursor_rect_px != Some(ime_cursor_rect_px)
1293 || self.egui_ctx.input(|i| !i.events.is_empty())
1294 {
1295 self.ime_cursor_rect_px = Some(ime_cursor_rect_px);
1296 profiling::scope!("set_ime_cursor_area");
1297 window.set_ime_cursor_area(
1298 winit::dpi::PhysicalPosition {
1299 x: ime_cursor_rect_px.min.x,
1300 y: ime_cursor_rect_px.min.y,
1301 },
1302 winit::dpi::PhysicalSize {
1303 width: ime_cursor_rect_px.width(),
1304 height: ime_cursor_rect_px.height(),
1305 },
1306 );
1307 }
1308 } else {
1309 self.ime_cursor_rect_px = None;
1310 }
1311
1312 #[cfg(feature = "accesskit")]
1313 if let Some(accesskit) = self.accesskit.as_mut()
1314 && let Some(update) = accesskit_update
1315 {
1316 profiling::scope!("accesskit");
1317 accesskit.update_if_active(|| update);
1318 }
1319
1320 #[cfg(not(feature = "accesskit"))]
1321 let _ = accesskit_update;
1322 }
1323
1324 fn apply_cursor(
1329 &mut self,
1330 window: &Window,
1331 event_loop: Option<&ActiveEventLoop>,
1332 cursor_icon: egui::CursorIcon,
1333 cursor_image: Option<&egui::CustomCursorImage>,
1334 ) {
1335 let is_pointer_in_window = self.pointer_pos_in_points.is_some();
1336 if !is_pointer_in_window {
1337 self.current_cursor_icon = None;
1341 self.current_custom_cursor = None;
1342 return;
1343 }
1344
1345 if let (Some(image), Some(event_loop)) = (cursor_image, event_loop) {
1350 let key = std::sync::Arc::as_ptr(&image.rgba).cast::<u8>() as usize;
1351 let cached = self
1352 .current_custom_cursor
1353 .as_ref()
1354 .filter(|(k, _)| *k == key)
1355 .map(|(_, c)| c.clone());
1356
1357 let custom = match cached {
1358 Some(c) => c,
1359 None => match winit::window::CustomCursor::from_rgba(
1360 image.rgba.to_vec(),
1361 image.size[0],
1362 image.size[1],
1363 image.hotspot[0],
1364 image.hotspot[1],
1365 ) {
1366 Ok(source) => {
1367 let c = event_loop.create_custom_cursor(source);
1368 self.current_custom_cursor = Some((key, c.clone()));
1369 c
1370 }
1371 Err(err) => {
1372 log::warn!(
1373 "egui-winit: invalid cursor bitmap, falling back to cursor_icon: {err:?}"
1374 );
1375 self.current_custom_cursor = None;
1376 self.set_cursor_icon_inner(window, cursor_icon);
1377 return;
1378 }
1379 },
1380 };
1381
1382 window.set_cursor_visible(true);
1383 window.set_cursor(custom);
1384 self.current_cursor_icon = None;
1387 return;
1388 }
1389
1390 self.current_custom_cursor = None;
1391 self.set_cursor_icon_inner(window, cursor_icon);
1392 }
1393
1394 fn set_cursor_icon_inner(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
1397 if self.current_cursor_icon == Some(cursor_icon) {
1398 return;
1401 }
1402
1403 self.current_cursor_icon = Some(cursor_icon);
1404
1405 if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
1406 window.set_cursor_visible(true);
1407 window.set_cursor(winit_cursor_icon);
1408 } else {
1409 window.set_cursor_visible(false);
1410 }
1411 }
1412}
1413
1414fn to_egui_touch_phase(phase: winit::event::TouchPhase) -> egui::TouchPhase {
1415 match phase {
1416 winit::event::TouchPhase::Started => egui::TouchPhase::Start,
1417 winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
1418 winit::event::TouchPhase::Ended => egui::TouchPhase::End,
1419 winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
1420 }
1421}
1422
1423fn to_egui_theme(theme: winit::window::Theme) -> Theme {
1424 match theme {
1425 winit::window::Theme::Dark => Theme::Dark,
1426 winit::window::Theme::Light => Theme::Light,
1427 }
1428}
1429
1430pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1431 let inner_pos_px = window.inner_position().ok()?;
1432 let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
1433
1434 let inner_size_px = window.inner_size();
1435 let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);
1436
1437 let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);
1438
1439 Some(inner_rect_px / pixels_per_point)
1440}
1441
1442pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1443 let outer_pos_px = window.outer_position().ok()?;
1444 let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);
1445
1446 let outer_size_px = window.outer_size();
1447 let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);
1448
1449 let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);
1450
1451 Some(outer_rect_px / pixels_per_point)
1452}
1453
1454pub fn update_viewport_info(
1460 viewport_info: &mut ViewportInfo,
1461 egui_ctx: &egui::Context,
1462 window: &Window,
1463 is_init: bool,
1464) {
1465 profiling::function_scope!();
1466 let pixels_per_point = pixels_per_point(egui_ctx, window);
1467
1468 let has_a_position = match window.is_minimized() {
1469 Some(true) => false,
1470 Some(false) | None => true,
1471 };
1472
1473 let inner_rect = if has_a_position {
1474 inner_rect_in_points(window, pixels_per_point)
1475 } else {
1476 None
1477 };
1478
1479 let outer_rect = if has_a_position {
1480 outer_rect_in_points(window, pixels_per_point)
1481 } else {
1482 None
1483 };
1484
1485 let monitor_size = {
1486 profiling::scope!("monitor_size");
1487 if let Some(monitor) = window.current_monitor() {
1488 let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
1489 Some(egui::vec2(size.width, size.height))
1490 } else {
1491 None
1492 }
1493 };
1494
1495 viewport_info.title = Some(window.title());
1496 viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
1497
1498 viewport_info.monitor_size = monitor_size;
1499 viewport_info.inner_rect = inner_rect;
1500 viewport_info.outer_rect = outer_rect;
1501
1502 if is_init || !cfg!(target_os = "macos") {
1503 viewport_info.maximized = Some(window.is_maximized());
1507 viewport_info.minimized = Some(window.is_minimized().unwrap_or(false));
1508 }
1509
1510 viewport_info.fullscreen = Some(window.fullscreen().is_some());
1511 viewport_info.focused = Some(window.has_focus());
1512}
1513
1514fn open_url_in_browser(_url: &str) {
1515 #[cfg(feature = "webbrowser")]
1516 if let Err(err) = webbrowser::open(_url) {
1517 log::warn!("Failed to open url: {err}");
1518 }
1519
1520 #[cfg(not(feature = "webbrowser"))]
1521 {
1522 log::warn!("Cannot open url - feature \"links\" not enabled.");
1523 }
1524}
1525
1526fn is_printable_char(chr: char) -> bool {
1531 let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
1532 || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
1533 || '\u{100000}' <= chr && chr <= '\u{10fffd}';
1534
1535 !is_in_private_use_area && !chr.is_ascii_control()
1536}
1537
1538fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1539 keycode == egui::Key::Cut
1540 || (modifiers.command_only() && keycode == egui::Key::X)
1541 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
1542}
1543
1544fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1545 keycode == egui::Key::Copy
1546 || (modifiers.command_only() && keycode == egui::Key::C)
1547 || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
1548}
1549
1550fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1551 keycode == egui::Key::Paste
1552 || (modifiers.command_only() && keycode == egui::Key::V)
1553 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
1554}
1555
1556fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
1557 match button {
1558 winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
1559 winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
1560 winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
1561 winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
1562 winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
1563 winit::event::MouseButton::Other(_) => None,
1564 }
1565}
1566
1567fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
1568 match key {
1569 winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
1570 winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
1571 winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
1572 }
1573}
1574
1575fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
1576 use egui::Key;
1577 use winit::keyboard::NamedKey;
1578
1579 Some(match named_key {
1580 NamedKey::Enter => Key::Enter,
1581 NamedKey::Tab => Key::Tab,
1582 NamedKey::ArrowDown => Key::ArrowDown,
1583 NamedKey::ArrowLeft => Key::ArrowLeft,
1584 NamedKey::ArrowRight => Key::ArrowRight,
1585 NamedKey::ArrowUp => Key::ArrowUp,
1586 NamedKey::End => Key::End,
1587 NamedKey::Home => Key::Home,
1588 NamedKey::PageDown => Key::PageDown,
1589 NamedKey::PageUp => Key::PageUp,
1590 NamedKey::Backspace => Key::Backspace,
1591 NamedKey::Delete => Key::Delete,
1592 NamedKey::Insert => Key::Insert,
1593 NamedKey::Escape => Key::Escape,
1594 NamedKey::Cut => Key::Cut,
1595 NamedKey::Copy => Key::Copy,
1596 NamedKey::Paste => Key::Paste,
1597
1598 NamedKey::Space => Key::Space,
1599
1600 NamedKey::F1 => Key::F1,
1601 NamedKey::F2 => Key::F2,
1602 NamedKey::F3 => Key::F3,
1603 NamedKey::F4 => Key::F4,
1604 NamedKey::F5 => Key::F5,
1605 NamedKey::F6 => Key::F6,
1606 NamedKey::F7 => Key::F7,
1607 NamedKey::F8 => Key::F8,
1608 NamedKey::F9 => Key::F9,
1609 NamedKey::F10 => Key::F10,
1610 NamedKey::F11 => Key::F11,
1611 NamedKey::F12 => Key::F12,
1612 NamedKey::F13 => Key::F13,
1613 NamedKey::F14 => Key::F14,
1614 NamedKey::F15 => Key::F15,
1615 NamedKey::F16 => Key::F16,
1616 NamedKey::F17 => Key::F17,
1617 NamedKey::F18 => Key::F18,
1618 NamedKey::F19 => Key::F19,
1619 NamedKey::F20 => Key::F20,
1620 NamedKey::F21 => Key::F21,
1621 NamedKey::F22 => Key::F22,
1622 NamedKey::F23 => Key::F23,
1623 NamedKey::F24 => Key::F24,
1624 NamedKey::F25 => Key::F25,
1625 NamedKey::F26 => Key::F26,
1626 NamedKey::F27 => Key::F27,
1627 NamedKey::F28 => Key::F28,
1628 NamedKey::F29 => Key::F29,
1629 NamedKey::F30 => Key::F30,
1630 NamedKey::F31 => Key::F31,
1631 NamedKey::F32 => Key::F32,
1632 NamedKey::F33 => Key::F33,
1633 NamedKey::F34 => Key::F34,
1634 NamedKey::F35 => Key::F35,
1635
1636 NamedKey::BrowserBack => Key::BrowserBack,
1637 NamedKey::BrowserForward => Key::BrowserForward,
1638 NamedKey::BrowserRefresh => Key::BrowserRefresh,
1639 NamedKey::BrowserSearch => Key::BrowserSearch,
1640 NamedKey::BrowserHome => Key::BrowserHome,
1641 NamedKey::BrowserFavorites => Key::BrowserFavorites,
1642 NamedKey::BrowserStop => Key::BrowserStop,
1643
1644 NamedKey::MediaPlayPause => Key::MediaPlayPause,
1645 NamedKey::MediaTrackNext => Key::MediaTrackNext,
1646 NamedKey::MediaTrackPrevious => Key::MediaTrackPrevious,
1647 NamedKey::MediaStop => Key::MediaStop,
1648 NamedKey::AudioVolumeMute => Key::AudioVolumeMute,
1649 NamedKey::AudioVolumeDown => Key::AudioVolumeDown,
1650 NamedKey::AudioVolumeUp => Key::AudioVolumeUp,
1651
1652 NamedKey::LaunchMail => Key::LaunchMail,
1653 NamedKey::LaunchApplication1 => Key::LaunchApp1,
1654 NamedKey::LaunchApplication2 => Key::LaunchApp2,
1655
1656 NamedKey::CapsLock => Key::CapsLock,
1657 NamedKey::NumLock => Key::NumLock,
1658 NamedKey::ScrollLock => Key::ScrollLock,
1659 NamedKey::PrintScreen => Key::PrintScreen,
1660 NamedKey::Pause => Key::Pause,
1661 NamedKey::ContextMenu => Key::Menu,
1662
1663 NamedKey::Fn => Key::Fn,
1664 NamedKey::Eject => Key::Eject,
1665 NamedKey::Help => Key::Help,
1666 NamedKey::Power => Key::Power,
1667 NamedKey::Standby => Key::Sleep,
1668 NamedKey::Clear => Key::Clear,
1669
1670 _ => {
1671 log::trace!("Unknown key: {named_key:?}");
1672 return None;
1673 }
1674 })
1675}
1676
1677fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
1678 use egui::Key;
1679 use winit::keyboard::KeyCode;
1680
1681 Some(match key {
1682 KeyCode::ArrowDown => Key::ArrowDown,
1683 KeyCode::ArrowLeft => Key::ArrowLeft,
1684 KeyCode::ArrowRight => Key::ArrowRight,
1685 KeyCode::ArrowUp => Key::ArrowUp,
1686
1687 KeyCode::Escape => Key::Escape,
1688 KeyCode::Tab => Key::Tab,
1689 KeyCode::Backspace => Key::Backspace,
1690 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
1691
1692 KeyCode::Insert => Key::Insert,
1693 KeyCode::Delete => Key::Delete,
1694 KeyCode::Home => Key::Home,
1695 KeyCode::End => Key::End,
1696 KeyCode::PageUp => Key::PageUp,
1697 KeyCode::PageDown => Key::PageDown,
1698
1699 KeyCode::Space => Key::Space,
1701 KeyCode::Comma => Key::Comma,
1702 KeyCode::Period => Key::Period,
1703 KeyCode::Semicolon => Key::Semicolon,
1705 KeyCode::Backslash => Key::Backslash,
1706 KeyCode::Slash | KeyCode::NumpadDivide => Key::Slash,
1707 KeyCode::BracketLeft => Key::OpenBracket,
1708 KeyCode::BracketRight => Key::CloseBracket,
1709 KeyCode::Backquote => Key::Backtick,
1710 KeyCode::Quote => Key::Quote,
1711
1712 KeyCode::Cut => Key::Cut,
1713 KeyCode::Copy => Key::Copy,
1714 KeyCode::Paste => Key::Paste,
1715 KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
1716 KeyCode::NumpadAdd => Key::Plus,
1717 KeyCode::Equal => Key::Equals,
1718
1719 KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
1720 KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
1721 KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
1722 KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
1723 KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
1724 KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
1725 KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
1726 KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
1727 KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
1728 KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
1729
1730 KeyCode::KeyA => Key::A,
1731 KeyCode::KeyB => Key::B,
1732 KeyCode::KeyC => Key::C,
1733 KeyCode::KeyD => Key::D,
1734 KeyCode::KeyE => Key::E,
1735 KeyCode::KeyF => Key::F,
1736 KeyCode::KeyG => Key::G,
1737 KeyCode::KeyH => Key::H,
1738 KeyCode::KeyI => Key::I,
1739 KeyCode::KeyJ => Key::J,
1740 KeyCode::KeyK => Key::K,
1741 KeyCode::KeyL => Key::L,
1742 KeyCode::KeyM => Key::M,
1743 KeyCode::KeyN => Key::N,
1744 KeyCode::KeyO => Key::O,
1745 KeyCode::KeyP => Key::P,
1746 KeyCode::KeyQ => Key::Q,
1747 KeyCode::KeyR => Key::R,
1748 KeyCode::KeyS => Key::S,
1749 KeyCode::KeyT => Key::T,
1750 KeyCode::KeyU => Key::U,
1751 KeyCode::KeyV => Key::V,
1752 KeyCode::KeyW => Key::W,
1753 KeyCode::KeyX => Key::X,
1754 KeyCode::KeyY => Key::Y,
1755 KeyCode::KeyZ => Key::Z,
1756
1757 KeyCode::F1 => Key::F1,
1758 KeyCode::F2 => Key::F2,
1759 KeyCode::F3 => Key::F3,
1760 KeyCode::F4 => Key::F4,
1761 KeyCode::F5 => Key::F5,
1762 KeyCode::F6 => Key::F6,
1763 KeyCode::F7 => Key::F7,
1764 KeyCode::F8 => Key::F8,
1765 KeyCode::F9 => Key::F9,
1766 KeyCode::F10 => Key::F10,
1767 KeyCode::F11 => Key::F11,
1768 KeyCode::F12 => Key::F12,
1769 KeyCode::F13 => Key::F13,
1770 KeyCode::F14 => Key::F14,
1771 KeyCode::F15 => Key::F15,
1772 KeyCode::F16 => Key::F16,
1773 KeyCode::F17 => Key::F17,
1774 KeyCode::F18 => Key::F18,
1775 KeyCode::F19 => Key::F19,
1776 KeyCode::F20 => Key::F20,
1777 KeyCode::F21 => Key::F21,
1778 KeyCode::F22 => Key::F22,
1779 KeyCode::F23 => Key::F23,
1780 KeyCode::F24 => Key::F24,
1781 KeyCode::F25 => Key::F25,
1782 KeyCode::F26 => Key::F26,
1783 KeyCode::F27 => Key::F27,
1784 KeyCode::F28 => Key::F28,
1785 KeyCode::F29 => Key::F29,
1786 KeyCode::F30 => Key::F30,
1787 KeyCode::F31 => Key::F31,
1788 KeyCode::F32 => Key::F32,
1789 KeyCode::F33 => Key::F33,
1790 KeyCode::F34 => Key::F34,
1791 KeyCode::F35 => Key::F35,
1792
1793 KeyCode::ShiftLeft => Key::ShiftLeft,
1798 KeyCode::ShiftRight => Key::ShiftRight,
1799 KeyCode::ControlLeft => Key::ControlLeft,
1800 KeyCode::ControlRight => Key::ControlRight,
1801 KeyCode::AltLeft => Key::AltLeft,
1802 KeyCode::AltRight => Key::AltRight,
1803 KeyCode::SuperLeft => Key::SuperLeft,
1804 KeyCode::SuperRight => Key::SuperRight,
1805
1806 KeyCode::IntlBackslash => Key::IntlBackslash,
1808
1809 KeyCode::CapsLock => Key::CapsLock,
1811 KeyCode::NumLock => Key::NumLock,
1812 KeyCode::ScrollLock => Key::ScrollLock,
1813 KeyCode::PrintScreen => Key::PrintScreen,
1814 KeyCode::Pause => Key::Pause,
1815 KeyCode::ContextMenu => Key::Menu,
1816
1817 KeyCode::BrowserBack => Key::BrowserBack,
1819 KeyCode::BrowserForward => Key::BrowserForward,
1820 KeyCode::BrowserRefresh => Key::BrowserRefresh,
1821 KeyCode::BrowserSearch => Key::BrowserSearch,
1822 KeyCode::BrowserHome => Key::BrowserHome,
1823 KeyCode::BrowserFavorites => Key::BrowserFavorites,
1824 KeyCode::BrowserStop => Key::BrowserStop,
1825
1826 KeyCode::MediaPlayPause => Key::MediaPlayPause,
1828 KeyCode::MediaTrackNext => Key::MediaTrackNext,
1829 KeyCode::MediaTrackPrevious => Key::MediaTrackPrevious,
1830 KeyCode::MediaStop => Key::MediaStop,
1831 KeyCode::AudioVolumeMute => Key::AudioVolumeMute,
1832 KeyCode::AudioVolumeDown => Key::AudioVolumeDown,
1833 KeyCode::AudioVolumeUp => Key::AudioVolumeUp,
1834
1835 KeyCode::LaunchMail => Key::LaunchMail,
1837 KeyCode::LaunchApp1 => Key::LaunchApp1,
1838 KeyCode::LaunchApp2 => Key::LaunchApp2,
1839
1840 KeyCode::Fn => Key::Fn,
1842 KeyCode::Eject => Key::Eject,
1843 KeyCode::Help => Key::Help,
1844 KeyCode::Power => Key::Power,
1845 KeyCode::Sleep => Key::Sleep,
1846
1847 _ => {
1848 return None;
1849 }
1850 })
1851}
1852
1853fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
1854 match cursor_icon {
1855 egui::CursorIcon::None => None,
1856
1857 egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
1858 egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
1859 egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
1860 egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
1861 egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
1862 egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1863 egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
1864 egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
1865 egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1866 egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
1867 egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
1868 egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1869 egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1870 egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
1871 egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
1872
1873 egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
1874 egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
1875 egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
1876 egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
1877
1878 egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
1879 egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
1880 egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
1881 egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
1882 egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
1883 egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
1884 egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
1885 egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
1886 egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
1887 egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
1888
1889 egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
1890 egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
1891 egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
1892 egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
1893 egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
1894 }
1895}
1896
1897#[derive(Debug)]
1900pub enum ActionRequested {
1901 Screenshot(egui::ScreenshotCallback),
1902 Cut,
1903 Copy,
1904 Paste,
1905
1906 PaintWhileHidden,
1915}
1916
1917impl ActionRequested {
1918 pub fn wants_paint(&self) -> bool {
1923 matches!(self, Self::Screenshot(_) | Self::PaintWhileHidden)
1924 }
1925}
1926
1927pub fn process_viewport_commands(
1928 egui_ctx: &egui::Context,
1929 info: &mut ViewportInfo,
1930 commands: impl IntoIterator<Item = ViewportCommand>,
1931 window: &Window,
1932 actions_requested: &mut Vec<ActionRequested>,
1933) {
1934 for command in commands {
1935 process_viewport_command(egui_ctx, window, command, info, actions_requested);
1936 }
1937}
1938
1939fn process_viewport_command(
1940 egui_ctx: &egui::Context,
1941 window: &Window,
1942 command: ViewportCommand,
1943 info: &mut ViewportInfo,
1944 actions_requested: &mut Vec<ActionRequested>,
1945) {
1946 profiling::function_scope!(&format!("{command:?}"));
1947
1948 use winit::window::ResizeDirection;
1949
1950 log::trace!("Processing ViewportCommand::{command:?}");
1951
1952 let pixels_per_point = pixels_per_point(egui_ctx, window);
1953
1954 match command {
1955 ViewportCommand::Close => {
1956 info.events.push(egui::ViewportEvent::Close);
1957 }
1958 ViewportCommand::CancelClose => {
1959 }
1961 ViewportCommand::StartDrag => {
1962 if window.has_focus()
1964 && let Err(err) = window.drag_window()
1965 {
1966 log::warn!("{command:?}: {err}");
1967 }
1968 }
1969 ViewportCommand::InnerSize(size) => {
1970 let width_px = pixels_per_point * size.x.max(1.0);
1971 let height_px = pixels_per_point * size.y.max(1.0);
1972 let requested_size = PhysicalSize::new(width_px, height_px);
1973 if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {
1974 info.inner_rect = inner_rect_in_points(window, pixels_per_point);
1988 info.outer_rect = outer_rect_in_points(window, pixels_per_point);
1989 } else {
1990 }
1994 }
1995 ViewportCommand::BeginResize(direction) => {
1996 if let Err(err) = window.drag_resize_window(match direction {
1997 egui::viewport::ResizeDirection::North => ResizeDirection::North,
1998 egui::viewport::ResizeDirection::South => ResizeDirection::South,
1999 egui::viewport::ResizeDirection::East => ResizeDirection::East,
2000 egui::viewport::ResizeDirection::West => ResizeDirection::West,
2001 egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
2002 egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
2003 egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
2004 egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
2005 }) {
2006 log::warn!("{command:?}: {err}");
2007 }
2008 }
2009 ViewportCommand::Title(title) => {
2010 window.set_title(&title);
2011 }
2012 ViewportCommand::Transparent(v) => window.set_transparent(v),
2013 ViewportCommand::Visible(v) => window.set_visible(v),
2014 ViewportCommand::OuterPosition(pos) => {
2015 window.set_outer_position(PhysicalPosition::new(
2016 pixels_per_point * pos.x,
2017 pixels_per_point * pos.y,
2018 ));
2019 }
2020 ViewportCommand::MinInnerSize(s) => {
2021 window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
2022 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
2023 ));
2024 }
2025 ViewportCommand::MaxInnerSize(s) => {
2026 window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
2027 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
2028 ));
2029 }
2030 ViewportCommand::ResizeIncrements(s) => {
2031 window.set_resize_increments(
2032 s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
2033 );
2034 }
2035 ViewportCommand::Resizable(v) => window.set_resizable(v),
2036 ViewportCommand::EnableButtons {
2037 close,
2038 minimized,
2039 maximize,
2040 } => window.set_enabled_buttons(
2041 if close {
2042 WindowButtons::CLOSE
2043 } else {
2044 WindowButtons::empty()
2045 } | if minimized {
2046 WindowButtons::MINIMIZE
2047 } else {
2048 WindowButtons::empty()
2049 } | if maximize {
2050 WindowButtons::MAXIMIZE
2051 } else {
2052 WindowButtons::empty()
2053 },
2054 ),
2055 ViewportCommand::Minimized(v) => {
2056 window.set_minimized(v);
2057 info.minimized = Some(v);
2058 }
2059 ViewportCommand::Maximized(v) => {
2060 window.set_maximized(v);
2061 info.maximized = Some(v);
2062 }
2063 ViewportCommand::Fullscreen(v) => {
2064 window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
2065 }
2066 ViewportCommand::SetMonitor(idx) => {
2067 if let Some(monitor) = window.available_monitors().nth(idx) {
2068 window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor))));
2069 } else {
2070 log::warn!(
2071 "ViewportCommand::SetMonitor({idx}): index out of range ({} monitors available)",
2072 window.available_monitors().count()
2073 );
2074 }
2075 }
2076 ViewportCommand::Decorations(v) => {
2077 window.set_decorations(v);
2078 #[cfg(target_os = "windows")]
2079 {
2080 use winit::platform::windows::WindowExtWindows as _;
2081
2082 window.set_undecorated_shadow(!v && window.fullscreen().is_none());
2084 }
2085 }
2086 ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
2087 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
2088 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
2089 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
2090 }),
2091 ViewportCommand::Icon(icon) => {
2092 let winit_icon = icon.and_then(|icon| to_winit_icon(&icon));
2093 window.set_window_icon(winit_icon);
2094 }
2095 ViewportCommand::IMERect(rect) => {
2096 window.set_ime_cursor_area(
2097 PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
2098 PhysicalSize::new(
2099 pixels_per_point * rect.size().x,
2100 pixels_per_point * rect.size().y,
2101 ),
2102 );
2103 }
2104 ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
2105 ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(to_winit_ime_purpose(p)),
2106 ViewportCommand::Focus => {
2107 if !window.has_focus() {
2108 window.focus_window();
2109 }
2110 }
2111 ViewportCommand::RequestUserAttention(a) => {
2112 window.request_user_attention(match a {
2113 egui::UserAttentionType::Reset => None,
2114 egui::UserAttentionType::Critical => {
2115 Some(winit::window::UserAttentionType::Critical)
2116 }
2117 egui::UserAttentionType::Informational => {
2118 Some(winit::window::UserAttentionType::Informational)
2119 }
2120 });
2121 }
2122 ViewportCommand::SetTheme(t) => {
2123 window.set_theme(match t {
2124 egui::SystemTheme::Light => Some(winit::window::Theme::Light),
2125 egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
2126 egui::SystemTheme::SystemDefault => None,
2127 });
2128
2129 #[cfg(target_os = "windows")]
2130 {
2131 refresh_windows_non_client_activation(window);
2132 }
2133 }
2134 ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
2135 ViewportCommand::CursorPosition(pos) => {
2136 if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
2137 pixels_per_point * pos.x,
2138 pixels_per_point * pos.y,
2139 )) {
2140 log::warn!("{command:?}: {err}");
2141 }
2142 }
2143 ViewportCommand::CursorGrab(o) => {
2144 if let Err(err) = window.set_cursor_grab(match o {
2145 egui::viewport::CursorGrab::None => CursorGrabMode::None,
2146 egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
2147 egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
2148 }) {
2149 log::warn!("{command:?}: {err}");
2150 }
2151 }
2152 ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
2153 ViewportCommand::MousePassthrough(passthrough) => {
2154 if let Err(err) = window.set_cursor_hittest(!passthrough) {
2155 log::warn!("{command:?}: {err}");
2156 }
2157 }
2158 ViewportCommand::Screenshot(callback) => {
2159 actions_requested.push(ActionRequested::Screenshot(callback));
2160 }
2161 ViewportCommand::RequestPaintWhileHidden => {
2162 actions_requested.push(ActionRequested::PaintWhileHidden);
2163 }
2164 ViewportCommand::RequestCut => {
2165 actions_requested.push(ActionRequested::Cut);
2166 }
2167 ViewportCommand::RequestCopy => {
2168 actions_requested.push(ActionRequested::Copy);
2169 }
2170 ViewportCommand::RequestPaste => {
2171 actions_requested.push(ActionRequested::Paste);
2172 }
2173 }
2174}
2175
2176fn to_winit_ime_purpose(purpose: egui::IMEPurpose) -> winit::window::ImePurpose {
2177 match purpose {
2178 egui::IMEPurpose::Password => winit::window::ImePurpose::Password,
2179 egui::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
2180 egui::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
2181 }
2182}
2183
2184pub fn create_window(
2191 egui_ctx: &egui::Context,
2192 event_loop: &ActiveEventLoop,
2193 viewport_builder: &ViewportBuilder,
2194) -> Result<Window, winit::error::OsError> {
2195 profiling::function_scope!();
2196
2197 let window_attributes = apply_monitor_to_window_attributes(
2198 create_winit_window_attributes(egui_ctx, viewport_builder.clone()),
2199 viewport_builder,
2200 event_loop,
2201 );
2202
2203 let window = event_loop.create_window(window_attributes)?;
2204 apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
2205 Ok(window)
2206}
2207
2208pub fn apply_monitor_to_window_attributes(
2220 mut window_attributes: winit::window::WindowAttributes,
2221 viewport_builder: &ViewportBuilder,
2222 event_loop: &ActiveEventLoop,
2223) -> winit::window::WindowAttributes {
2224 if let Some(idx) = viewport_builder.monitor {
2225 if let Some(monitor) = event_loop.available_monitors().nth(idx) {
2226 window_attributes = window_attributes
2227 .with_fullscreen(Some(winit::window::Fullscreen::Borderless(Some(monitor))));
2228 } else {
2229 log::warn!(
2230 "ViewportBuilder::with_monitor({idx}): index out of range ({} monitors available)",
2231 event_loop.available_monitors().count()
2232 );
2233 }
2234 }
2235 window_attributes
2236}
2237
2238pub fn create_winit_window_attributes(
2239 egui_ctx: &egui::Context,
2240 viewport_builder: ViewportBuilder,
2241) -> winit::window::WindowAttributes {
2242 profiling::function_scope!();
2243
2244 let ViewportBuilder {
2245 title,
2246 position,
2247 inner_size,
2248 min_inner_size,
2249 max_inner_size,
2250 fullscreen,
2251 maximized,
2252 resizable,
2253 transparent,
2254 decorations,
2255 icon,
2256 active,
2257 visible,
2258 close_button,
2259 minimize_button,
2260 maximize_button,
2261 window_level,
2262
2263 fullsize_content_view: _fullsize_content_view,
2265 movable_by_window_background: _movable_by_window_background,
2266 title_shown: _title_shown,
2267 titlebar_buttons_shown: _titlebar_buttons_shown,
2268 titlebar_shown: _titlebar_shown,
2269 has_shadow: _has_shadow,
2270
2271 drag_and_drop: _drag_and_drop,
2273 taskbar: _taskbar,
2274
2275 app_id: _app_id,
2277
2278 window_type: _window_type,
2280 override_redirect: _override_redirect,
2281
2282 mouse_passthrough: _, clamp_size_to_monitor_size: _, monitor: _, } = viewport_builder;
2286
2287 let mut window_attributes = winit::window::WindowAttributes::default()
2288 .with_title(title.unwrap_or_else(|| "egui window".to_owned()))
2289 .with_transparent(transparent.unwrap_or(false))
2290 .with_decorations(decorations.unwrap_or(true))
2291 .with_resizable(resizable.unwrap_or(true))
2292 .with_visible(visible.unwrap_or(true))
2293 .with_maximized(if cfg!(target_os = "ios") {
2294 true
2295 } else if cfg!(target_os = "windows") && visible == Some(false) {
2296 false
2299 } else {
2300 maximized.unwrap_or(false)
2301 })
2302 .with_window_level(match window_level.unwrap_or_default() {
2303 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
2304 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
2305 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
2306 })
2307 .with_fullscreen(
2308 fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),
2309 )
2310 .with_enabled_buttons({
2311 let mut buttons = WindowButtons::empty();
2312 if minimize_button.unwrap_or(true) {
2313 buttons |= WindowButtons::MINIMIZE;
2314 }
2315 if maximize_button.unwrap_or(true) {
2316 buttons |= WindowButtons::MAXIMIZE;
2317 }
2318 if close_button.unwrap_or(true) {
2319 buttons |= WindowButtons::CLOSE;
2320 }
2321 buttons
2322 })
2323 .with_active(active.unwrap_or(true));
2324
2325 #[expect(
2331 clippy::disallowed_types,
2332 reason = "zoom factor is manually accounted for"
2333 )]
2334 #[cfg(not(target_os = "ios"))]
2335 {
2336 use winit::dpi::{LogicalPosition, LogicalSize};
2337 let zoom_factor = egui_ctx.zoom_factor();
2338
2339 if let Some(size) = inner_size {
2340 window_attributes = window_attributes
2341 .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2342 }
2343
2344 if let Some(size) = min_inner_size {
2345 window_attributes = window_attributes
2346 .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2347 }
2348
2349 if let Some(size) = max_inner_size {
2350 window_attributes = window_attributes
2351 .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
2352 }
2353
2354 if let Some(pos) = position {
2355 window_attributes = window_attributes.with_position(LogicalPosition::new(
2356 zoom_factor * pos.x,
2357 zoom_factor * pos.y,
2358 ));
2359 }
2360 }
2361 #[cfg(target_os = "ios")]
2362 {
2363 _ = egui_ctx;
2365 _ = pixels_per_point;
2366 _ = position;
2367 _ = inner_size;
2368 _ = min_inner_size;
2369 _ = max_inner_size;
2370 }
2371
2372 if let Some(icon) = icon {
2373 let winit_icon = to_winit_icon(&icon);
2374 window_attributes = window_attributes.with_window_icon(winit_icon);
2375 }
2376
2377 #[cfg(all(feature = "wayland", target_os = "linux"))]
2378 if let Some(app_id) = _app_id {
2379 use winit::platform::wayland::WindowAttributesExtWayland as _;
2380 window_attributes = window_attributes.with_name(app_id, "");
2381 }
2382
2383 #[cfg(all(target_os = "linux", any(feature = "wayland", feature = "x11")))]
2398 {
2399 use winit::platform::startup_notify::{
2400 WindowAttributesExtStartupNotify as _, reset_activation_token_env,
2401 };
2402 let token = std::env::var("XDG_ACTIVATION_TOKEN")
2403 .or_else(|_| std::env::var("DESKTOP_STARTUP_ID"))
2404 .ok()
2405 .filter(|t| !t.is_empty());
2406 if let Some(token) = token {
2407 log::debug!("using the activation token from the environment to focus the window");
2408 reset_activation_token_env();
2409 window_attributes = window_attributes
2410 .with_activation_token(winit::window::ActivationToken::from_raw(token));
2411 }
2412 }
2413
2414 #[cfg(all(feature = "x11", target_os = "linux"))]
2415 {
2416 use winit::platform::x11::WindowAttributesExtX11 as _;
2417 if let Some(window_type) = _window_type {
2418 use winit::platform::x11::WindowType;
2419 window_attributes = window_attributes.with_x11_window_type(vec![match window_type {
2420 egui::X11WindowType::Normal => WindowType::Normal,
2421 egui::X11WindowType::Utility => WindowType::Utility,
2422 egui::X11WindowType::Dock => WindowType::Dock,
2423 egui::X11WindowType::Desktop => WindowType::Desktop,
2424 egui::X11WindowType::Toolbar => WindowType::Toolbar,
2425 egui::X11WindowType::Menu => WindowType::Menu,
2426 egui::X11WindowType::Splash => WindowType::Splash,
2427 egui::X11WindowType::Dialog => WindowType::Dialog,
2428 egui::X11WindowType::DropdownMenu => WindowType::DropdownMenu,
2429 egui::X11WindowType::PopupMenu => WindowType::PopupMenu,
2430 egui::X11WindowType::Tooltip => WindowType::Tooltip,
2431 egui::X11WindowType::Notification => WindowType::Notification,
2432 egui::X11WindowType::Combo => WindowType::Combo,
2433 egui::X11WindowType::Dnd => WindowType::Dnd,
2434 }]);
2435 }
2436 if let Some(override_redirect) = _override_redirect {
2437 window_attributes = window_attributes.with_override_redirect(override_redirect);
2438 }
2439 }
2440
2441 #[cfg(target_os = "windows")]
2442 {
2443 use winit::platform::windows::WindowAttributesExtWindows as _;
2444 if let Some(enable) = _drag_and_drop {
2445 window_attributes = window_attributes.with_drag_and_drop(enable);
2446 }
2447 if let Some(show) = _taskbar {
2448 window_attributes = window_attributes.with_skip_taskbar(!show);
2449 }
2450
2451 let want_undecorated_shadow = !decorations.unwrap_or(true) && !fullscreen.unwrap_or(false);
2453 window_attributes = window_attributes.with_undecorated_shadow(want_undecorated_shadow);
2454 }
2455
2456 #[cfg(target_os = "macos")]
2457 {
2458 use winit::platform::macos::WindowAttributesExtMacOS as _;
2459 window_attributes = window_attributes
2460 .with_title_hidden(!_title_shown.unwrap_or(true))
2461 .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true))
2462 .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true))
2463 .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false))
2464 .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false))
2465 .with_has_shadow(_has_shadow.unwrap_or(true));
2466 }
2467
2468 window_attributes
2469}
2470
2471fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
2472 if icon.is_empty() {
2473 None
2474 } else {
2475 profiling::function_scope!();
2476 match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
2477 Ok(winit_icon) => Some(winit_icon),
2478 Err(err) => {
2479 log::warn!("Invalid IconData: {err}");
2480 None
2481 }
2482 }
2483 }
2484}
2485
2486pub fn apply_viewport_builder_to_window(
2488 egui_ctx: &egui::Context,
2489 window: &Window,
2490 builder: &ViewportBuilder,
2491) {
2492 if let Some(mouse_passthrough) = builder.mouse_passthrough
2493 && let Err(err) = window.set_cursor_hittest(!mouse_passthrough)
2494 {
2495 log::warn!("set_cursor_hittest failed: {err}");
2496 }
2497
2498 {
2499 let pixels_per_point = pixels_per_point(egui_ctx, window);
2505
2506 if let Some(size) = builder.inner_size
2507 && window
2508 .request_inner_size(PhysicalSize::new(
2509 pixels_per_point * size.x,
2510 pixels_per_point * size.y,
2511 ))
2512 .is_some()
2513 {
2514 log::debug!("Failed to set window size");
2515 }
2516 if let Some(size) = builder.min_inner_size {
2517 window.set_min_inner_size(Some(PhysicalSize::new(
2518 pixels_per_point * size.x,
2519 pixels_per_point * size.y,
2520 )));
2521 }
2522 if let Some(size) = builder.max_inner_size {
2523 window.set_max_inner_size(Some(PhysicalSize::new(
2524 pixels_per_point * size.x,
2525 pixels_per_point * size.y,
2526 )));
2527 }
2528 if let Some(pos) = builder.position {
2529 let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
2530 window.set_outer_position(pos);
2531 }
2532 if let Some(maximized) = builder.maximized {
2533 set_maximized(window, maximized);
2534 }
2535 }
2536}
2537
2538pub fn set_maximized(window: &winit::window::Window, maximized: bool) {
2540 #[cfg(target_os = "windows")]
2541 if maximized && window.is_visible() == Some(false) {
2542 set_maximized_while_hidden(window);
2543 return;
2544 }
2545
2546 window.set_maximized(maximized);
2547}
2548
2549#[cfg(target_os = "windows")]
2558fn set_maximized_while_hidden(window: &winit::window::Window) {
2559 use raw_window_handle::{HasWindowHandle as _, RawWindowHandle};
2560 use windows_sys::Win32::Graphics::Dwm::{DWMWA_CLOAK, DwmSetWindowAttribute};
2561
2562 let Ok(RawWindowHandle::Win32(handle)) = window.window_handle().map(|h| h.as_raw()) else {
2563 window.set_maximized(true);
2564 return;
2565 };
2566 let hwnd = handle.hwnd.get() as _;
2567
2568 let set_cloaked = |cloaked: bool| {
2569 let value = i32::from(cloaked);
2570 #[expect(unsafe_code)]
2572 unsafe {
2573 DwmSetWindowAttribute(
2574 hwnd,
2575 DWMWA_CLOAK as u32,
2576 (&raw const value).cast(),
2577 size_of::<i32>() as u32,
2578 )
2579 }
2580 };
2581
2582 let cloaked = set_cloaked(true) >= 0;
2583 window.set_maximized(true);
2585 if cloaked {
2586 set_cloaked(false);
2587 }
2588}
2589
2590#[cfg(target_os = "windows")]
2591fn refresh_windows_non_client_activation(window: &winit::window::Window) {
2592 use raw_window_handle::{HasWindowHandle as _, RawWindowHandle};
2593 use windows_sys::Win32::UI::WindowsAndMessaging::{SendMessageW, WM_NCACTIVATE};
2594
2595 let Ok(window_handle) = window.window_handle() else {
2596 return;
2597 };
2598
2599 let RawWindowHandle::Win32(handle) = window_handle.as_raw() else {
2600 return;
2601 };
2602
2603 let hwnd = handle.hwnd.get() as _;
2604
2605 #[expect(unsafe_code)]
2611 unsafe {
2612 #[expect(clippy::branches_sharing_code)]
2613 if window.has_focus() {
2614 SendMessageW(hwnd, WM_NCACTIVATE, 0, 0);
2617 SendMessageW(hwnd, WM_NCACTIVATE, 1, 0);
2618 } else {
2619 SendMessageW(hwnd, WM_NCACTIVATE, 0, 0);
2621 }
2622 }
2623}
2624
2625pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'static str {
2630 use winit::event::DeviceEvent;
2631
2632 match event {
2633 DeviceEvent::Added => "DeviceEvent::Added",
2634 DeviceEvent::Removed => "DeviceEvent::Removed",
2635 DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion",
2636 DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel",
2637 DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
2638 DeviceEvent::Button { .. } => "DeviceEvent::Button",
2639 DeviceEvent::Key { .. } => "DeviceEvent::Key",
2640 }
2641}
2642
2643pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
2646 use winit::event::WindowEvent;
2647
2648 match event {
2649 WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
2650 WindowEvent::Resized { .. } => "WindowEvent::Resized",
2651 WindowEvent::Moved { .. } => "WindowEvent::Moved",
2652 WindowEvent::CloseRequested => "WindowEvent::CloseRequested",
2653 WindowEvent::Destroyed => "WindowEvent::Destroyed",
2654 WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
2655 WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
2656 WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled",
2657 WindowEvent::Focused { .. } => "WindowEvent::Focused",
2658 WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
2659 WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
2660 WindowEvent::Ime { .. } => "WindowEvent::Ime",
2661 WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
2662 WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
2663 WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
2664 WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
2665 WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
2666 WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture",
2667 WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested",
2668 WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture",
2669 WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture",
2670 WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",
2671 WindowEvent::AxisMotion { .. } => "WindowEvent::AxisMotion",
2672 WindowEvent::Touch { .. } => "WindowEvent::Touch",
2673 WindowEvent::ScaleFactorChanged { .. } => "WindowEvent::ScaleFactorChanged",
2674 WindowEvent::ThemeChanged { .. } => "WindowEvent::ThemeChanged",
2675 WindowEvent::Occluded { .. } => "WindowEvent::Occluded",
2676 WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
2677 }
2678}