Add a way to know whether the egui editor is open
This commit is contained in:
parent
08fe8e703e
commit
4260c5441c
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
Loading…
Reference in a new issue