1
0
Fork 0

Use new IcedEditor trait that forwards GuiContext

This commit is contained in:
Robbert van der Helm 2022-03-13 01:04:04 +01:00
parent 59f14a0361
commit 1213d59ae7
2 changed files with 133 additions and 39 deletions

View file

@ -4,8 +4,8 @@
use baseview::{Size, WindowOpenOptions, WindowScalePolicy}; use baseview::{Size, WindowOpenOptions, WindowScalePolicy};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle}; use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle};
use std::marker::PhantomData; use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -25,24 +25,73 @@ pub use iced_baseview::*;
/// field on your parameters struct. /// field on your parameters struct.
/// ///
/// See [`IcedState::from_size()`]. /// See [`IcedState::from_size()`].
pub fn create_iced_editor<A>( pub fn create_iced_editor<E: IcedEditor>(
iced_state: Arc<IcedState>, iced_state: Arc<IcedState>,
initialization_flags: A::Flags, initialization_flags: E::InitializationFlags,
) -> Option<Box<dyn Editor>> ) -> Option<Box<dyn Editor>> {
where Some(Box::new(IcedEditorWrapper::<E> {
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
Some(Box::new(IcedEditor::<A> {
iced_state, iced_state,
initialization_flags, initialization_flags,
scaling_factor: AtomicCell::new(None), 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<dyn GuiContext>` that the editor can
/// store to interact with the parameters. The editor should have a `Pin<Arc<impl Params>>` 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<dyn GuiContext>,
) -> (Self, Command<Self::Message>);
/// See [`Application::update`].
fn update(
&mut self,
window: &mut WindowQueue,
message: Self::Message,
) -> Command<Self::Message>;
/// See [`Application::subscription`].
fn subscription(
&self,
_window_subs: &mut WindowSubs<Self::Message>,
) -> Subscription<Self::Message> {
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 // 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. // to make this serializable (only restoring the size of course) so it can be persisted.
pub struct IcedState { pub struct IcedState {
@ -73,31 +122,16 @@ impl IcedState {
} }
/// An [`Editor`] implementation that renders an iced [`Application`]. /// An [`Editor`] implementation that renders an iced [`Application`].
struct IcedEditor<A> struct IcedEditorWrapper<E: IcedEditor> {
where
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
iced_state: Arc<IcedState>, iced_state: Arc<IcedState>,
initialization_flags: A::Flags, initialization_flags: E::InitializationFlags,
// FIXME:
// /// The plugin's state. This is kept in between editor openenings.
// user_state: Arc<RwLock<T>>,
// update: Arc<dyn Fn(&Context, &ParamSetter, &mut T) + 'static + Send + Sync>,
//
/// The scaling factor reported by the host, if any. On macOS this will never be set and we /// 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. /// should use the system scaling factor instead.
scaling_factor: AtomicCell<Option<f32>>, scaling_factor: AtomicCell<Option<f32>>,
_phantom: PhantomData<A>,
} }
impl<A> Editor for IcedEditor<A> impl<E: IcedEditor> Editor for IcedEditorWrapper<E> {
where
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
fn spawn( fn spawn(
&self, &self,
parent: ParentWindowHandle, parent: ParentWindowHandle,
@ -108,9 +142,10 @@ where
let (unscaled_width, unscaled_height) = self.iced_state.size(); let (unscaled_width, unscaled_height) = self.iced_state.size();
let scaling_factor = self.scaling_factor.load(); let scaling_factor = self.scaling_factor.load();
// TODO: iced_baseview does not have gracefuly error handling for context creation failures. // TODO: iced_baseview does not have gracefuly error handling for context creation failures.
// This will panic if the context could not be created. // This will panic if the context could not be created.
let window = IcedWindow::<A>::open_parented( let window = IcedWindow::<IcedEditorWrapperApplication<E>>::open_parented(
&parent, &parent,
Settings { Settings {
window: WindowOpenOptions { window: WindowOpenOptions {
@ -147,7 +182,8 @@ where
#[cfg(not(feature = "opengl"))] #[cfg(not(feature = "opengl"))]
gl_config: None, 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<Message: 'static + Send> { struct IcedEditorHandle<Message: 'static + Send> {
iced_state: Arc<IcedState>, iced_state: Arc<IcedState>,
window: iced_baseview::WindowHandle<Message>, window: iced_baseview::WindowHandle<Message>,
@ -185,3 +221,57 @@ impl<Message: Send> Drop for IcedEditorHandle<Message> {
self.window.close_window(); 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<E> {
editor: E,
}
impl<E: IcedEditor> Application for IcedEditorWrapperApplication<E> {
type Executor = E::Executor;
type Message = E::Message;
type Flags = (Arc<dyn GuiContext>, E::InitializationFlags);
fn new((context, flags): Self::Flags) -> (Self, Command<Self::Message>) {
let (editor, command) = E::new(flags, context);
(Self { editor }, command)
}
#[inline]
fn update(
&mut self,
window: &mut WindowQueue,
message: Self::Message,
) -> Command<Self::Message> {
self.editor.update(window, message)
}
#[inline]
fn subscription(
&self,
window_subs: &mut WindowSubs<Self::Message>,
) -> Subscription<Self::Message> {
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()
}
}

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use nih_plug::prelude::Editor; use nih_plug::prelude::{Editor, GuiContext};
use nih_plug_iced::{create_iced_editor, Application, Command, Element, IcedState}; use nih_plug_iced::{create_iced_editor, Command, Element, IcedEditor, IcedState};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
@ -35,16 +35,20 @@ pub fn create(
struct DiopserEditor { struct DiopserEditor {
params: Pin<Arc<DiopserParams>>, params: Pin<Arc<DiopserParams>>,
context: Arc<dyn GuiContext>,
} }
impl Application for DiopserEditor { impl IcedEditor for DiopserEditor {
type Executor = nih_plug_iced::executor::Default; type Executor = nih_plug_iced::executor::Default;
// TODO: // TODO:
type Message = (); type Message = ();
type Flags = Pin<Arc<DiopserParams>>; type InitializationFlags = Pin<Arc<DiopserParams>>;
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) { fn new(
let editor = DiopserEditor { params: flags }; params: Self::InitializationFlags,
context: Arc<dyn GuiContext>,
) -> (Self, Command<Self::Message>) {
let editor = DiopserEditor { params, context };
(editor, Command::none()) (editor, Command::none())
} }