1
0
Fork 0

Add a way to know whether the egui editor is open

This commit is contained in:
Robbert van der Helm 2022-02-08 20:16:39 +01:00
parent 08fe8e703e
commit 4260c5441c
2 changed files with 57 additions and 21 deletions

View file

@ -24,24 +24,27 @@ use egui::CtxRef;
use egui_baseview::EguiWindow; use egui_baseview::EguiWindow;
use nih_plug::{Editor, ParamSetter, ParentWindowHandle}; use nih_plug::{Editor, ParamSetter, ParentWindowHandle};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
/// Re-export for convenience. /// Re-export for convenience.
pub use crossbeam::atomic::AtomicCell; pub use crossbeam::atomic::AtomicCell;
pub use egui; pub use egui;
/// Create an [Editor] instance using an [::egui] GUI. Using the state is optional, but it can be /// Create an [Editor] instance using an [::egui] GUI. Using the user state parameter is optional,
/// useful for keeping track of some temporary GUI-only settings. See the `gui_gain` example for /// but it can be useful for keeping track of some temporary GUI-only settings. See the `gui_gain`
/// more information on how to use this. The size passed to this function is the GUI's intitial /// example for more information on how to use this. The [EguiState] passed to this function
/// size, and this is kept in sync whenever the GUI gets resized. If you want this size to be /// contains the GUI's intitial size, and this is kept in sync whenever the GUI gets resized. You
/// persisted when restoring a plugin instance, then you can store it in a `#[persist]` field on /// can also use this to know if the GUI is open, so you can avoid performing potentially expensive
/// your parameters struct. /// 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]` field on your parameters struct.
///
/// See [EguiState::from_size()].
// //
// TODO: DPI scaling, this needs to be implemented on the framework level // TODO: DPI scaling, this needs to be implemented on the framework level
// TODO: Add some way for the plugin to check whether the GUI is open
pub fn create_egui_editor<T, U>( pub fn create_egui_editor<T, U>(
size: Arc<AtomicCell<(u32, u32)>>, egui_state: Arc<EguiState>,
initial_state: T, user_state: T,
update: U, update: U,
) -> Option<Box<dyn Editor>> ) -> Option<Box<dyn Editor>>
where where
@ -49,17 +52,44 @@ where
U: Fn(&CtxRef, &ParamSetter, &mut T) + 'static + Send + Sync, U: Fn(&CtxRef, &ParamSetter, &mut T) + 'static + Send + Sync,
{ {
Some(Box::new(EguiEditor { Some(Box::new(EguiEditor {
size, egui_state,
state: Arc::new(RwLock::new(initial_state)), user_state: Arc::new(RwLock::new(user_state)),
update: Arc::new(update), update: Arc::new(update),
})) }))
} }
// 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 EguiState {
size: AtomicCell<(u32, u32)>,
open: AtomicBool,
}
impl EguiState {
/// Initialize the GUI's state. This is passed to [create_egui_editor()].
pub fn from_size(width: u32, height: u32) -> Arc<EguiState> {
Arc::new(EguiState {
size: AtomicCell::new((width, height)),
open: AtomicBool::new(false),
})
}
/// Return a `(width, height)` pair for the current size of the GUI.
pub fn size(&self) -> (u32, u32) {
self.size.load()
}
/// Whether the GUI is currently visible.
pub fn open(&self) -> bool {
self.open.load(Ordering::Acquire)
}
}
/// An [Editor] implementation that calls an egui draw loop. /// An [Editor] implementation that calls an egui draw loop.
struct EguiEditor<T> { struct EguiEditor<T> {
size: Arc<AtomicCell<(u32, u32)>>, egui_state: Arc<EguiState>,
/// The plugin's state. This is kept in between editor openenings. /// The plugin's state. This is kept in between editor openenings.
state: Arc<RwLock<T>>, user_state: Arc<RwLock<T>>,
update: Arc<dyn Fn(&CtxRef, &ParamSetter, &mut T) + 'static + Send + Sync>, update: Arc<dyn Fn(&CtxRef, &ParamSetter, &mut T) + 'static + Send + Sync>,
} }
@ -73,9 +103,9 @@ where
context: Arc<dyn nih_plug::GuiContext>, context: Arc<dyn nih_plug::GuiContext>,
) -> Box<dyn std::any::Any> { ) -> Box<dyn std::any::Any> {
let update = self.update.clone(); let update = self.update.clone();
let state = self.state.clone(); let state = self.user_state.clone();
let (width, height) = self.size.load(); let (width, height) = self.egui_state.size();
let window = EguiWindow::open_parented( let window = EguiWindow::open_parented(
&parent, &parent,
WindowOpenOptions { WindowOpenOptions {
@ -115,16 +145,21 @@ where
) )
.expect("We provided an OpenGL config, did we not?"); .expect("We provided an OpenGL config, did we not?");
Box::new(EguiEditorHandle { window }) self.egui_state.open.store(true, Ordering::Release);
Box::new(EguiEditorHandle {
egui_state: self.egui_state.clone(),
window,
})
} }
fn size(&self) -> (u32, u32) { fn size(&self) -> (u32, u32) {
self.size.load() self.egui_state.size()
} }
} }
/// The window handle used for [EguiEditor]. /// The window handle used for [EguiEditor].
struct EguiEditorHandle { struct EguiEditorHandle {
egui_state: Arc<EguiState>,
window: WindowHandle, window: WindowHandle,
} }
@ -135,6 +170,7 @@ unsafe impl Sync for EguiEditorHandle {}
impl Drop for EguiEditorHandle { impl Drop for EguiEditorHandle {
fn drop(&mut self) { 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 // XXX: This should automatically happen when the handle gets dropped, but apparently not
self.window.close(); self.window.close();
} }

View file

@ -23,14 +23,14 @@ use nih_plug::{
ProcessStatus, Vst3Plugin, ProcessStatus, Vst3Plugin,
}; };
use nih_plug::{FloatParam, Param, Params, Range, Smoother, SmoothingStyle}; use nih_plug::{FloatParam, Param, Params, Range, Smoother, SmoothingStyle};
use nih_plug_egui::{create_egui_editor, egui, AtomicCell}; use nih_plug_egui::{create_egui_editor, egui, EguiState};
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
/// This is mostly identical to the gain example, minus some fluff, and with a GUI. /// This is mostly identical to the gain example, minus some fluff, and with a GUI.
struct Gain { struct Gain {
params: Pin<Arc<GainParams>>, params: Pin<Arc<GainParams>>,
editor_size: Arc<AtomicCell<(u32, u32)>>, editor_state: Arc<EguiState>,
/// Needed to normalize the peak meter's response based on the sample rate. /// Needed to normalize the peak meter's response based on the sample rate.
peak_meter_decay_weight: f32, peak_meter_decay_weight: f32,
@ -52,7 +52,7 @@ impl Default for Gain {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Arc::pin(GainParams::default()), params: Arc::pin(GainParams::default()),
editor_size: Arc::new(AtomicCell::new((300, 100))), editor_state: EguiState::from_size(300, 100),
peak_meter_decay_weight: 1.0, peak_meter_decay_weight: 1.0,
peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)),
@ -101,7 +101,7 @@ impl Plugin for Gain {
let params = self.params.clone(); let params = self.params.clone();
let peak_meter = self.peak_meter.clone(); let peak_meter = self.peak_meter.clone();
create_egui_editor( create_egui_editor(
self.editor_size.clone(), self.editor_state.clone(),
(), (),
move |egui_ctx, setter, _state| { move |egui_ctx, setter, _state| {
egui::CentralPanel::default().show(egui_ctx, |ui| { egui::CentralPanel::default().show(egui_ctx, |ui| {