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