Move nih_plug_egui Editor impl to own module
This commit is contained in:
parent
0db23e5aee
commit
1448388353
127
nih_plug_egui/src/editor.rs
Normal file
127
nih_plug_egui/src/editor.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
//! An [`Editor`] implementation for egui.
|
||||||
|
|
||||||
|
use baseview::gl::GlConfig;
|
||||||
|
use baseview::{Size, WindowHandle, WindowOpenOptions, WindowScalePolicy};
|
||||||
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use egui::Context;
|
||||||
|
use egui_baseview::EguiWindow;
|
||||||
|
use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle};
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::EguiState;
|
||||||
|
|
||||||
|
/// An [`Editor`] implementation that calls an egui draw loop.
|
||||||
|
pub(crate) struct EguiEditor<T> {
|
||||||
|
pub(crate) egui_state: Arc<EguiState>,
|
||||||
|
/// The plugin's state. This is kept in between editor openenings.
|
||||||
|
pub(crate) user_state: Arc<RwLock<T>>,
|
||||||
|
|
||||||
|
/// The user's build function. Applied once at the start of the application.
|
||||||
|
pub(crate) build: Arc<dyn Fn(&Context, &mut T) + 'static + Send + Sync>,
|
||||||
|
/// The user's update function.
|
||||||
|
pub(crate) 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.
|
||||||
|
pub(crate) scaling_factor: AtomicCell<Option<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Editor for EguiEditor<T>
|
||||||
|
where
|
||||||
|
T: 'static + Send + Sync,
|
||||||
|
{
|
||||||
|
fn spawn(
|
||||||
|
&self,
|
||||||
|
parent: ParentWindowHandle,
|
||||||
|
context: Arc<dyn GuiContext>,
|
||||||
|
) -> Box<dyn std::any::Any + Send> {
|
||||||
|
let build = self.build.clone();
|
||||||
|
let update = self.update.clone();
|
||||||
|
let state = self.user_state.clone();
|
||||||
|
|
||||||
|
let (unscaled_width, unscaled_height) = self.egui_state.size();
|
||||||
|
let scaling_factor = self.scaling_factor.load();
|
||||||
|
let window = EguiWindow::open_parented(
|
||||||
|
&parent,
|
||||||
|
WindowOpenOptions {
|
||||||
|
title: String::from("egui 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(GlConfig {
|
||||||
|
version: (3, 2),
|
||||||
|
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()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
move |egui_ctx, _queue, state| build(egui_ctx, &mut state.write()),
|
||||||
|
move |egui_ctx, _queue, state| {
|
||||||
|
let setter = ParamSetter::new(context.as_ref());
|
||||||
|
|
||||||
|
// For now, just always redraw. Most plugin GUIs have meters, and those almost always
|
||||||
|
// need a redraw. Later we can try to be a bit more sophisticated about this. Without
|
||||||
|
// this we would also have a blank GUI when it gets first opened because most DAWs open
|
||||||
|
// their GUI while the window is still unmapped.
|
||||||
|
egui_ctx.request_repaint();
|
||||||
|
(update)(egui_ctx, &setter, &mut state.write());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.egui_state.open.store(true, Ordering::Release);
|
||||||
|
Box::new(EguiEditorHandle {
|
||||||
|
egui_state: self.egui_state.clone(),
|
||||||
|
window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> (u32, u32) {
|
||||||
|
self.egui_state.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scale_factor(&self, factor: f32) -> bool {
|
||||||
|
self.scaling_factor.store(Some(factor));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_values_changed(&self) {
|
||||||
|
// As mentioned above, for now we'll always force a redraw to allow meter widgets to work
|
||||||
|
// correctly. In the future we can use an `Arc<AtomicBool>` and only force a redraw when
|
||||||
|
// that boolean is set.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle used for [`EguiEditor`].
|
||||||
|
struct EguiEditorHandle {
|
||||||
|
egui_state: Arc<EguiState>,
|
||||||
|
window: WindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
||||||
|
/// having this requirement?
|
||||||
|
unsafe impl Send for EguiEditorHandle {}
|
||||||
|
|
||||||
|
impl Drop for EguiEditorHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.egui_state.open.store(false, Ordering::Release);
|
||||||
|
// XXX: This should automatically happen when the handle gets dropped, but apparently not
|
||||||
|
self.window.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,13 +5,10 @@
|
||||||
// See the comment in the main `nih_plug` crate
|
// See the comment in the main `nih_plug` crate
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
use baseview::gl::GlConfig;
|
|
||||||
use baseview::{Size, WindowHandle, WindowOpenOptions, WindowScalePolicy};
|
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use egui::Context;
|
use egui::Context;
|
||||||
use egui_baseview::EguiWindow;
|
|
||||||
use nih_plug::params::persist::PersistentField;
|
use nih_plug::params::persist::PersistentField;
|
||||||
use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle};
|
use nih_plug::prelude::{Editor, ParamSetter};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -23,6 +20,7 @@ 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;
|
||||||
|
|
||||||
|
mod editor;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
/// Create an [`Editor`] instance using an [`egui`][::egui] GUI. Using the user state parameter is
|
/// Create an [`Editor`] instance using an [`egui`][::egui] GUI. Using the user state parameter is
|
||||||
|
@ -46,7 +44,7 @@ where
|
||||||
B: Fn(&Context, &mut T) + 'static + Send + Sync,
|
B: Fn(&Context, &mut T) + 'static + Send + Sync,
|
||||||
U: Fn(&Context, &ParamSetter, &mut T) + 'static + Send + Sync,
|
U: Fn(&Context, &ParamSetter, &mut T) + 'static + Send + Sync,
|
||||||
{
|
{
|
||||||
Some(Box::new(EguiEditor {
|
Some(Box::new(editor::EguiEditor {
|
||||||
egui_state,
|
egui_state,
|
||||||
user_state: Arc::new(RwLock::new(user_state)),
|
user_state: Arc::new(RwLock::new(user_state)),
|
||||||
build: Arc::new(build),
|
build: Arc::new(build),
|
||||||
|
@ -107,117 +105,3 @@ impl EguiState {
|
||||||
self.open.load(Ordering::Acquire)
|
self.open.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`Editor`] implementation that calls an egui draw loop.
|
|
||||||
struct EguiEditor<T> {
|
|
||||||
egui_state: Arc<EguiState>,
|
|
||||||
/// The plugin's state. This is kept in between editor openenings.
|
|
||||||
user_state: Arc<RwLock<T>>,
|
|
||||||
|
|
||||||
/// The user's build function. Applied once at the start of the application.
|
|
||||||
build: Arc<dyn Fn(&Context, &mut T) + 'static + Send + Sync>,
|
|
||||||
/// The user's update function.
|
|
||||||
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>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Editor for EguiEditor<T>
|
|
||||||
where
|
|
||||||
T: 'static + Send + Sync,
|
|
||||||
{
|
|
||||||
fn spawn(
|
|
||||||
&self,
|
|
||||||
parent: ParentWindowHandle,
|
|
||||||
context: Arc<dyn GuiContext>,
|
|
||||||
) -> Box<dyn std::any::Any + Send> {
|
|
||||||
let build = self.build.clone();
|
|
||||||
let update = self.update.clone();
|
|
||||||
let state = self.user_state.clone();
|
|
||||||
|
|
||||||
let (unscaled_width, unscaled_height) = self.egui_state.size();
|
|
||||||
let scaling_factor = self.scaling_factor.load();
|
|
||||||
let window = EguiWindow::open_parented(
|
|
||||||
&parent,
|
|
||||||
WindowOpenOptions {
|
|
||||||
title: String::from("egui 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(GlConfig {
|
|
||||||
version: (3, 2),
|
|
||||||
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()
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
state,
|
|
||||||
move |egui_ctx, _queue, state| build(egui_ctx, &mut state.write()),
|
|
||||||
move |egui_ctx, _queue, state| {
|
|
||||||
let setter = ParamSetter::new(context.as_ref());
|
|
||||||
|
|
||||||
// For now, just always redraw. Most plugin GUIs have meters, and those almost always
|
|
||||||
// need a redraw. Later we can try to be a bit more sophisticated about this. Without
|
|
||||||
// this we would also have a blank GUI when it gets first opened because most DAWs open
|
|
||||||
// their GUI while the window is still unmapped.
|
|
||||||
egui_ctx.request_repaint();
|
|
||||||
(update)(egui_ctx, &setter, &mut state.write());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.egui_state.open.store(true, Ordering::Release);
|
|
||||||
Box::new(EguiEditorHandle {
|
|
||||||
egui_state: self.egui_state.clone(),
|
|
||||||
window,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> (u32, u32) {
|
|
||||||
self.egui_state.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_scale_factor(&self, factor: f32) -> bool {
|
|
||||||
self.scaling_factor.store(Some(factor));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn param_values_changed(&self) {
|
|
||||||
// As mentioned above, for now we'll always force a redraw to allow meter widgets to work
|
|
||||||
// correctly. In the future we can use an `Arc<AtomicBool>` and only force a redraw when
|
|
||||||
// that boolean is set.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The window handle used for [`EguiEditor`].
|
|
||||||
struct EguiEditorHandle {
|
|
||||||
egui_state: Arc<EguiState>,
|
|
||||||
window: WindowHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
|
||||||
/// having this requirement?
|
|
||||||
unsafe impl Send for EguiEditorHandle {}
|
|
||||||
|
|
||||||
impl Drop for EguiEditorHandle {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.egui_state.open.store(false, Ordering::Release);
|
|
||||||
// XXX: This should automatically happen when the handle gets dropped, but apparently not
|
|
||||||
self.window.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue