zng_view_api/types.rs
1//! General event types.
2
3use crate::{
4 access::{AccessCmd, AccessNodeId},
5 api_extension::{ApiExtensionId, ApiExtensionPayload, ApiExtensions},
6 audio::{AudioDecoded, AudioDeviceId, AudioDeviceInfo, AudioId, AudioMetadata, AudioOutputId, AudioOutputOpenData, AudioPlayId},
7 config::{AnimationsConfig, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
8 dialog::{DialogId, FileDialogResponse, MsgDialogResponse, NotificationResponse},
9 drag_drop::{DragDropData, DragDropEffect},
10 image::{ImageDecoded, ImageEncodeId, ImageId, ImageMetadata},
11 keyboard::{Key, KeyCode, KeyLocation, KeyState},
12 mouse::{ButtonState, MouseButton, MouseScrollDelta},
13 raw_input::{InputDeviceCapability, InputDeviceEvent, InputDeviceId, InputDeviceInfo},
14 touch::{TouchPhase, TouchUpdate},
15 window::{EventFrameRendered, HeadlessOpenData, MonitorId, MonitorInfo, WindowChanged, WindowId, WindowOpenData},
16};
17
18use serde::{Deserialize, Serialize};
19use std::fmt;
20use zng_task::channel::{ChannelError, IpcBytes};
21use zng_txt::Txt;
22use zng_unit::{DipPoint, Rgba};
23
24macro_rules! declare_id {
25 ($(
26 $(#[$docs:meta])+
27 pub struct $Id:ident(_);
28 )+) => {$(
29 $(#[$docs])+
30 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
31 #[serde(transparent)]
32 pub struct $Id(u32);
33
34 impl $Id {
35 /// Dummy ID, zero.
36 pub const INVALID: Self = Self(0);
37
38 /// Create the first valid ID.
39 pub const fn first() -> Self {
40 Self(1)
41 }
42
43 /// Create the next ID.
44 ///
45 /// IDs wrap around to [`first`] when the entire `u32` space is used, it is never `INVALID`.
46 ///
47 /// [`first`]: Self::first
48 #[must_use]
49 pub const fn next(self) -> Self {
50 let r = Self(self.0.wrapping_add(1));
51 if r.0 == Self::INVALID.0 {
52 Self::first()
53 } else {
54 r
55 }
56 }
57
58 /// Returns self and replace self with [`next`].
59 ///
60 /// [`next`]: Self::next
61 #[must_use]
62 pub fn incr(&mut self) -> Self {
63 std::mem::replace(self, self.next())
64 }
65
66 /// Get the raw ID.
67 pub const fn get(self) -> u32 {
68 self.0
69 }
70
71 /// Create an ID using a custom value.
72 ///
73 /// Note that only the documented process must generate IDs, and that it must only
74 /// generate IDs using this function or the [`next`] function.
75 ///
76 /// If the `id` is zero it will still be [`INVALID`] and handled differently by the other process,
77 /// zero is never valid.
78 ///
79 /// [`next`]: Self::next
80 /// [`INVALID`]: Self::INVALID
81 pub const fn from_raw(id: u32) -> Self {
82 Self(id)
83 }
84 }
85 )+};
86}
87
88pub(crate) use declare_id;
89
90declare_id! {
91 /// View-process generation, starts at one and changes every respawn, it is never zero.
92 ///
93 /// The View Process defines the ID.
94 pub struct ViewProcessGen(_);
95}
96
97/// Identifier for a specific analog axis on some device.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
99#[serde(transparent)]
100pub struct AxisId(pub u32);
101
102/// Identifier for a drag drop operation.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
104#[serde(transparent)]
105pub struct DragDropId(pub u32);
106
107/// View-process implementation info.
108///
109/// The view-process implementation may not cover the full API, depending on operating system, build, headless mode.
110/// When the view-process does not implement something it just logs an error and ignores the request, this struct contains
111/// detailed info about what operations are available in the view-process instance.
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113#[non_exhaustive]
114pub struct ViewProcessInfo {
115 /// View-process generation, changes after respawns and is never zero.
116 pub generation: ViewProcessGen,
117 /// If the view-process is a respawn from a previous crashed process.
118 pub is_respawn: bool,
119
120 /// Input device events implemented by the view-process.
121 pub input_device: InputDeviceCapability,
122
123 /// Window operations implemented by the view-process.
124 pub window: crate::window::WindowCapability,
125
126 /// Dialog operations implemented by the view-process.
127 pub dialog: crate::dialog::DialogCapability,
128
129 /// System menu capabilities.
130 pub menu: crate::menu::MenuCapability,
131
132 /// Clipboard data types and operations implemented by the view-process.
133 pub clipboard: crate::clipboard::ClipboardTypes,
134
135 /// Image decode and encode capabilities implemented by the view-process.
136 pub image: Vec<crate::image::ImageFormat>,
137
138 /// Audio decode and encode capabilities implemented by the view-process.
139 pub audio: Vec<crate::audio::AudioFormat>,
140
141 /// API extensions implemented by the view-process.
142 ///
143 /// The extension IDs will stay valid for the duration of the view-process.
144 pub extensions: ApiExtensions,
145}
146impl ViewProcessInfo {
147 /// New response.
148 pub const fn new(generation: ViewProcessGen, is_respawn: bool) -> Self {
149 Self {
150 generation,
151 is_respawn,
152 input_device: InputDeviceCapability::empty(),
153 window: crate::window::WindowCapability::empty(),
154 dialog: crate::dialog::DialogCapability::empty(),
155 menu: crate::menu::MenuCapability::empty(),
156 clipboard: crate::clipboard::ClipboardTypes::new(vec![], vec![], false),
157 image: vec![],
158 audio: vec![],
159 extensions: ApiExtensions::new(),
160 }
161 }
162}
163
164/// IME preview or insert event.
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
166pub enum Ime {
167 /// Preview an IME insert at the last non-preview caret/selection.
168 ///
169 /// The associated values are the preview string and caret/selection inside the preview string.
170 ///
171 /// The preview must visually replace the last non-preview selection or insert at the last non-preview
172 /// caret index. If the preview string is empty the preview must be cancelled.
173 Preview(Txt, (usize, usize)),
174
175 /// Apply an IME insert at the last non-preview caret/selection. The caret must be moved to
176 /// the end of the inserted sub-string.
177 Commit(Txt),
178}
179
180/// System and User events sent from the View Process.
181#[derive(Debug, Clone, Serialize, Deserialize)]
182#[non_exhaustive]
183pub enum Event {
184 /// View-process inited.
185 Inited(ViewProcessInfo),
186 /// View-process suspended.
187 Suspended,
188
189 /// The event channel disconnected, probably because the view-process crashed.
190 ///
191 /// The [`ViewProcessGen`] is the generation of the view-process that was lost, it must be passed to
192 /// [`Controller::handle_disconnect`].
193 ///
194 /// [`Controller::handle_disconnect`]: crate::Controller::handle_disconnect
195 Disconnected(ViewProcessGen),
196
197 /// Window, context and renderer have finished initializing and is ready to receive commands.
198 WindowOpened(WindowId, WindowOpenData),
199
200 /// Headless context and renderer have finished initializing and is ready to receive commands.
201 HeadlessOpened(WindowId, HeadlessOpenData),
202
203 /// Window open or headless context open request failed.
204 WindowOrHeadlessOpenError {
205 /// Id from the request.
206 id: WindowId,
207 /// Error message.
208 error: Txt,
209 },
210
211 /// A frame finished rendering.
212 FrameRendered(EventFrameRendered),
213
214 /// Window moved, resized, or minimized/maximized etc.
215 ///
216 /// This event aggregates events moves, resizes and other state changes into a
217 /// single event to simplify tracking composite changes, for example, the window changes size and position
218 /// when maximized, this can be trivially observed with this event.
219 ///
220 /// The [`EventCause`] can be used to identify a state change initiated by the app.
221 ///
222 /// [`EventCause`]: crate::window::EventCause
223 WindowChanged(WindowChanged),
224
225 /// A drag&drop gesture started dragging over the window.
226 DragHovered {
227 /// Window that is hovered.
228 window: WindowId,
229 /// Data payload.
230 data: Vec<DragDropData>,
231 /// Allowed effects.
232 allowed: DragDropEffect,
233 },
234 /// A drag&drop gesture moved over the window.
235 DragMoved {
236 /// Window that is hovered.
237 window: WindowId,
238 /// Cursor positions in between the previous event and this one.
239 coalesced_pos: Vec<DipPoint>,
240 /// Cursor position, relative to the window top-left in device independent pixels.
241 position: DipPoint,
242 },
243 /// A drag&drop gesture finished over the window.
244 DragDropped {
245 /// Window that received the file drop.
246 window: WindowId,
247 /// Data payload.
248 data: Vec<DragDropData>,
249 /// Allowed effects.
250 allowed: DragDropEffect,
251 /// ID of this drop operation.
252 ///
253 /// Handlers must call `drag_dropped` with this ID and what effect was applied to the data.
254 drop_id: DragDropId,
255 },
256 /// A drag&drop gesture stopped hovering the window without dropping.
257 DragCancelled {
258 /// Window that was previous hovered.
259 window: WindowId,
260 },
261 /// A drag started by the app was dropped or canceled.
262 AppDragEnded {
263 /// Window that started the drag.
264 window: WindowId,
265 /// Drag ID.
266 drag: DragDropId,
267 /// Effect applied to the data by the drop target.
268 ///
269 /// Is a single flag if the data was dropped in a valid drop target, or is empty if was canceled.
270 applied: DragDropEffect,
271 },
272
273 /// App window(s) focus changed.
274 FocusChanged {
275 /// Window that lost focus.
276 prev: Option<WindowId>,
277 /// Window that got focus.
278 new: Option<WindowId>,
279 },
280 /// An event from the keyboard has been received.
281 ///
282 /// This event is only send if the window is focused, all pressed keys should be considered released
283 /// after [`FocusChanged`] to `None`. Modifier keys receive special treatment, after they are pressed,
284 /// the modifier key state is monitored directly so that the `Released` event is always send, unless the
285 /// focus changed to none.
286 ///
287 /// [`FocusChanged`]: Self::FocusChanged
288 KeyboardInput {
289 /// Window that received the key event.
290 window: WindowId,
291 /// Device that generated the key event.
292 device: InputDeviceId,
293 /// Physical key.
294 key_code: KeyCode,
295 /// If the key was pressed or released.
296 state: KeyState,
297 /// The location of the key on the keyboard.
298 key_location: KeyLocation,
299
300 /// Semantic key unmodified.
301 ///
302 /// Pressing `Shift+A` key will produce `Key::Char('a')` in QWERTY keyboards, the modifiers are not applied. Note that
303 /// the numpad keys do not represents the numbers unmodified
304 key: Key,
305 /// Semantic key modified by the current active modifiers.
306 ///
307 /// Pressing `Shift+A` key will produce `Key::Char('A')` in QWERTY keyboards, the modifiers are applied.
308 key_modified: Key,
309 /// Text typed.
310 ///
311 /// This is only set during [`KeyState::Pressed`] of a key that generates text.
312 ///
313 /// This is usually the `key_modified` char, but is also `'\r'` for `Key::Enter`. On Windows when a dead key was
314 /// pressed earlier but cannot be combined with the character from this key press, the produced text
315 /// will consist of two characters: the dead-key-character followed by the character resulting from this key press.
316 text: Txt,
317 },
318 /// IME composition event.
319 Ime {
320 /// Window that received the IME event.
321 window: WindowId,
322 /// IME event.
323 ime: Ime,
324 },
325
326 /// The mouse cursor has moved on the window.
327 ///
328 /// This event can be coalesced, i.e. multiple cursor moves packed into the same event.
329 MouseMoved {
330 /// Window that received the cursor move.
331 window: WindowId,
332 /// Device that generated the cursor move.
333 device: InputDeviceId,
334
335 /// Cursor positions in between the previous event and this one.
336 coalesced_pos: Vec<DipPoint>,
337
338 /// Cursor position, relative to the window top-left in device independent pixels.
339 position: DipPoint,
340 },
341
342 /// The mouse cursor has entered the window.
343 MouseEntered {
344 /// Window that now is hovered by the cursor.
345 window: WindowId,
346 /// Device that generated the cursor move event.
347 device: InputDeviceId,
348 },
349 /// The mouse cursor has left the window.
350 MouseLeft {
351 /// Window that is no longer hovered by the cursor.
352 window: WindowId,
353 /// Device that generated the cursor move event.
354 device: InputDeviceId,
355 },
356 /// A mouse wheel movement or touchpad scroll occurred.
357 MouseWheel {
358 /// Window that was hovered by the cursor when the mouse wheel was used.
359 window: WindowId,
360 /// Device that generated the mouse wheel event.
361 device: InputDeviceId,
362 /// Delta of change in the mouse scroll wheel state.
363 delta: MouseScrollDelta,
364 /// Touch state if the device that generated the event is a touchpad.
365 phase: TouchPhase,
366 },
367 /// An mouse button press has been received.
368 MouseInput {
369 /// Window that was hovered by the cursor when the mouse button was used.
370 window: WindowId,
371 /// Mouse device that generated the event.
372 device: InputDeviceId,
373 /// If the button was pressed or released.
374 state: ButtonState,
375 /// The mouse button.
376 button: MouseButton,
377 },
378 /// Touchpad pressure event.
379 TouchpadPressure {
380 /// Window that was hovered when the touchpad was touched.
381 window: WindowId,
382 /// Touchpad device.
383 device: InputDeviceId,
384 /// Pressure level between 0 and 1.
385 pressure: f32,
386 /// Click level.
387 stage: i64,
388 },
389 /// Motion on some analog axis. May report data redundant to other, more specific events.
390 AxisMotion {
391 /// Window that was focused when the motion was realized.
392 window: WindowId,
393 /// Analog device.
394 device: InputDeviceId,
395 /// Axis.
396 axis: AxisId,
397 /// Motion value.
398 value: f64,
399 },
400 /// Touch event has been received.
401 Touch {
402 /// Window that was touched.
403 window: WindowId,
404 /// Touch device.
405 device: InputDeviceId,
406
407 /// Coalesced touch updates, never empty.
408 touches: Vec<TouchUpdate>,
409 },
410 /// The monitor’s scale factor has changed.
411 #[deprecated = "use `MonitorsChanged`"]
412 ScaleFactorChanged {
413 /// Monitor that has changed.
414 monitor: MonitorId,
415 /// Windows affected by this change.
416 ///
417 /// Note that a window's scale factor can also change if it is moved to another monitor,
418 /// the [`Event::WindowChanged`] event notifies this using the [`WindowChanged::monitor`].
419 windows: Vec<WindowId>,
420 /// The new scale factor.
421 scale_factor: f32,
422 },
423
424 /// The available monitors have changed or some property of a monitor changed.
425 MonitorsChanged(Vec<(MonitorId, MonitorInfo)>),
426 /// The available audio input and output devices have changed.
427 AudioDevicesChanged(Vec<(AudioDeviceId, AudioDeviceInfo)>),
428 /// The available raw input devices have changed.
429 InputDevicesChanged(Vec<(InputDeviceId, InputDeviceInfo)>),
430
431 /// The window has been requested to close.
432 WindowCloseRequested(WindowId),
433 /// The window has closed.
434 WindowClosed(WindowId),
435
436 /// An image resource already decoded header metadata.
437 ImageMetadataDecoded(ImageMetadata),
438 /// An image resource has partially or fully decoded.
439 ImageDecoded(ImageDecoded),
440 /// An image resource failed to decode, the image ID is not valid.
441 ImageDecodeError {
442 /// The image that failed to decode.
443 image: ImageId,
444 /// The error message.
445 error: Txt,
446 },
447 /// An image finished encoding.
448 ImageEncoded {
449 /// Id of the encode task.
450 task: ImageEncodeId,
451 /// The encoded image data.
452 data: IpcBytes,
453 },
454 /// An image failed to encode.
455 ImageEncodeError {
456 /// Id of the encode task.
457 task: ImageEncodeId,
458 /// The error message.
459 error: Txt,
460 },
461
462 /// An audio resource decoded header metadata.
463 AudioMetadataDecoded(AudioMetadata),
464 /// An audio resource decoded chunk or finished decoding.
465 AudioDecoded(AudioDecoded),
466 /// An audio resource failed to decode, the audio ID is not valid.
467 AudioDecodeError {
468 /// The audio that failed to decode.
469 audio: AudioId,
470 /// The error message.
471 error: Txt,
472 },
473
474 /// Audio output is connected with device and ready to receive commands.
475 AudioOutputOpened(AudioOutputId, AudioOutputOpenData),
476 /// Audio playback stream failed to connect.
477 AudioOutputOpenError {
478 /// The output ID.
479 id: AudioOutputId,
480 /// The error message.
481 error: Txt,
482 },
483 /// Audio playback failed.
484 AudioPlayError {
485 /// The request ID.
486 play: AudioPlayId,
487 /// The error message.
488 error: Txt,
489 },
490
491 /* Config events */
492 /// System fonts have changed.
493 FontsChanged,
494 /// System text anti-aliasing configuration has changed.
495 FontAaChanged(FontAntiAliasing),
496 /// System double-click definition changed.
497 MultiClickConfigChanged(MultiClickConfig),
498 /// System animations config changed.
499 AnimationsConfigChanged(AnimationsConfig),
500 /// System definition of pressed key repeat event changed.
501 KeyRepeatConfigChanged(KeyRepeatConfig),
502 /// System touch config changed.
503 TouchConfigChanged(TouchConfig),
504 /// System locale changed.
505 LocaleChanged(LocaleConfig),
506 /// System color scheme or colors changed.
507 ColorsConfigChanged(ColorsConfig),
508
509 /// Raw input device event.
510 InputDeviceEvent {
511 /// Device that generated the event.
512 device: InputDeviceId,
513 /// Event.
514 event: InputDeviceEvent,
515 },
516
517 /// User responded to a native message dialog.
518 MsgDialogResponse(DialogId, MsgDialogResponse),
519 /// User responded to a native file dialog.
520 FileDialogResponse(DialogId, FileDialogResponse),
521 /// User dismissed a notification dialog.
522 NotificationResponse(DialogId, NotificationResponse),
523
524 /// A system menu command was requested.
525 ///
526 /// The menu item can be from the application menu or tray icon.
527 MenuCommand {
528 /// Menu command ID.
529 id: Txt,
530 },
531
532 /// Accessibility info tree is now required for the window.
533 AccessInit {
534 /// Window that must now build access info.
535 window: WindowId,
536 },
537 /// Accessibility command.
538 AccessCommand {
539 /// Window that had pixels copied.
540 window: WindowId,
541 /// Target widget.
542 target: AccessNodeId,
543 /// Command.
544 command: AccessCmd,
545 },
546 /// Accessibility info tree is no longer needed for the window.
547 ///
548 /// Note that accessibility may be enabled again after this. It is not an error
549 /// to send access updates after this, but they will be ignored.
550 AccessDeinit {
551 /// Window that can release access info.
552 window: WindowId,
553 },
554
555 /// System low memory warning, some platforms may kill the app if it does not release memory.
556 LowMemory,
557
558 /// An internal component panicked, but the view-process managed to recover from it without
559 /// needing to respawn.
560 RecoveredFromComponentPanic {
561 /// Component identifier.
562 component: Txt,
563 /// How the view-process recovered from the panic.
564 recover: Txt,
565 /// The panic.
566 panic: Txt,
567 },
568
569 /// Represents a custom event send by the extension.
570 ExtensionEvent(ApiExtensionId, ApiExtensionPayload),
571
572 /// Signal the view-process is alive.
573 ///
574 /// The associated value must be the count requested by [`Api::ping`](crate::Api::ping).
575 Pong(u16),
576}
577impl Event {
578 /// Change `self` to incorporate `other` or returns `other` if both events cannot be coalesced.
579 #[expect(clippy::result_large_err)]
580 pub fn coalesce(&mut self, other: Event) -> Result<(), Event> {
581 use Event::*;
582
583 match (self, other) {
584 (
585 MouseMoved {
586 window,
587 device,
588 coalesced_pos,
589 position,
590 },
591 MouseMoved {
592 window: n_window,
593 device: n_device,
594 coalesced_pos: n_coal_pos,
595 position: n_pos,
596 },
597 ) if *window == n_window && *device == n_device => {
598 coalesced_pos.push(*position);
599 coalesced_pos.extend(n_coal_pos);
600 *position = n_pos;
601 }
602 (
603 DragMoved {
604 window,
605 coalesced_pos,
606 position,
607 },
608 DragMoved {
609 window: n_window,
610 coalesced_pos: n_coal_pos,
611 position: n_pos,
612 },
613 ) if *window == n_window => {
614 coalesced_pos.push(*position);
615 coalesced_pos.extend(n_coal_pos);
616 *position = n_pos;
617 }
618
619 (
620 InputDeviceEvent { device, event },
621 InputDeviceEvent {
622 device: n_device,
623 event: n_event,
624 },
625 ) if *device == n_device => {
626 if let Err(e) = event.coalesce(n_event) {
627 return Err(InputDeviceEvent {
628 device: n_device,
629 event: e,
630 });
631 }
632 }
633
634 // wheel scroll.
635 (
636 MouseWheel {
637 window,
638 device,
639 delta: MouseScrollDelta::LineDelta(delta_x, delta_y),
640 phase,
641 },
642 MouseWheel {
643 window: n_window,
644 device: n_device,
645 delta: MouseScrollDelta::LineDelta(n_delta_x, n_delta_y),
646 phase: n_phase,
647 },
648 ) if *window == n_window && *device == n_device && *phase == n_phase => {
649 *delta_x += n_delta_x;
650 *delta_y += n_delta_y;
651 }
652
653 // trackpad scroll-move.
654 (
655 MouseWheel {
656 window,
657 device,
658 delta: MouseScrollDelta::PixelDelta(delta_x, delta_y),
659 phase,
660 },
661 MouseWheel {
662 window: n_window,
663 device: n_device,
664 delta: MouseScrollDelta::PixelDelta(n_delta_x, n_delta_y),
665 phase: n_phase,
666 },
667 ) if *window == n_window && *device == n_device && *phase == n_phase => {
668 *delta_x += n_delta_x;
669 *delta_y += n_delta_y;
670 }
671
672 // touch
673 (
674 Touch { window, device, touches },
675 Touch {
676 window: n_window,
677 device: n_device,
678 touches: mut n_touches,
679 },
680 ) if *window == n_window && *device == n_device => {
681 touches.append(&mut n_touches);
682 }
683
684 // window changed.
685 (WindowChanged(change), WindowChanged(n_change))
686 if change.window == n_change.window && change.cause == n_change.cause && change.frame_wait_id.is_none() =>
687 {
688 if n_change.state.is_some() {
689 change.state = n_change.state;
690 }
691
692 if n_change.position.is_some() {
693 change.position = n_change.position;
694 }
695
696 if n_change.monitor.is_some() {
697 change.monitor = n_change.monitor;
698 }
699
700 if n_change.size.is_some() {
701 change.size = n_change.size;
702 }
703
704 if n_change.safe_padding.is_some() {
705 change.safe_padding = n_change.safe_padding;
706 }
707
708 if n_change.scale_factor.is_some() {
709 change.scale_factor = n_change.scale_factor;
710 }
711
712 if n_change.refresh_rate.is_some() {
713 change.refresh_rate = n_change.refresh_rate;
714 }
715
716 change.frame_wait_id = n_change.frame_wait_id;
717 }
718 // window focus changed.
719 (FocusChanged { prev, new }, FocusChanged { prev: n_prev, new: n_new })
720 if prev.is_some() && new.is_none() && n_prev.is_none() && n_new.is_some() =>
721 {
722 *new = n_new;
723 }
724 // IME commit replaces preview.
725 (
726 Ime {
727 window,
728 ime: ime @ self::Ime::Preview(_, _),
729 },
730 Ime {
731 window: n_window,
732 ime: n_ime @ self::Ime::Commit(_),
733 },
734 ) if *window == n_window => {
735 *ime = n_ime;
736 }
737 // scale factor.
738 #[allow(deprecated)]
739 (
740 ScaleFactorChanged {
741 monitor,
742 windows,
743 scale_factor,
744 },
745 ScaleFactorChanged {
746 monitor: n_monitor,
747 windows: n_windows,
748 scale_factor: n_scale_factor,
749 },
750 ) if *monitor == n_monitor => {
751 for w in n_windows {
752 if !windows.contains(&w) {
753 windows.push(w);
754 }
755 }
756 *scale_factor = n_scale_factor;
757 }
758 // fonts changed.
759 (FontsChanged, FontsChanged) => {}
760 // text aa.
761 (FontAaChanged(config), FontAaChanged(n_config)) => {
762 *config = n_config;
763 }
764 // double-click timeout.
765 (MultiClickConfigChanged(config), MultiClickConfigChanged(n_config)) => {
766 *config = n_config;
767 }
768 // touch config.
769 (TouchConfigChanged(config), TouchConfigChanged(n_config)) => {
770 *config = n_config;
771 }
772 // animation enabled and caret speed.
773 (AnimationsConfigChanged(config), AnimationsConfigChanged(n_config)) => {
774 *config = n_config;
775 }
776 // key repeat delay and speed.
777 (KeyRepeatConfigChanged(config), KeyRepeatConfigChanged(n_config)) => {
778 *config = n_config;
779 }
780 // locale
781 (LocaleChanged(config), LocaleChanged(n_config)) => {
782 *config = n_config;
783 }
784 // drag hovered
785 (
786 DragHovered {
787 window,
788 data,
789 allowed: effects,
790 },
791 DragHovered {
792 window: n_window,
793 data: mut n_data,
794 allowed: n_effects,
795 },
796 ) if *window == n_window && effects.contains(n_effects) => {
797 data.append(&mut n_data);
798 }
799 // drag dropped
800 (
801 DragDropped {
802 window,
803 data,
804 allowed,
805 drop_id,
806 },
807 DragDropped {
808 window: n_window,
809 data: mut n_data,
810 allowed: n_allowed,
811 drop_id: n_drop_id,
812 },
813 ) if *window == n_window && allowed.contains(n_allowed) && *drop_id == n_drop_id => {
814 data.append(&mut n_data);
815 }
816 // drag cancelled
817 (DragCancelled { window }, DragCancelled { window: n_window }) if *window == n_window => {}
818 // input devices changed
819 (InputDevicesChanged(devices), InputDevicesChanged(n_devices)) => {
820 *devices = n_devices;
821 }
822 // audio devices changed
823 (AudioDevicesChanged(devices), AudioDevicesChanged(n_devices)) => {
824 *devices = n_devices;
825 }
826 (_, e) => return Err(e),
827 }
828 Ok(())
829 }
830}
831
832/// View Process IPC result.
833pub(crate) type VpResult<T> = std::result::Result<T, ChannelError>;
834
835/// Offset and color in a gradient.
836#[repr(C)]
837#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
838pub struct GradientStop {
839 /// Offset in pixels.
840 pub offset: f32,
841 /// Color at the offset.
842 pub color: Rgba,
843}
844
845/// Border side line style and color.
846#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
847pub struct BorderSide {
848 /// Line color.
849 pub color: Rgba,
850 /// Line Style.
851 pub style: BorderStyle,
852}
853
854/// Defines if a widget is part of the same 3D space as the parent.
855#[derive(Default, Clone, Copy, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
856#[repr(u8)]
857pub enum TransformStyle {
858 /// Widget is not a part of the 3D space of the parent. If it has
859 /// 3D children they will be rendered into a flat plane that is placed in the 3D space of the parent.
860 #[default]
861 Flat = 0,
862 /// Widget is a part of the 3D space of the parent. If it has 3D children
863 /// they will be positioned relative to siblings in the same space.
864 ///
865 /// Note that some properties require a flat image to work on, in particular all pixel filter properties including opacity.
866 /// When such a property is set in a widget that is `Preserve3D` and has both a parent and one child also `Preserve3D` the
867 /// filters are ignored and a warning is logged. When the widget is `Preserve3D` and the parent is not the filters are applied
868 /// *outside* the 3D space, when the widget is `Preserve3D` with all `Flat` children the filters are applied *inside* the 3D space.
869 Preserve3D = 1,
870}
871impl fmt::Debug for TransformStyle {
872 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
873 if f.alternate() {
874 write!(f, "TransformStyle::")?;
875 }
876 match self {
877 Self::Flat => write!(f, "Flat"),
878 Self::Preserve3D => write!(f, "Preserve3D"),
879 }
880 }
881}
882
883/// Identifies a reference frame.
884///
885/// This ID is mostly defined by the app process. IDs that set the most significant
886/// bit of the second part (`id.1 & (1 << 63) != 0`) are reserved for the view process.
887#[derive(Default, Debug, Clone, Copy, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)]
888pub struct ReferenceFrameId(pub u64, pub u64);
889impl ReferenceFrameId {
890 /// If ID does not set the bit that indicates it is generated by the view process.
891 pub fn is_app_generated(&self) -> bool {
892 self.1 & (1 << 63) == 0
893 }
894}
895
896/// Nine-patch border repeat mode.
897///
898/// Defines how the edges and middle region of a nine-patch border is filled.
899#[repr(u8)]
900#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize, Default)]
901pub enum RepeatMode {
902 /// The source image's edge regions are stretched to fill the gap between each border.
903 #[default]
904 Stretch,
905 /// The source image's edge regions are tiled (repeated) to fill the gap between each
906 /// border. Tiles may be clipped to achieve the proper fit.
907 Repeat,
908 /// The source image's edge regions are tiled (repeated) to fill the gap between each
909 /// border. Tiles may be stretched to achieve the proper fit.
910 Round,
911 /// The source image's edge regions are tiled (repeated) to fill the gap between each
912 /// border. Extra space will be distributed in between tiles to achieve the proper fit.
913 Space,
914}
915#[cfg(feature = "var")]
916zng_var::impl_from_and_into_var! {
917 /// Converts `true` to `Repeat` and `false` to the default `Stretch`.
918 fn from(value: bool) -> RepeatMode {
919 match value {
920 true => RepeatMode::Repeat,
921 false => RepeatMode::Stretch,
922 }
923 }
924}
925
926/// Color mix blend mode.
927#[repr(u8)]
928#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Default)]
929#[non_exhaustive]
930pub enum MixBlendMode {
931 /// The final color is the top color, regardless of what the bottom color is.
932 /// The effect is like two opaque pieces of paper overlapping.
933 #[default]
934 Normal = 0,
935 /// The final color is the result of multiplying the top and bottom colors.
936 /// A black layer leads to a black final layer, and a white layer leads to no change.
937 /// The effect is like two images printed on transparent film overlapping.
938 Multiply = 1,
939 /// The final color is the result of inverting the colors, multiplying them, and inverting that value.
940 /// A black layer leads to no change, and a white layer leads to a white final layer.
941 /// The effect is like two images shining onto a projection screen.
942 Screen = 2,
943 /// The final color is the result of [`Multiply`] if the bottom color is darker, or [`Screen`] if the bottom color is lighter.
944 /// This blend mode is equivalent to [`HardLight`] but with the layers swapped.
945 ///
946 /// [`Multiply`]: MixBlendMode::Multiply
947 /// [`Screen`]: MixBlendMode::Screen
948 /// [`HardLight`]: MixBlendMode::HardLight
949 Overlay = 3,
950 /// The final color is composed of the darkest values of each color channel.
951 Darken = 4,
952 /// The final color is composed of the lightest values of each color channel.
953 Lighten = 5,
954 /// The final color is the result of dividing the bottom color by the inverse of the top color.
955 /// A black foreground leads to no change.
956 /// A foreground with the inverse color of the backdrop leads to a fully lit color.
957 /// This blend mode is similar to [`Screen`], but the foreground only needs to be as light as the inverse
958 /// of the backdrop to create a fully lit color.
959 ///
960 /// [`Screen`]: MixBlendMode::Screen
961 ColorDodge = 6,
962 /// The final color is the result of inverting the bottom color, dividing the value by the top color, and inverting that value.
963 /// A white foreground leads to no change. A foreground with the inverse color of the backdrop leads to a black final image.
964 /// This blend mode is similar to [`Multiply`], but the foreground only needs to be as dark as the inverse of the backdrop
965 /// to make the final image black.
966 ///
967 /// [`Multiply`]: MixBlendMode::Multiply
968 ColorBurn = 7,
969 /// The final color is the result of [`Multiply`] if the top color is darker, or [`Screen`] if the top color is lighter.
970 /// This blend mode is equivalent to [`Overlay`] but with the layers swapped.
971 /// The effect is similar to shining a harsh spotlight on the backdrop.
972 ///
973 /// The shorthand unit `HardLight!` converts into this.
974 ///
975 /// [`Multiply`]: MixBlendMode::Multiply
976 /// [`Screen`]: MixBlendMode::Screen
977 /// [`Overlay`]: MixBlendMode::Overlay
978 HardLight = 8,
979 /// The final color is similar to [`HardLight`], but softer. This blend mode behaves similar to [`HardLight`].
980 /// The effect is similar to shining a diffused spotlight on the backdrop.
981 ///
982 /// [`HardLight`]: MixBlendMode::HardLight
983 SoftLight = 9,
984 /// The final color is the result of subtracting the darker of the two colors from the lighter one.
985 /// A black layer has no effect, while a white layer inverts the other layer's color.
986 Difference = 10,
987 /// The final color is similar to [`Difference`], but with less contrast.
988 /// As with [`Difference`], a black layer has no effect, while a white layer inverts the other layer's color.
989 ///
990 /// [`Difference`]: MixBlendMode::Difference
991 Exclusion = 11,
992 /// The final color has the *hue* of the top color, while using the *saturation* and *luminosity* of the bottom color.
993 Hue = 12,
994 /// The final color has the *saturation* of the top color, while using the *hue* and *luminosity* of the bottom color.
995 /// A pure gray backdrop, having no saturation, will have no effect.
996 Saturation = 13,
997 /// The final color has the *hue* and *saturation* of the top color, while using the *luminosity* of the bottom color.
998 /// The effect preserves gray levels and can be used to colorize the foreground.
999 Color = 14,
1000 /// The final color has the *luminosity* of the top color, while using the *hue* and *saturation* of the bottom color.
1001 /// This blend mode is equivalent to [`Color`], but with the layers swapped.
1002 ///
1003 /// [`Color`]: MixBlendMode::Color
1004 Luminosity = 15,
1005 /// The final color adds the top color multiplied by alpha to the bottom color multiplied by alpha.
1006 /// This blend mode is particularly useful in cross fades where the opacity of both layers transition in reverse.
1007 PlusLighter = 16,
1008}
1009
1010/// Image scaling algorithm in the renderer.
1011///
1012/// If an image is not rendered at the same size as their source it must be up-scaled or
1013/// down-scaled. The algorithms used for this scaling can be selected using this `enum`.
1014///
1015/// Note that the algorithms used in the renderer value performance over quality and do a good
1016/// enough job for small or temporary changes in scale only, such as a small size correction or a scaling animation.
1017/// If and image is constantly rendered at a different scale you should considered scaling it on the CPU using a
1018/// slower but more complex algorithm or pre-scaling it before including in the app.
1019#[repr(u8)]
1020#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize)]
1021#[non_exhaustive]
1022pub enum ImageRendering {
1023 /// Let the renderer select the algorithm, currently this is the same as [`CrispEdges`].
1024 ///
1025 /// [`CrispEdges`]: ImageRendering::CrispEdges
1026 Auto = 0,
1027 /// The image is scaled with an algorithm that preserves contrast and edges in the image,
1028 /// and which does not smooth colors or introduce blur to the image in the process.
1029 ///
1030 /// Currently the [Bilinear] interpolation algorithm is used.
1031 ///
1032 /// [Bilinear]: https://en.wikipedia.org/wiki/Bilinear_interpolation
1033 CrispEdges = 1,
1034 /// When scaling the image up, the image appears to be composed of large pixels.
1035 ///
1036 /// Currently the [Nearest-neighbor] interpolation algorithm is used.
1037 ///
1038 /// [Nearest-neighbor]: https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
1039 Pixelated = 2,
1040}
1041
1042/// Gradient extend mode.
1043#[allow(missing_docs)]
1044#[repr(u8)]
1045#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
1046pub enum ExtendMode {
1047 Clamp,
1048 Repeat,
1049}
1050
1051/// Orientation of a straight line.
1052#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1053pub enum LineOrientation {
1054 /// Top-to-bottom line.
1055 Vertical,
1056 /// Left-to-right line.
1057 Horizontal,
1058}
1059impl fmt::Debug for LineOrientation {
1060 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1061 if f.alternate() {
1062 write!(f, "LineOrientation::")?;
1063 }
1064 match self {
1065 LineOrientation::Vertical => {
1066 write!(f, "Vertical")
1067 }
1068 LineOrientation::Horizontal => {
1069 write!(f, "Horizontal")
1070 }
1071 }
1072 }
1073}
1074
1075/// Represents a line style.
1076#[allow(missing_docs)]
1077#[repr(u8)]
1078#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1079#[non_exhaustive]
1080pub enum LineStyle {
1081 Solid,
1082 Dotted,
1083 Dashed,
1084
1085 /// A wavy line, like an error underline.
1086 ///
1087 /// The wave magnitude is defined by the overall line thickness, the associated value
1088 /// here defines the thickness of the wavy line.
1089 Wavy(f32),
1090}
1091
1092/// The line style for the sides of a widget's border.
1093#[repr(u8)]
1094#[derive(Default, Debug, Clone, Copy, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
1095#[non_exhaustive]
1096pub enum BorderStyle {
1097 /// No border line.
1098 #[default]
1099 None = 0,
1100
1101 /// A single straight solid line.
1102 Solid = 1,
1103 /// Two straight solid lines that add up to the pixel size defined by the side width.
1104 Double = 2,
1105
1106 /// Displays a series of rounded dots.
1107 Dotted = 3,
1108 /// Displays a series of short square-ended dashes or line segments.
1109 Dashed = 4,
1110
1111 /// Fully transparent line.
1112 Hidden = 5,
1113
1114 /// Displays a border with a carved appearance.
1115 Groove = 6,
1116 /// Displays a border with an extruded appearance.
1117 Ridge = 7,
1118
1119 /// Displays a border that makes the widget appear embedded.
1120 Inset = 8,
1121 /// Displays a border that makes the widget appear embossed.
1122 Outset = 9,
1123}
1124
1125/// Result of a focus request.
1126#[repr(u8)]
1127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1128#[non_exhaustive]
1129pub enum FocusResult {
1130 /// Focus was requested, an [`Event::FocusChanged`] will be send if the operating system gives focus to the window.
1131 Requested,
1132 /// Window is already focused.
1133 AlreadyFocused,
1134}
1135
1136/// Defines what raw device events the view-process instance should monitor and notify.
1137///
1138/// Raw device events are global and can be received even when the app has no visible window.
1139///
1140/// These events are disabled by default as they can impact performance or may require special security clearance,
1141/// depending on the view-process implementation and operating system.
1142#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1143#[non_exhaustive]
1144pub struct DeviceEventsFilter {
1145 /// What raw input events should be watched/send.
1146 ///
1147 /// Note that although the view-process will filter input device events using these flags setting
1148 /// just one of them may cause a general native listener to init.
1149 pub input: InputDeviceCapability,
1150}
1151impl DeviceEventsFilter {
1152 /// Default value, no device events are needed.
1153 pub fn empty() -> Self {
1154 Self {
1155 input: InputDeviceCapability::empty(),
1156 }
1157 }
1158
1159 /// If the filter does not include any event.
1160 pub fn is_empty(&self) -> bool {
1161 self.input.is_empty()
1162 }
1163
1164 /// New with input device events needed.
1165 pub fn new(input: InputDeviceCapability) -> Self {
1166 Self { input }
1167 }
1168}
1169impl Default for DeviceEventsFilter {
1170 fn default() -> Self {
1171 Self::empty()
1172 }
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178
1179 #[test]
1180 fn key_code_iter() {
1181 let mut iter = KeyCode::all_identified();
1182 let first = iter.next().unwrap();
1183 assert_eq!(first, KeyCode::Backquote);
1184
1185 for k in iter {
1186 assert_eq!(k.name(), &format!("{k:?}"));
1187 }
1188 }
1189}