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