Skip to main content

nice_plug_core/
editor.rs

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