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
19pub(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 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 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 let (use_system_scaling, window_scale_factor) = match window_scale_policy {
85 WindowScalePolicy::ScaleFactor(scale) => (false, scale),
86 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 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 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 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 )
254 .expect("Could not create skia surface")
255}