Skip to main content

egui_baseview/
window.rs

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