nice_plug_core/context/gui.rs
1//! A context passed to a plugin's editor.
2
3use std::sync::Arc;
4
5use crate::{
6 params::{Param, internals::ParamPtr},
7 plugin::{Plugin, PluginState},
8};
9
10use super::PluginApi;
11
12/// Callbacks the plugin can make when the user interacts with its GUI such as updating parameter
13/// values. This is passed to the plugin during [`Editor::spawn()`][crate::editor::Editor::spawn()].
14/// All of these functions assume they're being called from the main GUI thread.
15//
16// # Safety
17//
18// The implementing wrapper can assume that everything is being called from the main thread. Since
19// nice-plug doesn't own the GUI event loop, this invariant cannot be part of the interface.
20pub trait GuiContext: Send + Sync + 'static {
21 /// Get the current plugin API. This may be useful to display in the plugin's GUI as part of an
22 /// about screen.
23 fn plugin_api(&self) -> PluginApi;
24
25 /// Ask the host to resize the editor window to the size specified by
26 /// [`Editor::size()`][crate::editor::Editor::size()]. This will return false if the host
27 /// somehow didn't like this and rejected the resize, in which case the window should revert to
28 /// its old size. You should only actually resize your embedded window once this returns `true`.
29 ///
30 /// TODO: Host->Plugin resizing has not been implemented yet
31 fn request_resize(&self) -> bool;
32
33 /// Inform the host a parameter will be automated. Create a [`ParamSetter`] and use
34 /// [`ParamSetter::begin_set_parameter()`] instead for a safe, user friendly API.
35 ///
36 /// # Safety
37 ///
38 /// The implementing function still needs to check if `param` actually exists. This function is
39 /// mostly marked as unsafe for API reasons.
40 unsafe fn raw_begin_set_parameter(&self, param: ParamPtr);
41
42 /// Inform the host a parameter is being automated with an already normalized value. Create a
43 /// [`ParamSetter`] and use [`ParamSetter::set_parameter()`] instead for a safe, user friendly
44 /// API.
45 ///
46 /// # Safety
47 ///
48 /// The implementing function still needs to check if `param` actually exists. This function is
49 /// mostly marked as unsafe for API reasons.
50 unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32);
51
52 /// Inform the host a parameter has been automated. Create a [`ParamSetter`] and use
53 /// [`ParamSetter::end_set_parameter()`] instead for a safe, user friendly API.
54 ///
55 /// # Safety
56 ///
57 /// The implementing function still needs to check if `param` actually exists. This function is
58 /// mostly marked as unsafe for API reasons.
59 unsafe fn raw_end_set_parameter(&self, param: ParamPtr);
60
61 /// Serialize the plugin's current state to a serde-serializable object. Useful for implementing
62 /// preset handling within a plugin's GUI.
63 fn get_state(&self) -> PluginState;
64
65 /// Restore the state from a previously serialized state object. This will block the GUI thread
66 /// until the state has been restored and a parameter value rescan has been requested from the
67 /// host. If the plugin is currently processing audio, then the parameter values will be
68 /// restored at the end of the current processing cycle.
69 fn set_state(&self, state: PluginState);
70}
71
72/// An way to run background tasks from the plugin's GUI, equivalent to the
73/// [`ProcessContext::execute_background()`][crate::context::process::ProcessContext::execute_background()]
74/// and [`ProcessContext::execute_gui()`][crate::context::process::ProcessContext::execute_gui()]
75/// functions. This is passed directly to [`Plugin::editor()`] so the plugin can move it into its
76/// editor and use it later.
77///
78/// # Note
79///
80/// This is only intended to be used from the GUI. Use the methods on
81/// [`InitContext`][crate::context::init::InitContext] and
82/// [`ProcessContext`][crate::context::process::ProcessContext] to run tasks during the `initialize()`
83/// and `process()` functions.
84//
85// NOTE: This is separate from `GuiContext` because adding a type parameter there would clutter up a
86// lot of structs, and may even be incompatible with the way certain GUI libraries work.
87pub struct AsyncExecutor<P: Plugin> {
88 pub(crate) execute_background: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
89 pub(crate) execute_gui: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
90}
91
92impl<P: Plugin> AsyncExecutor<P> {
93 pub fn new(
94 execute_background: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
95 execute_gui: Arc<dyn Fn(P::BackgroundTask) + Send + Sync>,
96 ) -> Self {
97 Self {
98 execute_background,
99 execute_gui,
100 }
101 }
102}
103
104// Can't derive this since Rust then requires `P` to also be `Clone`able
105impl<P: Plugin> Clone for AsyncExecutor<P> {
106 fn clone(&self) -> Self {
107 Self {
108 execute_background: self.execute_background.clone(),
109 execute_gui: self.execute_gui.clone(),
110 }
111 }
112}
113
114/// A convenience helper for setting parameter values. Any changes made here will be broadcasted to
115/// the host and reflected in the plugin's [`Params`][crate::params::Params] object. These
116/// functions should only be called from the main thread.
117pub struct ParamSetter<'a> {
118 pub raw_context: &'a dyn GuiContext,
119}
120
121impl<P: Plugin> AsyncExecutor<P> {
122 /// Execute a task on a background thread using `[Plugin::task_executor]`. This allows you to
123 /// defer expensive tasks for later without blocking either the process function or the GUI
124 /// thread. As long as creating the `task` is realtime-safe, this operation is too.
125 ///
126 /// # Note
127 ///
128 /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
129 /// either prevent this from happening, or check whether the task still needs to be completed in
130 /// your task executor.
131 pub fn execute_background(&self, task: P::BackgroundTask) {
132 (self.execute_background)(task);
133 }
134
135 /// Execute a task on a background thread using `[Plugin::task_executor]`.
136 ///
137 /// # Note
138 ///
139 /// Scheduling the same task multiple times will cause those duplicate tasks to pile up. Try to
140 /// either prevent this from happening, or check whether the task still needs to be completed in
141 /// your task executor.
142 pub fn execute_gui(&self, task: P::BackgroundTask) {
143 (self.execute_gui)(task);
144 }
145}
146
147impl<'a> ParamSetter<'a> {
148 pub fn new(context: &'a dyn GuiContext) -> Self {
149 Self {
150 raw_context: context,
151 }
152 }
153
154 /// Inform the host that you will start automating a parameter. This needs to be called before
155 /// calling [`set_parameter()`][Self::set_parameter()] for the specified parameter.
156 pub fn begin_set_parameter<P: Param>(&self, param: &P) {
157 unsafe { self.raw_context.raw_begin_set_parameter(param.as_ptr()) };
158 }
159
160 /// Set a parameter to the specified parameter value. You will need to call
161 /// [`begin_set_parameter()`][Self::begin_set_parameter()] before and
162 /// [`end_set_parameter()`][Self::end_set_parameter()] after calling this so the host can
163 /// properly record automation for the parameter. This can be called multiple times in a row
164 /// before calling [`end_set_parameter()`][Self::end_set_parameter()], for instance when moving
165 /// a slider around.
166 ///
167 /// This function assumes you're already calling this from a GUI thread. Calling any of these
168 /// functions from any other thread may result in unexpected behavior.
169 pub fn set_parameter<P: Param>(&self, param: &P, value: P::Plain) {
170 let ptr = param.as_ptr();
171 let normalized = param.preview_normalized(value);
172 unsafe {
173 self.raw_context
174 .raw_set_parameter_normalized(ptr, normalized)
175 };
176 }
177
178 /// Set a parameter to an already normalized value. Works exactly the same as
179 /// [`set_parameter()`][Self::set_parameter()] and needs to follow the same rules, but this may
180 /// be useful when implementing a GUI.
181 ///
182 /// This does not perform any snapping. Consider converting the normalized value to a plain
183 /// value and setting that with [`set_parameter()`][Self::set_parameter()] instead so the
184 /// normalized value known to the host matches `param.normalized_value()`.
185 pub fn set_parameter_normalized<P: Param>(&self, param: &P, normalized: f32) {
186 let ptr = param.as_ptr();
187 unsafe {
188 self.raw_context
189 .raw_set_parameter_normalized(ptr, normalized)
190 };
191 }
192
193 /// Inform the host that you are done automating a parameter. This needs to be called after one
194 /// or more [`set_parameter()`][Self::set_parameter()] calls for a parameter so the host knows
195 /// the automation gesture has finished.
196 pub fn end_set_parameter<P: Param>(&self, param: &P) {
197 unsafe { self.raw_context.raw_end_set_parameter(param.as_ptr()) };
198 }
199}