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}