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 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<A>(
pub fn create_iced_editor<E: IcedEditor>(
iced_state: Arc<IcedState>,
initialization_flags: A::Flags,
) -> Option<Box<dyn Editor>>
where
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
Some(Box::new(IcedEditor::<A> {
initialization_flags: E::InitializationFlags,
) -> Option<Box<dyn Editor>> {
Some(Box::new(IcedEditorWrapper::<E> {
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<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
// 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<A>
where
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
struct IcedEditorWrapper<E: IcedEditor> {
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
/// should use the system scaling factor instead.
scaling_factor: AtomicCell<Option<f32>>,
_phantom: PhantomData<A>,
}
impl<A> Editor for IcedEditor<A>
where
A: Application + 'static + Send + Sync,
A::Flags: Clone + Sync,
{
impl<E: IcedEditor> Editor for IcedEditorWrapper<E> {
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::<A>::open_parented(
let window = IcedWindow::<IcedEditorWrapperApplication<E>>::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<Message: 'static + Send> {
iced_state: Arc<IcedState>,
window: iced_baseview::WindowHandle<Message>,
@ -185,3 +221,57 @@ impl<Message: Send> Drop for IcedEditorHandle<Message> {
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
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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<Arc<DiopserParams>>,
context: Arc<dyn GuiContext>,
}
impl Application for DiopserEditor {
impl IcedEditor for DiopserEditor {
type Executor = nih_plug_iced::executor::Default;
// TODO:
type Message = ();
type Flags = Pin<Arc<DiopserParams>>;
type InitializationFlags = Pin<Arc<DiopserParams>>;
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) {
let editor = DiopserEditor { params: flags };
fn new(
params: Self::InitializationFlags,
context: Arc<dyn GuiContext>,
) -> (Self, Command<Self::Message>) {
let editor = DiopserEditor { params, context };
(editor, Command::none())
}