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 safe_area;
25mod window_settings;
26
27pub use window_settings::WindowSettings;
28
29use raw_window_handle::HasDisplayHandle;
30
31use winit::{
32 dpi::{PhysicalPosition, PhysicalSize},
33 event::ElementState,
34 event_loop::ActiveEventLoop,
35 window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
36};
37
38pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
39 let size = if cfg!(target_os = "ios") {
40 window.outer_size()
44 } else {
45 window.inner_size()
46 };
47 egui::vec2(size.width as f32, size.height as f32)
48}
49
50pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
52 let native_pixels_per_point = window.scale_factor() as f32;
53 let egui_zoom_factor = egui_ctx.zoom_factor();
54 egui_zoom_factor * native_pixels_per_point
55}
56
57#[must_use]
60#[derive(Clone, Copy, Debug, Default)]
61pub struct EventResponse {
62 pub consumed: bool,
70
71 pub repaint: bool,
73}
74
75pub struct State {
81 egui_ctx: egui::Context,
83
84 viewport_id: ViewportId,
85 start_time: web_time::Instant,
86 egui_input: egui::RawInput,
87 pointer_pos_in_points: Option<egui::Pos2>,
88 any_pointer_button_down: bool,
89 current_cursor_icon: Option<egui::CursorIcon>,
90
91 clipboard: clipboard::Clipboard,
92
93 simulate_touch_screen: bool,
98
99 pointer_touch_id: Option<u64>,
103
104 has_sent_ime_enabled: bool,
106
107 #[cfg(feature = "accesskit")]
108 pub accesskit: Option<accesskit_winit::Adapter>,
109
110 allow_ime: bool,
111 ime_rect_px: Option<egui::Rect>,
112
113 #[cfg(target_os = "windows")]
117 pressed_processed_physical_keys: HashSet<winit::keyboard::PhysicalKey>,
118}
119
120impl State {
121 pub fn new(
123 egui_ctx: egui::Context,
124 viewport_id: ViewportId,
125 display_target: &dyn HasDisplayHandle,
126 native_pixels_per_point: Option<f32>,
127 theme: Option<winit::window::Theme>,
128 max_texture_side: Option<usize>,
129 ) -> Self {
130 profiling::function_scope!();
131
132 let egui_input = egui::RawInput {
133 focused: false, ..Default::default()
135 };
136
137 let mut slf = Self {
138 egui_ctx,
139 viewport_id,
140 start_time: web_time::Instant::now(),
141 egui_input,
142 pointer_pos_in_points: None,
143 any_pointer_button_down: false,
144 current_cursor_icon: None,
145
146 clipboard: clipboard::Clipboard::new(
147 display_target.display_handle().ok().map(|h| h.as_raw()),
148 ),
149
150 simulate_touch_screen: false,
151 pointer_touch_id: None,
152
153 has_sent_ime_enabled: false,
154
155 #[cfg(feature = "accesskit")]
156 accesskit: None,
157
158 allow_ime: false,
159 ime_rect_px: None,
160 #[cfg(target_os = "windows")]
161 pressed_processed_physical_keys: HashSet::new(),
162 };
163
164 slf.egui_input
165 .viewports
166 .entry(ViewportId::ROOT)
167 .or_default()
168 .native_pixels_per_point = native_pixels_per_point;
169 slf.egui_input.system_theme = theme.map(to_egui_theme);
170
171 if let Some(max_texture_side) = max_texture_side {
172 slf.set_max_texture_side(max_texture_side);
173 }
174 slf
175 }
176
177 #[cfg(feature = "accesskit")]
178 pub fn init_accesskit<T: From<accesskit_winit::Event> + Send>(
179 &mut self,
180 event_loop: &ActiveEventLoop,
181 window: &Window,
182 event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
183 ) {
184 profiling::function_scope!();
185
186 self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
187 event_loop,
188 window,
189 event_loop_proxy,
190 ));
191 }
192
193 pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
196 self.egui_input.max_texture_side = Some(max_texture_side);
197 }
198
199 pub fn clipboard_text(&mut self) -> Option<String> {
201 self.clipboard.get()
202 }
203
204 pub fn set_clipboard_text(&mut self, text: String) {
206 self.clipboard.set_text(text);
207 }
208
209 pub fn allow_ime(&self) -> bool {
211 self.allow_ime
212 }
213
214 pub fn set_allow_ime(&mut self, allow: bool) {
216 self.allow_ime = allow;
217 }
218
219 #[inline]
220 pub fn egui_ctx(&self) -> &egui::Context {
221 &self.egui_ctx
222 }
223
224 #[inline]
227 pub fn egui_input(&self) -> &egui::RawInput {
228 &self.egui_input
229 }
230
231 #[inline]
234 pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
235 &mut self.egui_input
236 }
237
238 pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
246 profiling::function_scope!();
247
248 self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
249
250 let screen_size_in_pixels = screen_size_in_pixels(window);
254 let screen_size_in_points =
255 screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window);
256
257 self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
258 && screen_size_in_points.y > 0.0)
259 .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
260
261 self.egui_input.viewport_id = self.viewport_id;
263
264 self.egui_input
265 .viewports
266 .entry(self.viewport_id)
267 .or_default()
268 .native_pixels_per_point = Some(window.scale_factor() as f32);
269
270 self.egui_input.take()
271 }
272
273 pub fn on_window_event(
277 &mut self,
278 window: &Window,
279 event: &winit::event::WindowEvent,
280 ) -> EventResponse {
281 profiling::function_scope!(short_window_event_description(event));
282
283 #[cfg(feature = "accesskit")]
284 if let Some(accesskit) = self.accesskit.as_mut() {
285 accesskit.process_event(window, event);
286 }
287
288 use winit::event::WindowEvent;
289
290 #[cfg(target_os = "ios")]
291 match &event {
292 WindowEvent::Resized(_)
293 | WindowEvent::ScaleFactorChanged { .. }
294 | WindowEvent::Focused(true)
295 | WindowEvent::Occluded(false) => {
296 self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
300 }
301 _ => {}
302 }
303
304 match event {
305 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
306 let native_pixels_per_point = *scale_factor as f32;
307
308 self.egui_input
309 .viewports
310 .entry(self.viewport_id)
311 .or_default()
312 .native_pixels_per_point = Some(native_pixels_per_point);
313
314 EventResponse {
315 repaint: true,
316 consumed: false,
317 }
318 }
319 WindowEvent::MouseInput { state, button, .. } => {
320 self.on_mouse_button_input(*state, *button);
321 EventResponse {
322 repaint: true,
323 consumed: self.egui_ctx.egui_wants_pointer_input(),
324 }
325 }
326 WindowEvent::MouseWheel { delta, phase, .. } => {
327 self.on_mouse_wheel(window, *delta, *phase);
328 EventResponse {
329 repaint: true,
330 consumed: self.egui_ctx.egui_wants_pointer_input(),
331 }
332 }
333 WindowEvent::CursorMoved { position, .. } => {
334 self.on_cursor_moved(window, *position);
335 EventResponse {
336 repaint: true,
337 consumed: self.egui_ctx.egui_is_using_pointer(),
338 }
339 }
340 WindowEvent::CursorLeft { .. } => {
341 self.pointer_pos_in_points = None;
342 self.egui_input.events.push(egui::Event::PointerGone);
343 EventResponse {
344 repaint: true,
345 consumed: false,
346 }
347 }
348 WindowEvent::Touch(touch) => {
350 self.on_touch(window, touch);
351 let consumed = match touch.phase {
352 winit::event::TouchPhase::Started
353 | winit::event::TouchPhase::Ended
354 | winit::event::TouchPhase::Cancelled => {
355 self.egui_ctx.egui_wants_pointer_input()
356 }
357 winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
358 };
359 EventResponse {
360 repaint: true,
361 consumed,
362 }
363 }
364
365 WindowEvent::Ime(ime) => {
366 self.on_ime(ime);
367
368 EventResponse {
369 repaint: true,
370 consumed: self.egui_ctx.egui_wants_keyboard_input(),
371 }
372 }
373 WindowEvent::KeyboardInput {
374 event,
375 is_synthetic,
376 ..
377 } => {
378 if *is_synthetic && event.state == ElementState::Pressed {
379 EventResponse {
384 repaint: true,
385 consumed: false,
386 }
387 } else {
388 let egui_wants_keyboard_input = self.egui_ctx.egui_wants_keyboard_input();
389
390 if let Some(response) =
391 self.try_on_ime_processed_keyboard_input(event, egui_wants_keyboard_input)
392 {
393 response
394 } else {
395 self.on_keyboard_input(event);
396
397 let consumed = egui_wants_keyboard_input
399 || event.logical_key
400 == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
401 EventResponse {
402 repaint: true,
403 consumed,
404 }
405 }
406 }
407 }
408 WindowEvent::Focused(focused) => {
409 let focused = if cfg!(target_os = "macos") {
410 window.has_focus()
414 } else {
415 *focused
416 };
417
418 self.egui_input.focused = focused;
419 self.egui_input
420 .events
421 .push(egui::Event::WindowFocused(focused));
422 EventResponse {
423 repaint: true,
424 consumed: false,
425 }
426 }
427 WindowEvent::ThemeChanged(winit_theme) => {
428 self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
429 EventResponse {
430 repaint: true,
431 consumed: false,
432 }
433 }
434 WindowEvent::HoveredFile(path) => {
435 self.egui_input.hovered_files.push(egui::HoveredFile {
436 path: Some(path.clone()),
437 ..Default::default()
438 });
439 EventResponse {
440 repaint: true,
441 consumed: false,
442 }
443 }
444 WindowEvent::HoveredFileCancelled => {
445 self.egui_input.hovered_files.clear();
446 EventResponse {
447 repaint: true,
448 consumed: false,
449 }
450 }
451 WindowEvent::DroppedFile(path) => {
452 self.egui_input.hovered_files.clear();
453 self.egui_input.dropped_files.push(egui::DroppedFile {
454 path: Some(path.clone()),
455 ..Default::default()
456 });
457 EventResponse {
458 repaint: true,
459 consumed: false,
460 }
461 }
462 WindowEvent::ModifiersChanged(state) => {
463 let state = state.state();
464
465 let alt = state.alt_key();
466 let ctrl = state.control_key();
467 let shift = state.shift_key();
468 let super_ = state.super_key();
469
470 self.egui_input.modifiers.alt = alt;
471 self.egui_input.modifiers.ctrl = ctrl;
472 self.egui_input.modifiers.shift = shift;
473 self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
474 self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
475 super_
476 } else {
477 ctrl
478 };
479
480 EventResponse {
481 repaint: true,
482 consumed: false,
483 }
484 }
485
486 WindowEvent::RedrawRequested
488 | WindowEvent::CursorEntered { .. }
489 | WindowEvent::Destroyed
490 | WindowEvent::Occluded(_)
491 | WindowEvent::Resized(_)
492 | WindowEvent::Moved(_)
493 | WindowEvent::TouchpadPressure { .. }
494 | WindowEvent::CloseRequested => EventResponse {
495 repaint: true,
496 consumed: false,
497 },
498
499 WindowEvent::ActivationTokenDone { .. }
501 | WindowEvent::AxisMotion { .. }
502 | WindowEvent::DoubleTapGesture { .. } => EventResponse {
503 repaint: false,
504 consumed: false,
505 },
506
507 WindowEvent::PinchGesture { delta, .. } => {
508 let zoom_factor = (*delta as f32).exp();
511 self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
512 EventResponse {
513 repaint: true,
514 consumed: self.egui_ctx.egui_wants_pointer_input(),
515 }
516 }
517
518 WindowEvent::RotationGesture { delta, .. } => {
519 self.egui_input
523 .events
524 .push(egui::Event::Rotate(-delta.to_radians()));
525 EventResponse {
526 repaint: true,
527 consumed: self.egui_ctx.egui_wants_pointer_input(),
528 }
529 }
530
531 WindowEvent::PanGesture { delta, phase, .. } => {
532 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
533
534 self.egui_input.events.push(egui::Event::MouseWheel {
535 unit: egui::MouseWheelUnit::Point,
536 delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
537 phase: to_egui_touch_phase(*phase),
538 modifiers: self.egui_input.modifiers,
539 });
540 EventResponse {
541 repaint: true,
542 consumed: self.egui_ctx.egui_wants_pointer_input(),
543 }
544 }
545 }
546 }
547
548 #[cfg(not(target_os = "windows"))]
549 #[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)]
550 #[inline(always)]
551 fn try_on_ime_processed_keyboard_input(
552 &mut self,
553 _event: &winit::event::KeyEvent,
554 _egui_wants_keyboard_input: bool,
555 ) -> Option<EventResponse> {
556 None
561 }
562
563 #[cfg(target_os = "windows")]
564 #[inline(always)]
565 fn try_on_ime_processed_keyboard_input(
566 &mut self,
567 event: &winit::event::KeyEvent,
568 egui_wants_keyboard_input: bool,
569 ) -> Option<EventResponse> {
570 if !self.allow_ime {
571 None
572 } else if event.logical_key == winit::keyboard::NamedKey::Process {
573 self.pressed_processed_physical_keys
602 .insert(event.physical_key);
603
604 Some(EventResponse {
605 repaint: false,
606 consumed: egui_wants_keyboard_input,
607 })
608 } else if event.state == ElementState::Released
609 && self
610 .pressed_processed_physical_keys
611 .remove(&event.physical_key)
612 {
613 Some(EventResponse {
625 repaint: false,
626 consumed: egui_wants_keyboard_input,
627 })
628 } else {
629 None
630 }
631 }
632
633 fn on_ime(&mut self, ime: &winit::event::Ime) {
674 match ime {
692 winit::event::Ime::Enabled => {
693 if cfg!(target_os = "linux") {
694 } else {
698 self.ime_event_enable();
699 }
700 }
701 winit::event::Ime::Preedit(text, Some(_cursor)) => {
702 self.ime_event_enable();
703 self.egui_input
704 .events
705 .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
706 }
707 winit::event::Ime::Commit(text) => {
708 self.egui_input
709 .events
710 .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
711 self.ime_event_disable();
712 }
713 winit::event::Ime::Disabled => {
714 self.ime_event_disable();
715 }
716 winit::event::Ime::Preedit(_, None) => {
717 if cfg!(target_os = "macos") {
718 self.egui_input
733 .events
734 .push(egui::Event::Ime(egui::ImeEvent::Preedit(String::new())));
735 }
736
737 self.ime_event_disable();
738 }
739 }
740 }
741
742 pub fn ime_event_enable(&mut self) {
743 if !self.has_sent_ime_enabled {
744 self.egui_input
745 .events
746 .push(egui::Event::Ime(egui::ImeEvent::Enabled));
747 self.has_sent_ime_enabled = true;
748 }
749 }
750
751 pub fn ime_event_disable(&mut self) {
752 self.egui_input
753 .events
754 .push(egui::Event::Ime(egui::ImeEvent::Disabled));
755 self.has_sent_ime_enabled = false;
756 }
757
758 pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool {
760 if !self.is_pointer_in_window() && !self.any_pointer_button_down {
761 return false;
762 }
763
764 self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
765 x: delta.0 as f32,
766 y: delta.1 as f32,
767 }));
768 true
769 }
770
771 pub fn is_pointer_in_window(&self) -> bool {
773 self.pointer_pos_in_points.is_some()
774 }
775
776 pub fn is_any_pointer_button_down(&self) -> bool {
778 self.any_pointer_button_down
779 }
780
781 #[cfg(feature = "accesskit")]
785 pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
786 self.egui_input
787 .events
788 .push(egui::Event::AccessKitActionRequest(request));
789 }
790
791 fn on_mouse_button_input(
792 &mut self,
793 state: winit::event::ElementState,
794 button: winit::event::MouseButton,
795 ) {
796 if let Some(pos) = self.pointer_pos_in_points
797 && let Some(button) = translate_mouse_button(button)
798 {
799 let pressed = state == winit::event::ElementState::Pressed;
800
801 self.egui_input.events.push(egui::Event::PointerButton {
802 pos,
803 button,
804 pressed,
805 modifiers: self.egui_input.modifiers,
806 });
807
808 if self.simulate_touch_screen {
809 if pressed {
810 self.any_pointer_button_down = true;
811
812 self.egui_input.events.push(egui::Event::Touch {
813 device_id: egui::TouchDeviceId(0),
814 id: egui::TouchId(0),
815 phase: egui::TouchPhase::Start,
816 pos,
817 force: None,
818 });
819 } else {
820 self.any_pointer_button_down = false;
821
822 self.egui_input.events.push(egui::Event::PointerGone);
823
824 self.egui_input.events.push(egui::Event::Touch {
825 device_id: egui::TouchDeviceId(0),
826 id: egui::TouchId(0),
827 phase: egui::TouchPhase::End,
828 pos,
829 force: None,
830 });
831 }
832 }
833 }
834 }
835
836 fn on_cursor_moved(
837 &mut self,
838 window: &Window,
839 pos_in_pixels: winit::dpi::PhysicalPosition<f64>,
840 ) {
841 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
842
843 let pos_in_points = egui::pos2(
844 pos_in_pixels.x as f32 / pixels_per_point,
845 pos_in_pixels.y as f32 / pixels_per_point,
846 );
847 self.pointer_pos_in_points = Some(pos_in_points);
848
849 if self.simulate_touch_screen {
850 if self.any_pointer_button_down {
851 self.egui_input
852 .events
853 .push(egui::Event::PointerMoved(pos_in_points));
854
855 self.egui_input.events.push(egui::Event::Touch {
856 device_id: egui::TouchDeviceId(0),
857 id: egui::TouchId(0),
858 phase: egui::TouchPhase::Move,
859 pos: pos_in_points,
860 force: None,
861 });
862 }
863 } else {
864 self.egui_input
865 .events
866 .push(egui::Event::PointerMoved(pos_in_points));
867 }
868 }
869
870 fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) {
871 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
872
873 self.egui_input.events.push(egui::Event::Touch {
875 device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
876 id: egui::TouchId::from(touch.id),
877 phase: to_egui_touch_phase(touch.phase),
878 pos: egui::pos2(
879 touch.location.x as f32 / pixels_per_point,
880 touch.location.y as f32 / pixels_per_point,
881 ),
882 force: match touch.force {
883 Some(winit::event::Force::Normalized(force)) => Some(force as f32),
884 Some(winit::event::Force::Calibrated {
885 force,
886 max_possible_force,
887 ..
888 }) => Some((force / max_possible_force) as f32),
889 None => None,
890 },
891 });
892 if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
895 {
896 match touch.phase {
898 winit::event::TouchPhase::Started => {
899 self.pointer_touch_id = Some(touch.id);
900 self.on_cursor_moved(window, touch.location);
902 self.on_mouse_button_input(
903 winit::event::ElementState::Pressed,
904 winit::event::MouseButton::Left,
905 );
906 }
907 winit::event::TouchPhase::Moved => {
908 self.on_cursor_moved(window, touch.location);
909 }
910 winit::event::TouchPhase::Ended => {
911 self.pointer_touch_id = None;
912 self.on_mouse_button_input(
913 winit::event::ElementState::Released,
914 winit::event::MouseButton::Left,
915 );
916 self.pointer_pos_in_points = None;
919 self.egui_input.events.push(egui::Event::PointerGone);
920 }
921 winit::event::TouchPhase::Cancelled => {
922 self.pointer_touch_id = None;
923 self.pointer_pos_in_points = None;
924 self.egui_input.events.push(egui::Event::PointerGone);
925 }
926 }
927 }
928 }
929
930 fn on_mouse_wheel(
931 &mut self,
932 window: &Window,
933 delta: winit::event::MouseScrollDelta,
934 phase: winit::event::TouchPhase,
935 ) {
936 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
937
938 {
939 let (unit, delta) = match delta {
940 winit::event::MouseScrollDelta::LineDelta(x, y) => {
941 (egui::MouseWheelUnit::Line, egui::vec2(x, y))
942 }
943 winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
944 x,
945 y,
946 }) => (
947 egui::MouseWheelUnit::Point,
948 egui::vec2(x as f32, y as f32) / pixels_per_point,
949 ),
950 };
951 let phase = to_egui_touch_phase(phase);
952 let modifiers = self.egui_input.modifiers;
953 self.egui_input.events.push(egui::Event::MouseWheel {
954 unit,
955 delta,
956 phase,
957 modifiers,
958 });
959 }
960 }
961
962 fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
963 let winit::event::KeyEvent {
964 physical_key,
972
973 logical_key: winit_logical_key,
978
979 text,
980
981 state,
982
983 location: _, repeat: _, ..
986 } = event;
987
988 let pressed = *state == winit::event::ElementState::Pressed;
989
990 let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
991 key_from_key_code(keycode)
992 } else {
993 None
994 };
995
996 let logical_key = key_from_winit_key(winit_logical_key);
997
998 log::trace!(
1000 "logical {:?} -> {:?}, physical {:?} -> {:?}",
1001 event.logical_key,
1002 logical_key,
1003 event.physical_key,
1004 physical_key
1005 );
1006
1007 if let Some(active_key) = logical_key.or(physical_key) {
1012 if pressed {
1013 if is_cut_command(self.egui_input.modifiers, active_key) {
1014 self.egui_input.events.push(egui::Event::Cut);
1015 return;
1016 } else if is_copy_command(self.egui_input.modifiers, active_key) {
1017 self.egui_input.events.push(egui::Event::Copy);
1018 return;
1019 } else if is_paste_command(self.egui_input.modifiers, active_key) {
1020 if let Some(contents) = self.clipboard.get() {
1021 let contents = contents.replace("\r\n", "\n");
1022 if !contents.is_empty() {
1023 self.egui_input.events.push(egui::Event::Paste(contents));
1024 }
1025 }
1026 return;
1027 }
1028 }
1029
1030 self.egui_input.events.push(egui::Event::Key {
1031 key: active_key,
1032 physical_key,
1033 pressed,
1034 repeat: false, modifiers: self.egui_input.modifiers,
1036 });
1037 }
1038
1039 if let Some(text) = text
1040 .as_ref()
1041 .map(|t| t.as_str())
1042 .or_else(|| winit_logical_key.to_text())
1043 {
1044 if !text.is_empty() && text.chars().all(is_printable_char) {
1047 let is_cmd = self.egui_input.modifiers.ctrl
1052 || self.egui_input.modifiers.command
1053 || self.egui_input.modifiers.mac_cmd;
1054 if pressed && !is_cmd {
1055 self.egui_input
1056 .events
1057 .push(egui::Event::Text(text.to_owned()));
1058 }
1059 }
1060 }
1061 }
1062
1063 pub fn handle_platform_output(
1072 &mut self,
1073 window: &Window,
1074 platform_output: egui::PlatformOutput,
1075 ) {
1076 profiling::function_scope!();
1077
1078 let egui::PlatformOutput {
1079 commands,
1080 cursor_icon,
1081 events: _, mutable_text_under_cursor: _, ime,
1084 accesskit_update,
1085 num_completed_passes: _, request_discard_reasons: _, } = platform_output;
1088
1089 for command in commands {
1090 match command {
1091 egui::OutputCommand::CopyText(text) => {
1092 self.clipboard.set_text(text);
1093 }
1094 egui::OutputCommand::CopyImage(image) => {
1095 self.clipboard.set_image(&image);
1096 }
1097 egui::OutputCommand::OpenUrl(open_url) => {
1098 open_url_in_browser(&open_url.url);
1099 }
1100 }
1101 }
1102
1103 self.set_cursor_icon(window, cursor_icon);
1104
1105 let allow_ime = ime.is_some();
1106 if self.allow_ime != allow_ime {
1107 self.allow_ime = allow_ime;
1108 #[cfg(target_os = "windows")]
1109 if !self.allow_ime {
1110 self.pressed_processed_physical_keys.clear();
1116 }
1117
1118 profiling::scope!("set_ime_allowed");
1119 window.set_ime_allowed(allow_ime);
1120 }
1121
1122 if let Some(ime) = ime {
1123 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
1124 let ime_rect_px = pixels_per_point * ime.rect;
1125 if self.ime_rect_px != Some(ime_rect_px)
1126 || self.egui_ctx.input(|i| !i.events.is_empty())
1127 {
1128 self.ime_rect_px = Some(ime_rect_px);
1129 profiling::scope!("set_ime_cursor_area");
1130 window.set_ime_cursor_area(
1131 winit::dpi::PhysicalPosition {
1132 x: ime_rect_px.min.x,
1133 y: ime_rect_px.min.y,
1134 },
1135 winit::dpi::PhysicalSize {
1136 width: ime_rect_px.width(),
1137 height: ime_rect_px.height(),
1138 },
1139 );
1140 }
1141 } else {
1142 self.ime_rect_px = None;
1143 }
1144
1145 #[cfg(feature = "accesskit")]
1146 if let Some(accesskit) = self.accesskit.as_mut()
1147 && let Some(update) = accesskit_update
1148 {
1149 profiling::scope!("accesskit");
1150 accesskit.update_if_active(|| update);
1151 }
1152
1153 #[cfg(not(feature = "accesskit"))]
1154 let _ = accesskit_update;
1155 }
1156
1157 fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
1158 if self.current_cursor_icon == Some(cursor_icon) {
1159 return;
1162 }
1163
1164 let is_pointer_in_window = self.pointer_pos_in_points.is_some();
1165 if is_pointer_in_window {
1166 self.current_cursor_icon = Some(cursor_icon);
1167
1168 if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
1169 window.set_cursor_visible(true);
1170 window.set_cursor(winit_cursor_icon);
1171 } else {
1172 window.set_cursor_visible(false);
1173 }
1174 } else {
1175 self.current_cursor_icon = None;
1177 }
1178 }
1179}
1180
1181fn to_egui_touch_phase(phase: winit::event::TouchPhase) -> egui::TouchPhase {
1182 match phase {
1183 winit::event::TouchPhase::Started => egui::TouchPhase::Start,
1184 winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
1185 winit::event::TouchPhase::Ended => egui::TouchPhase::End,
1186 winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
1187 }
1188}
1189
1190fn to_egui_theme(theme: winit::window::Theme) -> Theme {
1191 match theme {
1192 winit::window::Theme::Dark => Theme::Dark,
1193 winit::window::Theme::Light => Theme::Light,
1194 }
1195}
1196
1197pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1198 let inner_pos_px = window.inner_position().ok()?;
1199 let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
1200
1201 let inner_size_px = window.inner_size();
1202 let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);
1203
1204 let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);
1205
1206 Some(inner_rect_px / pixels_per_point)
1207}
1208
1209pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
1210 let outer_pos_px = window.outer_position().ok()?;
1211 let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);
1212
1213 let outer_size_px = window.outer_size();
1214 let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);
1215
1216 let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);
1217
1218 Some(outer_rect_px / pixels_per_point)
1219}
1220
1221pub fn update_viewport_info(
1227 viewport_info: &mut ViewportInfo,
1228 egui_ctx: &egui::Context,
1229 window: &Window,
1230 is_init: bool,
1231) {
1232 profiling::function_scope!();
1233 let pixels_per_point = pixels_per_point(egui_ctx, window);
1234
1235 let has_a_position = match window.is_minimized() {
1236 Some(true) => false,
1237 Some(false) | None => true,
1238 };
1239
1240 let inner_rect = if has_a_position {
1241 inner_rect_in_points(window, pixels_per_point)
1242 } else {
1243 None
1244 };
1245
1246 let outer_rect = if has_a_position {
1247 outer_rect_in_points(window, pixels_per_point)
1248 } else {
1249 None
1250 };
1251
1252 let monitor_size = {
1253 profiling::scope!("monitor_size");
1254 if let Some(monitor) = window.current_monitor() {
1255 let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
1256 Some(egui::vec2(size.width, size.height))
1257 } else {
1258 None
1259 }
1260 };
1261
1262 viewport_info.title = Some(window.title());
1263 viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
1264
1265 viewport_info.monitor_size = monitor_size;
1266 viewport_info.inner_rect = inner_rect;
1267 viewport_info.outer_rect = outer_rect;
1268
1269 if is_init || !cfg!(target_os = "macos") {
1270 viewport_info.maximized = Some(window.is_maximized());
1274 viewport_info.minimized = Some(window.is_minimized().unwrap_or(false));
1275 }
1276
1277 viewport_info.fullscreen = Some(window.fullscreen().is_some());
1278 viewport_info.focused = Some(window.has_focus());
1279}
1280
1281fn open_url_in_browser(_url: &str) {
1282 #[cfg(feature = "webbrowser")]
1283 if let Err(err) = webbrowser::open(_url) {
1284 log::warn!("Failed to open url: {err}");
1285 }
1286
1287 #[cfg(not(feature = "webbrowser"))]
1288 {
1289 log::warn!("Cannot open url - feature \"links\" not enabled.");
1290 }
1291}
1292
1293fn is_printable_char(chr: char) -> bool {
1298 let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
1299 || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
1300 || '\u{100000}' <= chr && chr <= '\u{10fffd}';
1301
1302 !is_in_private_use_area && !chr.is_ascii_control()
1303}
1304
1305fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1306 keycode == egui::Key::Cut
1307 || (modifiers.command && keycode == egui::Key::X)
1308 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
1309}
1310
1311fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1312 keycode == egui::Key::Copy
1313 || (modifiers.command && keycode == egui::Key::C)
1314 || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
1315}
1316
1317fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1318 keycode == egui::Key::Paste
1319 || (modifiers.command && keycode == egui::Key::V)
1320 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
1321}
1322
1323fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
1324 match button {
1325 winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
1326 winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
1327 winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
1328 winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
1329 winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
1330 winit::event::MouseButton::Other(_) => None,
1331 }
1332}
1333
1334fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
1335 match key {
1336 winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
1337 winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
1338 winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
1339 }
1340}
1341
1342fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
1343 use egui::Key;
1344 use winit::keyboard::NamedKey;
1345
1346 Some(match named_key {
1347 NamedKey::Enter => Key::Enter,
1348 NamedKey::Tab => Key::Tab,
1349 NamedKey::ArrowDown => Key::ArrowDown,
1350 NamedKey::ArrowLeft => Key::ArrowLeft,
1351 NamedKey::ArrowRight => Key::ArrowRight,
1352 NamedKey::ArrowUp => Key::ArrowUp,
1353 NamedKey::End => Key::End,
1354 NamedKey::Home => Key::Home,
1355 NamedKey::PageDown => Key::PageDown,
1356 NamedKey::PageUp => Key::PageUp,
1357 NamedKey::Backspace => Key::Backspace,
1358 NamedKey::Delete => Key::Delete,
1359 NamedKey::Insert => Key::Insert,
1360 NamedKey::Escape => Key::Escape,
1361 NamedKey::Cut => Key::Cut,
1362 NamedKey::Copy => Key::Copy,
1363 NamedKey::Paste => Key::Paste,
1364
1365 NamedKey::Space => Key::Space,
1366
1367 NamedKey::F1 => Key::F1,
1368 NamedKey::F2 => Key::F2,
1369 NamedKey::F3 => Key::F3,
1370 NamedKey::F4 => Key::F4,
1371 NamedKey::F5 => Key::F5,
1372 NamedKey::F6 => Key::F6,
1373 NamedKey::F7 => Key::F7,
1374 NamedKey::F8 => Key::F8,
1375 NamedKey::F9 => Key::F9,
1376 NamedKey::F10 => Key::F10,
1377 NamedKey::F11 => Key::F11,
1378 NamedKey::F12 => Key::F12,
1379 NamedKey::F13 => Key::F13,
1380 NamedKey::F14 => Key::F14,
1381 NamedKey::F15 => Key::F15,
1382 NamedKey::F16 => Key::F16,
1383 NamedKey::F17 => Key::F17,
1384 NamedKey::F18 => Key::F18,
1385 NamedKey::F19 => Key::F19,
1386 NamedKey::F20 => Key::F20,
1387 NamedKey::F21 => Key::F21,
1388 NamedKey::F22 => Key::F22,
1389 NamedKey::F23 => Key::F23,
1390 NamedKey::F24 => Key::F24,
1391 NamedKey::F25 => Key::F25,
1392 NamedKey::F26 => Key::F26,
1393 NamedKey::F27 => Key::F27,
1394 NamedKey::F28 => Key::F28,
1395 NamedKey::F29 => Key::F29,
1396 NamedKey::F30 => Key::F30,
1397 NamedKey::F31 => Key::F31,
1398 NamedKey::F32 => Key::F32,
1399 NamedKey::F33 => Key::F33,
1400 NamedKey::F34 => Key::F34,
1401 NamedKey::F35 => Key::F35,
1402
1403 NamedKey::BrowserBack => Key::BrowserBack,
1404 _ => {
1405 log::trace!("Unknown key: {named_key:?}");
1406 return None;
1407 }
1408 })
1409}
1410
1411fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
1412 use egui::Key;
1413 use winit::keyboard::KeyCode;
1414
1415 Some(match key {
1416 KeyCode::ArrowDown => Key::ArrowDown,
1417 KeyCode::ArrowLeft => Key::ArrowLeft,
1418 KeyCode::ArrowRight => Key::ArrowRight,
1419 KeyCode::ArrowUp => Key::ArrowUp,
1420
1421 KeyCode::Escape => Key::Escape,
1422 KeyCode::Tab => Key::Tab,
1423 KeyCode::Backspace => Key::Backspace,
1424 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
1425
1426 KeyCode::Insert => Key::Insert,
1427 KeyCode::Delete => Key::Delete,
1428 KeyCode::Home => Key::Home,
1429 KeyCode::End => Key::End,
1430 KeyCode::PageUp => Key::PageUp,
1431 KeyCode::PageDown => Key::PageDown,
1432
1433 KeyCode::Space => Key::Space,
1435 KeyCode::Comma => Key::Comma,
1436 KeyCode::Period => Key::Period,
1437 KeyCode::Semicolon => Key::Semicolon,
1439 KeyCode::Backslash => Key::Backslash,
1440 KeyCode::Slash | KeyCode::NumpadDivide => Key::Slash,
1441 KeyCode::BracketLeft => Key::OpenBracket,
1442 KeyCode::BracketRight => Key::CloseBracket,
1443 KeyCode::Backquote => Key::Backtick,
1444 KeyCode::Quote => Key::Quote,
1445
1446 KeyCode::Cut => Key::Cut,
1447 KeyCode::Copy => Key::Copy,
1448 KeyCode::Paste => Key::Paste,
1449 KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
1450 KeyCode::NumpadAdd => Key::Plus,
1451 KeyCode::Equal => Key::Equals,
1452
1453 KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
1454 KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
1455 KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
1456 KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
1457 KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
1458 KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
1459 KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
1460 KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
1461 KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
1462 KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
1463
1464 KeyCode::KeyA => Key::A,
1465 KeyCode::KeyB => Key::B,
1466 KeyCode::KeyC => Key::C,
1467 KeyCode::KeyD => Key::D,
1468 KeyCode::KeyE => Key::E,
1469 KeyCode::KeyF => Key::F,
1470 KeyCode::KeyG => Key::G,
1471 KeyCode::KeyH => Key::H,
1472 KeyCode::KeyI => Key::I,
1473 KeyCode::KeyJ => Key::J,
1474 KeyCode::KeyK => Key::K,
1475 KeyCode::KeyL => Key::L,
1476 KeyCode::KeyM => Key::M,
1477 KeyCode::KeyN => Key::N,
1478 KeyCode::KeyO => Key::O,
1479 KeyCode::KeyP => Key::P,
1480 KeyCode::KeyQ => Key::Q,
1481 KeyCode::KeyR => Key::R,
1482 KeyCode::KeyS => Key::S,
1483 KeyCode::KeyT => Key::T,
1484 KeyCode::KeyU => Key::U,
1485 KeyCode::KeyV => Key::V,
1486 KeyCode::KeyW => Key::W,
1487 KeyCode::KeyX => Key::X,
1488 KeyCode::KeyY => Key::Y,
1489 KeyCode::KeyZ => Key::Z,
1490
1491 KeyCode::F1 => Key::F1,
1492 KeyCode::F2 => Key::F2,
1493 KeyCode::F3 => Key::F3,
1494 KeyCode::F4 => Key::F4,
1495 KeyCode::F5 => Key::F5,
1496 KeyCode::F6 => Key::F6,
1497 KeyCode::F7 => Key::F7,
1498 KeyCode::F8 => Key::F8,
1499 KeyCode::F9 => Key::F9,
1500 KeyCode::F10 => Key::F10,
1501 KeyCode::F11 => Key::F11,
1502 KeyCode::F12 => Key::F12,
1503 KeyCode::F13 => Key::F13,
1504 KeyCode::F14 => Key::F14,
1505 KeyCode::F15 => Key::F15,
1506 KeyCode::F16 => Key::F16,
1507 KeyCode::F17 => Key::F17,
1508 KeyCode::F18 => Key::F18,
1509 KeyCode::F19 => Key::F19,
1510 KeyCode::F20 => Key::F20,
1511 KeyCode::F21 => Key::F21,
1512 KeyCode::F22 => Key::F22,
1513 KeyCode::F23 => Key::F23,
1514 KeyCode::F24 => Key::F24,
1515 KeyCode::F25 => Key::F25,
1516 KeyCode::F26 => Key::F26,
1517 KeyCode::F27 => Key::F27,
1518 KeyCode::F28 => Key::F28,
1519 KeyCode::F29 => Key::F29,
1520 KeyCode::F30 => Key::F30,
1521 KeyCode::F31 => Key::F31,
1522 KeyCode::F32 => Key::F32,
1523 KeyCode::F33 => Key::F33,
1524 KeyCode::F34 => Key::F34,
1525 KeyCode::F35 => Key::F35,
1526
1527 _ => {
1528 return None;
1529 }
1530 })
1531}
1532
1533fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
1534 match cursor_icon {
1535 egui::CursorIcon::None => None,
1536
1537 egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
1538 egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
1539 egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
1540 egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
1541 egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
1542 egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1543 egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
1544 egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
1545 egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1546 egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
1547 egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
1548 egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1549 egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1550 egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
1551 egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
1552
1553 egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
1554 egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
1555 egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
1556 egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
1557
1558 egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
1559 egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
1560 egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
1561 egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
1562 egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
1563 egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
1564 egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
1565 egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
1566 egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
1567 egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
1568
1569 egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
1570 egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
1571 egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
1572 egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
1573 egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
1574 }
1575}
1576
1577#[derive(PartialEq, Eq, Hash, Debug)]
1580pub enum ActionRequested {
1581 Screenshot(egui::UserData),
1582 Cut,
1583 Copy,
1584 Paste,
1585}
1586
1587pub fn process_viewport_commands(
1588 egui_ctx: &egui::Context,
1589 info: &mut ViewportInfo,
1590 commands: impl IntoIterator<Item = ViewportCommand>,
1591 window: &Window,
1592 actions_requested: &mut Vec<ActionRequested>,
1593) {
1594 for command in commands {
1595 process_viewport_command(egui_ctx, window, command, info, actions_requested);
1596 }
1597}
1598
1599fn process_viewport_command(
1600 egui_ctx: &egui::Context,
1601 window: &Window,
1602 command: ViewportCommand,
1603 info: &mut ViewportInfo,
1604 actions_requested: &mut Vec<ActionRequested>,
1605) {
1606 profiling::function_scope!(&format!("{command:?}"));
1607
1608 use winit::window::ResizeDirection;
1609
1610 log::trace!("Processing ViewportCommand::{command:?}");
1611
1612 let pixels_per_point = pixels_per_point(egui_ctx, window);
1613
1614 match command {
1615 ViewportCommand::Close => {
1616 info.events.push(egui::ViewportEvent::Close);
1617 }
1618 ViewportCommand::CancelClose => {
1619 }
1621 ViewportCommand::StartDrag => {
1622 if window.has_focus()
1624 && let Err(err) = window.drag_window()
1625 {
1626 log::warn!("{command:?}: {err}");
1627 }
1628 }
1629 ViewportCommand::InnerSize(size) => {
1630 let width_px = pixels_per_point * size.x.max(1.0);
1631 let height_px = pixels_per_point * size.y.max(1.0);
1632 let requested_size = PhysicalSize::new(width_px, height_px);
1633 if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {
1634 info.inner_rect = inner_rect_in_points(window, pixels_per_point);
1648 info.outer_rect = outer_rect_in_points(window, pixels_per_point);
1649 } else {
1650 }
1654 }
1655 ViewportCommand::BeginResize(direction) => {
1656 if let Err(err) = window.drag_resize_window(match direction {
1657 egui::viewport::ResizeDirection::North => ResizeDirection::North,
1658 egui::viewport::ResizeDirection::South => ResizeDirection::South,
1659 egui::viewport::ResizeDirection::East => ResizeDirection::East,
1660 egui::viewport::ResizeDirection::West => ResizeDirection::West,
1661 egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
1662 egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
1663 egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
1664 egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
1665 }) {
1666 log::warn!("{command:?}: {err}");
1667 }
1668 }
1669 ViewportCommand::Title(title) => {
1670 window.set_title(&title);
1671 }
1672 ViewportCommand::Transparent(v) => window.set_transparent(v),
1673 ViewportCommand::Visible(v) => window.set_visible(v),
1674 ViewportCommand::OuterPosition(pos) => {
1675 window.set_outer_position(PhysicalPosition::new(
1676 pixels_per_point * pos.x,
1677 pixels_per_point * pos.y,
1678 ));
1679 }
1680 ViewportCommand::MinInnerSize(s) => {
1681 window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
1682 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1683 ));
1684 }
1685 ViewportCommand::MaxInnerSize(s) => {
1686 window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
1687 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1688 ));
1689 }
1690 ViewportCommand::ResizeIncrements(s) => {
1691 window.set_resize_increments(
1692 s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
1693 );
1694 }
1695 ViewportCommand::Resizable(v) => window.set_resizable(v),
1696 ViewportCommand::EnableButtons {
1697 close,
1698 minimized,
1699 maximize,
1700 } => window.set_enabled_buttons(
1701 if close {
1702 WindowButtons::CLOSE
1703 } else {
1704 WindowButtons::empty()
1705 } | if minimized {
1706 WindowButtons::MINIMIZE
1707 } else {
1708 WindowButtons::empty()
1709 } | if maximize {
1710 WindowButtons::MAXIMIZE
1711 } else {
1712 WindowButtons::empty()
1713 },
1714 ),
1715 ViewportCommand::Minimized(v) => {
1716 window.set_minimized(v);
1717 info.minimized = Some(v);
1718 }
1719 ViewportCommand::Maximized(v) => {
1720 window.set_maximized(v);
1721 info.maximized = Some(v);
1722 }
1723 ViewportCommand::Fullscreen(v) => {
1724 window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
1725 }
1726 ViewportCommand::Decorations(v) => window.set_decorations(v),
1727 ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
1728 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1729 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1730 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1731 }),
1732 ViewportCommand::Icon(icon) => {
1733 let winit_icon = icon.and_then(|icon| to_winit_icon(&icon));
1734 window.set_window_icon(winit_icon);
1735 }
1736 ViewportCommand::IMERect(rect) => {
1737 window.set_ime_cursor_area(
1738 PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
1739 PhysicalSize::new(
1740 pixels_per_point * rect.size().x,
1741 pixels_per_point * rect.size().y,
1742 ),
1743 );
1744 }
1745 ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
1746 ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
1747 egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,
1748 egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
1749 egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
1750 }),
1751 ViewportCommand::Focus => {
1752 if !window.has_focus() {
1753 window.focus_window();
1754 }
1755 }
1756 ViewportCommand::RequestUserAttention(a) => {
1757 window.request_user_attention(match a {
1758 egui::UserAttentionType::Reset => None,
1759 egui::UserAttentionType::Critical => {
1760 Some(winit::window::UserAttentionType::Critical)
1761 }
1762 egui::UserAttentionType::Informational => {
1763 Some(winit::window::UserAttentionType::Informational)
1764 }
1765 });
1766 }
1767 ViewportCommand::SetTheme(t) => window.set_theme(match t {
1768 egui::SystemTheme::Light => Some(winit::window::Theme::Light),
1769 egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
1770 egui::SystemTheme::SystemDefault => None,
1771 }),
1772 ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
1773 ViewportCommand::CursorPosition(pos) => {
1774 if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
1775 pixels_per_point * pos.x,
1776 pixels_per_point * pos.y,
1777 )) {
1778 log::warn!("{command:?}: {err}");
1779 }
1780 }
1781 ViewportCommand::CursorGrab(o) => {
1782 if let Err(err) = window.set_cursor_grab(match o {
1783 egui::viewport::CursorGrab::None => CursorGrabMode::None,
1784 egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
1785 egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
1786 }) {
1787 log::warn!("{command:?}: {err}");
1788 }
1789 }
1790 ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
1791 ViewportCommand::MousePassthrough(passthrough) => {
1792 if let Err(err) = window.set_cursor_hittest(!passthrough) {
1793 log::warn!("{command:?}: {err}");
1794 }
1795 }
1796 ViewportCommand::Screenshot(user_data) => {
1797 actions_requested.push(ActionRequested::Screenshot(user_data));
1798 }
1799 ViewportCommand::RequestCut => {
1800 actions_requested.push(ActionRequested::Cut);
1801 }
1802 ViewportCommand::RequestCopy => {
1803 actions_requested.push(ActionRequested::Copy);
1804 }
1805 ViewportCommand::RequestPaste => {
1806 actions_requested.push(ActionRequested::Paste);
1807 }
1808 }
1809}
1810
1811pub fn create_window(
1818 egui_ctx: &egui::Context,
1819 event_loop: &ActiveEventLoop,
1820 viewport_builder: &ViewportBuilder,
1821) -> Result<Window, winit::error::OsError> {
1822 profiling::function_scope!();
1823
1824 let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone());
1825 let window = event_loop.create_window(window_attributes)?;
1826 apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
1827 Ok(window)
1828}
1829
1830pub fn create_winit_window_attributes(
1831 egui_ctx: &egui::Context,
1832 viewport_builder: ViewportBuilder,
1833) -> winit::window::WindowAttributes {
1834 profiling::function_scope!();
1835
1836 let ViewportBuilder {
1837 title,
1838 position,
1839 inner_size,
1840 min_inner_size,
1841 max_inner_size,
1842 fullscreen,
1843 maximized,
1844 resizable,
1845 transparent,
1846 decorations,
1847 icon,
1848 active,
1849 visible,
1850 close_button,
1851 minimize_button,
1852 maximize_button,
1853 window_level,
1854
1855 fullsize_content_view: _fullsize_content_view,
1857 movable_by_window_background: _movable_by_window_background,
1858 title_shown: _title_shown,
1859 titlebar_buttons_shown: _titlebar_buttons_shown,
1860 titlebar_shown: _titlebar_shown,
1861 has_shadow: _has_shadow,
1862
1863 drag_and_drop: _drag_and_drop,
1865 taskbar: _taskbar,
1866
1867 app_id: _app_id,
1869
1870 window_type: _window_type,
1872 override_redirect: _override_redirect,
1873
1874 mouse_passthrough: _, clamp_size_to_monitor_size: _, } = viewport_builder;
1877
1878 let mut window_attributes = winit::window::WindowAttributes::default()
1879 .with_title(title.unwrap_or_else(|| "egui window".to_owned()))
1880 .with_transparent(transparent.unwrap_or(false))
1881 .with_decorations(decorations.unwrap_or(true))
1882 .with_resizable(resizable.unwrap_or(true))
1883 .with_visible(visible.unwrap_or(true))
1884 .with_maximized(if cfg!(target_os = "ios") {
1885 true
1886 } else {
1887 maximized.unwrap_or(false)
1888 })
1889 .with_window_level(match window_level.unwrap_or_default() {
1890 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1891 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1892 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1893 })
1894 .with_fullscreen(
1895 fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),
1896 )
1897 .with_enabled_buttons({
1898 let mut buttons = WindowButtons::empty();
1899 if minimize_button.unwrap_or(true) {
1900 buttons |= WindowButtons::MINIMIZE;
1901 }
1902 if maximize_button.unwrap_or(true) {
1903 buttons |= WindowButtons::MAXIMIZE;
1904 }
1905 if close_button.unwrap_or(true) {
1906 buttons |= WindowButtons::CLOSE;
1907 }
1908 buttons
1909 })
1910 .with_active(active.unwrap_or(true));
1911
1912 #[expect(
1918 clippy::disallowed_types,
1919 reason = "zoom factor is manually accounted for"
1920 )]
1921 #[cfg(not(target_os = "ios"))]
1922 {
1923 use winit::dpi::{LogicalPosition, LogicalSize};
1924 let zoom_factor = egui_ctx.zoom_factor();
1925
1926 if let Some(size) = inner_size {
1927 window_attributes = window_attributes
1928 .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1929 }
1930
1931 if let Some(size) = min_inner_size {
1932 window_attributes = window_attributes
1933 .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1934 }
1935
1936 if let Some(size) = max_inner_size {
1937 window_attributes = window_attributes
1938 .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1939 }
1940
1941 if let Some(pos) = position {
1942 window_attributes = window_attributes.with_position(LogicalPosition::new(
1943 zoom_factor * pos.x,
1944 zoom_factor * pos.y,
1945 ));
1946 }
1947 }
1948 #[cfg(target_os = "ios")]
1949 {
1950 _ = egui_ctx;
1952 _ = pixels_per_point;
1953 _ = position;
1954 _ = inner_size;
1955 _ = min_inner_size;
1956 _ = max_inner_size;
1957 }
1958
1959 if let Some(icon) = icon {
1960 let winit_icon = to_winit_icon(&icon);
1961 window_attributes = window_attributes.with_window_icon(winit_icon);
1962 }
1963
1964 #[cfg(all(feature = "wayland", target_os = "linux"))]
1965 if let Some(app_id) = _app_id {
1966 use winit::platform::wayland::WindowAttributesExtWayland as _;
1967 window_attributes = window_attributes.with_name(app_id, "");
1968 }
1969
1970 #[cfg(all(feature = "x11", target_os = "linux"))]
1971 {
1972 use winit::platform::x11::WindowAttributesExtX11 as _;
1973 if let Some(window_type) = _window_type {
1974 use winit::platform::x11::WindowType;
1975 window_attributes = window_attributes.with_x11_window_type(vec![match window_type {
1976 egui::X11WindowType::Normal => WindowType::Normal,
1977 egui::X11WindowType::Utility => WindowType::Utility,
1978 egui::X11WindowType::Dock => WindowType::Dock,
1979 egui::X11WindowType::Desktop => WindowType::Desktop,
1980 egui::X11WindowType::Toolbar => WindowType::Toolbar,
1981 egui::X11WindowType::Menu => WindowType::Menu,
1982 egui::X11WindowType::Splash => WindowType::Splash,
1983 egui::X11WindowType::Dialog => WindowType::Dialog,
1984 egui::X11WindowType::DropdownMenu => WindowType::DropdownMenu,
1985 egui::X11WindowType::PopupMenu => WindowType::PopupMenu,
1986 egui::X11WindowType::Tooltip => WindowType::Tooltip,
1987 egui::X11WindowType::Notification => WindowType::Notification,
1988 egui::X11WindowType::Combo => WindowType::Combo,
1989 egui::X11WindowType::Dnd => WindowType::Dnd,
1990 }]);
1991 }
1992 if let Some(override_redirect) = _override_redirect {
1993 window_attributes = window_attributes.with_override_redirect(override_redirect);
1994 }
1995 }
1996
1997 #[cfg(target_os = "windows")]
1998 {
1999 use winit::platform::windows::WindowAttributesExtWindows as _;
2000 if let Some(enable) = _drag_and_drop {
2001 window_attributes = window_attributes.with_drag_and_drop(enable);
2002 }
2003 if let Some(show) = _taskbar {
2004 window_attributes = window_attributes.with_skip_taskbar(!show);
2005 }
2006 }
2007
2008 #[cfg(target_os = "macos")]
2009 {
2010 use winit::platform::macos::WindowAttributesExtMacOS as _;
2011 window_attributes = window_attributes
2012 .with_title_hidden(!_title_shown.unwrap_or(true))
2013 .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true))
2014 .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true))
2015 .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false))
2016 .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false))
2017 .with_has_shadow(_has_shadow.unwrap_or(true));
2018 }
2019
2020 window_attributes
2021}
2022
2023fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
2024 if icon.is_empty() {
2025 None
2026 } else {
2027 profiling::function_scope!();
2028 match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
2029 Ok(winit_icon) => Some(winit_icon),
2030 Err(err) => {
2031 log::warn!("Invalid IconData: {err}");
2032 None
2033 }
2034 }
2035 }
2036}
2037
2038pub fn apply_viewport_builder_to_window(
2040 egui_ctx: &egui::Context,
2041 window: &Window,
2042 builder: &ViewportBuilder,
2043) {
2044 if let Some(mouse_passthrough) = builder.mouse_passthrough
2045 && let Err(err) = window.set_cursor_hittest(!mouse_passthrough)
2046 {
2047 log::warn!("set_cursor_hittest failed: {err}");
2048 }
2049
2050 {
2051 let pixels_per_point = pixels_per_point(egui_ctx, window);
2057
2058 if let Some(size) = builder.inner_size
2059 && window
2060 .request_inner_size(PhysicalSize::new(
2061 pixels_per_point * size.x,
2062 pixels_per_point * size.y,
2063 ))
2064 .is_some()
2065 {
2066 log::debug!("Failed to set window size");
2067 }
2068 if let Some(size) = builder.min_inner_size {
2069 window.set_min_inner_size(Some(PhysicalSize::new(
2070 pixels_per_point * size.x,
2071 pixels_per_point * size.y,
2072 )));
2073 }
2074 if let Some(size) = builder.max_inner_size {
2075 window.set_max_inner_size(Some(PhysicalSize::new(
2076 pixels_per_point * size.x,
2077 pixels_per_point * size.y,
2078 )));
2079 }
2080 if let Some(pos) = builder.position {
2081 let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
2082 window.set_outer_position(pos);
2083 }
2084 if let Some(maximized) = builder.maximized {
2085 window.set_maximized(maximized);
2086 }
2087 }
2088}
2089
2090pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'static str {
2095 use winit::event::DeviceEvent;
2096
2097 match event {
2098 DeviceEvent::Added => "DeviceEvent::Added",
2099 DeviceEvent::Removed => "DeviceEvent::Removed",
2100 DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion",
2101 DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel",
2102 DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
2103 DeviceEvent::Button { .. } => "DeviceEvent::Button",
2104 DeviceEvent::Key { .. } => "DeviceEvent::Key",
2105 }
2106}
2107
2108pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
2111 use winit::event::WindowEvent;
2112
2113 match event {
2114 WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
2115 WindowEvent::Resized { .. } => "WindowEvent::Resized",
2116 WindowEvent::Moved { .. } => "WindowEvent::Moved",
2117 WindowEvent::CloseRequested => "WindowEvent::CloseRequested",
2118 WindowEvent::Destroyed => "WindowEvent::Destroyed",
2119 WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
2120 WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
2121 WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled",
2122 WindowEvent::Focused { .. } => "WindowEvent::Focused",
2123 WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
2124 WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
2125 WindowEvent::Ime { .. } => "WindowEvent::Ime",
2126 WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
2127 WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
2128 WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
2129 WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
2130 WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
2131 WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture",
2132 WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested",
2133 WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture",
2134 WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture",
2135 WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",
2136 WindowEvent::AxisMotion { .. } => "WindowEvent::AxisMotion",
2137 WindowEvent::Touch { .. } => "WindowEvent::Touch",
2138 WindowEvent::ScaleFactorChanged { .. } => "WindowEvent::ScaleFactorChanged",
2139 WindowEvent::ThemeChanged { .. } => "WindowEvent::ThemeChanged",
2140 WindowEvent::Occluded { .. } => "WindowEvent::Occluded",
2141 WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
2142 }
2143}