diff --git a/nih_plug_egui/src/lib.rs b/nih_plug_egui/src/lib.rs index e28ee6d6..09f9f38a 100644 --- a/nih_plug_egui/src/lib.rs +++ b/nih_plug_egui/src/lib.rs @@ -24,24 +24,27 @@ use egui::CtxRef; use egui_baseview::EguiWindow; use nih_plug::{Editor, ParamSetter, ParentWindowHandle}; use parking_lot::RwLock; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; /// Re-export for convenience. pub use crossbeam::atomic::AtomicCell; pub use egui; -/// Create an [Editor] instance using an [::egui] GUI. Using the state 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 size passed to this function is the GUI's intitial -/// size, and this is kept in sync whenever the GUI gets resized. 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. +/// Create an [Editor] instance using an [::egui] 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 [EguiState] 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]` field on your parameters struct. +/// +/// See [EguiState::from_size()]. // // 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( - size: Arc>, - initial_state: T, + egui_state: Arc, + user_state: T, update: U, ) -> Option> where @@ -49,17 +52,44 @@ where U: Fn(&CtxRef, &ParamSetter, &mut T) + 'static + Send + Sync, { Some(Box::new(EguiEditor { - size, - state: Arc::new(RwLock::new(initial_state)), + egui_state, + user_state: Arc::new(RwLock::new(user_state)), 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 { + 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. struct EguiEditor { - size: Arc>, + egui_state: Arc, /// The plugin's state. This is kept in between editor openenings. - state: Arc>, + user_state: Arc>, update: Arc, } @@ -73,9 +103,9 @@ where context: Arc, ) -> Box { 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( &parent, WindowOpenOptions { @@ -115,16 +145,21 @@ where ) .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) { - self.size.load() + self.egui_state.size() } } /// The window handle used for [EguiEditor]. struct EguiEditorHandle { + egui_state: Arc, window: WindowHandle, } @@ -135,6 +170,7 @@ unsafe impl Sync 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(); } diff --git a/plugins/examples/gain-gui/src/lib.rs b/plugins/examples/gain-gui/src/lib.rs index 037091fe..903203d7 100644 --- a/plugins/examples/gain-gui/src/lib.rs +++ b/plugins/examples/gain-gui/src/lib.rs @@ -23,14 +23,14 @@ use nih_plug::{ ProcessStatus, Vst3Plugin, }; 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::sync::Arc; /// This is mostly identical to the gain example, minus some fluff, and with a GUI. struct Gain { params: Pin>, - editor_size: Arc>, + editor_state: Arc, /// Needed to normalize the peak meter's response based on the sample rate. peak_meter_decay_weight: f32, @@ -52,7 +52,7 @@ impl Default for Gain { fn default() -> Self { Self { 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: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), @@ -101,7 +101,7 @@ impl Plugin for Gain { let params = self.params.clone(); let peak_meter = self.peak_meter.clone(); create_egui_editor( - self.editor_size.clone(), + self.editor_state.clone(), (), move |egui_ctx, setter, _state| { egui::CentralPanel::default().show(egui_ctx, |ui| {