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}