Skip to main content

nice_plug_core/
editor.rs

1//! Traits for working with plugin editors.
2
3use bitflags::bitflags;
4use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
5use std::any::Any;
6use std::ffi::c_void;
7use std::sync::Arc;
8
9use crate::context::gui::GuiContext;
10
11/// An editor for a [`Plugin`][crate::plugin::Plugin].
12pub trait Editor: Send {
13    /// Create an instance of the plugin's editor and embed it in the parent window. As explained in
14    /// [`Plugin::editor()`][crate::plugin::Plugin::editor()], you can then read the parameter
15    /// values directly from your [`Params`][crate::params::Params] object, and modifying the
16    /// values can be done using the functions on the [`ParamSetter`][crate::context::gui::ParamSetter].
17    /// When you change a parameter value that way it will be broadcasted to the host and also
18    /// updated in your [`Params`][crate::params::Params] struct.
19    ///
20    /// This function should return a handle to the editor, which will be dropped when the editor
21    /// gets closed. Implement the [`Drop`] trait on the returned handle if you need to explicitly
22    /// handle the editor's closing behavior.
23    ///
24    /// If [`set_scale_factor()`][Self::set_scale_factor()] has been called, then any created
25    /// windows should have their sizes multiplied by that factor.
26    ///
27    /// The wrapper guarantees that a previous handle has been dropped before this function is
28    /// called again.
29    //
30    // TODO: Think of how this would work with the event loop. On Linux the wrapper must provide a
31    //       timer using VST3's `IRunLoop` interface, but on Window and macOS the window would
32    //       normally register its own timer. Right now we just ignore this because it would
33    //       otherwise be basically impossible to have this still be GUI-framework agnostic. Any
34    //       callback that deos involve actual GUI operations will still be spooled to the IRunLoop
35    //       instance.
36    // TODO: This function should return an `Option` instead. Right now window opening failures are
37    //       always fatal. This would need to be fixed in baseview first.
38    fn spawn(
39        &self,
40        parent: ParentWindowHandle,
41        context: Arc<dyn GuiContext>,
42    ) -> Box<dyn Any + Send>;
43
44    /// Returns the (current) size of the editor in pixels as a `(width, height)` pair. This size
45    /// must be reported in _logical pixels_, i.e. the size before being multiplied by the DPI
46    /// scaling factor to get the actual physical screen pixels.
47    fn size(&self) -> (u32, u32);
48
49    /// Set the DPI scaling factor, if supported. The plugin APIs don't make any guarantees on when
50    /// this is called, but for now just assume it will be the first function that gets called
51    /// before creating the editor. If this is set, then any windows created by this editor should
52    /// have their sizes multiplied by this scaling factor on Windows and Linux.
53    ///
54    /// Right now this is never called on macOS since DPI scaling is built into the operating system
55    /// there.
56    fn set_scale_factor(&self, factor: f32) -> bool;
57
58    /// Called whenever a specific parameter's value has changed while the editor is open. You don't
59    /// need to do anything with this, but this can be used to force a redraw when the host sends a
60    /// new value for a parameter or when a parameter change sent to the host gets processed.
61    fn param_value_changed(&self, id: &str, normalized_value: f32);
62
63    /// Called whenever a specific parameter's monophonic modulation value has changed while the
64    /// editor is open.
65    fn param_modulation_changed(&self, id: &str, modulation_offset: f32);
66
67    /// Called whenever one or more parameter values or modulations have changed while the editor is
68    /// open. This may be called in place of [`param_value_changed()`][Self::param_value_changed()]
69    /// when multiple parameter values hcange at the same time. For example, when a preset is
70    /// loaded.
71    fn param_values_changed(&self);
72
73    /// Called when the host delivers a virtual-key event to the plugin's
74    /// view. Return `true` if the editor consumed the key (the wrapper
75    /// will tell the host to skip its own accelerator handling); return
76    /// `false` to let the host process the key normally.
77    ///
78    /// The wrapper only invokes this for non-character "virtual" keys
79    /// ([`VirtualKeyCode::Backspace`], the arrow keys, function keys,
80    /// modifier presses, etc.). Plain printable characters arrive through
81    /// the plugin window's native keyboard path (on macOS, AppKit
82    /// `keyDown:` + NSTextInputContext) and are not routed here; consuming
83    /// them through this hook would double-dispatch text input.
84    ///
85    /// Both key-down and key-up events are delivered; `is_down` is
86    /// `true` for press, `false` for release. Plug-ins that consume a
87    /// key on press should generally also return `true` for the
88    /// matching release so the host doesn't pick up the release as a
89    /// separate accelerator.
90    ///
91    /// This is primarily for text-input routing in hosts (notably
92    /// REAPER) that intercept certain keys (Space, Backspace, arrows,
93    /// Cmd-shortcuts) before they reach the plugin's native view. The
94    /// editor should only return `true` if a text input in the editor
95    /// currently has focus and can consume the key.
96    ///
97    /// # Parameters
98    ///
99    /// - `key_code`: the virtual key the host reports.
100    /// - `is_down`: `true` for key-down, `false` for key-up.
101    /// - `modifiers`: which modifier keys were held when the event was
102    ///   generated.
103    fn on_virtual_key_from_host(
104        &self,
105        _key_code: VirtualKeyCode,
106        _is_down: bool,
107        _modifiers: Modifiers,
108    ) -> bool {
109        false
110    }
111
112    /// Called by the wrapper when the host has resized the plugin's view (either
113    /// because the host accepted an earlier [`GuiContext::request_resize()`], or
114    /// because the user dragged a host-provided resize handle). The editor should
115    /// resize its own window and contents to match these _logical_ dimensions
116    /// (i.e. before DPI scaling).
117    ///
118    /// Return `true` if the editor applied the new size, `false` if it rejected
119    /// it (e.g. the size is outside what the GUI supports). The default
120    /// implementation is a no-op that returns `false`, so editors that don't
121    /// support being resized by the host keep their previous fixed-size
122    /// behavior without any changes.
123    ///
124    /// This is the counterpart to [`size()`][Self::size()]: after a successful
125    /// `set_size`, `size()` should report the new dimensions.
126    fn set_size(&self, _width: u32, _height: u32) -> bool {
127        false
128    }
129
130    /// Describes whether and how the host may resize this editor. The wrapper
131    /// reads this to answer the host's resize-capability queries (CLAP's
132    /// `gui.can_resize` / `gui.get_resize_hints`, VST3's `canResize`).
133    ///
134    /// The default is [`ResizeHint::default()`], which is **not** resizable, so
135    /// editors keep their fixed-size behavior unless they opt in. An editor that
136    /// supports host resizing should return a hint with `can_resize: true` (and
137    /// usually also implement [`set_size()`][Self::set_size()] to apply the new
138    /// size). See [`ResizeHint`] for the per-axis and aspect-ratio options.
139    fn resize_hint(&self) -> ResizeHint {
140        ResizeHint::default()
141    }
142
143    // TODO: Reconsider adding a tick function here for the Linux `IRunLoop`. To keep this platform
144    //       and API agnostic, add a way to ask the GuiContext if the wrapper already provides a
145    //       tick function. If it does not, then the Editor implementation must handle this by
146    //       itself. This would also need an associated `PREFERRED_FRAME_RATE` constant.
147}
148
149/// Describes whether and how a host may resize an [`Editor`], returned from
150/// [`Editor::resize_hint()`].
151///
152/// The default is non-resizable (`can_resize: false`), matching the previous
153/// fixed-size behavior. To make an editor resizable, return a hint with
154/// `can_resize: true`; the per-axis flags and aspect-ratio fields refine how.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct ResizeHint {
157    /// Whether the host may resize the editor at all. Drives CLAP's
158    /// `gui.can_resize` and VST3's `canResize`. When `false`, the other fields
159    /// are ignored.
160    pub can_resize: bool,
161    /// Whether the width may change. Only meaningful when `can_resize` is `true`.
162    pub can_resize_horizontally: bool,
163    /// Whether the height may change. Only meaningful when `can_resize` is `true`.
164    pub can_resize_vertically: bool,
165    /// If `true`, the host should keep the editor's aspect ratio fixed at
166    /// `aspect_ratio_width : aspect_ratio_height` while resizing.
167    pub preserve_aspect_ratio: bool,
168    /// Aspect-ratio numerator (only used when `preserve_aspect_ratio` is `true`).
169    pub aspect_ratio_width: u32,
170    /// Aspect-ratio denominator (only used when `preserve_aspect_ratio` is `true`).
171    pub aspect_ratio_height: u32,
172}
173
174impl Default for ResizeHint {
175    fn default() -> Self {
176        // Not resizable by default, so editors keep their fixed-size behavior
177        // unless they explicitly opt in.
178        Self {
179            can_resize: false,
180            can_resize_horizontally: true,
181            can_resize_vertically: true,
182            preserve_aspect_ratio: false,
183            aspect_ratio_width: 1,
184            aspect_ratio_height: 1,
185        }
186    }
187}
188
189impl ResizeHint {
190    /// A freely resizable editor: both axes, no aspect-ratio lock. Convenience
191    /// for the common case.
192    pub fn resizable() -> Self {
193        Self {
194            can_resize: true,
195            ..Self::default()
196        }
197    }
198}
199
200/// A raw window handle for platform and GUI framework agnostic editors. This implements
201/// [`HasRawWindowHandle`] so it can be used directly with GUI libraries that use the same
202/// [`raw_window_handle`] version. If the library links against a different version of
203/// `raw_window_handle`, then you'll need to wrap around this type and implement the trait yourself.
204#[derive(Debug, Clone, Copy)]
205pub enum ParentWindowHandle {
206    /// The ID of the host's parent window. Used with X11.
207    X11Window(u32),
208    /// A handle to the host's parent window. Used only on macOS.
209    AppKitNsView(*mut c_void),
210    /// A handle to the host's parent window. Used only on Windows.
211    Win32Hwnd(*mut c_void),
212}
213
214unsafe impl HasRawWindowHandle for ParentWindowHandle {
215    fn raw_window_handle(&self) -> RawWindowHandle {
216        match *self {
217            ParentWindowHandle::X11Window(window) => {
218                let mut handle = raw_window_handle::XcbWindowHandle::empty();
219                handle.window = window;
220                RawWindowHandle::Xcb(handle)
221            }
222            ParentWindowHandle::AppKitNsView(ns_view) => {
223                let mut handle = raw_window_handle::AppKitWindowHandle::empty();
224                handle.ns_view = ns_view;
225                RawWindowHandle::AppKit(handle)
226            }
227            ParentWindowHandle::Win32Hwnd(hwnd) => {
228                let mut handle = raw_window_handle::Win32WindowHandle::empty();
229                handle.hwnd = hwnd;
230                RawWindowHandle::Win32(handle)
231            }
232        }
233    }
234}
235
236/// A non-character key delivered to
237/// [`Editor::on_virtual_key_from_host`]. Variant names mirror standard
238/// keyboard nomenclature; printable ASCII characters never appear here
239/// because they flow through the plugin window's native keyboard path
240/// instead.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
242#[non_exhaustive]
243pub enum VirtualKeyCode {
244    Backspace,
245    Tab,
246    Clear,
247    Return,
248    Pause,
249    Escape,
250    Space,
251    Next,
252    End,
253    Home,
254    ArrowLeft,
255    ArrowUp,
256    ArrowRight,
257    ArrowDown,
258    PageUp,
259    PageDown,
260    Select,
261    Print,
262    /// Numpad enter (distinct from [`VirtualKeyCode::Return`]).
263    NumpadEnter,
264    Snapshot,
265    Insert,
266    Delete,
267    Help,
268    Numpad0,
269    Numpad1,
270    Numpad2,
271    Numpad3,
272    Numpad4,
273    Numpad5,
274    Numpad6,
275    Numpad7,
276    Numpad8,
277    Numpad9,
278    NumpadMultiply,
279    NumpadAdd,
280    NumpadSeparator,
281    NumpadSubtract,
282    NumpadDecimal,
283    NumpadDivide,
284    F1,
285    F2,
286    F3,
287    F4,
288    F5,
289    F6,
290    F7,
291    F8,
292    F9,
293    F10,
294    F11,
295    F12,
296    NumLock,
297    ScrollLock,
298    /// Shift key, delivered as a press/release on the modifier itself.
299    /// For most text-input purposes you want
300    /// [`Modifiers::SHIFT`] on the event's modifier set instead; the
301    /// dedicated press is useful only for editors that react to
302    /// modifier-only gestures.
303    Shift,
304    /// Control key (macOS Ctrl, platform-Ctrl elsewhere). See the note
305    /// on [`VirtualKeyCode::Shift`].
306    Control,
307    /// Alt / Option key. See the note on [`VirtualKeyCode::Shift`].
308    Alt,
309    Equals,
310    ContextMenu,
311    MediaPlay,
312    MediaStop,
313    MediaPrevTrack,
314    MediaNextTrack,
315    VolumeUp,
316    VolumeDown,
317    F13,
318    F14,
319    F15,
320    F16,
321    F17,
322    F18,
323    F19,
324    F20,
325    F21,
326    F22,
327    F23,
328    F24,
329    /// Super / Command / Windows key. See the note on
330    /// [`VirtualKeyCode::Shift`].
331    Super,
332}
333
334bitflags! {
335    /// Modifier keys held while a keyboard event was generated, as
336    /// reported by [`Editor::on_virtual_key_from_host`]. Use the
337    /// standard `bitflags` API (`contains`, `intersects`, `is_empty`,
338    /// etc.) to query individual modifiers.
339    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
340    pub struct Modifiers: u8 {
341        /// Shift key.
342        const SHIFT = 1 << 0;
343        /// Alt / Option key.
344        const ALT = 1 << 1;
345        /// Command key. On Windows / Linux this is typically the Ctrl
346        /// key. See [`Modifiers::CONTROL`] for the macOS Control key
347        /// specifically.
348        const COMMAND = 1 << 2;
349        /// Control key (macOS Ctrl, distinct from
350        /// [`Modifiers::COMMAND`]).
351        const CONTROL = 1 << 3;
352    }
353}