vizia_baseview/
application.rs

1use crate::window::create_surface;
2use crate::window::ViziaWindow;
3use baseview::{Window, WindowHandle, WindowScalePolicy};
4use gl_rs as gl;
5use gl_rs::types::GLint;
6use raw_window_handle::HasRawWindowHandle;
7use skia_safe::gpu::gl::FramebufferInfo;
8use vizia_core::events::EventManager;
9
10use crate::proxy::queue_get;
11use vizia_core::backend::*;
12use vizia_core::prelude::*;
13
14#[derive(Debug)]
15pub enum ApplicationError {}
16
17///Creating a new application creates a root `Window` and a `Context`. Views declared within the closure passed to `Application::new()` are added to the context and rendered into the root window.
18///
19/// # Example
20/// ```no_run
21/// # use vizia_core::prelude::*;
22/// # use vizia_baseview::Application;
23///
24/// Application::new(|cx|{
25///    // Content goes here
26/// })
27/// .run();
28///```
29/// Calling `run()` on the `Application` causes the program to enter the event loop and for the main window to display.
30pub struct Application<F>
31where
32    F: Fn(&mut Context) + Send + 'static,
33{
34    app: F,
35    window_description: WindowDescription,
36    window_scale_policy: WindowScalePolicy,
37    on_idle: Option<Box<dyn Fn(&mut Context) + Send>>,
38    ignore_default_theme: bool,
39}
40
41impl<F> Application<F>
42where
43    F: Fn(&mut Context),
44    F: 'static + Send,
45{
46    pub fn new(app: F) -> Self {
47        Self {
48            app,
49            window_description: WindowDescription::new(),
50            window_scale_policy: WindowScalePolicy::SystemScaleFactor,
51            on_idle: None,
52            ignore_default_theme: false,
53        }
54    }
55
56    /// Sets the default built-in theming to be ignored.
57    pub fn ignore_default_theme(mut self) -> Self {
58        self.ignore_default_theme = true;
59        self
60    }
61
62    /// Change the window's scale policy. Not part of [`new()`][Self::new] to keep the same
63    /// signature as the winit backend. This should only be used for HiDPI scaling, use
64    /// [`WindowDescription::scale_factor`] to set a separate arbitrary scale factor.
65    pub fn with_scale_policy(mut self, scale_policy: WindowScalePolicy) -> Self {
66        self.window_scale_policy = scale_policy;
67        self
68    }
69
70    pub fn title(mut self, title: &str) -> Self {
71        self.window_description.title = title.to_owned();
72
73        self
74    }
75
76    pub fn inner_size(mut self, size: impl Into<WindowSize>) -> Self {
77        self.window_description.inner_size = size.into();
78
79        self
80    }
81
82    /// A scale factor applied on top of any DPI scaling, defaults to 1.0.
83    pub fn user_scale_factor(mut self, factor: f64) -> Self {
84        self.window_description.user_scale_factor = factor;
85
86        self
87    }
88
89    /// Open a new window that blocks the current thread until the window is destroyed.
90    ///
91    /// Do **not** use this in the context of audio plugins, unless it is compiled as a
92    /// standalone application.
93    ///
94    /// * `app` - The Vizia application builder.
95    pub fn run(self) -> Result<(), ApplicationError> {
96        ViziaWindow::open_blocking(
97            self.window_description,
98            self.window_scale_policy,
99            self.app,
100            self.on_idle,
101            self.ignore_default_theme,
102        );
103
104        Ok(())
105    }
106
107    /// Open a new child window.
108    ///
109    /// This function does **not** block the current thread. This is only to be
110    /// used in the context of audio plugins.
111    ///
112    /// * `parent` - The parent window.
113    /// * `app` - The Vizia application builder.
114    pub fn open_parented<P: HasRawWindowHandle>(self, parent: &P) -> WindowHandle {
115        ViziaWindow::open_parented(
116            parent,
117            self.window_description,
118            self.window_scale_policy,
119            self.app,
120            self.on_idle,
121            self.ignore_default_theme,
122        )
123    }
124
125    /// Takes a closure which will be called at the end of every loop of the application.
126    ///
127    /// The callback provides a place to run 'idle' processing and happens at the end of each loop but before drawing.
128    /// If the callback pushes events into the queue in context then the event loop will re-run. Care must be taken not to
129    /// push events into the queue every time the callback runs unless this is intended.
130    ///
131    /// # Example
132    /// ```no_run
133    /// # use vizia_core::prelude::*;
134    /// # use vizia_baseview::Application;
135    /// Application::new(|cx|{
136    ///     // Build application here
137    /// })
138    /// .on_idle(|cx|{
139    ///     // Code here runs at the end of every event loop after OS and vizia events have been handled
140    /// })
141    /// .run();
142    /// ```
143    pub fn on_idle<I: 'static + Fn(&mut Context) + Send>(mut self, callback: I) -> Self {
144        self.on_idle = Some(Box::new(callback));
145
146        self
147    }
148}
149
150pub(crate) struct ApplicationRunner {
151    cx: BackendContext,
152    event_manager: EventManager,
153    pub gr_context: skia_safe::gpu::DirectContext,
154    should_redraw: bool,
155
156    /// If this is set to `true`, then `window_scale_factor` will be updated during
157    /// [`baseview::WindowEvent::Resized`] events in accordance to the system's reported DPI. This
158    /// can change at runtime when the window is dragged between displays. Otherwise
159    /// `window_scale_factor` will not change.
160    use_system_scaling: bool,
161    /// The scale factor for the window itself. This is either determined by either the operating
162    /// system or explicitly overridden by the creator of the window. In some cases window resize
163    /// events may change this scaling policy. This value is only used when translating logical
164    /// mouse coordinates to physical window coordinates. For any other use within VIZIA itself this
165    /// always needs to be multiplied by `user_scale_factor`.
166    window_scale_factor: f64,
167    // /// The scale factor applied on top of the `window_scale` to convert the window's logical size
168    // /// to a physical size. If this is different from `*cx.user_scale_factor` after handling the
169    // /// events then the window will be resized.
170    // current_user_scale_factor: f64,
171    // /// The window's current logical size, before `user_scale_factor` has been applied. Needed to
172    // /// resize the window when changing the scale factor.
173    // current_window_size: WindowSize,
174    pub surface: skia_safe::Surface,
175    pub dirty_surface: skia_safe::Surface,
176    window_description: WindowDescription,
177    is_initialized: bool,
178}
179
180impl ApplicationRunner {
181    pub fn new(
182        cx: BackendContext,
183        gr_context: skia_safe::gpu::DirectContext,
184        use_system_scaling: bool,
185        window_scale_factor: f64,
186        surface: skia_safe::Surface,
187        dirty_surface: skia_safe::Surface,
188        window_description: WindowDescription,
189    ) -> Self {
190        ApplicationRunner {
191            should_redraw: true,
192            gr_context,
193            event_manager: EventManager::new(),
194            use_system_scaling,
195            window_scale_factor,
196            //current_user_scale_factor: cx.user_scale_factor(),
197            //current_window_size: *cx.window_size(),
198            cx,
199            surface,
200            dirty_surface,
201            window_description,
202            is_initialized: false,
203        }
204    }
205
206    /// Handle all reactivity within a frame. The window instance is used to resize the window when
207    /// needed.
208    pub fn on_frame_update(&mut self, window: &mut Window) {
209        while let Some(event) = queue_get() {
210            self.cx.send_event(event);
211        }
212
213        // Events
214        self.event_manager.flush_events(self.cx.context(), |window_event| match window_event {
215            // For some reason calling window.close() crashes baseview on macos
216            // WindowEvent::WindowClose => *should_close = true,
217            WindowEvent::FocusIn => {
218                #[cfg(not(target_os = "linux"))] // not implemented for linux yet
219                if !window.has_focus() {
220                    window.focus();
221                }
222            }
223            _ => {}
224        });
225
226        // We need to resize the window to make sure that the new size is applied. This is a workaround
227        // for the fact that baseview does not resize the window when the scale factor changes.
228        if !self.is_initialized {
229            // Resizing the window doesn't apply unless the size has actually changed.
230            // So we resize the window slightly larger and then back again to force a resize event.
231            window.resize(baseview::Size {
232                width: self.window_description.inner_size.width as f64 + 1.0,
233                height: self.window_description.inner_size.height as f64 + 1.0,
234            });
235
236            window.resize(baseview::Size {
237                width: self.window_description.inner_size.width as f64,
238                height: self.window_description.inner_size.height as f64,
239            });
240            self.is_initialized = true;
241        }
242
243        // if *cx.window_size() != self.current_window_size
244        //     || cx.user_scale_factor() != self.current_user_scale_factor
245        // {
246        //     self.current_window_size = *cx.window_size();
247        //     self.current_user_scale_factor = cx.user_scale_factor();
248
249        //     // The user scale factor is not part of the HiDPI scaling, so baseview should treat it
250        //     // as part of our logical size
251        //     window.resize(baseview::Size {
252        //         width: self.current_window_size.width as f64 * self.current_user_scale_factor,
253        //         height: self.current_window_size.height as f64 * self.current_user_scale_factor,
254        //     });
255
256        //     // TODO: These calculations are now repeated in three places, should probably be moved
257        //     //       to a function
258        //     cx.set_scale_factor(self.window_scale_factor * self.current_user_scale_factor);
259        //     let new_physical_width =
260        //         self.current_window_size.width as f32 * cx.style().scale_factor();
261        //     let new_physical_height =
262        //         self.current_window_size.height as f32 * cx.style().scale_factor();
263
264        //     cx.set_window_size(new_physical_width, new_physical_height);
265
266        //     if let Some(surface) = cx.get_surface_mut(Entity::root()) {
267        //         if new_physical_width != 0.0 || new_physical_height != 0.0 {
268        //             let fb_info = {
269        //                 let mut fboid: GLint = 0;
270        //                 unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
271
272        //                 FramebufferInfo {
273        //                     fboid: fboid.try_into().unwrap(),
274        //                     format: skia_safe::gpu::gl::Format::RGBA8.into(),
275        //                     ..Default::default()
276        //                 }
277        //             };
278
279        //             let backend_render_target = backend_render_targets::make_gl(
280        //                 (new_physical_width as i32, new_physical_height as i32),
281        //                 None,
282        //                 8,
283        //                 fb_info,
284        //             );
285
286        //             surface.0 = gpu::surfaces::wrap_backend_render_target(
287        //                 &mut self.gr_context,
288        //                 &backend_render_target,
289        //                 SurfaceOrigin::BottomLeft,
290        //                 ColorType::RGBA8888,
291        //                 None,
292        //                 None,
293        //             )
294        //             .expect("Could not create skia surface");
295
296        //             surface.1 = surface
297        //                 .0
298        //                 .new_surface_with_dimensions((
299        //                     new_physical_width.max(1.0) as i32,
300        //                     new_physical_height.max(1.0) as i32,
301        //                 ))
302        //                 .unwrap();
303        //         }
304        //     }
305
306        //     cx.needs_refresh();
307
308        //     // hmmm why are we flushing events again?
309        //     // self.event_manager.flush_events(cx.context());
310        // }
311
312        let context = window.gl_context().expect("Window was created without OpenGL support");
313        unsafe { context.make_current() };
314        self.cx.process_style_updates();
315        unsafe { context.make_not_current() };
316
317        self.cx.process_animations();
318
319        self.cx.process_visual_updates();
320
321        if self.cx.0.windows.iter().any(|(_, window_state)| !window_state.redraw_list.is_empty()) {
322            self.should_redraw = true;
323        }
324    }
325
326    pub fn render(&mut self, window: &mut Window) {
327        if self.should_redraw {
328            let context = window.gl_context().expect("Window was created without OpenGL support");
329            unsafe { context.make_current() };
330            self.cx.draw(Entity::root(), &mut self.surface, &mut self.dirty_surface);
331            self.gr_context.flush_and_submit();
332            self.should_redraw = false;
333            context.swap_buffers();
334            unsafe { context.make_not_current() };
335        }
336    }
337
338    pub fn handle_event(&mut self, event: baseview::Event, should_quit: &mut bool) {
339        if requests_exit(&event) {
340            self.cx.send_event(Event::new(WindowEvent::WindowClose));
341            *should_quit = true;
342        }
343
344        let mut update_modifiers = |modifiers: vizia_input::KeyboardModifiers| {
345            self.cx
346                .modifiers()
347                .set(Modifiers::SHIFT, modifiers.contains(vizia_input::KeyboardModifiers::SHIFT));
348            self.cx
349                .modifiers()
350                .set(Modifiers::CTRL, modifiers.contains(vizia_input::KeyboardModifiers::CONTROL));
351            self.cx
352                .modifiers()
353                .set(Modifiers::SUPER, modifiers.contains(vizia_input::KeyboardModifiers::META));
354            self.cx
355                .modifiers()
356                .set(Modifiers::ALT, modifiers.contains(vizia_input::KeyboardModifiers::ALT));
357        };
358
359        match event {
360            baseview::Event::Mouse(event) => match event {
361                baseview::MouseEvent::CursorMoved { position, modifiers } => {
362                    update_modifiers(modifiers);
363
364                    // NOTE: We multiply by `self.window_scale_factor` and not by
365                    //       `self.context.style.dpi_factor`. Since the additional scaling by
366                    //       internally do additional scaling by `self.context.user_scale_factor` is
367                    //       done internally to be able to separate actual HiDPI scaling from
368                    //       arbitrary uniform scaling baseview only knows about its own scale
369                    //       factor.
370                    let physical_posx = position.x * self.window_scale_factor;
371                    let physical_posy = position.y * self.window_scale_factor;
372                    let cursor_x = (physical_posx) as f32;
373                    let cursor_y = (physical_posy) as f32;
374                    self.cx.emit_origin(WindowEvent::MouseMove(cursor_x, cursor_y));
375                }
376                baseview::MouseEvent::ButtonPressed { button, modifiers } => {
377                    update_modifiers(modifiers);
378
379                    let b = translate_mouse_button(button);
380                    self.cx.emit_origin(WindowEvent::MouseDown(b));
381                }
382                baseview::MouseEvent::ButtonReleased { button, modifiers } => {
383                    update_modifiers(modifiers);
384
385                    let b = translate_mouse_button(button);
386                    self.cx.emit_origin(WindowEvent::MouseUp(b));
387                }
388                baseview::MouseEvent::WheelScrolled { delta, modifiers } => {
389                    update_modifiers(modifiers);
390
391                    let (lines_x, lines_y) = match delta {
392                        baseview::ScrollDelta::Lines { x, y } => (x, y),
393                        baseview::ScrollDelta::Pixels { x, y } => (
394                            if x < 0.0 {
395                                -1.0
396                            } else if x > 1.0 {
397                                1.0
398                            } else {
399                                0.0
400                            },
401                            if y < 0.0 {
402                                -1.0
403                            } else if y > 1.0 {
404                                1.0
405                            } else {
406                                0.0
407                            },
408                        ),
409                    };
410
411                    self.cx.emit_origin(WindowEvent::MouseScroll(lines_x, lines_y));
412                }
413
414                baseview::MouseEvent::CursorEntered => {
415                    self.cx.emit_origin(WindowEvent::MouseEnter);
416                }
417
418                baseview::MouseEvent::CursorLeft => {
419                    self.cx.emit_origin(WindowEvent::MouseLeave);
420                }
421
422                _ => {}
423            },
424            baseview::Event::Keyboard(event) => {
425                let (s, pressed) = match event.state {
426                    vizia_input::KeyState::Down => (MouseButtonState::Pressed, true),
427                    vizia_input::KeyState::Up => (MouseButtonState::Released, false),
428                };
429
430                match event.code {
431                    Code::ShiftLeft | Code::ShiftRight => {
432                        self.cx.modifiers().set(Modifiers::SHIFT, pressed)
433                    }
434                    Code::ControlLeft | Code::ControlRight => {
435                        self.cx.modifiers().set(Modifiers::CTRL, pressed)
436                    }
437                    Code::AltLeft | Code::AltRight => {
438                        self.cx.modifiers().set(Modifiers::ALT, pressed)
439                    }
440                    Code::MetaLeft | Code::MetaRight => {
441                        self.cx.modifiers().set(Modifiers::SUPER, pressed)
442                    }
443                    _ => (),
444                }
445
446                match s {
447                    MouseButtonState::Pressed => {
448                        if let vizia_input::Key::Character(written) = &event.key {
449                            for chr in written.chars() {
450                                self.cx.emit_origin(WindowEvent::CharInput(chr));
451                            }
452                        }
453
454                        self.cx.emit_origin(WindowEvent::KeyDown(event.code, Some(event.key)));
455                    }
456
457                    MouseButtonState::Released => {
458                        self.cx.emit_origin(WindowEvent::KeyUp(event.code, Some(event.key)));
459                    }
460                }
461            }
462            baseview::Event::Window(event) => match event {
463                baseview::WindowEvent::Focused => self.cx.needs_refresh(Entity::root()),
464                baseview::WindowEvent::Resized(window_info) => {
465                    let fb_info = {
466                        let mut fboid: GLint = 0;
467                        unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
468
469                        FramebufferInfo {
470                            fboid: fboid.try_into().unwrap(),
471                            format: skia_safe::gpu::gl::Format::RGBA8.into(),
472                            ..Default::default()
473                        }
474                    };
475
476                    self.surface = create_surface(
477                        (
478                            window_info.physical_size().width as i32,
479                            window_info.physical_size().height as i32,
480                        ),
481                        fb_info,
482                        &mut self.gr_context,
483                    );
484
485                    self.dirty_surface = self
486                        .surface
487                        .new_surface_with_dimensions((
488                            window_info.physical_size().width as i32,
489                            window_info.physical_size().height as i32,
490                        ))
491                        .unwrap();
492
493                    // // We keep track of the current size before applying the user scale factor while
494                    // // baseview's logical size includes that factor so we need to compensate for it
495                    // self.current_window_size = *self.cx.window_size();
496                    // self.current_window_size.width = (window_info.logical_size().width
497                    //     / self.cx.user_scale_factor())
498                    // .round() as u32;
499                    // self.current_window_size.height = (window_info.logical_size().height
500                    //     / self.cx.user_scale_factor())
501                    // .round() as u32;
502                    // *self.cx.window_size() = self.current_window_size;
503
504                    // Only use new DPI settings when `WindowScalePolicy::SystemScaleFactor` was
505                    // used
506                    if self.use_system_scaling {
507                        self.window_scale_factor = window_info.scale();
508                    }
509
510                    //let user_scale_factor = self.cx.user_scale_factor();
511
512                    //self.cx.set_scale_factor(self.window_scale_factor * user_scale_factor);
513
514                    let physical_size =
515                        (window_info.physical_size().width, window_info.physical_size().height);
516
517                    self.cx.set_window_size(
518                        Entity::root(),
519                        physical_size.0 as f32,
520                        physical_size.1 as f32,
521                    );
522
523                    self.cx.needs_refresh(Entity::root());
524                }
525                baseview::WindowEvent::WillClose => {
526                    self.cx.send_event(Event::new(WindowEvent::WindowClose));
527                }
528                _ => {}
529            },
530        }
531    }
532
533    pub fn handle_idle(&mut self, on_idle: &Option<Box<dyn Fn(&mut Context) + Send>>) {
534        if let Some(idle_callback) = on_idle {
535            self.cx.set_current(Entity::root());
536            (idle_callback)(self.cx.context());
537        }
538    }
539}
540
541/// Returns true if the provided event should cause an [`Application`] to
542/// exit.
543pub fn requests_exit(event: &baseview::Event) -> bool {
544    match event {
545        baseview::Event::Window(baseview::WindowEvent::WillClose) => true,
546        #[cfg(target_os = "macos")]
547        baseview::Event::Keyboard(event) => {
548            if event.code == vizia_input::Code::KeyQ
549                && event.modifiers == vizia_input::KeyboardModifiers::META
550                && event.state == vizia_input::KeyState::Down
551            {
552                return true;
553            }
554
555            false
556        }
557        _ => false,
558    }
559}
560
561fn translate_mouse_button(button: baseview::MouseButton) -> MouseButton {
562    match button {
563        baseview::MouseButton::Left => MouseButton::Left,
564        baseview::MouseButton::Right => MouseButton::Right,
565        baseview::MouseButton::Middle => MouseButton::Middle,
566        baseview::MouseButton::Other(id) => MouseButton::Other(id as u16),
567        baseview::MouseButton::Back => MouseButton::Other(4),
568        baseview::MouseButton::Forward => MouseButton::Other(5),
569    }
570}