zng_view_api/
types.rs

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