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