1use std::cell::{Cell, RefCell};
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::time::{Duration, Instant};
5
6use baseview::dpi::{LogicalPosition, LogicalSize, Size};
7use baseview::{
8 Event, EventStatus, HandlerError, ParentWindowHandle, Window, WindowContext, WindowHandler,
9 WindowSettings, WindowSize,
10};
11use copypasta::ClipboardProvider;
12use egui::{FullOutput, Pos2, Rect, Rgba, ViewportCommand, pos2, vec2};
13use keyboard_types::Modifiers;
14use raw_window_handle::HasWindowHandle;
15
16use crate::App;
17use crate::{GraphicsConfig, renderer::Renderer};
18
19#[cfg(all(feature = "log", not(feature = "tracing")))]
20use log::{error, warn};
21#[cfg(feature = "tracing")]
22use tracing::{error, warn};
23
24#[derive(Debug, Clone)]
29pub struct RepaintNotifier {
30 repaint: Arc<AtomicBool>,
31}
32
33impl RepaintNotifier {
34 pub fn new() -> Self {
35 Self {
36 repaint: Arc::new(AtomicBool::new(true)),
37 }
38 }
39
40 pub fn request_repaint(&self) {
41 self.repaint.store(true, Ordering::Relaxed);
42 }
43
44 pub fn request_repaint_with(&self, repaint: bool) {
45 if repaint {
46 self.repaint.store(true, Ordering::Relaxed);
47 }
48 }
49
50 fn repaint_requested(&self) -> bool {
51 self.repaint.swap(false, Ordering::Relaxed)
52 }
53}
54
55impl Default for RepaintNotifier {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct EguiWindowSettings {
64 pub title: String,
66
67 pub size: Size,
69
70 pub min_size: Option<Size>,
72 pub max_size: Option<Size>,
74
75 pub resizable: bool,
77
78 pub zoom_factor: f32,
84
85 pub graphics: GraphicsConfig,
87
88 pub parent: Option<ParentWindowHandle>,
92
93 pub wait_for_parent: bool,
100
101 pub fallback_scale_factor: Option<f64>,
116
117 pub repaint_notifier: Option<RepaintNotifier>,
122
123 pub repaint_poll_interval: Duration,
128}
129
130impl EguiWindowSettings {
131 #[inline]
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 #[inline]
138 pub fn with_title(mut self, title: impl Into<String>) -> Self {
139 self.title = title.into();
140 self
141 }
142
143 #[inline]
145 pub fn with_size(mut self, size: impl Into<Size>) -> Self {
146 self.size = size.into();
147 self
148 }
149
150 #[inline]
154 pub fn with_resizable(mut self, resizable: bool) -> Self {
155 self.resizable = resizable;
156 self
157 }
158
159 #[inline]
163 pub fn with_min_size<S: Into<Size>>(mut self, min_size: Option<S>) -> Self {
164 self.min_size = min_size.map(|s| s.into());
165 self
166 }
167
168 #[inline]
172 pub fn with_max_size<S: Into<Size>>(mut self, max_size: Option<S>) -> Self {
173 self.max_size = max_size.map(|s| s.into());
174 self
175 }
176
177 #[inline]
185 pub fn with_zoom_factor(mut self, zoom_factor: f32) -> Self {
186 self.zoom_factor = zoom_factor;
187 self
188 }
189
190 #[inline]
194 pub fn with_parent<'a, P: HasWindowHandle + 'a>(
195 mut self,
196 parent: impl Into<Option<&'a P>>,
197 ) -> Self {
198 self.parent = parent.into().map(ParentWindowHandle::from_window);
199 self
200 }
201
202 pub fn with_wait_for_parent(mut self, wait_for_parent: bool) -> Self {
204 self.wait_for_parent = wait_for_parent;
205 self
206 }
207
208 #[inline]
210 pub fn wait_for_parent(mut self) -> Self {
211 self.wait_for_parent = true;
212 self
213 }
214
215 #[inline]
217 pub fn with_fallback_scale_factor(mut self, scale_factor: impl Into<Option<f64>>) -> Self {
218 self.fallback_scale_factor = scale_factor.into();
219 self
220 }
221
222 #[inline]
224 pub fn with_graphics_config(mut self, config: GraphicsConfig) -> Self {
225 self.graphics = config;
226 self
227 }
228
229 #[inline]
234 pub fn with_repaint_notifier(mut self, repaint_notifier: RepaintNotifier) -> Self {
235 self.repaint_notifier = Some(repaint_notifier);
236 self
237 }
238
239 #[inline]
244 pub fn with_repaint_poll_interval(mut self, interval: Duration) -> Self {
245 self.repaint_poll_interval = interval;
246 self
247 }
248}
249
250impl Default for EguiWindowSettings {
251 fn default() -> Self {
252 Self {
253 title: String::new(),
254 size: Size::Logical(LogicalSize {
255 width: 300.0,
256 height: 200.0,
257 }),
258 min_size: None,
259 max_size: None,
260 resizable: true,
261 zoom_factor: 1.0,
262 graphics: GraphicsConfig::default(),
263 parent: None,
264 wait_for_parent: false,
265 fallback_scale_factor: None,
266 repaint_notifier: None,
267 repaint_poll_interval: Duration::from_secs_f64(1.0 / 64.0),
268 }
269 }
270}
271
272pub struct Frame {
274 renderer: Renderer,
275 window: WindowContext,
276 clear_color: Rgba,
277 key_capture: KeyCapture,
278}
279
280impl Frame {
281 fn new(renderer: Renderer, window: WindowContext) -> Self {
282 Self {
283 renderer,
284 window,
285 clear_color: Rgba::BLACK,
286 key_capture: Default::default(),
287 }
288 }
289
290 pub fn set_clear_color(&mut self, clear_color: Rgba) {
292 self.clear_color = clear_color;
293 }
294
295 pub fn set_key_capture(&mut self, key_capture: KeyCapture) {
297 self.key_capture = key_capture;
298 }
299
300 pub fn baseview_window(&self) -> &WindowContext {
302 &self.window
303 }
304
305 #[cfg(feature = "opengl")]
319 pub fn gl(&self) -> &std::sync::Arc<egui_glow::glow::Context> {
320 &self.renderer.glow_context
321 }
322}
323
324#[derive(Default, Debug, Clone, PartialEq)]
326pub enum KeyCapture {
327 #[default]
328 CaptureAll,
330 IgnoreAll,
332 CaptureKeys(Vec<keyboard_types::Key>),
334 IgnoreKeys(Vec<keyboard_types::Key>),
336}
337
338struct EguiWindowInner<A: App> {
339 user_app: A,
340 egui_ctx: egui::Context,
341 clipboard_ctx: Option<copypasta::ClipboardContext>,
342 frame: Frame,
343}
344
345pub struct EguiWindow<A: App> {
347 inner: RefCell<EguiWindowInner<A>>,
348 egui_input: RefCell<egui::RawInput>,
349 viewport_id: egui::ViewportId,
350 start_time: Instant,
351 system_scale_factor: Cell<f64>,
352 zoom_factor: Cell<f32>,
353 pointer_logical_pos: Cell<Option<egui::Pos2>>,
354 current_cursor_icon: Cell<baseview::MouseCursor>,
355 repaint_notifier: Option<RepaintNotifier>,
356 modifiers: Cell<egui::Modifiers>,
357 poll_repaint_timer_handle: baseview::TimerHandle,
358
359 full_output: RefCell<FullOutput>,
361
362 pub window: WindowContext,
363}
364
365impl<A: App> EguiWindow<A> {
366 fn new(
367 window: WindowContext,
368 title: String,
369 graphics_config: GraphicsConfig,
370 mut user_app: A,
371 zoom_factor: f32,
372 repaint_notifier: Option<RepaintNotifier>,
373 repaint_poll_interval: Duration,
374 ) -> Result<EguiWindow<A>, HandlerError> {
375 let renderer = Renderer::new(window.clone(), graphics_config)?;
376 let egui_ctx = egui::Context::default();
377
378 egui_ctx.set_zoom_factor(zoom_factor);
379
380 {
381 let waker = window.waker();
382 egui_ctx.set_request_repaint_callback(move |request_repaint_info| {
383 waker.request_redraw_after(request_repaint_info.delay)
384 });
385 }
386
387 let size = window.size();
388
389 let system_scale_factor = size.scale_factor;
390 let total_scale_factor = system_scale_factor * zoom_factor as f64;
391
392 let logical_size: LogicalSize<f64> = size.physical.to_logical(total_scale_factor);
393
394 let screen_rect = Rect::from_min_size(
395 Pos2::new(0f32, 0f32),
396 vec2(logical_size.width as f32, logical_size.height as f32),
397 );
398
399 let viewport_info = egui::ViewportInfo {
400 parent: None,
401 title: Some(title),
402 native_pixels_per_point: Some(system_scale_factor as f32),
403 focused: Some(true),
404 inner_rect: Some(screen_rect),
405 ..Default::default()
406 };
407 let viewport_id = egui::ViewportId::default();
408
409 let mut egui_input = egui::RawInput {
410 max_texture_side: Some(renderer.max_texture_side()),
411 screen_rect: Some(screen_rect),
412 ..Default::default()
413 };
414 let _ = egui_input.viewports.insert(viewport_id, viewport_info);
415
416 let mut frame = Frame::new(renderer, window.clone());
417
418 user_app.build(egui_ctx.clone(), &mut frame)?;
419
420 let clipboard_ctx = match copypasta::ClipboardContext::new() {
421 Ok(clipboard_ctx) => Some(clipboard_ctx),
422 Err(e) => {
423 #[cfg(any(feature = "tracing", feature = "log"))]
424 error!("Failed to initialize clipboard: {}", e);
425
426 #[cfg(not(any(feature = "tracing", feature = "log")))]
427 let _ = e;
428
429 None
430 }
431 };
432
433 let poll_repaint_timer_handle = window.create_timer(repaint_poll_interval)?;
434
435 let start_time = Instant::now();
436
437 Ok(Self {
438 inner: RefCell::new(EguiWindowInner {
439 user_app,
440 egui_ctx,
441 clipboard_ctx,
442 frame,
443 }),
444 viewport_id,
445 start_time,
446 egui_input: egui_input.into(),
447 pointer_logical_pos: None.into(),
448 current_cursor_icon: baseview::MouseCursor::Default.into(),
449 system_scale_factor: system_scale_factor.into(),
450 zoom_factor: zoom_factor.into(),
451 window,
452 repaint_notifier,
453 modifiers: Cell::new(egui::Modifiers::default()),
454 full_output: RefCell::new(FullOutput::default()),
455 poll_repaint_timer_handle,
456 })
457 }
458
459 pub fn create(settings: EguiWindowSettings, app: A) -> Result<Window, baseview::Error> {
464 Self::create_with_host(settings, app, None)
465 }
466
467 pub fn create_with_host(
473 settings: EguiWindowSettings,
474 app: A,
475 host: Option<baseview::host::Host>,
476 ) -> Result<Window, baseview::Error> {
477 let size = match settings.size {
478 Size::Logical(size) => Size::Logical(LogicalSize {
479 width: size.width as f64 * settings.zoom_factor as f64,
480 height: size.height as f64 * settings.zoom_factor as f64,
481 }),
482 Size::Physical(size) => Size::Physical(size),
483 };
484
485 let mut options = WindowSettings::new()
486 .with_title(settings.title.clone())
487 .with_size(size)
488 .with_min_size::<Size>(settings.min_size)
489 .with_max_size::<Size>(settings.max_size)
490 .with_resizable(settings.resizable)
491 .with_wait_for_parent(settings.wait_for_parent)
492 .with_fallback_scale_factor(settings.fallback_scale_factor);
493
494 options.parent = settings.parent;
495
496 #[cfg(feature = "opengl")]
497 let options = { options.with_gl_config(Some(settings.graphics.gl_config.clone())) };
498
499 Window::create_with_host(
500 options,
501 move |window| {
502 EguiWindow::new(
503 window,
504 settings.title,
505 settings.graphics,
506 app,
507 settings.zoom_factor,
508 settings.repaint_notifier,
509 settings.repaint_poll_interval,
510 )
511 },
512 host,
513 )
514 }
515
516 fn poll_repaint_timer(&self) {
517 if let Some(repaint_notifier) = &self.repaint_notifier
518 && repaint_notifier.repaint_requested()
519 {
520 self.window.request_redraw();
521 }
522 }
523}
524
525fn update_modifiers(
527 old_modifiers: &Cell<egui::Modifiers>,
528 new_modifiers: &Modifiers,
529 egui_input: &mut egui::RawInput,
530) {
531 let (new_mac_cmd, new_command) = if cfg!(target_os = "macos") {
532 let m = new_modifiers.meta();
533 (m, m)
534 } else {
535 (false, new_modifiers.ctrl())
536 };
537
538 let new_modifiers = egui::Modifiers {
539 alt: new_modifiers.alt(),
540 ctrl: new_modifiers.ctrl(),
541 shift: new_modifiers.shift(),
542 mac_cmd: new_mac_cmd,
543 command: new_command,
544 };
545
546 if old_modifiers.get() != new_modifiers {
547 old_modifiers.set(new_modifiers);
548 egui_input
549 .events
550 .push(egui::Event::ModifiersChanged(new_modifiers));
551 }
552}
553
554impl<A: App> WindowHandler for EguiWindow<A> {
555 fn poll(&self) {
556 self.poll_repaint_timer();
557 }
558
559 fn draw(&self) -> Result<(), HandlerError> {
560 let (egui_input, logical_size) = {
561 let mut egui_input = self.egui_input.borrow_mut();
562 egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
563
564 let zoom_factor = self.zoom_factor.get();
565
566 let size = self.window.size();
567 let logical_size: LogicalSize<f32> = size
568 .physical
569 .to_logical(size.scale_factor * zoom_factor as f64);
570
571 let screen_rect = Rect::from_min_size(
572 Pos2::new(0f32, 0f32),
573 vec2(logical_size.width, logical_size.height),
574 );
575
576 egui_input.screen_rect = Some(screen_rect);
577 (egui_input.take(), logical_size)
578 };
579
580 let mut full_output = self.full_output.borrow_mut();
582
583 *full_output = {
584 let mut inner = self.inner.borrow_mut();
585 let EguiWindowInner {
586 user_app,
587 egui_ctx,
588 clipboard_ctx: _,
589 frame,
590 } = &mut *inner;
591
592 egui_ctx.run_ui(egui_input, |ui| user_app.ui(ui, frame))
593 };
594
595 let Some(viewport_output) = full_output.viewport_output.get(&self.viewport_id) else {
596 self.window.request_close();
598 return Ok(());
599 };
600
601 let mut new_size = None;
602 let new_zoom = { self.inner.borrow().egui_ctx.zoom_factor() };
603
604 if self.zoom_factor.get() != new_zoom {
605 self.zoom_factor.set(new_zoom);
606
607 let mut inner = self.inner.borrow_mut();
608
609 inner.egui_ctx.set_zoom_factor(new_zoom);
610 inner.user_app.zoom_factor_changed(new_zoom);
611
612 new_size = Some(Size::Logical(LogicalSize::new(
613 (logical_size.width * new_zoom) as f64,
614 (logical_size.height * new_zoom) as f64,
615 )));
616 }
617
618 for command in viewport_output.commands.iter() {
619 match command {
620 ViewportCommand::Close => {
621 self.window.request_close();
622 }
623 ViewportCommand::InnerSize(size) => {
624 new_size = Some(Size::Logical(LogicalSize::new(
625 (size.x * new_zoom) as f64,
626 (size.y * new_zoom) as f64,
627 )));
628 }
629 ViewportCommand::Focus => {
630 self.window.focus()?;
631 }
632 _ => {}
633 }
634 }
635
636 if let Some(new_size) = new_size {
637 if let Err(e) = self.window.resize(new_size) {
638 error!("Failed to resize window: {}", e);
639 }
640 }
641
642 {
643 let mut inner = self.inner.borrow_mut();
644 let EguiWindowInner {
645 user_app: _,
646 egui_ctx,
647 clipboard_ctx,
648 frame,
649 } = &mut *inner;
650
651 let size = self.window.size();
652 frame.renderer.render(
653 &self.window,
654 frame.clear_color,
655 size.physical,
656 size.scale_factor as f32 * egui_ctx.zoom_factor(),
657 egui_ctx,
658 &mut full_output,
659 );
660
661 for command in full_output.platform_output.commands.drain(..) {
662 match command {
663 egui::OutputCommand::CopyText(text) => {
664 if let Some(clipboard_ctx) = clipboard_ctx.as_mut()
665 && let Err(err) = clipboard_ctx.set_contents(text)
666 {
667 #[cfg(any(feature = "tracing", feature = "log"))]
668 error!("Copy/Cut error: {}", err);
669
670 #[cfg(not(any(feature = "tracing", feature = "log")))]
671 let _ = err;
672 }
673 }
674 egui::OutputCommand::CopyImage(_) => {
675 #[cfg(any(feature = "tracing", feature = "log"))]
676 warn!("Copying images is not supported in egui_baseview.");
677 }
678 egui::OutputCommand::OpenUrl(open_url) => {
679 if let Err(err) = open::that_detached(&open_url.url) {
680 #[cfg(any(feature = "tracing", feature = "log"))]
681 error!("Open error: {}", err);
682
683 #[cfg(not(any(feature = "tracing", feature = "log")))]
684 let _ = err;
685 }
686 }
687 egui::OutputCommand::TextSelectionSettled(_) => {}
689 }
690 }
691 }
692
693 let cursor_icon =
694 crate::translate::translate_cursor_icon(full_output.platform_output.cursor_icon);
695 if self.current_cursor_icon.get() != cursor_icon {
696 self.current_cursor_icon.set(cursor_icon);
697
698 self.window.set_mouse_cursor(cursor_icon)?;
699 }
700
701 #[cfg(feature = "keyboard_focus_workaround")]
704 {
705 if !full_output.platform_output.events.is_empty()
706 || full_output.platform_output.ime.is_some()
707 {
708 self.window.focus();
709 }
710 }
711
712 Ok(())
713 }
714
715 fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> {
716 let zoom_factor = self.inner.borrow().egui_ctx.zoom_factor();
717
718 let total_scale_factor = new_size.scale_factor * zoom_factor as f64;
719 let logical_size: LogicalSize<f64> = new_size.physical.to_logical(total_scale_factor);
720
721 let screen_rect = Rect::from_min_size(
722 Pos2::new(0f32, 0f32),
723 vec2(logical_size.width as f32, logical_size.height as f32),
724 );
725
726 let mut egui_input = self.egui_input.borrow_mut();
727
728 egui_input.screen_rect = Some(screen_rect);
729
730 let viewport_info = egui_input.viewports.get_mut(&self.viewport_id).unwrap();
731 viewport_info.native_pixels_per_point = Some(new_size.scale_factor as f32);
732 viewport_info.inner_rect = Some(screen_rect);
733
734 self.system_scale_factor.set(new_size.scale_factor);
735
736 let mut inner = self.inner.borrow_mut();
737
738 inner.user_app.resized(WindowSize {
739 physical: new_size.physical,
740 logical: logical_size,
741 scale_factor: total_scale_factor,
742 });
743
744 Ok(())
745 }
746
747 fn on_event(&self, event: Event) -> EventStatus {
748 let mut return_status = EventStatus::Captured;
749
750 if matches!(
755 event,
756 baseview::Event::Mouse(baseview::MouseEvent::ButtonPressed { .. })
757 ) && !self.window.has_focus()
758 {
759 self.window.focus().unwrap();
760 }
761
762 let mut egui_input = self.egui_input.borrow_mut();
763
764 let mut do_repaint = true;
765
766 match &event {
767 baseview::Event::Mouse(event) => match event {
768 baseview::MouseEvent::CursorMoved {
769 position,
770 modifiers,
771 } => {
772 update_modifiers(&self.modifiers, modifiers, &mut egui_input);
773
774 let logical_pos: LogicalPosition<f32> = position.to_logical(
775 self.system_scale_factor.get()
776 * self.inner.borrow().egui_ctx.zoom_factor() as f64,
777 );
778 let pos = pos2(logical_pos.x, logical_pos.y);
779
780 self.pointer_logical_pos.set(Some(pos));
781 egui_input.events.push(egui::Event::PointerMoved(pos));
782 }
783 baseview::MouseEvent::ButtonPressed { button, modifiers } => {
784 update_modifiers(&self.modifiers, modifiers, &mut egui_input);
785
786 if let Some(pos) = self.pointer_logical_pos.get()
787 && let Some(button) = crate::translate::translate_mouse_button(*button)
788 {
789 egui_input.events.push(egui::Event::PointerButton {
790 pos,
791 button,
792 pressed: true,
793 modifiers: self.modifiers.get(),
794 });
795 }
796 }
797 baseview::MouseEvent::ButtonReleased { button, modifiers } => {
798 update_modifiers(&self.modifiers, modifiers, &mut egui_input);
799
800 if let Some(pos) = self.pointer_logical_pos.get()
801 && let Some(button) = crate::translate::translate_mouse_button(*button)
802 {
803 egui_input.events.push(egui::Event::PointerButton {
804 pos,
805 button,
806 pressed: false,
807 modifiers: self.modifiers.get(),
808 });
809 }
810 }
811 baseview::MouseEvent::WheelScrolled {
812 delta: scroll_delta,
813 modifiers,
814 } => {
815 update_modifiers(&self.modifiers, modifiers, &mut egui_input);
816
817 #[allow(unused_mut)]
818 let (unit, mut delta) = match scroll_delta {
819 baseview::ScrollDelta::Lines { x, y } => {
820 (egui::MouseWheelUnit::Line, egui::vec2(*x, *y))
821 }
822
823 baseview::ScrollDelta::Pixels { x, y } => (
824 egui::MouseWheelUnit::Point,
825 egui::vec2(*x, *y) * self.window.scale_factor() as f32,
826 ),
827 };
828
829 if cfg!(target_os = "macos") {
830 delta.x *= -1.0;
835 }
836
837 egui_input.events.push(egui::Event::MouseWheel {
838 unit,
839 delta,
840 modifiers: self.modifiers.get(),
841 phase: egui::TouchPhase::Move,
842 source: egui::MouseWheelSource::Unknown,
843 });
844 }
845 baseview::MouseEvent::CursorLeft => {
846 self.pointer_logical_pos.set(None);
847 egui_input.events.push(egui::Event::PointerGone);
848 }
849 _ => do_repaint = false,
850 },
851 baseview::Event::Keyboard(event) => {
852 update_modifiers(&self.modifiers, &event.modifiers, &mut egui_input);
853
854 let pressed = event.state == keyboard_types::KeyState::Down;
855
856 let modifiers = self.modifiers.get();
857
858 if let Some(key) = crate::translate::translate_virtual_key(&event.key) {
859 egui_input.events.push(egui::Event::Key {
860 key,
861 physical_key: None,
862 pressed,
863 repeat: event.repeat,
864 modifiers,
865 });
866 }
867
868 if pressed {
869 if is_cut_command(modifiers, event.code) {
874 egui_input.events.push(egui::Event::Cut);
875 } else if is_copy_command(modifiers, event.code) {
876 egui_input.events.push(egui::Event::Copy);
877 } else if is_paste_command(modifiers, event.code) {
878 if let Some(clipboard_ctx) = self.inner.borrow_mut().clipboard_ctx.as_mut()
879 {
880 match clipboard_ctx.get_contents() {
881 Ok(contents) => egui_input.events.push(egui::Event::Text(contents)),
882 Err(err) => {
883 #[cfg(any(feature = "tracing", feature = "log"))]
884 error!("Paste error: {}", err);
885
886 #[cfg(not(any(feature = "tracing", feature = "log")))]
887 let _ = err;
888 }
889 }
890 }
891 } else if let keyboard_types::Key::Character(written) = &event.key
892 && !modifiers.ctrl
893 && !modifiers.command
894 {
895 egui_input.events.push(egui::Event::Text(written.clone()));
896 }
897 }
898
899 match &self.inner.borrow().frame.key_capture {
900 KeyCapture::CaptureAll => {}
901 KeyCapture::IgnoreAll => return_status = EventStatus::Ignored,
902 KeyCapture::CaptureKeys(keys) => {
903 if !keys.contains(&event.key) {
904 return_status = EventStatus::Ignored
905 }
906 }
907 KeyCapture::IgnoreKeys(keys) => {
908 if keys.contains(&event.key) {
909 return_status = EventStatus::Ignored
910 }
911 }
912 }
913 }
914 baseview::Event::Window(event) => match event {
915 baseview::WindowEvent::Focused => {
916 egui_input.events.push(egui::Event::WindowFocused(true));
917 egui_input
918 .viewports
919 .get_mut(&self.viewport_id)
920 .unwrap()
921 .focused = Some(true);
922 }
923 baseview::WindowEvent::Unfocused => {
924 egui_input.events.push(egui::Event::WindowFocused(false));
925 egui_input
926 .viewports
927 .get_mut(&self.viewport_id)
928 .unwrap()
929 .focused = Some(false);
930 }
931 baseview::WindowEvent::WillClose => {}
932 _ => {}
933 },
934 _ => do_repaint = false,
935 }
936
937 if do_repaint {
938 self.window.request_redraw();
939 }
940
941 match &event {
944 baseview::Event::Keyboard(_) => {
945 let egui_ctx = &self.inner.borrow().egui_ctx;
946 if return_status == EventStatus::Captured && !egui_ctx.egui_wants_keyboard_input() {
947 EventStatus::Ignored
948 } else {
949 return_status
950 }
951 }
952 baseview::Event::Mouse(_) => {
953 let egui_ctx = &self.inner.borrow().egui_ctx;
954 if egui_ctx.egui_is_using_pointer() || egui_ctx.egui_wants_pointer_input() {
955 EventStatus::Captured
956 } else {
957 EventStatus::Ignored
958 }
959 }
960 baseview::Event::Window(_) => EventStatus::Captured,
961 _ => EventStatus::Ignored,
962 }
963 }
964
965 fn on_timer(&self, timer: &baseview::TimerHandle) {
966 if timer == self.poll_repaint_timer_handle {
967 self.poll_repaint_timer();
968 }
969 }
970}
971
972fn is_cut_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
973 (modifiers.command && keycode == keyboard_types::Code::KeyX)
974 || (cfg!(target_os = "windows")
975 && modifiers.shift
976 && keycode == keyboard_types::Code::Delete)
977}
978
979fn is_copy_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
980 (modifiers.command && keycode == keyboard_types::Code::KeyC)
981 || (cfg!(target_os = "windows")
982 && modifiers.ctrl
983 && keycode == keyboard_types::Code::Insert)
984}
985
986fn is_paste_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
987 (modifiers.command && keycode == keyboard_types::Code::KeyV)
988 || (cfg!(target_os = "windows")
989 && modifiers.shift
990 && keycode == keyboard_types::Code::Insert)
991}