diff --git a/nih_plug_vizia/src/editor.rs b/nih_plug_vizia/src/editor.rs new file mode 100644 index 00000000..2588ce7b --- /dev/null +++ b/nih_plug_vizia/src/editor.rs @@ -0,0 +1,126 @@ +//! The [`Editor`] trait implementation for Vizia editors. + +use baseview::{WindowHandle, WindowScalePolicy}; +use crossbeam::atomic::AtomicCell; +use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle}; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use vizia::prelude::*; + +use crate::{assets, widgets, ViziaState, ViziaTheming}; + +/// An [`Editor`] implementation that calls an vizia draw loop. +pub(crate) struct ViziaEditor { + pub(crate) vizia_state: Arc, + /// The user's app function. + pub(crate) app: Arc) + 'static + Send + Sync>, + /// What level of theming to apply. See [`ViziaEditorTheming`]. + pub(crate) theming: ViziaTheming, + + /// The scaling factor reported by the host, if any. On macOS this will never be set and we + /// should use the system scaling factor instead. + pub(crate) scaling_factor: AtomicCell>, +} + +impl Editor for ViziaEditor { + fn spawn( + &self, + parent: ParentWindowHandle, + context: Arc, + ) -> Box { + let app = self.app.clone(); + let vizia_state = self.vizia_state.clone(); + let theming = self.theming; + + let (unscaled_width, unscaled_height) = vizia_state.inner_logical_size(); + let system_scaling_factor = self.scaling_factor.load(); + let user_scale_factor = vizia_state.user_scale_factor(); + + let mut application = Application::new(move |cx| { + // Set some default styles to match the iced integration + if theming >= ViziaTheming::Custom { + // NOTE: vizia's font rendering looks way too dark and thick. Going one font weight + // lower seems to compensate for this. + cx.set_default_font(assets::NOTO_SANS_LIGHT); + cx.add_theme(include_str!("../assets/theme.css")); + + // There doesn't seem to be any way to bundle styles with a widget, so we'll always + // include the style sheet for our custom widgets at context creation + widgets::register_theme(cx); + } + + // Any widget can change the parameters by emitting `ParamEvent` events. This model will + // handle them automatically. + widgets::ParamModel { + context: context.clone(), + } + .build(cx); + + // And we'll link `WindowEvent::ResizeWindow` and `WindowEvent::SetScale` events to our + // `ViziaState`. We'll notify the host when any of these change. + widgets::WindowModel { + context: context.clone(), + vizia_state: vizia_state.clone(), + } + .build(cx); + + app(cx, context.clone()) + }) + .with_scale_policy( + system_scaling_factor + .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) + .unwrap_or(WindowScalePolicy::SystemScaleFactor), + ) + .inner_size((unscaled_width, unscaled_height)) + .user_scale_factor(user_scale_factor); + + // This way the plugin can decide to use none of the built in theming + if theming == ViziaTheming::None { + application = application.ignore_default_theme(); + } + + let window = application.open_parented(&parent); + + self.vizia_state.open.store(true, Ordering::Release); + Box::new(ViziaEditorHandle { + vizia_state: self.vizia_state.clone(), + window, + }) + } + + fn size(&self) -> (u32, u32) { + // This includes the user scale factor if set, but not any HiDPI scaling + self.vizia_state.scaled_logical_size() + } + + fn set_scale_factor(&self, factor: f32) -> bool { + // We're making things a bit more complicated by having both a system scale factor, which is + // used for HiDPI and also known to the host, and a user scale factor that the user can use + // to arbitrarily resize the GUI + self.scaling_factor.store(Some(factor)); + true + } + + fn param_values_changed(&self) { + // TODO: Update the GUI when this happens, right now this happens automatically as a result + // of of the reactivity + } +} + +/// The window handle used for [`ViziaEditor`]. +struct ViziaEditorHandle { + vizia_state: Arc, + window: WindowHandle, +} + +/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around +/// having this requirement? +unsafe impl Send for ViziaEditorHandle {} + +impl Drop for ViziaEditorHandle { + fn drop(&mut self) { + self.vizia_state.open.store(false, Ordering::Release); + // XXX: This should automatically happen when the handle gets dropped, but apparently not + self.window.close(); + } +} diff --git a/nih_plug_vizia/src/lib.rs b/nih_plug_vizia/src/lib.rs index b3b67baa..68319335 100644 --- a/nih_plug_vizia/src/lib.rs +++ b/nih_plug_vizia/src/lib.rs @@ -3,10 +3,9 @@ // See the comment in the main `nih_plug` crate #![allow(clippy::type_complexity)] -use baseview::{WindowHandle, WindowScalePolicy}; use crossbeam::atomic::AtomicCell; use nih_plug::params::persist::PersistentField; -use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle}; +use nih_plug::prelude::{Editor, GuiContext}; use serde::{Deserialize, Serialize}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -16,6 +15,7 @@ use vizia::prelude::*; pub use vizia; pub mod assets; +mod editor; pub mod vizia_assets; pub mod widgets; @@ -47,7 +47,7 @@ pub fn create_vizia_editor( where F: Fn(&mut Context, Arc) + 'static + Send + Sync, { - Some(Box::new(ViziaEditor { + Some(Box::new(editor::ViziaEditor { vizia_state, app: Arc::new(app), theming, @@ -161,119 +161,3 @@ impl ViziaState { self.open.load(Ordering::Acquire) } } - -/// An [`Editor`] implementation that calls an vizia draw loop. -struct ViziaEditor { - vizia_state: Arc, - /// The user's app function. - app: Arc) + 'static + Send + Sync>, - /// What level of theming to apply. See [`ViziaEditorTheming`]. - theming: ViziaTheming, - - /// The scaling factor reported by the host, if any. On macOS this will never be set and we - /// should use the system scaling factor instead. - scaling_factor: AtomicCell>, -} - -impl Editor for ViziaEditor { - fn spawn( - &self, - parent: ParentWindowHandle, - context: Arc, - ) -> Box { - let app = self.app.clone(); - let vizia_state = self.vizia_state.clone(); - let theming = self.theming; - - let (unscaled_width, unscaled_height) = vizia_state.inner_logical_size(); - let system_scaling_factor = self.scaling_factor.load(); - let user_scale_factor = vizia_state.user_scale_factor(); - - let mut application = Application::new(move |cx| { - // Set some default styles to match the iced integration - if theming >= ViziaTheming::Custom { - // NOTE: vizia's font rendering looks way too dark and thick. Going one font weight - // lower seems to compensate for this. - cx.set_default_font(assets::NOTO_SANS_LIGHT); - cx.add_theme(include_str!("../assets/theme.css")); - - // There doesn't seem to be any way to bundle styles with a widget, so we'll always - // include the style sheet for our custom widgets at context creation - widgets::register_theme(cx); - } - - // Any widget can change the parameters by emitting `ParamEvent` events. This model will - // handle them automatically. - widgets::ParamModel { - context: context.clone(), - } - .build(cx); - - // And we'll link `WindowEvent::ResizeWindow` and `WindowEvent::SetScale` events to our - // `ViziaState`. We'll notify the host when any of these change. - widgets::WindowModel { - context: context.clone(), - vizia_state: vizia_state.clone(), - } - .build(cx); - - app(cx, context.clone()) - }) - .with_scale_policy( - system_scaling_factor - .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) - .unwrap_or(WindowScalePolicy::SystemScaleFactor), - ) - .inner_size((unscaled_width, unscaled_height)) - .user_scale_factor(user_scale_factor); - - // This way the plugin can decide to use none of the built in theming - if theming == ViziaTheming::None { - application = application.ignore_default_theme(); - } - - let window = application.open_parented(&parent); - - self.vizia_state.open.store(true, Ordering::Release); - Box::new(ViziaEditorHandle { - vizia_state: self.vizia_state.clone(), - window, - }) - } - - fn size(&self) -> (u32, u32) { - // This includes the user scale factor if set, but not any HiDPI scaling - self.vizia_state.scaled_logical_size() - } - - fn set_scale_factor(&self, factor: f32) -> bool { - // We're making things a bit more complicated by having both a system scale factor, which is - // used for HiDPI and also known to the host, and a user scale factor that the user can use - // to arbitrarily resize the GUI - self.scaling_factor.store(Some(factor)); - true - } - - fn param_values_changed(&self) { - // TODO: Update the GUI when this happens, right now this happens automatically as a result - // of of the reactivity - } -} - -/// The window handle used for [`ViziaEditor`]. -struct ViziaEditorHandle { - vizia_state: Arc, - window: WindowHandle, -} - -/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around -/// having this requirement? -unsafe impl Send for ViziaEditorHandle {} - -impl Drop for ViziaEditorHandle { - fn drop(&mut self) { - self.vizia_state.open.store(false, Ordering::Release); - // XXX: This should automatically happen when the handle gets dropped, but apparently not - self.window.close(); - } -}