1#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
8#![allow(clippy::manual_range_contains)]
11
12#[cfg(feature = "accesskit")]
13pub use accesskit_winit;
14pub use egui;
15#[cfg(feature = "accesskit")]
16use egui::accesskit;
17use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
18pub use winit;
19
20pub mod clipboard;
21mod safe_area;
22mod window_settings;
23
24pub use window_settings::WindowSettings;
25
26use raw_window_handle::HasDisplayHandle;
27
28use winit::{
29 dpi::{PhysicalPosition, PhysicalSize},
30 event::ElementState,
31 event_loop::ActiveEventLoop,
32 window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
33};
34
35pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
36 let size = if cfg!(target_os = "ios") {
37 window.outer_size()
41 } else {
42 window.inner_size()
43 };
44 egui::vec2(size.width as f32, size.height as f32)
45}
46
47pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
49 let native_pixels_per_point = window.scale_factor() as f32;
50 let egui_zoom_factor = egui_ctx.zoom_factor();
51 egui_zoom_factor * native_pixels_per_point
52}
53
54#[must_use]
57#[derive(Clone, Copy, Debug, Default)]
58pub struct EventResponse {
59 pub consumed: bool,
67
68 pub repaint: bool,
70}
71
72pub struct State {
78 egui_ctx: egui::Context,
80
81 viewport_id: ViewportId,
82 start_time: web_time::Instant,
83 egui_input: egui::RawInput,
84 pointer_pos_in_points: Option<egui::Pos2>,
85 any_pointer_button_down: bool,
86 current_cursor_icon: Option<egui::CursorIcon>,
87
88 clipboard: clipboard::Clipboard,
89
90 simulate_touch_screen: bool,
95
96 pointer_touch_id: Option<u64>,
100
101 has_sent_ime_enabled: bool,
103
104 #[cfg(feature = "accesskit")]
105 accesskit: Option<accesskit_winit::Adapter>,
106
107 allow_ime: bool,
108 ime_rect_px: Option<egui::Rect>,
109}
110
111impl State {
112 pub fn new(
114 egui_ctx: egui::Context,
115 viewport_id: ViewportId,
116 display_target: &dyn HasDisplayHandle,
117 native_pixels_per_point: Option<f32>,
118 theme: Option<winit::window::Theme>,
119 max_texture_side: Option<usize>,
120 ) -> Self {
121 profiling::function_scope!();
122
123 let egui_input = egui::RawInput {
124 focused: false, ..Default::default()
126 };
127
128 let mut slf = Self {
129 egui_ctx,
130 viewport_id,
131 start_time: web_time::Instant::now(),
132 egui_input,
133 pointer_pos_in_points: None,
134 any_pointer_button_down: false,
135 current_cursor_icon: None,
136
137 clipboard: clipboard::Clipboard::new(
138 display_target.display_handle().ok().map(|h| h.as_raw()),
139 ),
140
141 simulate_touch_screen: false,
142 pointer_touch_id: None,
143
144 has_sent_ime_enabled: false,
145
146 #[cfg(feature = "accesskit")]
147 accesskit: None,
148
149 allow_ime: false,
150 ime_rect_px: None,
151 };
152
153 slf.egui_input
154 .viewports
155 .entry(ViewportId::ROOT)
156 .or_default()
157 .native_pixels_per_point = native_pixels_per_point;
158 slf.egui_input.system_theme = theme.map(to_egui_theme);
159
160 if let Some(max_texture_side) = max_texture_side {
161 slf.set_max_texture_side(max_texture_side);
162 }
163 slf
164 }
165
166 #[cfg(feature = "accesskit")]
167 pub fn init_accesskit<T: From<accesskit_winit::Event> + Send>(
168 &mut self,
169 event_loop: &ActiveEventLoop,
170 window: &Window,
171 event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
172 ) {
173 profiling::function_scope!();
174
175 self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
176 event_loop,
177 window,
178 event_loop_proxy,
179 ));
180 }
181
182 pub fn set_max_texture_side(&mut self, max_texture_side: usize) {
185 self.egui_input.max_texture_side = Some(max_texture_side);
186 }
187
188 pub fn clipboard_text(&mut self) -> Option<String> {
190 self.clipboard.get()
191 }
192
193 pub fn set_clipboard_text(&mut self, text: String) {
195 self.clipboard.set_text(text);
196 }
197
198 pub fn allow_ime(&self) -> bool {
200 self.allow_ime
201 }
202
203 pub fn set_allow_ime(&mut self, allow: bool) {
205 self.allow_ime = allow;
206 }
207
208 #[inline]
209 pub fn egui_ctx(&self) -> &egui::Context {
210 &self.egui_ctx
211 }
212
213 #[inline]
216 pub fn egui_input(&self) -> &egui::RawInput {
217 &self.egui_input
218 }
219
220 #[inline]
223 pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
224 &mut self.egui_input
225 }
226
227 pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
235 profiling::function_scope!();
236
237 self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
238
239 let screen_size_in_pixels = screen_size_in_pixels(window);
243 let screen_size_in_points =
244 screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window);
245
246 self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
247 && screen_size_in_points.y > 0.0)
248 .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
249
250 self.egui_input.viewport_id = self.viewport_id;
252
253 self.egui_input
254 .viewports
255 .entry(self.viewport_id)
256 .or_default()
257 .native_pixels_per_point = Some(window.scale_factor() as f32);
258
259 self.egui_input.take()
260 }
261
262 pub fn on_window_event(
266 &mut self,
267 window: &Window,
268 event: &winit::event::WindowEvent,
269 ) -> EventResponse {
270 profiling::function_scope!(short_window_event_description(event));
271
272 #[cfg(feature = "accesskit")]
273 if let Some(accesskit) = self.accesskit.as_mut() {
274 accesskit.process_event(window, event);
275 }
276
277 use winit::event::WindowEvent;
278
279 #[cfg(target_os = "ios")]
280 match &event {
281 WindowEvent::Resized(_)
282 | WindowEvent::ScaleFactorChanged { .. }
283 | WindowEvent::Focused(true)
284 | WindowEvent::Occluded(false) => {
285 self.egui_input_mut().safe_area_insets = Some(safe_area::get_safe_area_insets());
289 }
290 _ => {}
291 }
292
293 match event {
294 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
295 let native_pixels_per_point = *scale_factor as f32;
296
297 self.egui_input
298 .viewports
299 .entry(self.viewport_id)
300 .or_default()
301 .native_pixels_per_point = Some(native_pixels_per_point);
302
303 EventResponse {
304 repaint: true,
305 consumed: false,
306 }
307 }
308 WindowEvent::MouseInput { state, button, .. } => {
309 self.on_mouse_button_input(*state, *button);
310 EventResponse {
311 repaint: true,
312 consumed: self.egui_ctx.wants_pointer_input(),
313 }
314 }
315 WindowEvent::MouseWheel { delta, .. } => {
316 self.on_mouse_wheel(window, *delta);
317 EventResponse {
318 repaint: true,
319 consumed: self.egui_ctx.wants_pointer_input(),
320 }
321 }
322 WindowEvent::CursorMoved { position, .. } => {
323 self.on_cursor_moved(window, *position);
324 EventResponse {
325 repaint: true,
326 consumed: self.egui_ctx.is_using_pointer(),
327 }
328 }
329 WindowEvent::CursorLeft { .. } => {
330 self.pointer_pos_in_points = None;
331 self.egui_input.events.push(egui::Event::PointerGone);
332 EventResponse {
333 repaint: true,
334 consumed: false,
335 }
336 }
337 WindowEvent::Touch(touch) => {
339 self.on_touch(window, touch);
340 let consumed = match touch.phase {
341 winit::event::TouchPhase::Started
342 | winit::event::TouchPhase::Ended
343 | winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(),
344 winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(),
345 };
346 EventResponse {
347 repaint: true,
348 consumed,
349 }
350 }
351
352 WindowEvent::Ime(ime) => {
353 match ime {
367 winit::event::Ime::Enabled => {
368 if cfg!(target_os = "linux") {
369 } else {
373 self.ime_event_enable();
374 }
375 }
376 winit::event::Ime::Preedit(text, Some(_cursor)) => {
377 self.ime_event_enable();
378 self.egui_input
379 .events
380 .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
381 }
382 winit::event::Ime::Commit(text) => {
383 self.egui_input
384 .events
385 .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
386 self.ime_event_disable();
387 }
388 winit::event::Ime::Disabled | winit::event::Ime::Preedit(_, None) => {
389 self.ime_event_disable();
390 }
391 }
392
393 EventResponse {
394 repaint: true,
395 consumed: self.egui_ctx.wants_keyboard_input(),
396 }
397 }
398 WindowEvent::KeyboardInput {
399 event,
400 is_synthetic,
401 ..
402 } => {
403 if *is_synthetic && event.state == ElementState::Pressed {
408 EventResponse {
409 repaint: true,
410 consumed: false,
411 }
412 } else {
413 self.on_keyboard_input(event);
414
415 let consumed = self.egui_ctx.wants_keyboard_input()
417 || event.logical_key
418 == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
419 EventResponse {
420 repaint: true,
421 consumed,
422 }
423 }
424 }
425 WindowEvent::Focused(focused) => {
426 let focused = if cfg!(target_os = "macos") {
427 window.has_focus()
431 } else {
432 *focused
433 };
434
435 self.egui_input.focused = focused;
436 self.egui_input
437 .events
438 .push(egui::Event::WindowFocused(focused));
439 EventResponse {
440 repaint: true,
441 consumed: false,
442 }
443 }
444 WindowEvent::ThemeChanged(winit_theme) => {
445 self.egui_input.system_theme = Some(to_egui_theme(*winit_theme));
446 EventResponse {
447 repaint: true,
448 consumed: false,
449 }
450 }
451 WindowEvent::HoveredFile(path) => {
452 self.egui_input.hovered_files.push(egui::HoveredFile {
453 path: Some(path.clone()),
454 ..Default::default()
455 });
456 EventResponse {
457 repaint: true,
458 consumed: false,
459 }
460 }
461 WindowEvent::HoveredFileCancelled => {
462 self.egui_input.hovered_files.clear();
463 EventResponse {
464 repaint: true,
465 consumed: false,
466 }
467 }
468 WindowEvent::DroppedFile(path) => {
469 self.egui_input.hovered_files.clear();
470 self.egui_input.dropped_files.push(egui::DroppedFile {
471 path: Some(path.clone()),
472 ..Default::default()
473 });
474 EventResponse {
475 repaint: true,
476 consumed: false,
477 }
478 }
479 WindowEvent::ModifiersChanged(state) => {
480 let state = state.state();
481
482 let alt = state.alt_key();
483 let ctrl = state.control_key();
484 let shift = state.shift_key();
485 let super_ = state.super_key();
486
487 self.egui_input.modifiers.alt = alt;
488 self.egui_input.modifiers.ctrl = ctrl;
489 self.egui_input.modifiers.shift = shift;
490 self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
491 self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
492 super_
493 } else {
494 ctrl
495 };
496
497 EventResponse {
498 repaint: true,
499 consumed: false,
500 }
501 }
502
503 WindowEvent::RedrawRequested
505 | WindowEvent::CursorEntered { .. }
506 | WindowEvent::Destroyed
507 | WindowEvent::Occluded(_)
508 | WindowEvent::Resized(_)
509 | WindowEvent::Moved(_)
510 | WindowEvent::TouchpadPressure { .. }
511 | WindowEvent::CloseRequested => EventResponse {
512 repaint: true,
513 consumed: false,
514 },
515
516 WindowEvent::ActivationTokenDone { .. }
518 | WindowEvent::AxisMotion { .. }
519 | WindowEvent::DoubleTapGesture { .. } => EventResponse {
520 repaint: false,
521 consumed: false,
522 },
523
524 WindowEvent::PinchGesture { delta, .. } => {
525 let zoom_factor = (*delta as f32).exp();
528 self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
529 EventResponse {
530 repaint: true,
531 consumed: self.egui_ctx.wants_pointer_input(),
532 }
533 }
534
535 WindowEvent::RotationGesture { delta, .. } => {
536 self.egui_input
540 .events
541 .push(egui::Event::Rotate(-delta.to_radians()));
542 EventResponse {
543 repaint: true,
544 consumed: self.egui_ctx.wants_pointer_input(),
545 }
546 }
547
548 WindowEvent::PanGesture { delta, .. } => {
549 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
550
551 self.egui_input.events.push(egui::Event::MouseWheel {
552 unit: egui::MouseWheelUnit::Point,
553 delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
554 modifiers: self.egui_input.modifiers,
555 });
556 EventResponse {
557 repaint: true,
558 consumed: self.egui_ctx.wants_pointer_input(),
559 }
560 }
561 }
562 }
563
564 pub fn ime_event_enable(&mut self) {
565 if !self.has_sent_ime_enabled {
566 self.egui_input
567 .events
568 .push(egui::Event::Ime(egui::ImeEvent::Enabled));
569 self.has_sent_ime_enabled = true;
570 }
571 }
572
573 pub fn ime_event_disable(&mut self) {
574 self.egui_input
575 .events
576 .push(egui::Event::Ime(egui::ImeEvent::Disabled));
577 self.has_sent_ime_enabled = false;
578 }
579
580 pub fn on_mouse_motion(&mut self, delta: (f64, f64)) {
581 self.egui_input.events.push(egui::Event::MouseMoved(Vec2 {
582 x: delta.0 as f32,
583 y: delta.1 as f32,
584 }));
585 }
586
587 #[cfg(feature = "accesskit")]
591 pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
592 self.egui_input
593 .events
594 .push(egui::Event::AccessKitActionRequest(request));
595 }
596
597 fn on_mouse_button_input(
598 &mut self,
599 state: winit::event::ElementState,
600 button: winit::event::MouseButton,
601 ) {
602 if let Some(pos) = self.pointer_pos_in_points
603 && let Some(button) = translate_mouse_button(button)
604 {
605 let pressed = state == winit::event::ElementState::Pressed;
606
607 self.egui_input.events.push(egui::Event::PointerButton {
608 pos,
609 button,
610 pressed,
611 modifiers: self.egui_input.modifiers,
612 });
613
614 if self.simulate_touch_screen {
615 if pressed {
616 self.any_pointer_button_down = true;
617
618 self.egui_input.events.push(egui::Event::Touch {
619 device_id: egui::TouchDeviceId(0),
620 id: egui::TouchId(0),
621 phase: egui::TouchPhase::Start,
622 pos,
623 force: None,
624 });
625 } else {
626 self.any_pointer_button_down = false;
627
628 self.egui_input.events.push(egui::Event::PointerGone);
629
630 self.egui_input.events.push(egui::Event::Touch {
631 device_id: egui::TouchDeviceId(0),
632 id: egui::TouchId(0),
633 phase: egui::TouchPhase::End,
634 pos,
635 force: None,
636 });
637 }
638 }
639 }
640 }
641
642 fn on_cursor_moved(
643 &mut self,
644 window: &Window,
645 pos_in_pixels: winit::dpi::PhysicalPosition<f64>,
646 ) {
647 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
648
649 let pos_in_points = egui::pos2(
650 pos_in_pixels.x as f32 / pixels_per_point,
651 pos_in_pixels.y as f32 / pixels_per_point,
652 );
653 self.pointer_pos_in_points = Some(pos_in_points);
654
655 if self.simulate_touch_screen {
656 if self.any_pointer_button_down {
657 self.egui_input
658 .events
659 .push(egui::Event::PointerMoved(pos_in_points));
660
661 self.egui_input.events.push(egui::Event::Touch {
662 device_id: egui::TouchDeviceId(0),
663 id: egui::TouchId(0),
664 phase: egui::TouchPhase::Move,
665 pos: pos_in_points,
666 force: None,
667 });
668 }
669 } else {
670 self.egui_input
671 .events
672 .push(egui::Event::PointerMoved(pos_in_points));
673 }
674 }
675
676 fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) {
677 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
678
679 self.egui_input.events.push(egui::Event::Touch {
681 device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),
682 id: egui::TouchId::from(touch.id),
683 phase: match touch.phase {
684 winit::event::TouchPhase::Started => egui::TouchPhase::Start,
685 winit::event::TouchPhase::Moved => egui::TouchPhase::Move,
686 winit::event::TouchPhase::Ended => egui::TouchPhase::End,
687 winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
688 },
689 pos: egui::pos2(
690 touch.location.x as f32 / pixels_per_point,
691 touch.location.y as f32 / pixels_per_point,
692 ),
693 force: match touch.force {
694 Some(winit::event::Force::Normalized(force)) => Some(force as f32),
695 Some(winit::event::Force::Calibrated {
696 force,
697 max_possible_force,
698 ..
699 }) => Some((force / max_possible_force) as f32),
700 None => None,
701 },
702 });
703 if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id
706 {
707 match touch.phase {
709 winit::event::TouchPhase::Started => {
710 self.pointer_touch_id = Some(touch.id);
711 self.on_cursor_moved(window, touch.location);
713 self.on_mouse_button_input(
714 winit::event::ElementState::Pressed,
715 winit::event::MouseButton::Left,
716 );
717 }
718 winit::event::TouchPhase::Moved => {
719 self.on_cursor_moved(window, touch.location);
720 }
721 winit::event::TouchPhase::Ended => {
722 self.pointer_touch_id = None;
723 self.on_mouse_button_input(
724 winit::event::ElementState::Released,
725 winit::event::MouseButton::Left,
726 );
727 self.pointer_pos_in_points = None;
730 self.egui_input.events.push(egui::Event::PointerGone);
731 }
732 winit::event::TouchPhase::Cancelled => {
733 self.pointer_touch_id = None;
734 self.pointer_pos_in_points = None;
735 self.egui_input.events.push(egui::Event::PointerGone);
736 }
737 }
738 }
739 }
740
741 fn on_mouse_wheel(&mut self, window: &Window, delta: winit::event::MouseScrollDelta) {
742 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
743
744 {
745 let (unit, delta) = match delta {
746 winit::event::MouseScrollDelta::LineDelta(x, y) => {
747 (egui::MouseWheelUnit::Line, egui::vec2(x, y))
748 }
749 winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
750 x,
751 y,
752 }) => (
753 egui::MouseWheelUnit::Point,
754 egui::vec2(x as f32, y as f32) / pixels_per_point,
755 ),
756 };
757 let modifiers = self.egui_input.modifiers;
758 self.egui_input.events.push(egui::Event::MouseWheel {
759 unit,
760 delta,
761 modifiers,
762 });
763 }
764 }
765
766 fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) {
767 let winit::event::KeyEvent {
768 physical_key,
776
777 logical_key: winit_logical_key,
782
783 text,
784
785 state,
786
787 location: _, repeat: _, ..
790 } = event;
791
792 let pressed = *state == winit::event::ElementState::Pressed;
793
794 let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
795 key_from_key_code(keycode)
796 } else {
797 None
798 };
799
800 let logical_key = key_from_winit_key(winit_logical_key);
801
802 log::trace!(
804 "logical {:?} -> {:?}, physical {:?} -> {:?}",
805 event.logical_key,
806 logical_key,
807 event.physical_key,
808 physical_key
809 );
810
811 if let Some(active_key) = logical_key.or(physical_key) {
816 if pressed {
817 if is_cut_command(self.egui_input.modifiers, active_key) {
818 self.egui_input.events.push(egui::Event::Cut);
819 return;
820 } else if is_copy_command(self.egui_input.modifiers, active_key) {
821 self.egui_input.events.push(egui::Event::Copy);
822 return;
823 } else if is_paste_command(self.egui_input.modifiers, active_key) {
824 if let Some(contents) = self.clipboard.get() {
825 let contents = contents.replace("\r\n", "\n");
826 if !contents.is_empty() {
827 self.egui_input.events.push(egui::Event::Paste(contents));
828 }
829 }
830 return;
831 }
832 }
833
834 self.egui_input.events.push(egui::Event::Key {
835 key: active_key,
836 physical_key,
837 pressed,
838 repeat: false, modifiers: self.egui_input.modifiers,
840 });
841 }
842
843 if let Some(text) = text
844 .as_ref()
845 .map(|t| t.as_str())
846 .or_else(|| winit_logical_key.to_text())
847 {
848 if !text.is_empty() && text.chars().all(is_printable_char) {
851 let is_cmd = self.egui_input.modifiers.ctrl
856 || self.egui_input.modifiers.command
857 || self.egui_input.modifiers.mac_cmd;
858 if pressed && !is_cmd {
859 self.egui_input
860 .events
861 .push(egui::Event::Text(text.to_owned()));
862 }
863 }
864 }
865 }
866
867 pub fn handle_platform_output(
876 &mut self,
877 window: &Window,
878 platform_output: egui::PlatformOutput,
879 ) {
880 profiling::function_scope!();
881
882 let egui::PlatformOutput {
883 commands,
884 cursor_icon,
885 events: _, mutable_text_under_cursor: _, ime,
888 #[cfg(feature = "accesskit")]
889 accesskit_update,
890 num_completed_passes: _, request_discard_reasons: _, } = platform_output;
893
894 for command in commands {
895 match command {
896 egui::OutputCommand::CopyText(text) => {
897 self.clipboard.set_text(text);
898 }
899 egui::OutputCommand::CopyImage(image) => {
900 self.clipboard.set_image(&image);
901 }
902 egui::OutputCommand::OpenUrl(open_url) => {
903 open_url_in_browser(&open_url.url);
904 }
905 }
906 }
907
908 self.set_cursor_icon(window, cursor_icon);
909
910 let allow_ime = ime.is_some();
911 if self.allow_ime != allow_ime {
912 self.allow_ime = allow_ime;
913 profiling::scope!("set_ime_allowed");
914 window.set_ime_allowed(allow_ime);
915 }
916
917 if let Some(ime) = ime {
918 let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
919 let ime_rect_px = pixels_per_point * ime.rect;
920 if self.ime_rect_px != Some(ime_rect_px)
921 || self.egui_ctx.input(|i| !i.events.is_empty())
922 {
923 self.ime_rect_px = Some(ime_rect_px);
924 profiling::scope!("set_ime_cursor_area");
925 window.set_ime_cursor_area(
926 winit::dpi::PhysicalPosition {
927 x: ime_rect_px.min.x,
928 y: ime_rect_px.min.y,
929 },
930 winit::dpi::PhysicalSize {
931 width: ime_rect_px.width(),
932 height: ime_rect_px.height(),
933 },
934 );
935 }
936 } else {
937 self.ime_rect_px = None;
938 }
939
940 #[cfg(feature = "accesskit")]
941 if let Some(accesskit) = self.accesskit.as_mut()
942 && let Some(update) = accesskit_update
943 {
944 profiling::scope!("accesskit");
945 accesskit.update_if_active(|| update);
946 }
947 }
948
949 fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
950 if self.current_cursor_icon == Some(cursor_icon) {
951 return;
954 }
955
956 let is_pointer_in_window = self.pointer_pos_in_points.is_some();
957 if is_pointer_in_window {
958 self.current_cursor_icon = Some(cursor_icon);
959
960 if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
961 window.set_cursor_visible(true);
962 window.set_cursor(winit_cursor_icon);
963 } else {
964 window.set_cursor_visible(false);
965 }
966 } else {
967 self.current_cursor_icon = None;
969 }
970 }
971}
972
973fn to_egui_theme(theme: winit::window::Theme) -> Theme {
974 match theme {
975 winit::window::Theme::Dark => Theme::Dark,
976 winit::window::Theme::Light => Theme::Light,
977 }
978}
979
980pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
981 let inner_pos_px = window.inner_position().ok()?;
982 let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);
983
984 let inner_size_px = window.inner_size();
985 let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);
986
987 let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);
988
989 Some(inner_rect_px / pixels_per_point)
990}
991
992pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<Rect> {
993 let outer_pos_px = window.outer_position().ok()?;
994 let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);
995
996 let outer_size_px = window.outer_size();
997 let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);
998
999 let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);
1000
1001 Some(outer_rect_px / pixels_per_point)
1002}
1003
1004pub fn update_viewport_info(
1010 viewport_info: &mut ViewportInfo,
1011 egui_ctx: &egui::Context,
1012 window: &Window,
1013 is_init: bool,
1014) {
1015 profiling::function_scope!();
1016 let pixels_per_point = pixels_per_point(egui_ctx, window);
1017
1018 let has_a_position = match window.is_minimized() {
1019 Some(true) => false,
1020 Some(false) | None => true,
1021 };
1022
1023 let inner_rect = if has_a_position {
1024 inner_rect_in_points(window, pixels_per_point)
1025 } else {
1026 None
1027 };
1028
1029 let outer_rect = if has_a_position {
1030 outer_rect_in_points(window, pixels_per_point)
1031 } else {
1032 None
1033 };
1034
1035 let monitor_size = {
1036 profiling::scope!("monitor_size");
1037 if let Some(monitor) = window.current_monitor() {
1038 let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
1039 Some(egui::vec2(size.width, size.height))
1040 } else {
1041 None
1042 }
1043 };
1044
1045 viewport_info.title = Some(window.title());
1046 viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
1047
1048 viewport_info.monitor_size = monitor_size;
1049 viewport_info.inner_rect = inner_rect;
1050 viewport_info.outer_rect = outer_rect;
1051
1052 if is_init || !cfg!(target_os = "macos") {
1053 viewport_info.maximized = Some(window.is_maximized());
1057 viewport_info.minimized = Some(window.is_minimized().unwrap_or(false));
1058 }
1059
1060 viewport_info.fullscreen = Some(window.fullscreen().is_some());
1061 viewport_info.focused = Some(window.has_focus());
1062}
1063
1064fn open_url_in_browser(_url: &str) {
1065 #[cfg(feature = "webbrowser")]
1066 if let Err(err) = webbrowser::open(_url) {
1067 log::warn!("Failed to open url: {err}");
1068 }
1069
1070 #[cfg(not(feature = "webbrowser"))]
1071 {
1072 log::warn!("Cannot open url - feature \"links\" not enabled.");
1073 }
1074}
1075
1076fn is_printable_char(chr: char) -> bool {
1081 let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
1082 || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
1083 || '\u{100000}' <= chr && chr <= '\u{10fffd}';
1084
1085 !is_in_private_use_area && !chr.is_ascii_control()
1086}
1087
1088fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1089 keycode == egui::Key::Cut
1090 || (modifiers.command && keycode == egui::Key::X)
1091 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
1092}
1093
1094fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1095 keycode == egui::Key::Copy
1096 || (modifiers.command && keycode == egui::Key::C)
1097 || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
1098}
1099
1100fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
1101 keycode == egui::Key::Paste
1102 || (modifiers.command && keycode == egui::Key::V)
1103 || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
1104}
1105
1106fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
1107 match button {
1108 winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
1109 winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
1110 winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
1111 winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
1112 winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
1113 winit::event::MouseButton::Other(_) => None,
1114 }
1115}
1116
1117fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
1118 match key {
1119 winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
1120 winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
1121 winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
1122 }
1123}
1124
1125fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
1126 use egui::Key;
1127 use winit::keyboard::NamedKey;
1128
1129 Some(match named_key {
1130 NamedKey::Enter => Key::Enter,
1131 NamedKey::Tab => Key::Tab,
1132 NamedKey::ArrowDown => Key::ArrowDown,
1133 NamedKey::ArrowLeft => Key::ArrowLeft,
1134 NamedKey::ArrowRight => Key::ArrowRight,
1135 NamedKey::ArrowUp => Key::ArrowUp,
1136 NamedKey::End => Key::End,
1137 NamedKey::Home => Key::Home,
1138 NamedKey::PageDown => Key::PageDown,
1139 NamedKey::PageUp => Key::PageUp,
1140 NamedKey::Backspace => Key::Backspace,
1141 NamedKey::Delete => Key::Delete,
1142 NamedKey::Insert => Key::Insert,
1143 NamedKey::Escape => Key::Escape,
1144 NamedKey::Cut => Key::Cut,
1145 NamedKey::Copy => Key::Copy,
1146 NamedKey::Paste => Key::Paste,
1147
1148 NamedKey::Space => Key::Space,
1149
1150 NamedKey::F1 => Key::F1,
1151 NamedKey::F2 => Key::F2,
1152 NamedKey::F3 => Key::F3,
1153 NamedKey::F4 => Key::F4,
1154 NamedKey::F5 => Key::F5,
1155 NamedKey::F6 => Key::F6,
1156 NamedKey::F7 => Key::F7,
1157 NamedKey::F8 => Key::F8,
1158 NamedKey::F9 => Key::F9,
1159 NamedKey::F10 => Key::F10,
1160 NamedKey::F11 => Key::F11,
1161 NamedKey::F12 => Key::F12,
1162 NamedKey::F13 => Key::F13,
1163 NamedKey::F14 => Key::F14,
1164 NamedKey::F15 => Key::F15,
1165 NamedKey::F16 => Key::F16,
1166 NamedKey::F17 => Key::F17,
1167 NamedKey::F18 => Key::F18,
1168 NamedKey::F19 => Key::F19,
1169 NamedKey::F20 => Key::F20,
1170 NamedKey::F21 => Key::F21,
1171 NamedKey::F22 => Key::F22,
1172 NamedKey::F23 => Key::F23,
1173 NamedKey::F24 => Key::F24,
1174 NamedKey::F25 => Key::F25,
1175 NamedKey::F26 => Key::F26,
1176 NamedKey::F27 => Key::F27,
1177 NamedKey::F28 => Key::F28,
1178 NamedKey::F29 => Key::F29,
1179 NamedKey::F30 => Key::F30,
1180 NamedKey::F31 => Key::F31,
1181 NamedKey::F32 => Key::F32,
1182 NamedKey::F33 => Key::F33,
1183 NamedKey::F34 => Key::F34,
1184 NamedKey::F35 => Key::F35,
1185
1186 NamedKey::BrowserBack => Key::BrowserBack,
1187 _ => {
1188 log::trace!("Unknown key: {named_key:?}");
1189 return None;
1190 }
1191 })
1192}
1193
1194fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
1195 use egui::Key;
1196 use winit::keyboard::KeyCode;
1197
1198 Some(match key {
1199 KeyCode::ArrowDown => Key::ArrowDown,
1200 KeyCode::ArrowLeft => Key::ArrowLeft,
1201 KeyCode::ArrowRight => Key::ArrowRight,
1202 KeyCode::ArrowUp => Key::ArrowUp,
1203
1204 KeyCode::Escape => Key::Escape,
1205 KeyCode::Tab => Key::Tab,
1206 KeyCode::Backspace => Key::Backspace,
1207 KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
1208
1209 KeyCode::Insert => Key::Insert,
1210 KeyCode::Delete => Key::Delete,
1211 KeyCode::Home => Key::Home,
1212 KeyCode::End => Key::End,
1213 KeyCode::PageUp => Key::PageUp,
1214 KeyCode::PageDown => Key::PageDown,
1215
1216 KeyCode::Space => Key::Space,
1218 KeyCode::Comma => Key::Comma,
1219 KeyCode::Period => Key::Period,
1220 KeyCode::Semicolon => Key::Semicolon,
1222 KeyCode::Backslash => Key::Backslash,
1223 KeyCode::Slash | KeyCode::NumpadDivide => Key::Slash,
1224 KeyCode::BracketLeft => Key::OpenBracket,
1225 KeyCode::BracketRight => Key::CloseBracket,
1226 KeyCode::Backquote => Key::Backtick,
1227 KeyCode::Quote => Key::Quote,
1228
1229 KeyCode::Cut => Key::Cut,
1230 KeyCode::Copy => Key::Copy,
1231 KeyCode::Paste => Key::Paste,
1232 KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
1233 KeyCode::NumpadAdd => Key::Plus,
1234 KeyCode::Equal => Key::Equals,
1235
1236 KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
1237 KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
1238 KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
1239 KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
1240 KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
1241 KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
1242 KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
1243 KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
1244 KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
1245 KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
1246
1247 KeyCode::KeyA => Key::A,
1248 KeyCode::KeyB => Key::B,
1249 KeyCode::KeyC => Key::C,
1250 KeyCode::KeyD => Key::D,
1251 KeyCode::KeyE => Key::E,
1252 KeyCode::KeyF => Key::F,
1253 KeyCode::KeyG => Key::G,
1254 KeyCode::KeyH => Key::H,
1255 KeyCode::KeyI => Key::I,
1256 KeyCode::KeyJ => Key::J,
1257 KeyCode::KeyK => Key::K,
1258 KeyCode::KeyL => Key::L,
1259 KeyCode::KeyM => Key::M,
1260 KeyCode::KeyN => Key::N,
1261 KeyCode::KeyO => Key::O,
1262 KeyCode::KeyP => Key::P,
1263 KeyCode::KeyQ => Key::Q,
1264 KeyCode::KeyR => Key::R,
1265 KeyCode::KeyS => Key::S,
1266 KeyCode::KeyT => Key::T,
1267 KeyCode::KeyU => Key::U,
1268 KeyCode::KeyV => Key::V,
1269 KeyCode::KeyW => Key::W,
1270 KeyCode::KeyX => Key::X,
1271 KeyCode::KeyY => Key::Y,
1272 KeyCode::KeyZ => Key::Z,
1273
1274 KeyCode::F1 => Key::F1,
1275 KeyCode::F2 => Key::F2,
1276 KeyCode::F3 => Key::F3,
1277 KeyCode::F4 => Key::F4,
1278 KeyCode::F5 => Key::F5,
1279 KeyCode::F6 => Key::F6,
1280 KeyCode::F7 => Key::F7,
1281 KeyCode::F8 => Key::F8,
1282 KeyCode::F9 => Key::F9,
1283 KeyCode::F10 => Key::F10,
1284 KeyCode::F11 => Key::F11,
1285 KeyCode::F12 => Key::F12,
1286 KeyCode::F13 => Key::F13,
1287 KeyCode::F14 => Key::F14,
1288 KeyCode::F15 => Key::F15,
1289 KeyCode::F16 => Key::F16,
1290 KeyCode::F17 => Key::F17,
1291 KeyCode::F18 => Key::F18,
1292 KeyCode::F19 => Key::F19,
1293 KeyCode::F20 => Key::F20,
1294 KeyCode::F21 => Key::F21,
1295 KeyCode::F22 => Key::F22,
1296 KeyCode::F23 => Key::F23,
1297 KeyCode::F24 => Key::F24,
1298 KeyCode::F25 => Key::F25,
1299 KeyCode::F26 => Key::F26,
1300 KeyCode::F27 => Key::F27,
1301 KeyCode::F28 => Key::F28,
1302 KeyCode::F29 => Key::F29,
1303 KeyCode::F30 => Key::F30,
1304 KeyCode::F31 => Key::F31,
1305 KeyCode::F32 => Key::F32,
1306 KeyCode::F33 => Key::F33,
1307 KeyCode::F34 => Key::F34,
1308 KeyCode::F35 => Key::F35,
1309
1310 _ => {
1311 return None;
1312 }
1313 })
1314}
1315
1316fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {
1317 match cursor_icon {
1318 egui::CursorIcon::None => None,
1319
1320 egui::CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),
1321 egui::CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),
1322 egui::CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),
1323 egui::CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),
1324 egui::CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),
1325 egui::CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),
1326 egui::CursorIcon::Default => Some(winit::window::CursorIcon::Default),
1327 egui::CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),
1328 egui::CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),
1329 egui::CursorIcon::Help => Some(winit::window::CursorIcon::Help),
1330 egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
1331 egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
1332 egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
1333 egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
1334 egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
1335
1336 egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
1337 egui::CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),
1338 egui::CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),
1339 egui::CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),
1340
1341 egui::CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),
1342 egui::CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),
1343 egui::CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),
1344 egui::CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),
1345 egui::CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),
1346 egui::CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),
1347 egui::CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),
1348 egui::CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),
1349 egui::CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),
1350 egui::CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),
1351
1352 egui::CursorIcon::Text => Some(winit::window::CursorIcon::Text),
1353 egui::CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),
1354 egui::CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),
1355 egui::CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),
1356 egui::CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),
1357 }
1358}
1359
1360#[derive(PartialEq, Eq, Hash, Debug)]
1363pub enum ActionRequested {
1364 Screenshot(egui::UserData),
1365 Cut,
1366 Copy,
1367 Paste,
1368}
1369
1370pub fn process_viewport_commands(
1371 egui_ctx: &egui::Context,
1372 info: &mut ViewportInfo,
1373 commands: impl IntoIterator<Item = ViewportCommand>,
1374 window: &Window,
1375 actions_requested: &mut Vec<ActionRequested>,
1376) {
1377 for command in commands {
1378 process_viewport_command(egui_ctx, window, command, info, actions_requested);
1379 }
1380}
1381
1382fn process_viewport_command(
1383 egui_ctx: &egui::Context,
1384 window: &Window,
1385 command: ViewportCommand,
1386 info: &mut ViewportInfo,
1387 actions_requested: &mut Vec<ActionRequested>,
1388) {
1389 profiling::function_scope!(&format!("{command:?}"));
1390
1391 use winit::window::ResizeDirection;
1392
1393 log::trace!("Processing ViewportCommand::{command:?}");
1394
1395 let pixels_per_point = pixels_per_point(egui_ctx, window);
1396
1397 match command {
1398 ViewportCommand::Close => {
1399 info.events.push(egui::ViewportEvent::Close);
1400 }
1401 ViewportCommand::CancelClose => {
1402 }
1404 ViewportCommand::StartDrag => {
1405 if window.has_focus()
1407 && let Err(err) = window.drag_window()
1408 {
1409 log::warn!("{command:?}: {err}");
1410 }
1411 }
1412 ViewportCommand::InnerSize(size) => {
1413 let width_px = pixels_per_point * size.x.max(1.0);
1414 let height_px = pixels_per_point * size.y.max(1.0);
1415 let requested_size = PhysicalSize::new(width_px, height_px);
1416 if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {
1417 info.inner_rect = inner_rect_in_points(window, pixels_per_point);
1431 info.outer_rect = outer_rect_in_points(window, pixels_per_point);
1432 } else {
1433 }
1437 }
1438 ViewportCommand::BeginResize(direction) => {
1439 if let Err(err) = window.drag_resize_window(match direction {
1440 egui::viewport::ResizeDirection::North => ResizeDirection::North,
1441 egui::viewport::ResizeDirection::South => ResizeDirection::South,
1442 egui::viewport::ResizeDirection::East => ResizeDirection::East,
1443 egui::viewport::ResizeDirection::West => ResizeDirection::West,
1444 egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
1445 egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
1446 egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
1447 egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
1448 }) {
1449 log::warn!("{command:?}: {err}");
1450 }
1451 }
1452 ViewportCommand::Title(title) => {
1453 window.set_title(&title);
1454 }
1455 ViewportCommand::Transparent(v) => window.set_transparent(v),
1456 ViewportCommand::Visible(v) => window.set_visible(v),
1457 ViewportCommand::OuterPosition(pos) => {
1458 window.set_outer_position(PhysicalPosition::new(
1459 pixels_per_point * pos.x,
1460 pixels_per_point * pos.y,
1461 ));
1462 }
1463 ViewportCommand::MinInnerSize(s) => {
1464 window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
1465 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1466 ));
1467 }
1468 ViewportCommand::MaxInnerSize(s) => {
1469 window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
1470 PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
1471 ));
1472 }
1473 ViewportCommand::ResizeIncrements(s) => {
1474 window.set_resize_increments(
1475 s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
1476 );
1477 }
1478 ViewportCommand::Resizable(v) => window.set_resizable(v),
1479 ViewportCommand::EnableButtons {
1480 close,
1481 minimized,
1482 maximize,
1483 } => window.set_enabled_buttons(
1484 if close {
1485 WindowButtons::CLOSE
1486 } else {
1487 WindowButtons::empty()
1488 } | if minimized {
1489 WindowButtons::MINIMIZE
1490 } else {
1491 WindowButtons::empty()
1492 } | if maximize {
1493 WindowButtons::MAXIMIZE
1494 } else {
1495 WindowButtons::empty()
1496 },
1497 ),
1498 ViewportCommand::Minimized(v) => {
1499 window.set_minimized(v);
1500 info.minimized = Some(v);
1501 }
1502 ViewportCommand::Maximized(v) => {
1503 window.set_maximized(v);
1504 info.maximized = Some(v);
1505 }
1506 ViewportCommand::Fullscreen(v) => {
1507 window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
1508 }
1509 ViewportCommand::Decorations(v) => window.set_decorations(v),
1510 ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
1511 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1512 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1513 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1514 }),
1515 ViewportCommand::Icon(icon) => {
1516 let winit_icon = icon.and_then(|icon| to_winit_icon(&icon));
1517 window.set_window_icon(winit_icon);
1518 }
1519 ViewportCommand::IMERect(rect) => {
1520 window.set_ime_cursor_area(
1521 PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
1522 PhysicalSize::new(
1523 pixels_per_point * rect.size().x,
1524 pixels_per_point * rect.size().y,
1525 ),
1526 );
1527 }
1528 ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
1529 ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
1530 egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,
1531 egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
1532 egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
1533 }),
1534 ViewportCommand::Focus => {
1535 if !window.has_focus() {
1536 window.focus_window();
1537 }
1538 }
1539 ViewportCommand::RequestUserAttention(a) => {
1540 window.request_user_attention(match a {
1541 egui::UserAttentionType::Reset => None,
1542 egui::UserAttentionType::Critical => {
1543 Some(winit::window::UserAttentionType::Critical)
1544 }
1545 egui::UserAttentionType::Informational => {
1546 Some(winit::window::UserAttentionType::Informational)
1547 }
1548 });
1549 }
1550 ViewportCommand::SetTheme(t) => window.set_theme(match t {
1551 egui::SystemTheme::Light => Some(winit::window::Theme::Light),
1552 egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
1553 egui::SystemTheme::SystemDefault => None,
1554 }),
1555 ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
1556 ViewportCommand::CursorPosition(pos) => {
1557 if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
1558 pixels_per_point * pos.x,
1559 pixels_per_point * pos.y,
1560 )) {
1561 log::warn!("{command:?}: {err}");
1562 }
1563 }
1564 ViewportCommand::CursorGrab(o) => {
1565 if let Err(err) = window.set_cursor_grab(match o {
1566 egui::viewport::CursorGrab::None => CursorGrabMode::None,
1567 egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
1568 egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
1569 }) {
1570 log::warn!("{command:?}: {err}");
1571 }
1572 }
1573 ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
1574 ViewportCommand::MousePassthrough(passthrough) => {
1575 if let Err(err) = window.set_cursor_hittest(!passthrough) {
1576 log::warn!("{command:?}: {err}");
1577 }
1578 }
1579 ViewportCommand::Screenshot(user_data) => {
1580 actions_requested.push(ActionRequested::Screenshot(user_data));
1581 }
1582 ViewportCommand::RequestCut => {
1583 actions_requested.push(ActionRequested::Cut);
1584 }
1585 ViewportCommand::RequestCopy => {
1586 actions_requested.push(ActionRequested::Copy);
1587 }
1588 ViewportCommand::RequestPaste => {
1589 actions_requested.push(ActionRequested::Paste);
1590 }
1591 }
1592}
1593
1594pub fn create_window(
1601 egui_ctx: &egui::Context,
1602 event_loop: &ActiveEventLoop,
1603 viewport_builder: &ViewportBuilder,
1604) -> Result<Window, winit::error::OsError> {
1605 profiling::function_scope!();
1606
1607 let window_attributes = create_winit_window_attributes(egui_ctx, viewport_builder.clone());
1608 let window = event_loop.create_window(window_attributes)?;
1609 apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
1610 Ok(window)
1611}
1612
1613pub fn create_winit_window_attributes(
1614 egui_ctx: &egui::Context,
1615 viewport_builder: ViewportBuilder,
1616) -> winit::window::WindowAttributes {
1617 profiling::function_scope!();
1618
1619 let ViewportBuilder {
1620 title,
1621 position,
1622 inner_size,
1623 min_inner_size,
1624 max_inner_size,
1625 fullscreen,
1626 maximized,
1627 resizable,
1628 transparent,
1629 decorations,
1630 icon,
1631 active,
1632 visible,
1633 close_button,
1634 minimize_button,
1635 maximize_button,
1636 window_level,
1637
1638 fullsize_content_view: _fullsize_content_view,
1640 movable_by_window_background: _movable_by_window_background,
1641 title_shown: _title_shown,
1642 titlebar_buttons_shown: _titlebar_buttons_shown,
1643 titlebar_shown: _titlebar_shown,
1644 has_shadow: _has_shadow,
1645
1646 drag_and_drop: _drag_and_drop,
1648 taskbar: _taskbar,
1649
1650 app_id: _app_id,
1652
1653 window_type: _window_type,
1655
1656 mouse_passthrough: _, clamp_size_to_monitor_size: _, } = viewport_builder;
1659
1660 let mut window_attributes = winit::window::WindowAttributes::default()
1661 .with_title(title.unwrap_or_else(|| "egui window".to_owned()))
1662 .with_transparent(transparent.unwrap_or(false))
1663 .with_decorations(decorations.unwrap_or(true))
1664 .with_resizable(resizable.unwrap_or(true))
1665 .with_visible(visible.unwrap_or(true))
1666 .with_maximized(if cfg!(target_os = "ios") {
1667 true
1668 } else {
1669 maximized.unwrap_or(false)
1670 })
1671 .with_window_level(match window_level.unwrap_or_default() {
1672 egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
1673 egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
1674 egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
1675 })
1676 .with_fullscreen(
1677 fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),
1678 )
1679 .with_enabled_buttons({
1680 let mut buttons = WindowButtons::empty();
1681 if minimize_button.unwrap_or(true) {
1682 buttons |= WindowButtons::MINIMIZE;
1683 }
1684 if maximize_button.unwrap_or(true) {
1685 buttons |= WindowButtons::MAXIMIZE;
1686 }
1687 if close_button.unwrap_or(true) {
1688 buttons |= WindowButtons::CLOSE;
1689 }
1690 buttons
1691 })
1692 .with_active(active.unwrap_or(true));
1693
1694 #[expect(
1700 clippy::disallowed_types,
1701 reason = "zoom factor is manually accounted for"
1702 )]
1703 #[cfg(not(target_os = "ios"))]
1704 {
1705 use winit::dpi::{LogicalPosition, LogicalSize};
1706 let zoom_factor = egui_ctx.zoom_factor();
1707
1708 if let Some(size) = inner_size {
1709 window_attributes = window_attributes
1710 .with_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1711 }
1712
1713 if let Some(size) = min_inner_size {
1714 window_attributes = window_attributes
1715 .with_min_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1716 }
1717
1718 if let Some(size) = max_inner_size {
1719 window_attributes = window_attributes
1720 .with_max_inner_size(LogicalSize::new(zoom_factor * size.x, zoom_factor * size.y));
1721 }
1722
1723 if let Some(pos) = position {
1724 window_attributes = window_attributes.with_position(LogicalPosition::new(
1725 zoom_factor * pos.x,
1726 zoom_factor * pos.y,
1727 ));
1728 }
1729 }
1730 #[cfg(target_os = "ios")]
1731 {
1732 _ = egui_ctx;
1734 _ = pixels_per_point;
1735 _ = position;
1736 _ = inner_size;
1737 _ = min_inner_size;
1738 _ = max_inner_size;
1739 }
1740
1741 if let Some(icon) = icon {
1742 let winit_icon = to_winit_icon(&icon);
1743 window_attributes = window_attributes.with_window_icon(winit_icon);
1744 }
1745
1746 #[cfg(all(feature = "wayland", target_os = "linux"))]
1747 if let Some(app_id) = _app_id {
1748 use winit::platform::wayland::WindowAttributesExtWayland as _;
1749 window_attributes = window_attributes.with_name(app_id, "");
1750 }
1751
1752 #[cfg(all(feature = "x11", target_os = "linux"))]
1753 {
1754 if let Some(window_type) = _window_type {
1755 use winit::platform::x11::WindowAttributesExtX11 as _;
1756 use winit::platform::x11::WindowType;
1757 window_attributes = window_attributes.with_x11_window_type(vec![match window_type {
1758 egui::X11WindowType::Normal => WindowType::Normal,
1759 egui::X11WindowType::Utility => WindowType::Utility,
1760 egui::X11WindowType::Dock => WindowType::Dock,
1761 egui::X11WindowType::Desktop => WindowType::Desktop,
1762 egui::X11WindowType::Toolbar => WindowType::Toolbar,
1763 egui::X11WindowType::Menu => WindowType::Menu,
1764 egui::X11WindowType::Splash => WindowType::Splash,
1765 egui::X11WindowType::Dialog => WindowType::Dialog,
1766 egui::X11WindowType::DropdownMenu => WindowType::DropdownMenu,
1767 egui::X11WindowType::PopupMenu => WindowType::PopupMenu,
1768 egui::X11WindowType::Tooltip => WindowType::Tooltip,
1769 egui::X11WindowType::Notification => WindowType::Notification,
1770 egui::X11WindowType::Combo => WindowType::Combo,
1771 egui::X11WindowType::Dnd => WindowType::Dnd,
1772 }]);
1773 }
1774 }
1775
1776 #[cfg(target_os = "windows")]
1777 {
1778 use winit::platform::windows::WindowAttributesExtWindows as _;
1779 if let Some(enable) = _drag_and_drop {
1780 window_attributes = window_attributes.with_drag_and_drop(enable);
1781 }
1782 if let Some(show) = _taskbar {
1783 window_attributes = window_attributes.with_skip_taskbar(!show);
1784 }
1785 }
1786
1787 #[cfg(target_os = "macos")]
1788 {
1789 use winit::platform::macos::WindowAttributesExtMacOS as _;
1790 window_attributes = window_attributes
1791 .with_title_hidden(!_title_shown.unwrap_or(true))
1792 .with_titlebar_buttons_hidden(!_titlebar_buttons_shown.unwrap_or(true))
1793 .with_titlebar_transparent(!_titlebar_shown.unwrap_or(true))
1794 .with_fullsize_content_view(_fullsize_content_view.unwrap_or(false))
1795 .with_movable_by_window_background(_movable_by_window_background.unwrap_or(false))
1796 .with_has_shadow(_has_shadow.unwrap_or(true));
1797 }
1798
1799 window_attributes
1800}
1801
1802fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
1803 if icon.is_empty() {
1804 None
1805 } else {
1806 profiling::function_scope!();
1807 match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
1808 Ok(winit_icon) => Some(winit_icon),
1809 Err(err) => {
1810 log::warn!("Invalid IconData: {err}");
1811 None
1812 }
1813 }
1814 }
1815}
1816
1817pub fn apply_viewport_builder_to_window(
1819 egui_ctx: &egui::Context,
1820 window: &Window,
1821 builder: &ViewportBuilder,
1822) {
1823 if let Some(mouse_passthrough) = builder.mouse_passthrough
1824 && let Err(err) = window.set_cursor_hittest(!mouse_passthrough)
1825 {
1826 log::warn!("set_cursor_hittest failed: {err}");
1827 }
1828
1829 {
1830 let pixels_per_point = pixels_per_point(egui_ctx, window);
1836
1837 if let Some(size) = builder.inner_size
1838 && window
1839 .request_inner_size(PhysicalSize::new(
1840 pixels_per_point * size.x,
1841 pixels_per_point * size.y,
1842 ))
1843 .is_some()
1844 {
1845 log::debug!("Failed to set window size");
1846 }
1847 if let Some(size) = builder.min_inner_size {
1848 window.set_min_inner_size(Some(PhysicalSize::new(
1849 pixels_per_point * size.x,
1850 pixels_per_point * size.y,
1851 )));
1852 }
1853 if let Some(size) = builder.max_inner_size {
1854 window.set_max_inner_size(Some(PhysicalSize::new(
1855 pixels_per_point * size.x,
1856 pixels_per_point * size.y,
1857 )));
1858 }
1859 if let Some(pos) = builder.position {
1860 let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
1861 window.set_outer_position(pos);
1862 }
1863 if let Some(maximized) = builder.maximized {
1864 window.set_maximized(maximized);
1865 }
1866 }
1867}
1868
1869pub fn short_device_event_description(event: &winit::event::DeviceEvent) -> &'static str {
1874 use winit::event::DeviceEvent;
1875
1876 match event {
1877 DeviceEvent::Added => "DeviceEvent::Added",
1878 DeviceEvent::Removed => "DeviceEvent::Removed",
1879 DeviceEvent::MouseMotion { .. } => "DeviceEvent::MouseMotion",
1880 DeviceEvent::MouseWheel { .. } => "DeviceEvent::MouseWheel",
1881 DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
1882 DeviceEvent::Button { .. } => "DeviceEvent::Button",
1883 DeviceEvent::Key { .. } => "DeviceEvent::Key",
1884 }
1885}
1886
1887pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
1890 use winit::event::WindowEvent;
1891
1892 match event {
1893 WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
1894 WindowEvent::Resized { .. } => "WindowEvent::Resized",
1895 WindowEvent::Moved { .. } => "WindowEvent::Moved",
1896 WindowEvent::CloseRequested => "WindowEvent::CloseRequested",
1897 WindowEvent::Destroyed => "WindowEvent::Destroyed",
1898 WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
1899 WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
1900 WindowEvent::HoveredFileCancelled => "WindowEvent::HoveredFileCancelled",
1901 WindowEvent::Focused { .. } => "WindowEvent::Focused",
1902 WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
1903 WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
1904 WindowEvent::Ime { .. } => "WindowEvent::Ime",
1905 WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
1906 WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
1907 WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
1908 WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
1909 WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
1910 WindowEvent::PinchGesture { .. } => "WindowEvent::PinchGesture",
1911 WindowEvent::RedrawRequested => "WindowEvent::RedrawRequested",
1912 WindowEvent::DoubleTapGesture { .. } => "WindowEvent::DoubleTapGesture",
1913 WindowEvent::RotationGesture { .. } => "WindowEvent::RotationGesture",
1914 WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",
1915 WindowEvent::AxisMotion { .. } => "WindowEvent::AxisMotion",
1916 WindowEvent::Touch { .. } => "WindowEvent::Touch",
1917 WindowEvent::ScaleFactorChanged { .. } => "WindowEvent::ScaleFactorChanged",
1918 WindowEvent::ThemeChanged { .. } => "WindowEvent::ThemeChanged",
1919 WindowEvent::Occluded { .. } => "WindowEvent::Occluded",
1920 WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
1921 }
1922}