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    /// Called right after the editor is initialized when running in standalone mode (if the
305    /// user has enabled the `unsafe_standalone_dpi_fix` feature in nice-plug).
306    ///
307    /// Here you can call
308    /// [`baseview::assume_standalone_in_process()`](https://docs.rs/baseview/0.3.4/baseview/fn.assume_standalone_in_process.html)
309    /// as a fix for <https://github.com/RustAudio/baseview/issues/321>.
310    ///
311    /// # Safety
312    ///
313    /// This method must *NOT* be called in the following cases:
314    /// * The current binary is a plugin that can be loaded into an external host
315    /// * Multiple baseview versions are present in the final binary
316    /// * baseview is being used in conjunction with other platform windowing libraries (e.g. winit, SDL, etc.)
317    /// * The current process may host other plugins that need to interact with the platform's GUI capabilities.
318    unsafe fn assume_standalone_in_process(&self) {}
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub struct DummyEditorError;
323impl Error for DummyEditorError {}
324impl std::fmt::Display for DummyEditorError {
325    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326        write!(f, "Plugin does not implement an editor")
327    }
328}
329
330impl EditorHandle for () {
331    type Window = ();
332    type Error = DummyEditorError;
333
334    fn run_until_closed(_window: Self::Window) -> Result<(), Self::Error> {
335        Err(DummyEditorError)
336    }
337
338    fn set_parent(
339        &self,
340        _parent: ParentWindowHandle,
341        _window: &Self::Window,
342    ) -> Result<(), Self::Error> {
343        Err(DummyEditorError)
344    }
345
346    fn show(&self, _window: &Self::Window) -> Result<(), Self::Error> {
347        Err(DummyEditorError)
348    }
349
350    fn hide(&self, _window: &Self::Window) -> Result<(), Self::Error> {
351        Err(DummyEditorError)
352    }
353
354    fn host_main_thread_callback(&self, _window: &Self::Window) {}
355
356    fn set_size(
357        &self,
358        _new_size: NativeSize<u32>,
359        _window: &Self::Window,
360    ) -> Result<(), Self::Error> {
361        Err(DummyEditorError)
362    }
363
364    fn param_value_changed(&self, _id: &str, _normalized_value: f32) {}
365
366    fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {}
367}
368
369impl Editor for () {
370    type Handle = ();
371
372    fn spawn(
373        &self,
374        _parent: Option<ParentWindowHandle>,
375        _wait_for_parent: bool,
376        _fallback_scale_factor: Option<f64>,
377        _gui_context: GuiContext,
378        _host: Option<HostMethods>,
379    ) -> Result<SpawnedEditor<Self::Handle>, Box<dyn Error>> {
380        Err(String::from("Plugin does not implement an editor").into())
381    }
382
383    fn size(&self) -> NativeSize<u32> {
384        NativeSize {
385            width: 0,
386            height: 0,
387        }
388    }
389}
390
391/// The minimum/maximum size constraints for an editor window.
392#[derive(Debug, Clone, Copy, PartialEq)]
393pub enum SizeConstraints {
394    /// Min/max size in logical points (before scaling is applied).
395    Logical {
396        min_size: Option<LogicalSize<f32>>,
397        max_size: Option<LogicalSize<f32>>,
398    },
399    /// Min/max size in physical pixels.
400    Physical {
401        min_size: Option<PhysicalSize<u32>>,
402        max_size: Option<PhysicalSize<u32>>,
403    },
404}
405
406impl SizeConstraints {
407    /// Minimum size in logical points (before scaling is applied).
408    pub const fn min_logical_size(min_size: LogicalSize<f32>) -> Self {
409        Self::Logical {
410            min_size: Some(min_size),
411            max_size: None,
412        }
413    }
414
415    /// Minimum size in physical pixels.
416    pub const fn min_physical_size(min_size: PhysicalSize<u32>) -> Self {
417        Self::Physical {
418            min_size: Some(min_size),
419            max_size: None,
420        }
421    }
422
423    /// Min/max size in logical points (before scaling is applied).
424    pub const fn logical(
425        min_size: Option<LogicalSize<f32>>,
426        max_size: Option<LogicalSize<f32>>,
427    ) -> Self {
428        Self::Logical { min_size, max_size }
429    }
430
431    /// Min/max size in physical pixels.
432    pub const fn physical(
433        min_size: Option<PhysicalSize<u32>>,
434        max_size: Option<PhysicalSize<u32>>,
435    ) -> Self {
436        Self::Physical { min_size, max_size }
437    }
438}
439
440impl Default for SizeConstraints {
441    fn default() -> Self {
442        Self::Logical {
443            min_size: None,
444            max_size: None,
445        }
446    }
447}
448
449/// Describes whether and how a host may resize an [`Editor`], returned from
450/// [`Editor::resize_hint()`].
451///
452/// The default is non-resizable (`can_resize: false`), matching the previous
453/// fixed-size behavior. To make an editor resizable, return a hint with
454/// `can_resize: true`; the per-axis flags and aspect-ratio fields refine how.
455#[derive(Debug, Clone, Copy, PartialEq)]
456pub struct ResizeHint {
457    /// Whether the host may resize the editor at all. Drives CLAP's
458    /// `gui.can_resize` and VST3's `canResize`. When `false`, the other fields
459    /// are ignored.
460    pub can_resize: bool,
461    /// Whether the width may change. Only meaningful when `can_resize` is `true`.
462    pub can_resize_horizontally: bool,
463    /// Whether the height may change. Only meaningful when `can_resize` is `true`.
464    pub can_resize_vertically: bool,
465    /// If `true`, the host should keep the editor's aspect ratio fixed at
466    /// `aspect_ratio_width : aspect_ratio_height` while resizing.
467    pub preserve_aspect_ratio: bool,
468    /// Aspect-ratio numerator (only used when `preserve_aspect_ratio` is `true`).
469    pub aspect_ratio_width: u32,
470    /// Aspect-ratio denominator (only used when `preserve_aspect_ratio` is `true`).
471    pub aspect_ratio_height: u32,
472    /// The minimum/maximum size constraints for the window.
473    pub size_constraints: SizeConstraints,
474}
475
476impl Default for ResizeHint {
477    fn default() -> Self {
478        // Not resizable by default, so editors keep their fixed-size behavior
479        // unless they explicitly opt in.
480        Self::NON_RESIZABLE
481    }
482}
483
484impl ResizeHint {
485    /// A non-resizable editor. This is the default value.
486    pub const NON_RESIZABLE: Self = Self {
487        size_constraints: SizeConstraints::Logical {
488            min_size: None,
489            max_size: None,
490        },
491        can_resize: false,
492        can_resize_horizontally: false,
493        can_resize_vertically: false,
494        preserve_aspect_ratio: false,
495        aspect_ratio_width: 1,
496        aspect_ratio_height: 1,
497    };
498
499    /// A freely resizable editor: both axes, no aspect-ratio lock. Convenience
500    /// for the common case.
501    pub const RESIZABLE: Self = Self {
502        can_resize: true,
503        can_resize_horizontally: true,
504        can_resize_vertically: true,
505        ..Self::NON_RESIZABLE
506    };
507
508    /// A non-resizable editor. This is the default value.
509    pub const fn non_resizable() -> Self {
510        Self::NON_RESIZABLE
511    }
512
513    /// A freely resizable editor: both axes, no aspect-ratio lock. Convenience
514    /// for the common case.
515    pub const fn resizable() -> Self {
516        Self::RESIZABLE
517    }
518
519    /// Set the minimum size for the window in logical points (before scaling is applied).
520    pub const fn with_min_logical_size(mut self, min_size: LogicalSize<f32>) -> Self {
521        self.size_constraints = SizeConstraints::Logical {
522            min_size: Some(min_size),
523            max_size: None,
524        };
525        self
526    }
527
528    /// Set the minimum/maximum size for the window in logical points (before scaling is applied).
529    pub const fn with_min_max_logical_size(
530        mut self,
531        min_size: Option<LogicalSize<f32>>,
532        max_size: Option<LogicalSize<f32>>,
533    ) -> Self {
534        self.size_constraints = SizeConstraints::Logical { min_size, max_size };
535        self
536    }
537
538    /// Set the minimum size for the window in physical pixels.
539    pub const fn with_min_physical_size(mut self, min_size: PhysicalSize<u32>) -> Self {
540        self.size_constraints = SizeConstraints::Physical {
541            min_size: Some(min_size),
542            max_size: None,
543        };
544        self
545    }
546
547    /// Set the minimum/maximum size for the window in physical pixels.
548    pub const fn with_min_max_physical_size(
549        mut self,
550        min_size: Option<PhysicalSize<u32>>,
551        max_size: Option<PhysicalSize<u32>>,
552    ) -> Self {
553        self.size_constraints = SizeConstraints::Physical { min_size, max_size };
554        self
555    }
556
557    /// Use the given minimum/maximum size constraints.
558    pub const fn with_size_constraints(mut self, size_constraints: SizeConstraints) -> Self {
559        self.size_constraints = size_constraints;
560        self
561    }
562
563    /// Lock the window to the given aspect ratio.
564    ///
565    /// * `aspect_ratio_width`: aspect-ratio numerator
566    /// * `aspect_ratio_height`: aspect-ratio denominator
567    pub const fn with_aspect_ratio(
568        mut self,
569        aspect_ratio_width: u32,
570        aspect_ratio_height: u32,
571    ) -> Self {
572        assert!(aspect_ratio_width != 0);
573        assert!(aspect_ratio_height != 0);
574
575        self.aspect_ratio_width = aspect_ratio_width;
576        self.aspect_ratio_height = aspect_ratio_height;
577
578        self
579    }
580
581    /// Returns whether or not the given size in physical pixels is valid.
582    pub fn is_size_valid(
583        &self,
584        new_size: NativeSize<u32>,
585        current_size: NativeSize<u32>,
586        scale_factor: f64,
587    ) -> bool {
588        let adjusted_size = self.adjust_size(new_size, current_size, scale_factor);
589        new_size == adjusted_size
590    }
591
592    /// Adjust the new requested size to the closest size that is compatible
593    /// with this plugin.
594    pub fn adjust_size(
595        &self,
596        new_size: NativeSize<u32>,
597        current_size: NativeSize<u32>,
598        scale_factor: f64,
599    ) -> NativeSize<u32> {
600        if !self.can_resize {
601            return current_size;
602        }
603
604        let mut new_physical_size = new_size.to_physical(scale_factor);
605        let current_physical_size = current_size.to_physical(scale_factor);
606
607        let (min_phy_size, max_phy_size) = match self.size_constraints {
608            SizeConstraints::Logical { min_size, max_size } => (
609                min_size.map(|s| PhysicalSize {
610                    width: (s.width as f64 * scale_factor).round() as u32,
611                    height: (s.height as f64 * scale_factor).round() as u32,
612                }),
613                max_size.map(|s| PhysicalSize {
614                    width: (s.width as f64 * scale_factor).round() as u32,
615                    height: (s.height as f64 * scale_factor).round() as u32,
616                }),
617            ),
618            SizeConstraints::Physical { min_size, max_size } => (min_size, max_size),
619        };
620
621        if let Some(min_size) = min_phy_size {
622            new_physical_size.width = new_physical_size.width.max(min_size.width);
623            new_physical_size.height = new_physical_size.height.max(min_size.height);
624        }
625        if let Some(max_size) = max_phy_size {
626            new_physical_size.width = new_physical_size.width.min(max_size.width);
627            new_physical_size.height = new_physical_size.height.min(max_size.height);
628        }
629
630        new_physical_size.width = new_physical_size.width.max(1);
631        new_physical_size.height = new_physical_size.height.max(1);
632
633        if self.preserve_aspect_ratio {
634            let adjusted_width = (new_physical_size.height as f32 * self.aspect_ratio_width as f32
635                / self.aspect_ratio_height as f32)
636                .round() as u32;
637
638            if let Some(min_size) = min_phy_size
639                && adjusted_width < min_size.width
640            {
641                new_physical_size = min_size;
642            } else if let Some(max_size) = max_phy_size
643                && adjusted_width > max_size.width
644            {
645                new_physical_size = max_size;
646            } else {
647                new_physical_size.width = adjusted_width;
648            }
649        } else {
650            if !self.can_resize_horizontally {
651                new_physical_size.width = current_physical_size.width;
652            }
653            if !self.can_resize_vertically {
654                new_physical_size.height = current_physical_size.height;
655            }
656        }
657
658        NativeSize::from_size(new_physical_size.into(), scale_factor)
659    }
660}
661
662/// A raw window handle for platform and GUI framework agnostic editors. This implements
663/// [`HasWindowHandle`] so it can be used directly with GUI libraries that use the same
664/// [`raw_window_handle`] version. If the library links against a different version of
665/// `raw_window_handle`, then you'll need to wrap around this type and implement the trait yourself.
666#[derive(Debug, Clone, Copy)]
667pub enum ParentWindowHandle {
668    /// The ID of the host's parent window. Used with X11.
669    XlibWindow(c_ulong),
670    /// The ID of the host's parent window. Used with X11.
671    XcbWindow(NonZeroU32),
672    /// A handle to the host's parent window. Used only on macOS.
673    AppKitNsView(NonNull<c_void>),
674    /// A handle to the host's parent window. Used only on Windows.
675    Win32Hwnd(NonZeroIsize),
676}
677
678impl HasWindowHandle for ParentWindowHandle {
679    fn window_handle(
680        &self,
681    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
682        let raw = match *self {
683            ParentWindowHandle::XlibWindow(window) => {
684                RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle::new(window))
685            }
686            ParentWindowHandle::XcbWindow(window) => {
687                RawWindowHandle::Xcb(raw_window_handle::XcbWindowHandle::new(window))
688            }
689            ParentWindowHandle::AppKitNsView(ns_view) => {
690                RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle::new(ns_view))
691            }
692            ParentWindowHandle::Win32Hwnd(hwnd) => {
693                RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::new(hwnd))
694            }
695        };
696
697        Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
698    }
699}
700
701/// A non-character key delivered to
702/// [`EditorHandle::on_virtual_key_from_host`]. Variant names mirror standard
703/// keyboard nomenclature; printable ASCII characters never appear here
704/// because they flow through the plugin window's native keyboard path
705/// instead.
706#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
707#[non_exhaustive]
708pub enum VirtualKeyCode {
709    Backspace,
710    Tab,
711    Clear,
712    Return,
713    Pause,
714    Escape,
715    Space,
716    Next,
717    End,
718    Home,
719    ArrowLeft,
720    ArrowUp,
721    ArrowRight,
722    ArrowDown,
723    PageUp,
724    PageDown,
725    Select,
726    Print,
727    /// Numpad enter (distinct from [`VirtualKeyCode::Return`]).
728    NumpadEnter,
729    Snapshot,
730    Insert,
731    Delete,
732    Help,
733    Numpad0,
734    Numpad1,
735    Numpad2,
736    Numpad3,
737    Numpad4,
738    Numpad5,
739    Numpad6,
740    Numpad7,
741    Numpad8,
742    Numpad9,
743    NumpadMultiply,
744    NumpadAdd,
745    NumpadSeparator,
746    NumpadSubtract,
747    NumpadDecimal,
748    NumpadDivide,
749    F1,
750    F2,
751    F3,
752    F4,
753    F5,
754    F6,
755    F7,
756    F8,
757    F9,
758    F10,
759    F11,
760    F12,
761    NumLock,
762    ScrollLock,
763    /// Shift key, delivered as a press/release on the modifier itself.
764    /// For most text-input purposes you want
765    /// [`Modifiers::SHIFT`] on the event's modifier set instead; the
766    /// dedicated press is useful only for editors that react to
767    /// modifier-only gestures.
768    Shift,
769    /// Control key (macOS Ctrl, platform-Ctrl elsewhere). See the note
770    /// on [`VirtualKeyCode::Shift`].
771    Control,
772    /// Alt / Option key. See the note on [`VirtualKeyCode::Shift`].
773    Alt,
774    Equals,
775    ContextMenu,
776    MediaPlay,
777    MediaStop,
778    MediaPrevTrack,
779    MediaNextTrack,
780    VolumeUp,
781    VolumeDown,
782    F13,
783    F14,
784    F15,
785    F16,
786    F17,
787    F18,
788    F19,
789    F20,
790    F21,
791    F22,
792    F23,
793    F24,
794    /// Super / Command / Windows key. See the note on
795    /// [`VirtualKeyCode::Shift`].
796    Super,
797}
798
799bitflags! {
800    /// Modifier keys held while a keyboard event was generated, as
801    /// reported by [`Editor::on_virtual_key_from_host`]. Use the
802    /// standard `bitflags` API (`contains`, `intersects`, `is_empty`,
803    /// etc.) to query individual modifiers.
804    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
805    pub struct Modifiers: u8 {
806        /// Shift key.
807        const SHIFT = 1 << 0;
808        /// Alt / Option key.
809        const ALT = 1 << 1;
810        /// Command key. On Windows / Linux this is typically the Ctrl
811        /// key. See [`Modifiers::CONTROL`] for the macOS Control key
812        /// specifically.
813        const COMMAND = 1 << 2;
814        /// Control key (macOS Ctrl, distinct from
815        /// [`Modifiers::COMMAND`]).
816        const CONTROL = 1 << 3;
817    }
818}