Skip to main content

nice_plug_core/
editor.rs

1//! Traits for working with plugin editors.
2
3use bitflags::bitflags;
4use dpi::{LogicalSize, PhysicalSize, Size};
5use raw_window_handle::{HasWindowHandle, RawWindowHandle};
6use std::error::Error;
7use std::ffi::{c_ulong, c_void};
8use std::num::{NonZeroIsize, NonZeroU32};
9use std::ptr::NonNull;
10
11pub use dpi;
12
13use crate::context::gui::GuiContext;
14
15pub struct SpawnedEditor<E: EditorHandle> {
16    /// A handle to the instance of an open [`Editor`].
17    ///
18    /// When this handle is dropped, the editor instance is also dropped.
19    pub handle: E,
20
21    /// The owned window handle
22    ///
23    /// This will usually be [`baseview::Window`](). This is type-erased to avoid needing nice-plug-core
24    /// to depend on `baseview` until it has a stable version.
25    ///
26    /// When this is dropped, the window should be automatically closed.
27    pub window: E::Window,
28}
29
30/// A handler for baseview windows to interact with their host.
31///
32/// (This is a re-implementation of
33/// [`baseview::host::HostCallbacks`](https://docs.rs/baseview/latest/baseview/host/trait.HostCallbacks.html)
34/// to avoid directly depending on `baseview` until it is stabilized.)
35pub trait HostCallbacks: 'static {
36    /// Requests the parent window to be resized to accommodate the child window with
37    /// the given new size.
38    ///
39    /// # Errors
40    ///
41    /// This can return any type of error, indicating the host either failed or denied
42    /// to handle the resize request. If it does, the error is logged and the resize
43    /// operation is canceled or reverted.
44    fn request_resize(&mut self, new_size: Size, scale_factor: f64) -> Result<(), Box<dyn Error>>;
45
46    /// Notifies the host that the child window has been destroyed for a reason outside
47    /// the host’s control.
48    ///
49    /// This can be because the display connection was lost, because the window handler
50    /// crashed, or because the window handler decided to close the window itself.
51    ///
52    /// The host should close its parent window, as it will not show anything useful
53    /// anymore.
54    fn destroyed(&mut self);
55}
56
57/// A special handler for the Window thread to wake up and call methods on the main thread.
58///
59/// (This is a re-implementation of
60/// [`baseview::host::HostMainThreadCaller`](https://docs.rs/baseview/latest/baseview/host/trait.HostMainThreadCaller.html)
61/// to avoid directly depending on `baseview` until it is stabilized.)
62///
63/// # Platform compatibility notes
64///
65/// This is only needed on X11, as Windows and macOS windows already run on the main thread.
66pub trait HostMainThreadCaller: Send + 'static {
67    /// Schedules a callback on the main thread.
68    ///
69    /// # Platform compatibility notes
70    ///
71    /// Only X11 needs this. This can be implemented as a no-op on Windows and macOS.
72    fn call_main_thread(&mut self);
73}
74
75pub struct HostMethods {
76    pub callbacks: Box<dyn HostCallbacks>,
77    pub main_thread_caller: Box<dyn HostMainThreadCaller>,
78}
79
80/// A handle to spawned instance of an [`Editor`].
81///
82/// The host uses this to resize the editor's window and to dispatch key events.
83pub trait EditorHandle: Send + 'static {
84    type Window;
85    type Error: Error;
86
87    /// Open the window, and block the current thread until the window is
88    /// closed. Used only for standalone targets.
89    fn run_until_closed(window: Self::Window) -> Result<(), Self::Error>;
90
91    fn set_parent(
92        &self,
93        parent: ParentWindowHandle,
94        window: &Self::Window,
95    ) -> Result<(), Self::Error>;
96
97    /// Show the window
98    ///
99    /// This will never be called on the standalone target.
100    fn show(&self, window: &Self::Window) -> Result<(), Self::Error>;
101
102    /// Hide the window
103    ///
104    /// This will never be called on the standalone target.
105    fn hide(&self, window: &Self::Window) -> Result<(), Self::Error>;
106
107    /// Called by the wrapper when the host has resized the plugin's view. The
108    /// editor should resize its own window and contents to match these dimensions.
109    ///
110    /// This is the counterpart to [`size()`][Editor::size()]: after a successful
111    /// `set_size`, `size()` should report the new dimensions.
112    ///
113    /// This will never be called on the standalone target.
114    fn set_size(
115        &self,
116        new_size: PhysicalSize<u32>,
117        window: &Self::Window,
118    ) -> Result<(), Self::Error>;
119
120    fn host_main_thread_callback(&self, window: &Self::Window);
121
122    /// Return the closest supported size.
123    ///
124    /// This will never be called on the standalone target.
125    fn adjust_size(
126        &self,
127        new_size: PhysicalSize<u32>,
128        window: &Self::Window,
129    ) -> Option<PhysicalSize<u32>> {
130        let _ = new_size;
131        let _ = window;
132        None
133    }
134
135    /// Called when the host has a new suggested scale factor to use.
136    ///
137    /// Right now this is never called on macOS since DPI scaling is built into the
138    /// operating system there.
139    ///
140    /// This will never be called on the standalone target.
141    fn set_fallback_scale_factor(
142        &self,
143        scale_factor: f64,
144        window: &Self::Window,
145    ) -> Result<(), Self::Error> {
146        let _ = scale_factor;
147        let _ = window;
148        Ok(())
149    }
150
151    /// Called when the host delivers a virtual-key event to the plugin's
152    /// view. Return `true` if the editor consumed the key (the wrapper
153    /// will tell the host to skip its own accelerator handling); return
154    /// `false` to let the host process the key normally.
155    ///
156    /// The wrapper only invokes this for non-character "virtual" keys
157    /// ([`VirtualKeyCode::Backspace`], the arrow keys, function keys,
158    /// modifier presses, etc.). Plain printable characters arrive through
159    /// the plugin window's native keyboard path (on macOS, AppKit
160    /// `keyDown:` + NSTextInputContext) and are not routed here; consuming
161    /// them through this hook would double-dispatch text input.
162    ///
163    /// Both key-down and key-up events are delivered; `is_down` is
164    /// `true` for press, `false` for release. Plug-ins that consume a
165    /// key on press should generally also return `true` for the
166    /// matching release so the host doesn't pick up the release as a
167    /// separate accelerator.
168    ///
169    /// This is primarily for text-input routing in hosts (notably
170    /// REAPER) that intercept certain keys (Space, Backspace, arrows,
171    /// Cmd-shortcuts) before they reach the plugin's native view. The
172    /// editor should only return `true` if a text input in the editor
173    /// currently has focus and can consume the key.
174    ///
175    /// This will never be called on the standalone target.
176    ///
177    /// # Parameters
178    ///
179    /// - `key_code`: the virtual key the host reports.
180    /// - `is_down`: `true` for key-down, `false` for key-up.
181    /// - `modifiers`: which modifier keys were held when the event was
182    ///   generated.
183    fn on_virtual_key_from_host(
184        &self,
185        key_code: VirtualKeyCode,
186        is_down: bool,
187        modifiers: Modifiers,
188    ) -> bool {
189        let _ = key_code;
190        let _ = is_down;
191        let _ = modifiers;
192        false
193    }
194
195    /// Called when the plugin's state has changed (i.e. a preset was loaded). The
196    /// editor should rescan all of its parameters.
197    fn state_changed(&self) {}
198
199    /// Called whenever a specific parameter's value has changed. You don't
200    /// need to do anything with this, but this can be used to force a redraw when the host sends a
201    /// new value for a parameter or when a parameter change sent to the host gets processed.
202    fn param_value_changed(&self, id: &str, normalized_value: f32);
203
204    /// Called whenever a specific parameter's monophonic modulation value has changed.
205    fn param_modulation_changed(&self, id: &str, modulation_offset: f32);
206}
207
208/// An editor for a [`Plugin`][crate::plugin::Plugin].
209pub trait Editor: Send {
210    type Handle: EditorHandle;
211
212    /// Create an instance of the plugin's editor and embed it in the parent window. As explained in
213    /// [`Plugin::editor()`][crate::plugin::Plugin::editor()], you can then read the parameter
214    /// values directly from your [`Params`][crate::params::Params] object, and modifying the
215    /// values can be done using the functions on the [`ParamSetter`][crate::context::gui::ParamSetter].
216    /// When you change a parameter value that way it will be broadcasted to the host and also
217    /// updated in your [`Params`][crate::params::Params] struct.
218    ///
219    /// This function should return a handle to the editor, which will be dropped when the editor
220    /// gets closed. Implement the [`Drop`] trait on the returned handle if you need to explicitly
221    /// handle the editor's closing behavior.
222    ///
223    /// If an error is returned, then the editor will not open.
224    ///
225    /// If [`EditorHandle::set_fallback_scale_factor()`] has been called, then any created
226    /// windows should have their sizes multiplied by that factor.
227    ///
228    /// The wrapper guarantees that a previous handle has been dropped before this function is
229    /// called again.
230    //
231    // TODO: Think of how this would work with the event loop. On Linux the wrapper must provide a
232    //       timer using VST3's `IRunLoop` interface, but on Window and macOS the window would
233    //       normally register its own timer. Right now we just ignore this because it would
234    //       otherwise be basically impossible to have this still be GUI-framework agnostic. Any
235    //       callback that deos involve actual GUI operations will still be spooled to the IRunLoop
236    //       instance.
237    fn spawn(
238        &self,
239        parent: Option<ParentWindowHandle>,
240        wait_for_parent: bool,
241        fallback_scale_factor: Option<f64>,
242        gui_context: GuiContext,
243        host: Option<HostMethods>,
244    ) -> Result<SpawnedEditor<Self::Handle>, Box<dyn Error>>;
245
246    /// Returns the (current) size of the editor in physical pixels.
247    fn size(&self) -> PhysicalSize<u32>;
248
249    /// Describes whether and how the host may resize this editor. The wrapper
250    /// reads this to answer the host's resize-capability queries (CLAP's
251    /// `gui.can_resize` / `gui.get_resize_hints`, VST3's `canResize`).
252    ///
253    /// The default is [`ResizeHint::default()`], which is **not** resizable, so
254    /// editors keep their fixed-size behavior unless they opt in. An editor that
255    /// supports host resizing should return a hint with `can_resize: true` (and
256    /// usually also implement [`EditorHandle::set_size()`] to apply the new
257    /// size). See [`ResizeHint`] for the per-axis and aspect-ratio options.
258    fn resize_hint(&self) -> ResizeHint {
259        ResizeHint::default()
260    }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub struct DummyEditorError;
265impl Error for DummyEditorError {}
266impl std::fmt::Display for DummyEditorError {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        write!(f, "Plugin does not implement an editor")
269    }
270}
271
272impl EditorHandle for () {
273    type Window = ();
274    type Error = DummyEditorError;
275
276    fn run_until_closed(_window: Self::Window) -> Result<(), Self::Error> {
277        Err(DummyEditorError)
278    }
279
280    fn set_parent(
281        &self,
282        _parent: ParentWindowHandle,
283        _window: &Self::Window,
284    ) -> Result<(), Self::Error> {
285        Err(DummyEditorError)
286    }
287
288    fn show(&self, _window: &Self::Window) -> Result<(), Self::Error> {
289        Err(DummyEditorError)
290    }
291
292    fn hide(&self, _window: &Self::Window) -> Result<(), Self::Error> {
293        Err(DummyEditorError)
294    }
295
296    fn host_main_thread_callback(&self, _window: &Self::Window) {}
297
298    fn set_size(
299        &self,
300        _new_size: PhysicalSize<u32>,
301        _window: &Self::Window,
302    ) -> Result<(), Self::Error> {
303        Err(DummyEditorError)
304    }
305
306    fn param_value_changed(&self, _id: &str, _normalized_value: f32) {}
307
308    fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {}
309}
310
311impl Editor for () {
312    type Handle = ();
313
314    fn spawn(
315        &self,
316        _parent: Option<ParentWindowHandle>,
317        _wait_for_parent: bool,
318        _fallback_scale_factor: Option<f64>,
319        _gui_context: GuiContext,
320        _host: Option<HostMethods>,
321    ) -> Result<SpawnedEditor<Self::Handle>, Box<dyn Error>> {
322        Err(String::from("Plugin does not implement an editor").into())
323    }
324
325    fn size(&self) -> PhysicalSize<u32> {
326        PhysicalSize::default()
327    }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq)]
331pub enum SizeConstraints {
332    Logical {
333        min_size: Option<LogicalSize<f32>>,
334        max_size: Option<LogicalSize<f32>>,
335    },
336    Physical {
337        min_size: Option<PhysicalSize<u32>>,
338        max_size: Option<PhysicalSize<u32>>,
339    },
340}
341
342impl SizeConstraints {
343    pub const fn min_logical_size(min_size: LogicalSize<f32>) -> Self {
344        Self::Logical {
345            min_size: Some(min_size),
346            max_size: None,
347        }
348    }
349
350    pub const fn min_physical_size(min_size: PhysicalSize<u32>) -> Self {
351        Self::Physical {
352            min_size: Some(min_size),
353            max_size: None,
354        }
355    }
356
357    pub const fn logical(
358        min_size: Option<LogicalSize<f32>>,
359        max_size: Option<LogicalSize<f32>>,
360    ) -> Self {
361        Self::Logical { min_size, max_size }
362    }
363
364    pub const fn physical(
365        min_size: Option<PhysicalSize<u32>>,
366        max_size: Option<PhysicalSize<u32>>,
367    ) -> Self {
368        Self::Physical { min_size, max_size }
369    }
370}
371
372impl Default for SizeConstraints {
373    fn default() -> Self {
374        Self::Logical {
375            min_size: None,
376            max_size: None,
377        }
378    }
379}
380
381/// Describes whether and how a host may resize an [`Editor`], returned from
382/// [`Editor::resize_hint()`].
383///
384/// The default is non-resizable (`can_resize: false`), matching the previous
385/// fixed-size behavior. To make an editor resizable, return a hint with
386/// `can_resize: true`; the per-axis flags and aspect-ratio fields refine how.
387#[derive(Debug, Clone, Copy, PartialEq)]
388pub struct ResizeHint {
389    /// Whether the host may resize the editor at all. Drives CLAP's
390    /// `gui.can_resize` and VST3's `canResize`. When `false`, the other fields
391    /// are ignored.
392    pub can_resize: bool,
393    /// Whether the width may change. Only meaningful when `can_resize` is `true`.
394    pub can_resize_horizontally: bool,
395    /// Whether the height may change. Only meaningful when `can_resize` is `true`.
396    pub can_resize_vertically: bool,
397    /// If `true`, the host should keep the editor's aspect ratio fixed at
398    /// `aspect_ratio_width : aspect_ratio_height` while resizing.
399    pub preserve_aspect_ratio: bool,
400    /// Aspect-ratio numerator (only used when `preserve_aspect_ratio` is `true`).
401    pub aspect_ratio_width: u32,
402    /// Aspect-ratio denominator (only used when `preserve_aspect_ratio` is `true`).
403    pub aspect_ratio_height: u32,
404    pub size_constraints: SizeConstraints,
405}
406
407impl Default for ResizeHint {
408    fn default() -> Self {
409        // Not resizable by default, so editors keep their fixed-size behavior
410        // unless they explicitly opt in.
411        Self::NON_RESIZABLE
412    }
413}
414
415impl ResizeHint {
416    /// A non-resizable editor. This is the default value.
417    pub const NON_RESIZABLE: Self = Self {
418        size_constraints: SizeConstraints::Logical {
419            min_size: None,
420            max_size: None,
421        },
422        can_resize: false,
423        can_resize_horizontally: false,
424        can_resize_vertically: false,
425        preserve_aspect_ratio: false,
426        aspect_ratio_width: 1,
427        aspect_ratio_height: 1,
428    };
429
430    /// A freely resizable editor: both axes, no aspect-ratio lock. Convenience
431    /// for the common case.
432    pub const RESIZABLE: Self = Self {
433        can_resize: true,
434        can_resize_horizontally: true,
435        can_resize_vertically: true,
436        ..Self::NON_RESIZABLE
437    };
438
439    pub const fn non_resizable() -> Self {
440        Self::NON_RESIZABLE
441    }
442
443    pub const fn resizable() -> Self {
444        Self::RESIZABLE
445    }
446
447    pub const fn with_min_logical_size(mut self, min_size: LogicalSize<f32>) -> Self {
448        self.size_constraints = SizeConstraints::Logical {
449            min_size: Some(min_size),
450            max_size: None,
451        };
452        self
453    }
454
455    pub const fn with_min_max_logical_size(
456        mut self,
457        min_size: Option<LogicalSize<f32>>,
458        max_size: Option<LogicalSize<f32>>,
459    ) -> Self {
460        self.size_constraints = SizeConstraints::Logical { min_size, max_size };
461        self
462    }
463
464    pub const fn with_min_physical_size(mut self, min_size: PhysicalSize<u32>) -> Self {
465        self.size_constraints = SizeConstraints::Physical {
466            min_size: Some(min_size),
467            max_size: None,
468        };
469        self
470    }
471
472    pub const fn with_min_max_physical_size(
473        mut self,
474        min_size: Option<PhysicalSize<u32>>,
475        max_size: Option<PhysicalSize<u32>>,
476    ) -> Self {
477        self.size_constraints = SizeConstraints::Physical { min_size, max_size };
478        self
479    }
480
481    pub const fn with_size_constraints(mut self, size_constraints: SizeConstraints) -> Self {
482        self.size_constraints = size_constraints;
483        self
484    }
485
486    /// * `aspect_ratio_width`: aspect-ratio numerator
487    /// * `aspect_ratio_height`: aspect-ratio denominator
488    pub const fn with_aspect_ratio(
489        mut self,
490        aspect_ratio_width: u32,
491        aspect_ratio_height: u32,
492    ) -> Self {
493        assert!(aspect_ratio_width != 0);
494        assert!(aspect_ratio_height != 0);
495
496        self.aspect_ratio_width = aspect_ratio_width;
497        self.aspect_ratio_height = aspect_ratio_height;
498
499        self
500    }
501
502    /// Returns whether or not the given size in physical pixels is valid.
503    pub fn is_size_valid(
504        &self,
505        new_size: PhysicalSize<u32>,
506        current_size: PhysicalSize<u32>,
507        scale_factor: f64,
508    ) -> bool {
509        let adjusted_size = self.adjust_size(new_size, current_size, scale_factor);
510        new_size == adjusted_size
511    }
512
513    /// Adjust the new requested size to the closest size that is compatible
514    /// with this plugin.
515    pub fn adjust_size(
516        &self,
517        mut new_size: PhysicalSize<u32>,
518        current_size: PhysicalSize<u32>,
519        scale_factor: f64,
520    ) -> PhysicalSize<u32> {
521        if !self.can_resize {
522            return current_size;
523        }
524
525        let (min_phy_size, max_phy_size) = match self.size_constraints {
526            SizeConstraints::Logical { min_size, max_size } => (
527                min_size.map(|s| PhysicalSize {
528                    width: (s.width as f64 * scale_factor).round() as u32,
529                    height: (s.height as f64 * scale_factor).round() as u32,
530                }),
531                max_size.map(|s| PhysicalSize {
532                    width: (s.width as f64 * scale_factor).round() as u32,
533                    height: (s.height as f64 * scale_factor).round() as u32,
534                }),
535            ),
536            SizeConstraints::Physical { min_size, max_size } => (min_size, max_size),
537        };
538
539        if let Some(min_size) = min_phy_size {
540            new_size.width = new_size.width.max(min_size.width);
541            new_size.height = new_size.height.max(min_size.height);
542        }
543        if let Some(max_size) = max_phy_size {
544            new_size.width = new_size.width.min(max_size.width);
545            new_size.height = new_size.height.min(max_size.height);
546        }
547
548        new_size.width = new_size.width.max(1);
549        new_size.height = new_size.height.max(1);
550
551        if self.preserve_aspect_ratio {
552            let adjusted_width = (new_size.height as f32 * self.aspect_ratio_width as f32
553                / self.aspect_ratio_height as f32)
554                .round() as u32;
555
556            if let Some(min_size) = min_phy_size
557                && adjusted_width < min_size.width
558            {
559                new_size = min_size;
560            } else if let Some(max_size) = max_phy_size
561                && adjusted_width > max_size.width
562            {
563                new_size = max_size;
564            } else {
565                new_size.width = adjusted_width;
566            }
567        } else {
568            if !self.can_resize_horizontally {
569                new_size.width = current_size.width;
570            }
571            if !self.can_resize_vertically {
572                new_size.height = current_size.height;
573            }
574        }
575
576        new_size
577    }
578}
579
580/// A raw window handle for platform and GUI framework agnostic editors. This implements
581/// [`HasWindowHandle`] so it can be used directly with GUI libraries that use the same
582/// [`raw_window_handle`] version. If the library links against a different version of
583/// `raw_window_handle`, then you'll need to wrap around this type and implement the trait yourself.
584#[derive(Debug, Clone, Copy)]
585pub enum ParentWindowHandle {
586    /// The ID of the host's parent window. Used with X11.
587    XlibWindow(c_ulong),
588    /// The ID of the host's parent window. Used with X11.
589    XcbWindow(NonZeroU32),
590    /// A handle to the host's parent window. Used only on macOS.
591    AppKitNsView(NonNull<c_void>),
592    /// A handle to the host's parent window. Used only on Windows.
593    Win32Hwnd(NonZeroIsize),
594}
595
596impl HasWindowHandle for ParentWindowHandle {
597    fn window_handle(
598        &self,
599    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
600        let raw = match *self {
601            ParentWindowHandle::XlibWindow(window) => {
602                RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle::new(window))
603            }
604            ParentWindowHandle::XcbWindow(window) => {
605                RawWindowHandle::Xcb(raw_window_handle::XcbWindowHandle::new(window))
606            }
607            ParentWindowHandle::AppKitNsView(ns_view) => {
608                RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle::new(ns_view))
609            }
610            ParentWindowHandle::Win32Hwnd(hwnd) => {
611                RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::new(hwnd))
612            }
613        };
614
615        Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
616    }
617}
618
619/// A non-character key delivered to
620/// [`EditorHandle::on_virtual_key_from_host`]. Variant names mirror standard
621/// keyboard nomenclature; printable ASCII characters never appear here
622/// because they flow through the plugin window's native keyboard path
623/// instead.
624#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
625#[non_exhaustive]
626pub enum VirtualKeyCode {
627    Backspace,
628    Tab,
629    Clear,
630    Return,
631    Pause,
632    Escape,
633    Space,
634    Next,
635    End,
636    Home,
637    ArrowLeft,
638    ArrowUp,
639    ArrowRight,
640    ArrowDown,
641    PageUp,
642    PageDown,
643    Select,
644    Print,
645    /// Numpad enter (distinct from [`VirtualKeyCode::Return`]).
646    NumpadEnter,
647    Snapshot,
648    Insert,
649    Delete,
650    Help,
651    Numpad0,
652    Numpad1,
653    Numpad2,
654    Numpad3,
655    Numpad4,
656    Numpad5,
657    Numpad6,
658    Numpad7,
659    Numpad8,
660    Numpad9,
661    NumpadMultiply,
662    NumpadAdd,
663    NumpadSeparator,
664    NumpadSubtract,
665    NumpadDecimal,
666    NumpadDivide,
667    F1,
668    F2,
669    F3,
670    F4,
671    F5,
672    F6,
673    F7,
674    F8,
675    F9,
676    F10,
677    F11,
678    F12,
679    NumLock,
680    ScrollLock,
681    /// Shift key, delivered as a press/release on the modifier itself.
682    /// For most text-input purposes you want
683    /// [`Modifiers::SHIFT`] on the event's modifier set instead; the
684    /// dedicated press is useful only for editors that react to
685    /// modifier-only gestures.
686    Shift,
687    /// Control key (macOS Ctrl, platform-Ctrl elsewhere). See the note
688    /// on [`VirtualKeyCode::Shift`].
689    Control,
690    /// Alt / Option key. See the note on [`VirtualKeyCode::Shift`].
691    Alt,
692    Equals,
693    ContextMenu,
694    MediaPlay,
695    MediaStop,
696    MediaPrevTrack,
697    MediaNextTrack,
698    VolumeUp,
699    VolumeDown,
700    F13,
701    F14,
702    F15,
703    F16,
704    F17,
705    F18,
706    F19,
707    F20,
708    F21,
709    F22,
710    F23,
711    F24,
712    /// Super / Command / Windows key. See the note on
713    /// [`VirtualKeyCode::Shift`].
714    Super,
715}
716
717bitflags! {
718    /// Modifier keys held while a keyboard event was generated, as
719    /// reported by [`Editor::on_virtual_key_from_host`]. Use the
720    /// standard `bitflags` API (`contains`, `intersects`, `is_empty`,
721    /// etc.) to query individual modifiers.
722    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
723    pub struct Modifiers: u8 {
724        /// Shift key.
725        const SHIFT = 1 << 0;
726        /// Alt / Option key.
727        const ALT = 1 << 1;
728        /// Command key. On Windows / Linux this is typically the Ctrl
729        /// key. See [`Modifiers::CONTROL`] for the macOS Control key
730        /// specifically.
731        const COMMAND = 1 << 2;
732        /// Control key (macOS Ctrl, distinct from
733        /// [`Modifiers::COMMAND`]).
734        const CONTROL = 1 << 3;
735    }
736}