Skip to main content

nice_plug_egui/
lib.rs

1//! [egui](https://github.com/emilk/egui) editor support for nice-plug.
2//!
3//! TODO: Proper usage example, for now check out the gain_gui example
4
5// See the comment in the main `nice-plug` crate
6#![allow(clippy::type_complexity)]
7
8use crossbeam::atomic::AtomicCell;
9use egui::{Context, Ui};
10use nice_plug_core::context::gui::ParamSetter;
11use nice_plug_core::editor::Editor;
12use nice_plug_core::params::persist::PersistentField;
13use parking_lot::Mutex;
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17
18#[cfg(not(any(feature = "opengl", feature = "wgpu")))]
19compile_error!("There's currently no software rendering support for egui");
20
21/// Re-export for convenience.
22pub use egui_baseview::*;
23
24#[cfg(all(feature = "opengl", not(feature = "wgpu")))]
25pub use baseview::gl::{GlConfig, Profile};
26
27mod editor;
28pub mod resizable_window;
29pub mod widgets;
30
31#[derive(Default, Debug, Clone)]
32pub struct EguiSettings {
33    pub graphics_config: GraphicsConfig,
34
35    #[cfg(all(feature = "opengl", not(feature = "wgpu")))]
36    /// By default this is set to `false`.
37    pub enable_vsync_on_x11: bool,
38
39    #[cfg(all(feature = "opengl", not(feature = "wgpu")))]
40    /// The configuration of the OpenGL context.
41    ///
42    /// By default this is set to:
43    /// ```ignore
44    /// GlConfig {
45    ///     version: (3, 2),
46    ///     profile: Profile::Core,
47    ///     red_bits: 8,
48    ///     blue_bits: 8,
49    ///     green_bits: 8,
50    ///     alpha_bits: 8,
51    ///     depth_bits: 24,
52    ///     stencil_bits: 8,
53    ///     samples: None,
54    ///     srgb: true,
55    ///     double_buffer: true,
56    ///     vsync: false,
57    /// }
58    /// ```
59    pub gl_config: GlConfig,
60}
61
62/// Create an [`Editor`] instance using an [`egui`] GUI. Using the user state parameter is
63/// optional, but it can be useful for keeping track of some temporary GUI-only settings. See the
64/// `nice-plug_gain_egui` example for more information on how to use this. The [`EguiState`] passed
65/// to this function contains the GUI's intitial size, and this is kept in sync whenever the GUI gets
66/// resized. You can also use this to know if the GUI is open, so you can avoid performing
67/// potentially expensive calculations while the GUI is not open. If you want this size to be
68/// persisted when restoring a plugin instance, then you can store it in a `#[persist = "key"]`
69/// field on your parameters struct.
70///
71/// See [`EguiState::from_size()`].
72pub fn create_egui_editor<T, B, U>(
73    egui_state: Arc<EguiState>,
74    user_state: T,
75    settings: EguiSettings,
76    build: B,
77    update: U,
78) -> Option<Box<dyn Editor>>
79where
80    T: 'static + Send,
81    B: Fn(&Context, &mut Queue, &mut T) + 'static + Send + Sync,
82    U: Fn(&mut Ui, &ParamSetter, &mut Queue, &mut T) + 'static + Send + Sync,
83{
84    Some(Box::new(editor::EguiEditor {
85        egui_state,
86        user_state: Arc::new(Mutex::new(user_state)),
87        settings: Arc::new(settings),
88        build: Arc::new(build),
89        update: Arc::new(update),
90
91        // TODO: We can't get the size of the window when baseview does its own scaling, so if the
92        //       host does not set a scale factor on Windows or Linux we should just use a factor of
93        //       1. That may make the GUI tiny but it also prevents it from getting cut off.
94        #[cfg(target_os = "macos")]
95        scaling_factor: AtomicCell::new(None),
96        #[cfg(not(target_os = "macos"))]
97        scaling_factor: AtomicCell::new(Some(1.0)),
98    }))
99}
100
101/// State for an `nice-plug-egui` editor.
102#[derive(Debug, Serialize, Deserialize)]
103pub struct EguiState {
104    /// The window's size in logical pixels before applying `scale_factor`.
105    #[serde(with = "nice_plug_core::params::persist::serialize_atomic_cell")]
106    size: AtomicCell<(u32, u32)>,
107
108    /// The new size of the window, if it was requested to resize by the GUI.
109    #[serde(skip)]
110    requested_size: AtomicCell<Option<(u32, u32)>>,
111
112    /// Whether the editor's window is currently open.
113    #[serde(skip)]
114    open: AtomicBool,
115}
116
117impl<'a> PersistentField<'a, EguiState> for Arc<EguiState> {
118    fn set(&self, new_value: EguiState) {
119        self.size.store(new_value.size.load());
120    }
121
122    fn map<F, R>(&self, f: F) -> R
123    where
124        F: Fn(&EguiState) -> R,
125    {
126        f(self)
127    }
128}
129
130impl EguiState {
131    /// Initialize the GUI's state. This value can be passed to [`create_egui_editor()`]. The window
132    /// size is in logical pixels, so before it is multiplied by the DPI scaling factor.
133    pub fn from_size(width: u32, height: u32) -> Arc<EguiState> {
134        Arc::new(EguiState {
135            size: AtomicCell::new((width, height)),
136            requested_size: Default::default(),
137            open: AtomicBool::new(false),
138        })
139    }
140
141    /// Returns a `(width, height)` pair for the current size of the GUI in logical pixels.
142    pub fn size(&self) -> (u32, u32) {
143        self.size.load()
144    }
145
146    /// Whether the GUI is currently visible.
147    // Called `is_open()` instead of `open()` to avoid the ambiguity.
148    pub fn is_open(&self) -> bool {
149        self.open.load(Ordering::Acquire)
150    }
151
152    /// Set the new size (in logical pixels) that will be used to resize the window if the host allows.
153    pub fn set_requested_size(&self, new_size: (u32, u32)) {
154        self.requested_size.store(Some(new_size));
155    }
156}