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}