diff --git a/nih_plug_iced/src/editor.rs b/nih_plug_iced/src/editor.rs new file mode 100644 index 00000000..de140245 --- /dev/null +++ b/nih_plug_iced/src/editor.rs @@ -0,0 +1,128 @@ +//! And [`Editor`] implementation for iced. + +use baseview::{WindowOpenOptions, WindowScalePolicy}; +use crossbeam::atomic::AtomicCell; +use crossbeam::channel; +pub use iced_baseview::*; +use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle}; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +use crate::{wrapper, IcedEditor, IcedState, ParameterUpdate}; + +/// An [`Editor`] implementation that renders an iced [`Application`]. +pub(crate) struct IcedEditorWrapper { + pub(crate) iced_state: Arc, + pub(crate) initialization_flags: E::InitializationFlags, + + /// 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>, + + /// A subscription for sending messages about parameter updates to the `IcedEditor`. + pub(crate) parameter_updates_sender: channel::Sender, + pub(crate) parameter_updates_receiver: Arc>, +} + +impl Editor for IcedEditorWrapper { + fn spawn( + &self, + parent: ParentWindowHandle, + context: Arc, + ) -> Box { + let (unscaled_width, unscaled_height) = self.iced_state.size(); + let scaling_factor = self.scaling_factor.load(); + + // TODO: iced_baseview does not have gracefuly error handling for context creation failures. + // This will panic if the context could not be created. + let window = IcedWindow::>::open_parented( + &parent, + Settings { + window: WindowOpenOptions { + title: String::from("iced window"), + // Baseview should be doing the DPI scaling for us + size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64), + // NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but + // not the mouse events. + scale: scaling_factor + .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) + .unwrap_or(WindowScalePolicy::SystemScaleFactor), + + #[cfg(feature = "opengl")] + gl_config: Some(baseview::gl::GlConfig { + // FIXME: glow_glyph forgot to add an `#extension`, so this won't work under + // OpenGL 3.2 at the moment. With that change applied this should work on + // OpenGL 3.2/macOS. + version: (3, 3), + red_bits: 8, + blue_bits: 8, + green_bits: 8, + alpha_bits: 8, + depth_bits: 24, + stencil_bits: 8, + samples: None, + srgb: true, + double_buffer: true, + vsync: true, + ..Default::default() + }), + // FIXME: Rust analyzer always thinks baseview/opengl is enabled even if we + // don't explicitly enable it, so you'd get a compile error if this line + // is missing + #[cfg(not(feature = "opengl"))] + gl_config: None, + }, + iced_baseview: IcedBaseviewSettings { + ignore_non_modifier_keys: false, + always_redraw: true, + }, + // We use this wrapper to be able to pass the GUI context to the editor + flags: ( + context, + self.parameter_updates_receiver.clone(), + self.initialization_flags.clone(), + ), + }, + ); + + self.iced_state.open.store(true, Ordering::Release); + Box::new(IcedEditorHandle { + iced_state: self.iced_state.clone(), + window, + }) + } + + fn size(&self) -> (u32, u32) { + self.iced_state.size() + } + + fn set_scale_factor(&self, factor: f32) -> bool { + self.scaling_factor.store(Some(factor)); + true + } + + fn param_values_changed(&self) { + if self.iced_state.is_open() { + // If there's already a paramter change notification in the channel then we don't need + // to do anything else. This avoids queueing up redundant GUI redraws. + let _ = self.parameter_updates_sender.try_send(ParameterUpdate); + } + } +} + +/// The window handle used for [`IcedEditorWrapper`]. +struct IcedEditorHandle { + iced_state: Arc, + window: iced_baseview::WindowHandle, +} + +/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around +/// having this requirement? +unsafe impl Send for IcedEditorHandle {} + +impl Drop for IcedEditorHandle { + fn drop(&mut self) { + self.iced_state.open.store(false, Ordering::Release); + self.window.close_window(); + } +} diff --git a/nih_plug_iced/src/lib.rs b/nih_plug_iced/src/lib.rs index 8de875fc..2b373664 100644 --- a/nih_plug_iced/src/lib.rs +++ b/nih_plug_iced/src/lib.rs @@ -89,11 +89,11 @@ //! } //! ``` -use baseview::{WindowOpenOptions, WindowScalePolicy}; +use baseview::WindowScalePolicy; use crossbeam::atomic::AtomicCell; use crossbeam::channel; 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::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; @@ -107,6 +107,7 @@ use crate::widgets::ParamMessage; pub use iced_baseview::*; pub mod assets; +mod editor; pub mod widgets; mod wrapper; @@ -130,7 +131,7 @@ pub fn create_iced_editor( // which parameter so we'd need to redraw the entire GUI either way. let (parameter_updates_sender, parameter_updates_receiver) = channel::bounded(1); - Some(Box::new(IcedEditorWrapper:: { + Some(Box::new(editor::IcedEditorWrapper:: { iced_state, initialization_flags, @@ -279,120 +280,3 @@ impl IcedState { /// A marker struct to indicate that a parameter update has happened. pub(crate) struct ParameterUpdate; - -/// An [`Editor`] implementation that renders an iced [`Application`]. -struct IcedEditorWrapper { - iced_state: Arc, - initialization_flags: E::InitializationFlags, - - /// 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>, - - /// A subscription for sending messages about parameter updates to the `IcedEditor`. - parameter_updates_sender: channel::Sender, - parameter_updates_receiver: Arc>, -} - -impl Editor for IcedEditorWrapper { - fn spawn( - &self, - parent: ParentWindowHandle, - context: Arc, - ) -> Box { - let (unscaled_width, unscaled_height) = self.iced_state.size(); - let scaling_factor = self.scaling_factor.load(); - - // TODO: iced_baseview does not have gracefuly error handling for context creation failures. - // This will panic if the context could not be created. - let window = IcedWindow::>::open_parented( - &parent, - Settings { - window: WindowOpenOptions { - title: String::from("iced window"), - // Baseview should be doing the DPI scaling for us - size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64), - // NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but - // not the mouse events. - scale: scaling_factor - .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) - .unwrap_or(WindowScalePolicy::SystemScaleFactor), - - #[cfg(feature = "opengl")] - gl_config: Some(baseview::gl::GlConfig { - // FIXME: glow_glyph forgot to add an `#extension`, so this won't work under - // OpenGL 3.2 at the moment. With that change applied this should work on - // OpenGL 3.2/macOS. - version: (3, 3), - red_bits: 8, - blue_bits: 8, - green_bits: 8, - alpha_bits: 8, - depth_bits: 24, - stencil_bits: 8, - samples: None, - srgb: true, - double_buffer: true, - vsync: true, - ..Default::default() - }), - // FIXME: Rust analyzer always thinks baseview/opengl is enabled even if we - // don't explicitly enable it, so you'd get a compile error if this line - // is missing - #[cfg(not(feature = "opengl"))] - gl_config: None, - }, - iced_baseview: IcedBaseviewSettings { - ignore_non_modifier_keys: false, - always_redraw: true, - }, - // We use this wrapper to be able to pass the GUI context to the editor - flags: ( - context, - self.parameter_updates_receiver.clone(), - self.initialization_flags.clone(), - ), - }, - ); - - self.iced_state.open.store(true, Ordering::Release); - Box::new(IcedEditorHandle { - iced_state: self.iced_state.clone(), - window, - }) - } - - fn size(&self) -> (u32, u32) { - self.iced_state.size() - } - - fn set_scale_factor(&self, factor: f32) -> bool { - self.scaling_factor.store(Some(factor)); - true - } - - fn param_values_changed(&self) { - if self.iced_state.is_open() { - // If there's already a paramter change notification in the channel then we don't need - // to do anything else. This avoids queueing up redundant GUI redraws. - let _ = self.parameter_updates_sender.try_send(ParameterUpdate); - } - } -} - -/// The window handle used for [`IcedEditorWrapper`]. -struct IcedEditorHandle { - iced_state: Arc, - window: iced_baseview::WindowHandle, -} - -/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around -/// having this requirement? -unsafe impl Send for IcedEditorHandle {} - -impl Drop for IcedEditorHandle { - fn drop(&mut self) { - self.iced_state.open.store(false, Ordering::Release); - self.window.close_window(); - } -} diff --git a/nih_plug_iced/src/wrapper.rs b/nih_plug_iced/src/wrapper.rs index 192f85aa..c149489e 100644 --- a/nih_plug_iced/src/wrapper.rs +++ b/nih_plug_iced/src/wrapper.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use crate::futures::FutureExt; use crate::{ - futures, subscription, Application, Color, Command, Element, IcedEditor, Subscription, - WindowQueue, WindowScalePolicy, WindowSubs, + futures, subscription, Application, Color, Command, Element, IcedEditor, ParameterUpdate, + Subscription, WindowQueue, WindowScalePolicy, WindowSubs, }; /// Wraps an `iced_baseview` [`Application`] around [`IcedEditor`]. Needed to allow editors to @@ -19,7 +19,7 @@ pub(crate) struct IcedEditorWrapperApplication { /// We will receive notifications about parameters being changed on here. Whenever a parameter /// update gets sent, we will trigger a [`Message::parameterUpdate`] which causes the UI to be /// redrawn. - parameter_updates_receiver: Arc>, + parameter_updates_receiver: Arc>, } /// This wraps around `E::Message` to add a parameter update message which can be handled directly @@ -53,7 +53,7 @@ impl Application for IcedEditorWrapperApplication { type Message = Message; type Flags = ( Arc, - Arc>, + Arc>, E::InitializationFlags, );