vizia_baseview/
window.rs

1use crate::application::ApplicationRunner;
2use baseview::gl::GlConfig;
3use baseview::{
4    Event, EventStatus, Window, WindowHandle, WindowHandler, WindowOpenOptions, WindowScalePolicy,
5};
6use gl::types::GLint;
7use gl_rs as gl;
8use raw_window_handle::HasRawWindowHandle;
9use skia_safe::gpu::gl::FramebufferInfo;
10use skia_safe::gpu::{
11    self, backend_render_targets, ganesh::context_options, ContextOptions, SurfaceOrigin,
12};
13use skia_safe::{ColorSpace, ColorType, PixelGeometry, Surface, SurfaceProps, SurfacePropsFlags};
14
15use crate::proxy::BaseviewProxy;
16use vizia_core::backend::*;
17use vizia_core::prelude::*;
18
19/// Handles a vizia_baseview application
20pub(crate) struct ViziaWindow {
21    application: ApplicationRunner,
22    #[allow(clippy::type_complexity)]
23    on_idle: Option<Box<dyn Fn(&mut Context) + Send>>,
24}
25
26impl ViziaWindow {
27    fn new(
28        mut cx: BackendContext,
29        win_desc: WindowDescription,
30        window_scale_policy: WindowScalePolicy,
31        window: &mut baseview::Window,
32        builder: Option<Box<dyn FnOnce(&mut Context) + Send>>,
33        on_idle: Option<Box<dyn Fn(&mut Context) + Send>>,
34    ) -> ViziaWindow {
35        let context = window.gl_context().expect("Window was created without OpenGL support");
36
37        unsafe { context.make_current() };
38
39        // Build skia renderer
40        gl::load_with(|s| context.get_proc_address(s));
41        let interface = skia_safe::gpu::gl::Interface::new_load_with(|name| {
42            if name == "eglGetCurrentDisplay" {
43                return std::ptr::null();
44            }
45            context.get_proc_address(name)
46        })
47        .expect("Could not create interface");
48
49        // https://github.com/rust-skia/rust-skia/issues/476
50        let mut context_options = ContextOptions::new();
51        context_options.skip_gl_error_checks = context_options::Enable::Yes;
52
53        let mut gr_context = skia_safe::gpu::direct_contexts::make_gl(interface, &context_options)
54            .expect("Could not create direct context");
55
56        let fb_info = {
57            let mut fboid: GLint = 0;
58            unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
59
60            FramebufferInfo {
61                fboid: fboid.try_into().unwrap(),
62                format: skia_safe::gpu::gl::Format::RGBA8.into(),
63                ..Default::default()
64            }
65        };
66
67        let mut surface = create_surface(
68            (win_desc.inner_size.width as i32, win_desc.inner_size.height as i32),
69            fb_info,
70            &mut gr_context,
71        );
72
73        let dirty_surface = surface
74            .new_surface_with_dimensions((
75                win_desc.inner_size.width as i32,
76                win_desc.inner_size.height as i32,
77            ))
78            .unwrap();
79
80        // Assume scale for now until there is an event with a new one.
81        // Scaling is a combination of the window's scale factor (which is usually determined by the
82        // operating system, or explicitly overridden by a hosting application) and a custom user
83        // scale factor.
84        let (use_system_scaling, window_scale_factor) = match window_scale_policy {
85            WindowScalePolicy::ScaleFactor(scale) => (false, scale),
86            // NOTE: This is not correct, but we should get a `Resized` event to correct this
87            //       immediately after the window is created
88            WindowScalePolicy::SystemScaleFactor => (true, 1.25),
89        };
90        let dpi_factor = window_scale_factor * win_desc.user_scale_factor;
91
92        cx.add_main_window(Entity::root(), &win_desc, dpi_factor as f32);
93        cx.add_window(WindowView {});
94
95        cx.0.windows.insert(
96            Entity::root(),
97            WindowState { window_description: win_desc.clone(), ..Default::default() },
98        );
99
100        cx.context().remove_user_themes();
101        if let Some(builder) = builder {
102            (builder)(cx.context());
103        }
104
105        let application = ApplicationRunner::new(
106            cx,
107            gr_context,
108            use_system_scaling,
109            window_scale_factor,
110            surface,
111            dirty_surface,
112            win_desc,
113        );
114        unsafe { context.make_not_current() };
115
116        ViziaWindow { application, on_idle }
117    }
118
119    /// Open a new child window.
120    ///
121    /// * `parent` - The parent window.
122    /// * `app` - The Vizia application builder.
123    pub fn open_parented<P, F>(
124        parent: &P,
125        win_desc: WindowDescription,
126        scale_policy: WindowScalePolicy,
127        app: F,
128        on_idle: Option<Box<dyn Fn(&mut Context) + Send>>,
129        ignore_default_theme: bool,
130    ) -> WindowHandle
131    where
132        P: HasRawWindowHandle,
133        F: Fn(&mut Context),
134        F: 'static + Send,
135    {
136        let window_settings = WindowOpenOptions {
137            title: win_desc.title.clone(),
138            size: baseview::Size::new(
139                // We have our own uniform non-DPI scaling factor that gets applied in addition to
140                // the DPI scaling since both can change independently at runtime
141                win_desc.inner_size.width as f64 * win_desc.user_scale_factor,
142                win_desc.inner_size.height as f64 * win_desc.user_scale_factor,
143            ),
144            scale: scale_policy,
145            gl_config: Some(GlConfig { vsync: true, ..GlConfig::default() }),
146        };
147
148        Window::open_parented(
149            parent,
150            window_settings,
151            move |window: &mut baseview::Window<'_>| -> ViziaWindow {
152                let mut cx = Context::new();
153
154                cx.ignore_default_theme = ignore_default_theme;
155                cx.remove_user_themes();
156
157                let mut cx = BackendContext::new(cx);
158
159                cx.set_event_proxy(Box::new(BaseviewProxy));
160                ViziaWindow::new(cx, win_desc, scale_policy, window, Some(Box::new(app)), on_idle)
161            },
162        )
163    }
164
165    /// Open a new window that blocks the current thread until the window is destroyed.
166    ///
167    /// * `app` - The Vizia application builder.
168    pub fn open_blocking<F>(
169        win_desc: WindowDescription,
170        scale_policy: WindowScalePolicy,
171        app: F,
172        on_idle: Option<Box<dyn Fn(&mut Context) + Send>>,
173        ignore_default_theme: bool,
174    ) where
175        F: Fn(&mut Context),
176        F: 'static + Send,
177    {
178        let window_settings = WindowOpenOptions {
179            title: win_desc.title.clone(),
180            size: baseview::Size::new(
181                win_desc.inner_size.width as f64 * win_desc.user_scale_factor,
182                win_desc.inner_size.height as f64 * win_desc.user_scale_factor,
183            ),
184            scale: scale_policy,
185            gl_config: Some(GlConfig { vsync: true, ..GlConfig::default() }),
186        };
187
188        Window::open_blocking(
189            window_settings,
190            move |window: &mut baseview::Window<'_>| -> ViziaWindow {
191                let mut cx = Context::new();
192
193                cx.ignore_default_theme = ignore_default_theme;
194                cx.remove_user_themes();
195
196                let mut cx = BackendContext::new(cx);
197
198                cx.set_event_proxy(Box::new(BaseviewProxy));
199                ViziaWindow::new(cx, win_desc, scale_policy, window, Some(Box::new(app)), on_idle)
200            },
201        )
202    }
203}
204
205impl WindowHandler for ViziaWindow {
206    fn on_frame(&mut self, window: &mut Window) {
207        self.application.on_frame_update(window);
208
209        self.application.render(window);
210    }
211
212    fn on_event(&mut self, window: &mut Window<'_>, event: Event) -> EventStatus {
213        let mut should_quit = false;
214
215        self.application.handle_event(event, &mut should_quit);
216
217        self.application.handle_idle(&self.on_idle);
218
219        if should_quit {
220            window.close();
221        }
222
223        EventStatus::Ignored
224    }
225}
226
227pub struct WindowView {}
228
229impl View for WindowView {}
230
231pub fn create_surface(
232    size: (i32, i32),
233    fb_info: FramebufferInfo,
234    gr_context: &mut skia_safe::gpu::DirectContext,
235) -> Surface {
236    let backend_render_target = backend_render_targets::make_gl(size, None, 8, fb_info);
237
238    let surface_props = SurfaceProps::new_with_text_properties(
239        SurfacePropsFlags::default(),
240        PixelGeometry::default(),
241        0.5,
242        0.0,
243    );
244
245    gpu::surfaces::wrap_backend_render_target(
246        gr_context,
247        &backend_render_target,
248        SurfaceOrigin::BottomLeft,
249        ColorType::RGBA8888,
250        ColorSpace::new_srgb(),
251        Some(surface_props).as_ref(),
252        // None,
253    )
254    .expect("Could not create skia surface")
255}