Add most bits and pieces for iced support
This commit is contained in:
parent
cd5a4f01bf
commit
3ebe34c870
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1938,6 +1938,7 @@ name = "nih_plug_iced"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"baseview",
|
"baseview",
|
||||||
|
"crossbeam",
|
||||||
"iced_baseview",
|
"iced_baseview",
|
||||||
"nih_plug",
|
"nih_plug",
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,6 +7,10 @@ license = "ISC"
|
||||||
|
|
||||||
description = "An adapter to use egui GUIs with NIH-plug"
|
description = "An adapter to use egui GUIs with NIH-plug"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["opengl"]
|
||||||
|
opengl = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nih_plug = { path = ".." }
|
nih_plug = { path = ".." }
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ use parking_lot::RwLock;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "opengl"))]
|
||||||
|
compile_error!("There's currently no software rendering support for egui");
|
||||||
|
|
||||||
/// Re-export for convenience.
|
/// Re-export for convenience.
|
||||||
pub use egui;
|
pub use egui;
|
||||||
|
|
||||||
|
@ -111,6 +114,8 @@ where
|
||||||
scale: scaling_factor
|
scale: scaling_factor
|
||||||
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||||
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||||
|
|
||||||
|
#[cfg(feature = "opengl")]
|
||||||
gl_config: Some(GlConfig {
|
gl_config: Some(GlConfig {
|
||||||
version: (3, 2),
|
version: (3, 2),
|
||||||
red_bits: 8,
|
red_bits: 8,
|
||||||
|
|
|
@ -12,7 +12,9 @@ default = ["wgpu"]
|
||||||
|
|
||||||
# Use wgpu rendering, which translates to Vulkan, Metal, or Direct3D12 depending
|
# Use wgpu rendering, which translates to Vulkan, Metal, or Direct3D12 depending
|
||||||
# on the platform.
|
# on the platform.
|
||||||
wgpu = ["iced_baseview/wgpu"]
|
# NOTE: The OpenGL support in baseview is not used, this is just a workaround
|
||||||
|
# for a rust analyzer bug. See the comment in lib.rs.
|
||||||
|
wgpu = ["iced_baseview/wgpu", "baseview/opengl"]
|
||||||
# Use OpenGL instead of wgpu for the rendering. This should increase platform
|
# Use OpenGL instead of wgpu for the rendering. This should increase platform
|
||||||
# compatibility at the cost of some iced features not being available.
|
# compatibility at the cost of some iced features not being available.
|
||||||
opengl = ["iced_baseview/glow"]
|
opengl = ["iced_baseview/glow"]
|
||||||
|
@ -54,6 +56,7 @@ smol = ["iced_baseview/smol"]
|
||||||
nih_plug = { path = ".." }
|
nih_plug = { path = ".." }
|
||||||
|
|
||||||
baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/mouse-event-modifiers" }
|
baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/mouse-event-modifiers" }
|
||||||
|
crossbeam = "0.8"
|
||||||
# Upstream doesn't work with the current iced version, this branch also contains
|
# Upstream doesn't work with the current iced version, this branch also contains
|
||||||
# additional features
|
# additional features
|
||||||
iced_baseview = { git = "https://github.com/robbert-vdh/iced_baseview.git", branch = "feature/update-dependencies", default_features = false }
|
iced_baseview = { git = "https://github.com/robbert-vdh/iced_baseview.git", branch = "feature/update-dependencies", default_features = false }
|
||||||
|
|
|
@ -2,5 +2,186 @@
|
||||||
//!
|
//!
|
||||||
//! TODO: Proper usage example, for now check out the gain_gui example
|
//! TODO: Proper usage example, for now check out the gain_gui example
|
||||||
|
|
||||||
|
use baseview::{Size, WindowOpenOptions, WindowScalePolicy};
|
||||||
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Re-export for convenience.
|
/// Re-export for convenience.
|
||||||
pub use iced_baseview::*;
|
pub use iced_baseview::*;
|
||||||
|
|
||||||
|
/// FIXME: Document how this works once everything actually works. The below comment is from the
|
||||||
|
/// egui version.
|
||||||
|
///
|
||||||
|
/// Create an [`Editor`] instance using an [`iced`][::iced] GUI. Using the user state parameter is
|
||||||
|
/// optional, but it can be useful for keeping track of some temporary GUI-only settings. See the
|
||||||
|
/// `gui_gain` example for more information on how to use this. The [`IcedState`] passed to this
|
||||||
|
/// function contains the GUI's intitial size, and this is kept in sync whenever the GUI gets
|
||||||
|
/// resized. You can also use this to know if the GUI is open, so you can avoid performing
|
||||||
|
/// potentially expensive calculations while the GUI is not open. If you want this size to be
|
||||||
|
/// persisted when restoring a plugin instance, then you can store it in a `#[persist = "key"]`
|
||||||
|
/// field on your parameters struct.
|
||||||
|
///
|
||||||
|
/// See [`IcedState::from_size()`].
|
||||||
|
pub fn create_iced_editor<A>(
|
||||||
|
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> {
|
||||||
|
iced_state,
|
||||||
|
initialization_flags,
|
||||||
|
|
||||||
|
scaling_factor: AtomicCell::new(None),
|
||||||
|
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
size: AtomicCell<(u32, u32)>,
|
||||||
|
open: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IcedState {
|
||||||
|
/// Initialize the GUI's state. This value can be passed to [`create_iced_editor()`]. The window
|
||||||
|
/// size is in logical pixels, so before it is multiplied by the DPI scaling factor.
|
||||||
|
pub fn from_size(width: u32, height: u32) -> Arc<IcedState> {
|
||||||
|
Arc::new(IcedState {
|
||||||
|
size: AtomicCell::new((width, height)),
|
||||||
|
open: AtomicBool::new(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `(width, height)` pair for the current size of the GUI in logical pixels.
|
||||||
|
pub fn size(&self) -> (u32, u32) {
|
||||||
|
self.size.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the GUI is currently visible.
|
||||||
|
// Called `is_open()` instead of `open()` to avoid the ambiguity.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.open.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [`Editor`] implementation that renders an iced [`Application`].
|
||||||
|
struct IcedEditor<A>
|
||||||
|
where
|
||||||
|
A: Application + 'static + Send + Sync,
|
||||||
|
A::Flags: Clone + Sync,
|
||||||
|
{
|
||||||
|
iced_state: Arc<IcedState>,
|
||||||
|
initialization_flags: A::Flags,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
{
|
||||||
|
fn spawn(
|
||||||
|
&self,
|
||||||
|
parent: ParentWindowHandle,
|
||||||
|
context: Arc<dyn GuiContext>,
|
||||||
|
) -> Box<dyn std::any::Any + Send + Sync> {
|
||||||
|
// FIXME: Somehow get the context/parametersetter to the GUI. Another trait that adds a
|
||||||
|
// `set_context()` would be the easiest way but perhaps not the cleanest.
|
||||||
|
|
||||||
|
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(
|
||||||
|
&parent,
|
||||||
|
Settings {
|
||||||
|
window: WindowOpenOptions {
|
||||||
|
title: String::from("iced window"),
|
||||||
|
// Baseview should be doing the DPI scaling for us
|
||||||
|
size: 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,
|
||||||
|
},
|
||||||
|
flags: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle used for [`IcedEditor`].
|
||||||
|
struct IcedEditorHandle<Message: 'static + Send> {
|
||||||
|
iced_state: Arc<IcedState>,
|
||||||
|
window: iced_baseview::WindowHandle<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
||||||
|
/// having this requirement?
|
||||||
|
unsafe impl<Message: Send> Send for IcedEditorHandle<Message> {}
|
||||||
|
unsafe impl<Message: Send> Sync for IcedEditorHandle<Message> {}
|
||||||
|
|
||||||
|
impl<Message: Send> Drop for IcedEditorHandle<Message> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.iced_state.open.store(false, Ordering::Release);
|
||||||
|
self.window.close_window();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue