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    // TODO: Reconsider adding a tick function here for the Linux `IRunLoop`. To keep this platform
113    //       and API agnostic, add a way to ask the GuiContext if the wrapper already provides a
114    //       tick function. If it does not, then the Editor implementation must handle this by
115    //       itself. This would also need an associated `PREFERRED_FRAME_RATE` constant.
116    // TODO: Host->Plugin resizing
117}
118
119/// A raw window handle for platform and GUI framework agnostic editors. This implements
120/// [`HasRawWindowHandle`] so it can be used directly with GUI libraries that use the same
121/// [`raw_window_handle`] version. If the library links against a different version of
122/// `raw_window_handle`, then you'll need to wrap around this type and implement the trait yourself.
123#[derive(Debug, Clone, Copy)]
124pub enum ParentWindowHandle {
125    /// The ID of the host's parent window. Used with X11.
126    X11Window(u32),
127    /// A handle to the host's parent window. Used only on macOS.
128    AppKitNsView(*mut c_void),
129    /// A handle to the host's parent window. Used only on Windows.
130    Win32Hwnd(*mut c_void),
131}
132
133unsafe impl HasRawWindowHandle for ParentWindowHandle {
134    fn raw_window_handle(&self) -> RawWindowHandle {
135        match *self {
136            ParentWindowHandle::X11Window(window) => {
137                let mut handle = raw_window_handle::XcbWindowHandle::empty();
138                handle.window = window;
139                RawWindowHandle::Xcb(handle)
140            }
141            ParentWindowHandle::AppKitNsView(ns_view) => {
142                let mut handle = raw_window_handle::AppKitWindowHandle::empty();
143                handle.ns_view = ns_view;
144                RawWindowHandle::AppKit(handle)
145            }
146            ParentWindowHandle::Win32Hwnd(hwnd) => {
147                let mut handle = raw_window_handle::Win32WindowHandle::empty();
148                handle.hwnd = hwnd;
149                RawWindowHandle::Win32(handle)
150            }
151        }
152    }
153}
154
155/// A non-character key delivered to
156/// [`Editor::on_virtual_key_from_host`]. Variant names mirror standard
157/// keyboard nomenclature; printable ASCII characters never appear here
158/// because they flow through the plugin window's native keyboard path
159/// instead.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
161#[non_exhaustive]
162pub enum VirtualKeyCode {
163    Backspace,
164    Tab,
165    Clear,
166    Return,
167    Pause,
168    Escape,
169    Space,
170    Next,
171    End,
172    Home,
173    ArrowLeft,
174    ArrowUp,
175    ArrowRight,
176    ArrowDown,
177    PageUp,
178    PageDown,
179    Select,
180    Print,
181    /// Numpad enter (distinct from [`VirtualKeyCode::Return`]).
182    NumpadEnter,
183    Snapshot,
184    Insert,
185    Delete,
186    Help,
187    Numpad0,
188    Numpad1,
189    Numpad2,
190    Numpad3,
191    Numpad4,
192    Numpad5,
193    Numpad6,
194    Numpad7,
195    Numpad8,
196    Numpad9,
197    NumpadMultiply,
198    NumpadAdd,
199    NumpadSeparator,
200    NumpadSubtract,
201    NumpadDecimal,
202    NumpadDivide,
203    F1,
204    F2,
205    F3,
206    F4,
207    F5,
208    F6,
209    F7,
210    F8,
211    F9,
212    F10,
213    F11,
214    F12,
215    NumLock,
216    ScrollLock,
217    /// Shift key, delivered as a press/release on the modifier itself.
218    /// For most text-input purposes you want
219    /// [`Modifiers::SHIFT`] on the event's modifier set instead; the
220    /// dedicated press is useful only for editors that react to
221    /// modifier-only gestures.
222    Shift,
223    /// Control key (macOS Ctrl, platform-Ctrl elsewhere). See the note
224    /// on [`VirtualKeyCode::Shift`].
225    Control,
226    /// Alt / Option key. See the note on [`VirtualKeyCode::Shift`].
227    Alt,
228    Equals,
229    ContextMenu,
230    MediaPlay,
231    MediaStop,
232    MediaPrevTrack,
233    MediaNextTrack,
234    VolumeUp,
235    VolumeDown,
236    F13,
237    F14,
238    F15,
239    F16,
240    F17,
241    F18,
242    F19,
243    F20,
244    F21,
245    F22,
246    F23,
247    F24,
248    /// Super / Command / Windows key. See the note on
249    /// [`VirtualKeyCode::Shift`].
250    Super,
251}
252
253bitflags! {
254    /// Modifier keys held while a keyboard event was generated, as
255    /// reported by [`Editor::on_virtual_key_from_host`]. Use the
256    /// standard `bitflags` API (`contains`, `intersects`, `is_empty`,
257    /// etc.) to query individual modifiers.
258    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
259    pub struct Modifiers: u8 {
260        /// Shift key.
261        const SHIFT = 1 << 0;
262        /// Alt / Option key.
263        const ALT = 1 << 1;
264        /// Command key. On Windows / Linux this is typically the Ctrl
265        /// key. See [`Modifiers::CONTROL`] for the macOS Control key
266        /// specifically.
267        const COMMAND = 1 << 2;
268        /// Control key (macOS Ctrl, distinct from
269        /// [`Modifiers::COMMAND`]).
270        const CONTROL = 1 << 3;
271    }
272}