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}