diff --git a/nih_plug_iced/src/lib.rs b/nih_plug_iced/src/lib.rs index e9dd0ff3..0741da44 100644 --- a/nih_plug_iced/src/lib.rs +++ b/nih_plug_iced/src/lib.rs @@ -4,8 +4,8 @@ use baseview::{Size, WindowOpenOptions, WindowScalePolicy}; use crossbeam::atomic::AtomicCell; -use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle}; -use std::marker::PhantomData; +use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle}; +use std::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -25,24 +25,73 @@ pub use iced_baseview::*; /// field on your parameters struct. /// /// See [`IcedState::from_size()`]. -pub fn create_iced_editor( +pub fn create_iced_editor( iced_state: Arc, - initialization_flags: A::Flags, -) -> Option> -where - A: Application + 'static + Send + Sync, - A::Flags: Clone + Sync, -{ - Some(Box::new(IcedEditor:: { + initialization_flags: E::InitializationFlags, +) -> Option> { + Some(Box::new(IcedEditorWrapper:: { iced_state, initialization_flags, scaling_factor: AtomicCell::new(None), - - _phantom: PhantomData, })) } +/// A plugin editor using `iced`. This wraps around [`Application`] with the only change being that +/// the usual `new()` function now additionally takes a `Arc` that the editor can +/// store to interact with the parameters. The editor should have a `Pin>` as part +/// of their [`Flags`][Self::Flags] so it can read the current parameter values. See [`Application`] +/// for more information. +pub trait IcedEditor: 'static + Send + Sync + Sized { + /// See [`Application::Executor`]. You'll likely want to use [`crate::executor::Default`]. + type Executor: Executor; + /// See [`Application::Message`]. + type Message: 'static + Clone + Debug + Send; + /// See [`Application::Flags`]. + type InitializationFlags: 'static + Clone + Send + Sync; + + /// See [`Application::new`]. This also receivs the GUI context in addition to the flags. + fn new( + initialization_fags: Self::InitializationFlags, + context: Arc, + ) -> (Self, Command); + + /// See [`Application::update`]. + fn update( + &mut self, + window: &mut WindowQueue, + message: Self::Message, + ) -> Command; + + /// See [`Application::subscription`]. + fn subscription( + &self, + _window_subs: &mut WindowSubs, + ) -> Subscription { + Subscription::none() + } + + /// See [`Application::view`]. + fn view(&mut self) -> Element<'_, Self::Message>; + + /// See [`Application::background_color`]. + fn background_color(&self) -> Color { + Color::WHITE + } + + /// See [`Application::scale_policy`]. + /// + /// TODO: Is this needed? Editors shouldn't change the scale policy. + fn scale_policy(&self) -> WindowScalePolicy { + WindowScalePolicy::SystemScaleFactor + } + + /// See [`Application::renderer_settings`]. + fn renderer_settings() -> iced_baseview::renderer::settings::Settings { + iced_baseview::renderer::settings::Settings::default() + } +} + // TODO: Once we add resizing, we may want to be able to remember the GUI size. In that case we need // to make this serializable (only restoring the size of course) so it can be persisted. pub struct IcedState { @@ -73,31 +122,16 @@ impl IcedState { } /// An [`Editor`] implementation that renders an iced [`Application`]. -struct IcedEditor -where - A: Application + 'static + Send + Sync, - A::Flags: Clone + Sync, -{ +struct IcedEditorWrapper { iced_state: Arc, - initialization_flags: A::Flags, + initialization_flags: E::InitializationFlags, - // FIXME: - // /// The plugin's state. This is kept in between editor openenings. - // user_state: Arc>, - // update: Arc, - // /// 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>, - - _phantom: PhantomData, } -impl Editor for IcedEditor -where - A: Application + 'static + Send + Sync, - A::Flags: Clone + Sync, -{ +impl Editor for IcedEditorWrapper { fn spawn( &self, parent: ParentWindowHandle, @@ -108,9 +142,10 @@ where 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( + let window = IcedWindow::>::open_parented( &parent, Settings { window: WindowOpenOptions { @@ -147,7 +182,8 @@ where #[cfg(not(feature = "opengl"))] gl_config: None, }, - flags: self.initialization_flags.clone(), + // We use this wrapper to be able to pass the GUI context to the editor + flags: (context, self.initialization_flags.clone()), }, ); @@ -168,7 +204,7 @@ where } } -/// The window handle used for [`IcedEditor`]. +/// The window handle used for [`IcedEditorWrapper`]. struct IcedEditorHandle { iced_state: Arc, window: iced_baseview::WindowHandle, @@ -185,3 +221,57 @@ impl Drop for IcedEditorHandle { self.window.close_window(); } } + +/// Wraps an `iced_baseview` [`Application`] around [`IcedEditor`]. Needed to allow editors to +/// always receive a copy of the GUI context. +struct IcedEditorWrapperApplication { + editor: E, +} + +impl Application for IcedEditorWrapperApplication { + type Executor = E::Executor; + type Message = E::Message; + type Flags = (Arc, E::InitializationFlags); + + fn new((context, flags): Self::Flags) -> (Self, Command) { + let (editor, command) = E::new(flags, context); + (Self { editor }, command) + } + + #[inline] + fn update( + &mut self, + window: &mut WindowQueue, + message: Self::Message, + ) -> Command { + self.editor.update(window, message) + } + + #[inline] + fn subscription( + &self, + window_subs: &mut WindowSubs, + ) -> Subscription { + self.editor.subscription(window_subs) + } + + #[inline] + fn view(&mut self) -> Element<'_, Self::Message> { + self.editor.view() + } + + #[inline] + fn background_color(&self) -> Color { + self.editor.background_color() + } + + #[inline] + fn scale_policy(&self) -> WindowScalePolicy { + self.editor.scale_policy() + } + + #[inline] + fn renderer_settings() -> iced_baseview::renderer::settings::Settings { + E::renderer_settings() + } +} diff --git a/plugins/diopser/src/editor.rs b/plugins/diopser/src/editor.rs index 1954c53c..a7111101 100644 --- a/plugins/diopser/src/editor.rs +++ b/plugins/diopser/src/editor.rs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use nih_plug::prelude::Editor; -use nih_plug_iced::{create_iced_editor, Application, Command, Element, IcedState}; +use nih_plug::prelude::{Editor, GuiContext}; +use nih_plug_iced::{create_iced_editor, Command, Element, IcedEditor, IcedState}; use std::pin::Pin; use std::sync::Arc; @@ -35,16 +35,20 @@ pub fn create( struct DiopserEditor { params: Pin>, + context: Arc, } -impl Application for DiopserEditor { +impl IcedEditor for DiopserEditor { type Executor = nih_plug_iced::executor::Default; // TODO: type Message = (); - type Flags = Pin>; + type InitializationFlags = Pin>; - fn new(flags: Self::Flags) -> (Self, Command) { - let editor = DiopserEditor { params: flags }; + fn new( + params: Self::InitializationFlags, + context: Arc, + ) -> (Self, Command) { + let editor = DiopserEditor { params, context }; (editor, Command::none()) }