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}