Skip to main content

nice_plug_core/
editor.rs

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