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