Skip to main content

egui_baseview/
window.rs

1use std::cell::{Cell, RefCell};
2use std::time::Instant;
3
4use baseview::dpi::{LogicalPosition, LogicalSize, Size};
5use baseview::{
6    Event, EventStatus, Window, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions,
7    WindowScalePolicy, WindowSize,
8};
9use copypasta::ClipboardProvider;
10use egui::{FullOutput, Pos2, Rect, Rgba, ViewportCommand, ViewportOutput, pos2, vec2};
11use keyboard_types::Modifiers;
12use raw_window_handle::HasWindowHandle;
13
14use crate::{GraphicsConfig, renderer::Renderer};
15
16#[cfg(feature = "nice-log")]
17use nice_plug_core::{nice_error as error, nice_warn as warn};
18#[cfg(all(feature = "tracing", not(feature = "nice-log")))]
19use tracing::{error, warn};
20
21#[derive(Debug, Clone)]
22pub struct EguiWindowSettings {
23    pub title: String,
24
25    /// The size of the window
26    pub size: Size,
27
28    /// The dpi scaling policy
29    pub scale_policy: WindowScalePolicy,
30
31    pub graphics: GraphicsConfig,
32}
33
34impl EguiWindowSettings {
35    #[inline]
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    #[inline]
41    pub fn with_tile(mut self, title: impl Into<String>) -> Self {
42        self.title = title.into();
43        self
44    }
45
46    #[inline]
47    pub fn with_size(mut self, size: impl Into<Size>) -> Self {
48        self.size = size.into();
49        self
50    }
51
52    #[inline]
53    pub fn with_scale_policy(mut self, scale_policy: impl Into<WindowScalePolicy>) -> Self {
54        self.scale_policy = scale_policy.into();
55        self
56    }
57
58    #[inline]
59    pub fn with_graphics_config(mut self, config: GraphicsConfig) -> Self {
60        self.graphics = config;
61        self
62    }
63}
64
65impl Default for EguiWindowSettings {
66    fn default() -> Self {
67        Self {
68            title: String::new(),
69            size: Size::Logical(LogicalSize {
70                width: 300.0,
71                height: 200.0,
72            }),
73            scale_policy: WindowScalePolicy::default(),
74            graphics: GraphicsConfig::default(),
75        }
76    }
77}
78
79/// Extra egui-baseview related output commands.
80pub struct ExtraOutputCommands {
81    clear_color: Option<Rgba>,
82    key_capture: Option<KeyCapture>,
83}
84
85impl ExtraOutputCommands {
86    pub(crate) fn new() -> Self {
87        Self {
88            clear_color: None,
89            key_capture: None,
90        }
91    }
92
93    /// Set the clear color of the renderer.
94    pub fn clear_color(&mut self, clear_color: Rgba) {
95        self.clear_color = Some(clear_color);
96    }
97
98    /// Set how to handle capturing key events from the host.
99    pub fn set_key_capture(&mut self, key_capture: KeyCapture) {
100        self.key_capture = Some(key_capture);
101    }
102}
103
104/// Describes how to handle capturing key events from the host.
105#[derive(Default, Debug, Clone, PartialEq)]
106pub enum KeyCapture {
107    #[default]
108    /// All keys will be captured from the host.
109    CaptureAll,
110    /// No keys will be captured from the host.
111    IgnoreAll,
112    /// Only the given keys will be captured from the host.
113    CaptureKeys(Vec<keyboard_types::Key>),
114    /// All keys except the given ones will be captured from the host.
115    IgnoreKeys(Vec<keyboard_types::Key>),
116}
117
118struct EguiWindowInner<State, U, O>
119where
120    State: 'static + Send,
121    U: FnMut(&mut egui::Ui, &mut ExtraOutputCommands, &mut State),
122    U: 'static + Send,
123    O: FnMut(&FullOutput, &ViewportOutput, &mut State),
124    O: 'static + Send,
125{
126    user_state: State,
127    user_update: U,
128    user_output: O,
129    egui_ctx: egui::Context,
130    clipboard_ctx: Option<copypasta::ClipboardContext>,
131    renderer: Renderer,
132    clear_color: Rgba,
133    key_capture: KeyCapture,
134}
135
136/// Handles an egui-baseview application
137pub struct EguiWindow<State, U, O>
138where
139    State: 'static + Send,
140    U: FnMut(&mut egui::Ui, &mut ExtraOutputCommands, &mut State),
141    U: 'static + Send,
142    O: FnMut(&FullOutput, &ViewportOutput, &mut State),
143    O: 'static + Send,
144{
145    inner: RefCell<EguiWindowInner<State, U, O>>,
146    egui_input: RefCell<egui::RawInput>,
147    viewport_id: egui::ViewportId,
148    start_time: Instant,
149    scale_factor: Cell<f64>,
150    pointer_logical_pos: Cell<Option<egui::Pos2>>,
151    current_cursor_icon: Cell<baseview::MouseCursor>,
152    repaint_after: Cell<Option<Instant>>,
153
154    pub window: WindowContext,
155}
156
157impl<State, U, O> EguiWindow<State, U, O>
158where
159    State: 'static + Send,
160    U: FnMut(&mut egui::Ui, &mut ExtraOutputCommands, &mut State),
161    U: 'static + Send,
162    O: FnMut(&FullOutput, &ViewportOutput, &mut State),
163    O: 'static + Send,
164{
165    fn new<B>(
166        window: WindowContext,
167        title: String,
168        graphics_config: GraphicsConfig,
169        mut build: B,
170        output: O,
171        update: U,
172        mut state: State,
173    ) -> EguiWindow<State, U, O>
174    where
175        B: FnMut(&egui::Context, &mut ExtraOutputCommands, &mut State),
176        B: 'static + Send,
177    {
178        let renderer = Renderer::new(window.clone(), graphics_config).unwrap_or_else(|err| {
179            // TODO: better error log and not panicking, but that's gonna require baseview changes
180            error!("oops! the gpu backend couldn't initialize! \n {err}");
181            panic!("gpu backend failed to initialize: \n {err}")
182        });
183        let egui_ctx = egui::Context::default();
184
185        let size = window.size();
186
187        let screen_rect = logical_screen_rect(size);
188        let scale_factor = size.scale_factor;
189
190        let viewport_info = egui::ViewportInfo {
191            parent: None,
192            title: Some(title),
193            native_pixels_per_point: Some(scale_factor as f32),
194            focused: Some(true),
195            inner_rect: Some(screen_rect),
196            ..Default::default()
197        };
198        let viewport_id = egui::ViewportId::default();
199
200        let mut egui_input = egui::RawInput {
201            max_texture_side: Some(renderer.max_texture_side()),
202            screen_rect: Some(screen_rect),
203            ..Default::default()
204        };
205        let _ = egui_input.viewports.insert(viewport_id, viewport_info);
206
207        let mut commands = ExtraOutputCommands::new();
208
209        (build)(&egui_ctx, &mut commands, &mut state);
210
211        let clipboard_ctx = match copypasta::ClipboardContext::new() {
212            Ok(clipboard_ctx) => Some(clipboard_ctx),
213            Err(e) => {
214                error!("Failed to initialize clipboard: {}", e);
215                None
216            }
217        };
218
219        let start_time = Instant::now();
220
221        Self {
222            inner: RefCell::new(EguiWindowInner {
223                user_state: state,
224                user_update: update,
225                user_output: output,
226                egui_ctx,
227                clipboard_ctx,
228                renderer,
229                clear_color: commands.clear_color.unwrap_or(Rgba::BLACK),
230                key_capture: commands.key_capture.unwrap_or_default(),
231            }),
232            viewport_id,
233            start_time,
234            egui_input: egui_input.into(),
235            pointer_logical_pos: None.into(),
236            current_cursor_icon: baseview::MouseCursor::Default.into(),
237            scale_factor: scale_factor.into(),
238            repaint_after: Some(start_time).into(),
239            window,
240        }
241    }
242
243    /// Open a new child window.
244    ///
245    /// * `parent` - The parent window.
246    /// * `settings` - The settings of the window.
247    /// * `state` - The initial state of your application.
248    /// * `build` - Called once before the first frame. Allows you to do setup code and to
249    ///   call `ctx.set_fonts()`. Optional.
250    /// * `output` - Called after each `update`. Can be used to read egui's output commands to
251    ///   perform actions, i.e. asking the host to resize the window if a command to resize
252    ///   the window is present. Optional.
253    /// * `update` - Called before each frame. Here you should update the state of your
254    ///   application and build the UI.
255    pub fn open_parented<P, B>(
256        parent: &P,
257        settings: EguiWindowSettings,
258        state: State,
259        build: B,
260        output: O,
261        update: U,
262    ) -> WindowHandle
263    where
264        P: HasWindowHandle,
265        B: FnMut(&egui::Context, &mut ExtraOutputCommands, &mut State),
266        B: 'static + Send,
267    {
268        let options = WindowOpenOptions::new()
269            .with_title(settings.title.clone())
270            .with_size(settings.size)
271            .with_scale_policy(settings.scale_policy);
272
273        #[cfg(feature = "opengl")]
274        let options = { options.with_gl_config(Some(settings.graphics.gl_config.clone())) };
275
276        Window::open_parented(parent, options, move |window| -> EguiWindow<State, U, O> {
277            EguiWindow::new(
278                window,
279                settings.title,
280                settings.graphics,
281                build,
282                output,
283                update,
284                state,
285            )
286        })
287    }
288
289    /// Open a new window that blocks the current thread until the window is destroyed.
290    ///
291    /// * `settings` - The settings of the window.
292    /// * `state` - The initial state of your application.
293    /// * `build` - Called once before the first frame. Allows you to do setup code and to
294    ///   call `ctx.set_fonts()`. Optional.
295    /// * `output` - Called after each `update`. Can be used to read egui's output commands to
296    ///   perform actions, i.e. asking the host to resize the window if a command to resize
297    ///   the window is present. Optional.
298    /// * `update` - Called before each frame. Here you should update the state of your
299    ///   application and build the UI.
300    pub fn open_blocking<B>(
301        settings: EguiWindowSettings,
302        state: State,
303        build: B,
304        output: O,
305        update: U,
306    ) where
307        B: FnMut(&egui::Context, &mut ExtraOutputCommands, &mut State),
308        B: 'static + Send,
309    {
310        let options = WindowOpenOptions::new()
311            .with_title(settings.title.clone())
312            .with_size(settings.size)
313            .with_scale_policy(settings.scale_policy);
314
315        #[cfg(feature = "opengl")]
316        let options = { options.with_gl_config(Some(settings.graphics.gl_config.clone())) };
317
318        Window::open_blocking(options, move |window| -> EguiWindow<State, U, O> {
319            EguiWindow::new(
320                window,
321                settings.title,
322                settings.graphics,
323                build,
324                output,
325                update,
326                state,
327            )
328        })
329    }
330
331    /// Update the pressed key modifiers when a mouse event has sent a new set of modifiers.
332    fn update_modifiers(&self, modifiers: &Modifiers) {
333        let mut egui_input = self.egui_input.borrow_mut();
334        egui_input.modifiers.alt = !(*modifiers & Modifiers::ALT).is_empty();
335        egui_input.modifiers.shift = !(*modifiers & Modifiers::SHIFT).is_empty();
336        egui_input.modifiers.command = !(*modifiers & Modifiers::CONTROL).is_empty();
337    }
338}
339
340impl<State, U, O> WindowHandler for EguiWindow<State, U, O>
341where
342    State: 'static + Send,
343    U: FnMut(&mut egui::Ui, &mut ExtraOutputCommands, &mut State),
344    U: 'static + Send,
345    O: FnMut(&FullOutput, &ViewportOutput, &mut State),
346    O: 'static + Send,
347{
348    fn on_frame(&self) {
349        let egui_input = {
350            let mut egui_input = self.egui_input.borrow_mut();
351            egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
352            egui_input.screen_rect = Some(logical_screen_rect(self.window.size()));
353            egui_input.take()
354        };
355
356        let mut full_output = {
357            let mut extra_commands = ExtraOutputCommands::new();
358
359            let mut inner = self.inner.borrow_mut();
360            let EguiWindowInner {
361                user_state,
362                user_update,
363                user_output,
364                egui_ctx,
365                clipboard_ctx: _,
366                renderer: _,
367                clear_color,
368                key_capture,
369            } = &mut *inner;
370
371            let output = egui_ctx.run_ui(egui_input, |ui| {
372                user_update(ui, &mut extra_commands, user_state)
373            });
374
375            if let Some(c) = extra_commands.clear_color.take() {
376                *clear_color = c;
377            }
378            if let Some(k) = extra_commands.key_capture.take() {
379                *key_capture = k;
380            }
381
382            if let Some(viewport_output) = output.viewport_output.get(&self.viewport_id) {
383                user_output(&output, viewport_output, user_state);
384            }
385
386            output
387        };
388
389        let Some(viewport_output) = full_output.viewport_output.get(&self.viewport_id) else {
390            // The main window was closed by egui.
391            self.window.request_close();
392            return;
393        };
394
395        for command in viewport_output.commands.iter() {
396            match command {
397                ViewportCommand::Close => {
398                    self.window.request_close();
399                }
400                ViewportCommand::InnerSize(size) => self.window.resize(LogicalSize {
401                    width: size.x.max(1.0),
402                    height: size.y.max(1.0),
403                }),
404                ViewportCommand::Focus => {
405                    self.window.focus();
406                }
407                _ => {}
408            }
409        }
410
411        {
412            let mut inner = self.inner.borrow_mut();
413            let EguiWindowInner {
414                user_state: _,
415                user_update: _,
416                user_output: _,
417                egui_ctx,
418                clipboard_ctx,
419                renderer,
420                clear_color: bg_color,
421                key_capture: _,
422            } = &mut *inner;
423
424            let now = Instant::now();
425            let do_repaint_now = if let Some(t) = self.repaint_after.get() {
426                now >= t || viewport_output.repaint_delay.is_zero()
427            } else {
428                viewport_output.repaint_delay.is_zero()
429            };
430
431            if do_repaint_now {
432                let size = self.window.size();
433                renderer.render(
434                    &self.window,
435                    *bg_color,
436                    size.physical,
437                    size.scale_factor as f32,
438                    egui_ctx,
439                    &mut full_output,
440                );
441
442                self.repaint_after.set(None);
443            } else if let Some(t) = now.checked_add(viewport_output.repaint_delay) {
444                // Schedule to repaint after the requested time has elapsed.
445                self.repaint_after.set(Some(t));
446            }
447
448            for command in full_output.platform_output.commands {
449                match command {
450                    egui::OutputCommand::CopyText(text) => {
451                        if let Some(clipboard_ctx) = clipboard_ctx.as_mut()
452                            && let Err(err) = clipboard_ctx.set_contents(text)
453                        {
454                            error!("Copy/Cut error: {}", err);
455                        }
456                    }
457                    egui::OutputCommand::CopyImage(_) => {
458                        warn!("Copying images is not supported in egui_baseview.");
459                    }
460                    egui::OutputCommand::OpenUrl(open_url) => {
461                        if let Err(err) = open::that_detached(&open_url.url) {
462                            error!("Open error: {}", err);
463                        }
464                    }
465                }
466            }
467        }
468
469        let cursor_icon =
470            crate::translate::translate_cursor_icon(full_output.platform_output.cursor_icon);
471        if self.current_cursor_icon.get() != cursor_icon {
472            self.current_cursor_icon.set(cursor_icon);
473
474            self.window.set_mouse_cursor(cursor_icon);
475        }
476
477        // A temporary workaround for keyboard input not working sometimes.
478        // See https://codeberg.org/RustAudio/egui-baseview/issues/20
479        #[cfg(feature = "keyboard_focus_workaround")]
480        {
481            if !full_output.platform_output.events.is_empty()
482                || full_output.platform_output.ime.is_some()
483            {
484                window.focus();
485            }
486        }
487    }
488
489    fn resized(&self, new_size: WindowSize) {
490        let screen_rect = logical_screen_rect(new_size);
491
492        let mut egui_input = self.egui_input.borrow_mut();
493
494        egui_input.screen_rect = Some(screen_rect);
495
496        let viewport_info = egui_input.viewports.get_mut(&self.viewport_id).unwrap();
497        viewport_info.native_pixels_per_point = Some(new_size.scale_factor as f32);
498        viewport_info.inner_rect = Some(screen_rect);
499
500        // Schedule to repaint on the next frame.
501        self.repaint_after.set(Some(Instant::now()));
502
503        self.scale_factor.set(new_size.scale_factor);
504    }
505
506    fn on_event(&self, event: Event) -> EventStatus {
507        let mut return_status = EventStatus::Captured;
508
509        // Parent/embedded windows do not always gain keyboard focus
510        // Automatically on click. Request focus explicitly before forwarding the event.
511        //
512        // TODO: Check if this is still necessary.
513        if matches!(
514            event,
515            baseview::Event::Mouse(baseview::MouseEvent::ButtonPressed { .. })
516        ) && !self.window.has_focus()
517        {
518            self.window.focus();
519        }
520
521        match &event {
522            baseview::Event::Mouse(event) => match event {
523                baseview::MouseEvent::CursorMoved {
524                    position,
525                    modifiers,
526                } => {
527                    self.update_modifiers(modifiers);
528
529                    let logical_pos: LogicalPosition<f32> =
530                        position.to_logical(self.scale_factor.get());
531                    let pos = pos2(logical_pos.x, logical_pos.y);
532
533                    self.pointer_logical_pos.set(Some(pos));
534                    self.egui_input
535                        .borrow_mut()
536                        .events
537                        .push(egui::Event::PointerMoved(pos));
538                }
539                baseview::MouseEvent::ButtonPressed { button, modifiers } => {
540                    self.update_modifiers(modifiers);
541
542                    if let Some(pos) = self.pointer_logical_pos.get()
543                        && let Some(button) = crate::translate::translate_mouse_button(*button)
544                    {
545                        let mut egui_input = self.egui_input.borrow_mut();
546                        let modifiers = egui_input.modifiers;
547                        egui_input.events.push(egui::Event::PointerButton {
548                            pos,
549                            button,
550                            pressed: true,
551                            modifiers,
552                        });
553                    }
554                }
555                baseview::MouseEvent::ButtonReleased { button, modifiers } => {
556                    self.update_modifiers(modifiers);
557
558                    if let Some(pos) = self.pointer_logical_pos.get()
559                        && let Some(button) = crate::translate::translate_mouse_button(*button)
560                    {
561                        let mut egui_input = self.egui_input.borrow_mut();
562                        let modifiers = egui_input.modifiers;
563                        egui_input.events.push(egui::Event::PointerButton {
564                            pos,
565                            button,
566                            pressed: false,
567                            modifiers,
568                        });
569                    }
570                }
571                baseview::MouseEvent::WheelScrolled {
572                    delta: scroll_delta,
573                    modifiers,
574                } => {
575                    self.update_modifiers(modifiers);
576
577                    #[allow(unused_mut)]
578                    let (unit, mut delta) = match scroll_delta {
579                        baseview::ScrollDelta::Lines { x, y } => {
580                            (egui::MouseWheelUnit::Line, egui::vec2(*x, *y))
581                        }
582
583                        baseview::ScrollDelta::Pixels { x, y } => (
584                            egui::MouseWheelUnit::Point,
585                            egui::vec2(*x, *y) * self.window.scale_factor() as f32,
586                        ),
587                    };
588
589                    if cfg!(target_os = "macos") {
590                        // This is still buggy in winit despite
591                        // https://github.com/rust-windowing/winit/issues/1695 being closed
592                        //
593                        // TODO: See if this is an issue in baseview as well.
594                        delta.x *= -1.0;
595                    }
596
597                    let mut egui_input = self.egui_input.borrow_mut();
598                    let modifiers = egui_input.modifiers;
599                    egui_input.events.push(egui::Event::MouseWheel {
600                        unit,
601                        delta,
602                        modifiers,
603                        phase: egui::TouchPhase::Move,
604                    });
605                }
606                baseview::MouseEvent::CursorLeft => {
607                    self.pointer_logical_pos.set(None);
608                    self.egui_input
609                        .borrow_mut()
610                        .events
611                        .push(egui::Event::PointerGone);
612                }
613                _ => {}
614            },
615            baseview::Event::Keyboard(event) => {
616                use keyboard_types::Code;
617
618                let pressed = event.state == keyboard_types::KeyState::Down;
619                let mut egui_input = self.egui_input.borrow_mut();
620
621                match event.code {
622                    Code::ShiftLeft | Code::ShiftRight => egui_input.modifiers.shift = pressed,
623                    Code::ControlLeft | Code::ControlRight => {
624                        egui_input.modifiers.ctrl = pressed;
625
626                        #[cfg(not(target_os = "macos"))]
627                        {
628                            egui_input.modifiers.command = pressed;
629                        }
630                    }
631                    Code::AltLeft | Code::AltRight => egui_input.modifiers.alt = pressed,
632                    Code::MetaLeft | Code::MetaRight => {
633                        #[cfg(target_os = "macos")]
634                        {
635                            egui_input.modifiers.mac_cmd = pressed;
636                            egui_input.modifiers.command = pressed;
637                        }
638                        // prevent `rustfmt` from breaking this
639                    }
640                    _ => (),
641                }
642
643                if let Some(key) = crate::translate::translate_virtual_key(&event.key) {
644                    let modifiers = egui_input.modifiers;
645                    egui_input.events.push(egui::Event::Key {
646                        key,
647                        physical_key: None,
648                        pressed,
649                        repeat: event.repeat,
650                        modifiers,
651                    });
652                }
653
654                if pressed {
655                    // VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
656                    // so we detect these things manually:
657                    //
658                    // TODO: See if this is an issue in baseview as well.
659                    if is_cut_command(egui_input.modifiers, event.code) {
660                        egui_input.events.push(egui::Event::Cut);
661                    } else if is_copy_command(egui_input.modifiers, event.code) {
662                        egui_input.events.push(egui::Event::Copy);
663                    } else if is_paste_command(egui_input.modifiers, event.code) {
664                        if let Some(clipboard_ctx) = self.inner.borrow_mut().clipboard_ctx.as_mut()
665                        {
666                            match clipboard_ctx.get_contents() {
667                                Ok(contents) => egui_input.events.push(egui::Event::Text(contents)),
668                                Err(err) => {
669                                    error!("Paste error: {}", err);
670                                }
671                            }
672                        }
673                    } else if let keyboard_types::Key::Character(written) = &event.key
674                        && !egui_input.modifiers.ctrl
675                        && !egui_input.modifiers.command
676                    {
677                        egui_input.events.push(egui::Event::Text(written.clone()));
678                    }
679                }
680
681                match &self.inner.borrow().key_capture {
682                    KeyCapture::CaptureAll => {}
683                    KeyCapture::IgnoreAll => return_status = EventStatus::Ignored,
684                    KeyCapture::CaptureKeys(keys) => {
685                        if !keys.contains(&event.key) {
686                            return_status = EventStatus::Ignored
687                        }
688                    }
689                    KeyCapture::IgnoreKeys(keys) => {
690                        if keys.contains(&event.key) {
691                            return_status = EventStatus::Ignored
692                        }
693                    }
694                }
695            }
696            baseview::Event::Window(event) => match event {
697                baseview::WindowEvent::Focused => {
698                    let mut egui_input = self.egui_input.borrow_mut();
699                    egui_input.events.push(egui::Event::WindowFocused(true));
700                    egui_input
701                        .viewports
702                        .get_mut(&self.viewport_id)
703                        .unwrap()
704                        .focused = Some(true);
705                }
706                baseview::WindowEvent::Unfocused => {
707                    let mut egui_input = self.egui_input.borrow_mut();
708                    egui_input.events.push(egui::Event::WindowFocused(false));
709                    egui_input
710                        .viewports
711                        .get_mut(&self.viewport_id)
712                        .unwrap()
713                        .focused = Some(false);
714                }
715                baseview::WindowEvent::WillClose => {}
716                _ => {}
717            },
718            _ => {}
719        }
720
721        // For keyboard events, also check if egui actually wants keyboard input
722        // This allows DAW shortcuts (spacebar, etc.) to pass through when no text field is focused
723        match &event {
724            baseview::Event::Keyboard(_) => {
725                let egui_ctx = &self.inner.borrow().egui_ctx;
726                if return_status == EventStatus::Captured && !egui_ctx.egui_wants_keyboard_input() {
727                    EventStatus::Ignored
728                } else {
729                    return_status
730                }
731            }
732            baseview::Event::Mouse(_) => {
733                let egui_ctx = &self.inner.borrow().egui_ctx;
734                if egui_ctx.egui_is_using_pointer() || egui_ctx.egui_wants_pointer_input() {
735                    EventStatus::Captured
736                } else {
737                    EventStatus::Ignored
738                }
739            }
740            baseview::Event::Window(_) => EventStatus::Captured,
741            _ => EventStatus::Ignored,
742        }
743    }
744}
745
746fn is_cut_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
747    (modifiers.command && keycode == keyboard_types::Code::KeyX)
748        || (cfg!(target_os = "windows")
749            && modifiers.shift
750            && keycode == keyboard_types::Code::Delete)
751}
752
753fn is_copy_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
754    (modifiers.command && keycode == keyboard_types::Code::KeyC)
755        || (cfg!(target_os = "windows")
756            && modifiers.ctrl
757            && keycode == keyboard_types::Code::Insert)
758}
759
760fn is_paste_command(modifiers: egui::Modifiers, keycode: keyboard_types::Code) -> bool {
761    (modifiers.command && keycode == keyboard_types::Code::KeyV)
762        || (cfg!(target_os = "windows")
763            && modifiers.shift
764            && keycode == keyboard_types::Code::Insert)
765}
766
767/// Calculate screen rectangle in logical size.
768fn logical_screen_rect(size: WindowSize) -> Rect {
769    Rect::from_min_size(
770        Pos2::new(0f32, 0f32),
771        vec2(size.logical.width as f32, size.logical.height as f32),
772    )
773}