zng_view_api/
window.rs

1//! Window, surface and frame types.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zng_txt::Txt;
7
8use crate::{
9    api_extension::{ApiExtensionId, ApiExtensionPayload},
10    display_list::{DisplayList, FrameValueUpdate},
11    image::{ImageDecoded, ImageId, ImageMaskMode},
12};
13use zng_unit::{
14    Dip, DipPoint, DipRect, DipSideOffsets, DipSize, DipToPx as _, Factor, Frequency, Px, PxPoint, PxSize, PxToDip, PxTransform, Rgba,
15};
16
17crate::declare_id! {
18    /// Window ID in channel.
19    ///
20    /// In the View Process this is mapped to a system id.
21    ///
22    /// In the App Process this is an unique id that survives View crashes.
23    ///
24    /// The App Process defines the ID.
25    pub struct WindowId(_);
26
27    /// Monitor screen ID in channel.
28    ///
29    /// In the View Process this is mapped to a system id.
30    ///
31    /// In the App Process this is mapped to an unique id, but does not survived View crashes.
32    ///
33    /// The View Process defines the ID.
34    pub struct MonitorId(_);
35
36    /// Identifies a frame request for collaborative resize in [`WindowChanged`].
37    ///
38    /// The View Process defines the ID.
39    pub struct FrameWaitId(_);
40}
41
42/// Render backend preference.
43///
44/// This is mostly a trade-off between performance, power consumption and cold startup time.
45#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
46#[non_exhaustive]
47pub enum RenderMode {
48    /// Prefer the best dedicated GPU, probably the best performance after initialization, but also the
49    /// most power consumption.
50    ///
51    /// Falls back to `Integrated`, then `Software`.
52    Dedicated,
53
54    /// Prefer the integrated GPU (provided by the CPU), probably the best power consumption and good performance for most GUI applications,
55    /// this is the default value.
56    ///
57    /// Falls back to `Dedicated`, then `Software`.
58    Integrated,
59
60    /// Use a software render fallback, this has the best compatibility and best initialization time. This is probably the
61    /// best pick for one frame render tasks and small windows where the initialization time of a GPU context may not offset
62    /// the render time gains.
63    ///
64    /// If the view-process implementation has no software, falls back to `Integrated`, then `Dedicated`.
65    Software,
66}
67impl Default for RenderMode {
68    /// [`RenderMode::Integrated`].
69    fn default() -> Self {
70        RenderMode::Integrated
71    }
72}
73impl RenderMode {
74    /// Returns fallbacks that view-process implementers will try if `self` is not available.
75    pub fn fallbacks(self) -> [RenderMode; 2] {
76        use RenderMode::*;
77        match self {
78            Dedicated => [Integrated, Software],
79            Integrated => [Dedicated, Software],
80            Software => [Integrated, Dedicated],
81        }
82    }
83
84    /// Returns `self` plus [`fallbacks`].
85    ///
86    /// [`fallbacks`]: Self::fallbacks
87    pub fn with_fallbacks(self) -> [RenderMode; 3] {
88        let [f0, f1] = self.fallbacks();
89        [self, f0, f1]
90    }
91}
92
93#[cfg(feature = "var")]
94zng_var::impl_from_and_into_var! {
95    fn from(some: RenderMode) -> Option<RenderMode>;
96}
97
98/// Configuration of a new headless surface.
99///
100/// Headless surfaces are always [`capture_mode`] enabled.
101///
102/// [`capture_mode`]: WindowRequest::capture_mode
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[non_exhaustive]
105pub struct HeadlessRequest {
106    /// ID that will identify the new headless surface.
107    ///
108    /// The surface is identified by a [`WindowId`] so that some API methods
109    /// can apply to both windows or surfaces, no actual window is created.
110    pub id: WindowId,
111
112    /// Scale for the layout units in this config.
113    pub scale_factor: Factor,
114
115    /// Surface area (viewport size).
116    pub size: DipSize,
117
118    /// Render mode preference for this headless surface.
119    pub render_mode: RenderMode,
120
121    /// Initial payload for API extensions.
122    ///
123    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure`
124    /// with the payload.
125    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
126}
127impl HeadlessRequest {
128    /// New request.
129    pub fn new(
130        id: WindowId,
131        scale_factor: Factor,
132        size: DipSize,
133        render_mode: RenderMode,
134        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
135    ) -> Self {
136        Self {
137            id,
138            scale_factor,
139            size,
140            render_mode,
141            extensions,
142        }
143    }
144}
145
146/// Information about a monitor screen.
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148#[non_exhaustive]
149pub struct MonitorInfo {
150    /// Readable name of the monitor.
151    pub name: Txt,
152    /// Top-left offset of the monitor region in the virtual screen, in pixels.
153    pub position: PxPoint,
154    /// Width/height of the monitor region in the virtual screen, in pixels.
155    pub size: PxSize,
156    /// The monitor scale factor.
157    pub scale_factor: Factor,
158    /// The refresh rate of this monitor in normal desktop.
159    ///
160    /// If a window is set to exclusive fullscreen use the [`VideoMode::refresh_rate`] instead.
161    pub refresh_rate: Frequency,
162
163    /// Exclusive fullscreen video modes.
164    pub video_modes: Vec<VideoMode>,
165
166    /// If could determine this monitor is the primary.
167    pub is_primary: bool,
168}
169impl MonitorInfo {
170    /// New info.
171    pub fn new(name: Txt, position: PxPoint, size: PxSize, scale_factor: Factor, video_modes: Vec<VideoMode>, is_primary: bool) -> Self {
172        Self {
173            name,
174            position,
175            size,
176            scale_factor,
177            video_modes,
178            is_primary,
179            refresh_rate: Frequency::from_hertz(60.0),
180        }
181    }
182
183    /// Returns the `size` descaled using the `scale_factor`.
184    pub fn dip_size(&self) -> DipSize {
185        self.size.to_dip(self.scale_factor)
186    }
187}
188
189/// Exclusive video mode info.
190///
191/// You can get the options for a monitor using [`MonitorInfo::video_modes`].
192///
193/// Note that actual system video mode is selected by approximation,
194/// closest `size`, then `bit_depth`, then `refresh_rate`.
195///
196/// [`MonitorInfo::video_modes`]: crate::window::MonitorInfo::video_modes
197#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
198#[non_exhaustive]
199pub struct VideoMode {
200    /// Resolution of this video mode.
201    pub size: PxSize,
202    /// The bit depth of this video mode.
203    /// This is generally 24 bits or 32 bits on modern systems,
204    /// depending on whether the alpha channel is counted or not.
205    pub bit_depth: u16,
206    /// The refresh rate of this video mode, in millihertz.
207    pub refresh_rate: u32, // TODO(breaking) use Frequency unit
208}
209impl Default for VideoMode {
210    fn default() -> Self {
211        Self::MAX
212    }
213}
214impl VideoMode {
215    /// New video mode.
216    pub fn new(size: PxSize, bit_depth: u16, refresh_rate: u32) -> Self {
217        Self {
218            size,
219            bit_depth,
220            refresh_rate,
221        }
222    }
223
224    /// Default value, matches with the largest size, greatest bit-depth and refresh rate.
225    pub const MAX: VideoMode = VideoMode {
226        size: PxSize::new(Px::MAX, Px::MAX),
227        bit_depth: u16::MAX,
228        refresh_rate: u32::MAX,
229    };
230}
231impl fmt::Display for VideoMode {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        if *self == Self::MAX {
234            write!(f, "MAX")
235        } else {
236            write!(
237                f,
238                "{}x{}, {}, {}hz",
239                self.size.width.0,
240                self.size.height.0,
241                self.bit_depth,
242                (self.refresh_rate as f32 * 0.001).round()
243            )
244        }
245    }
246}
247
248/// Information about a successfully opened window.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[non_exhaustive]
251pub struct WindowOpenData {
252    /// Window complete state.
253    pub state: WindowStateAll,
254
255    /// Monitor that contains the window, if any.
256    pub monitor: Option<MonitorId>,
257
258    /// Actual top-left offset of the window (excluding outer chrome).
259    ///
260    /// The values are the global position and the position in the monitor.
261    pub position: (PxPoint, DipPoint),
262    /// Actual dimensions of the client area of the window (excluding outer chrome).
263    pub size: DipSize,
264
265    /// Actual scale factor used for the window.
266    pub scale_factor: Factor,
267
268    /// Actual refresh rate used for the window, in millihertz.
269    pub refresh_rate: Frequency,
270
271    /// Actual render mode, can be different from the requested mode if it is not available.
272    pub render_mode: RenderMode,
273
274    /// Padding that must be applied to the window content so that it stays clear of screen obstructions
275    /// such as a camera notch cutout.
276    ///
277    /// Note that the *unsafe* area must still be rendered as it may be partially visible, just don't place nay
278    /// interactive or important content outside of this padding.
279    pub safe_padding: DipSideOffsets,
280}
281impl WindowOpenData {
282    /// New response.
283    pub fn new(
284        state: WindowStateAll,
285        monitor: Option<MonitorId>,
286        position: (PxPoint, DipPoint),
287        size: DipSize,
288        scale_factor: Factor,
289        render_mode: RenderMode,
290        safe_padding: DipSideOffsets,
291    ) -> Self {
292        Self {
293            state,
294            monitor,
295            position,
296            size,
297            scale_factor,
298            render_mode,
299            safe_padding,
300            refresh_rate: Frequency::from_hertz(60.0),
301        }
302    }
303}
304
305/// Information about a successfully opened headless surface.
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[non_exhaustive]
308pub struct HeadlessOpenData {
309    /// Actual render mode, can be different from the requested mode if it is not available.
310    pub render_mode: RenderMode,
311}
312impl HeadlessOpenData {
313    /// New response.
314    pub fn new(render_mode: RenderMode) -> Self {
315        Self { render_mode }
316    }
317}
318
319/// Represents a focus request indicator.
320#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
321#[non_exhaustive]
322pub enum FocusIndicator {
323    /// Activate critical focus request.
324    Critical,
325    /// Activate informational focus request.
326    Info,
327}
328
329/// Frame image capture request.
330#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331#[non_exhaustive]
332pub enum FrameCapture {
333    /// Don't capture the frame.
334    #[default]
335    None,
336    /// Captures a full BGRA8 image.
337    Full,
338    /// Captures an A8 mask image.
339    Mask(ImageMaskMode),
340}
341
342/// Data for rendering a new frame.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344#[non_exhaustive]
345pub struct FrameRequest {
346    /// ID of the new frame.
347    pub id: FrameId,
348
349    /// Frame clear color.
350    pub clear_color: Rgba,
351
352    /// Display list.
353    pub display_list: DisplayList,
354
355    /// Create an image or mask from this rendered frame.
356    ///
357    /// The [`Event::FrameRendered`] will have the frame image.
358    ///
359    /// [`Event::FrameRendered`]: crate::Event::FrameRendered
360    pub capture: FrameCapture,
361
362    /// Identifies this frame as the response to the [`WindowChanged`] resized frame request.
363    pub wait_id: Option<FrameWaitId>,
364}
365impl FrameRequest {
366    /// New request.
367    pub fn new(id: FrameId, clear_color: Rgba, display_list: DisplayList, capture: FrameCapture, wait_id: Option<FrameWaitId>) -> Self {
368        Self {
369            id,
370            clear_color,
371            display_list,
372            capture,
373            wait_id,
374        }
375    }
376}
377
378/// Data for rendering a new frame that is derived from the current frame.
379#[derive(Clone, Serialize, Deserialize)]
380#[non_exhaustive]
381pub struct FrameUpdateRequest {
382    /// ID of the new frame.
383    pub id: FrameId,
384
385    /// Bound transforms.
386    pub transforms: Vec<FrameValueUpdate<PxTransform>>,
387    /// Bound floats.
388    pub floats: Vec<FrameValueUpdate<f32>>,
389    /// Bound colors.
390    pub colors: Vec<FrameValueUpdate<Rgba>>,
391
392    /// New clear color.
393    pub clear_color: Option<Rgba>,
394
395    /// Create an image or mask from this rendered frame.
396    ///
397    /// The [`Event::FrameRendered`] will have the image.
398    ///
399    /// [`Event::FrameRendered`]: crate::Event::FrameRendered
400    pub capture: FrameCapture,
401
402    /// Identifies this frame as the response to the [`WindowChanged`] resized frame request.
403    pub wait_id: Option<FrameWaitId>,
404
405    /// Update payload for API extensions.
406    ///
407    /// The `zng-view` crate implements this by calling `DisplayListExtension::update` with the payload.
408    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
409}
410impl FrameUpdateRequest {
411    /// New request.
412    #[allow(clippy::too_many_arguments)] // already grouping stuff>
413    pub fn new(
414        id: FrameId,
415        transforms: Vec<FrameValueUpdate<PxTransform>>,
416        floats: Vec<FrameValueUpdate<f32>>,
417        colors: Vec<FrameValueUpdate<Rgba>>,
418        clear_color: Option<Rgba>,
419        capture: FrameCapture,
420        wait_id: Option<FrameWaitId>,
421        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
422    ) -> Self {
423        Self {
424            id,
425            transforms,
426            floats,
427            colors,
428            extensions,
429            clear_color,
430            capture,
431            wait_id,
432        }
433    }
434
435    /// A request that does nothing, apart from re-rendering the frame.
436    pub fn empty(id: FrameId) -> FrameUpdateRequest {
437        FrameUpdateRequest {
438            id,
439            transforms: vec![],
440            floats: vec![],
441            colors: vec![],
442            extensions: vec![],
443            clear_color: None,
444            capture: FrameCapture::None,
445            wait_id: None,
446        }
447    }
448
449    /// If some property updates are requested.
450    pub fn has_bounds(&self) -> bool {
451        !(self.transforms.is_empty() && self.floats.is_empty() && self.colors.is_empty())
452    }
453
454    /// If this request does not do anything, apart from notifying
455    /// a new frame if send to the renderer.
456    pub fn is_empty(&self) -> bool {
457        !self.has_bounds() && self.extensions.is_empty() && self.clear_color.is_none() && self.capture != FrameCapture::None
458    }
459}
460impl fmt::Debug for FrameUpdateRequest {
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462        f.debug_struct("FrameUpdateRequest")
463            .field("id", &self.id)
464            .field("transforms", &self.transforms)
465            .field("floats", &self.floats)
466            .field("colors", &self.colors)
467            .field("clear_color", &self.clear_color)
468            .field("capture", &self.capture)
469            .finish()
470    }
471}
472
473/// Configuration of a new window.
474#[derive(Debug, Clone, Serialize, Deserialize)]
475#[non_exhaustive]
476pub struct WindowRequest {
477    /// ID that will identify the new window.
478    pub id: WindowId,
479    /// Title text.
480    pub title: Txt,
481
482    /// Window state, position, size and restore rectangle.
483    pub state: WindowStateAll,
484
485    /// Lock-in kiosk mode.
486    ///
487    /// If `true` the app-process will only set fullscreen states, never hide or minimize the window, never
488    /// make the window chrome visible and only request an opaque window. The view-process implementer is expected
489    /// to also never exit the fullscreen state, even temporally.
490    ///
491    /// The app-process does not expect the view-process to configure the operating system to run in kiosk mode, but
492    /// if possible to detect the view-process can assert that it is running in kiosk mode, logging an error if the assert fails.
493    pub kiosk: bool,
494
495    /// If the initial position should be provided the operating system,
496    /// if this is not possible the `state.restore_rect.origin` is used.
497    pub default_position: bool,
498
499    /// Video mode used when the window is in exclusive state.
500    pub video_mode: VideoMode,
501
502    /// Window visibility.
503    pub visible: bool,
504    /// Window taskbar icon visibility.
505    pub taskbar_visible: bool,
506    /// If the window is "top-most".
507    pub always_on_top: bool,
508    /// If the user can move the window.
509    pub movable: bool,
510    /// If the user can resize the window.
511    pub resizable: bool,
512    /// Window icon.
513    pub icon: Option<ImageId>,
514    /// Window cursor icon and visibility.
515    pub cursor: Option<CursorIcon>,
516    /// Window custom cursor with hotspot.
517    pub cursor_image: Option<(ImageId, PxPoint)>,
518    /// If the window is see-through in pixels that are not fully opaque.
519    pub transparent: bool,
520
521    /// If all or most frames will be *screen captured*.
522    ///
523    /// If `false` all resources for capturing frame images
524    /// are discarded after each screenshot request.
525    pub capture_mode: bool,
526
527    /// Render mode preference for this window.
528    pub render_mode: RenderMode,
529
530    /// Focus request indicator on init.
531    pub focus_indicator: Option<FocusIndicator>,
532
533    /// Ensures the window is focused after open, if not set the initial focus is decided by
534    /// the windows manager, usually focusing the new window only if the process that causes the window has focus.
535    pub focus: bool,
536
537    /// IME cursor area, if IME is enabled.
538    pub ime_area: Option<DipRect>,
539
540    /// Enabled window chrome buttons.
541    pub enabled_buttons: WindowButton,
542
543    /// System shutdown warning associated with the window.
544    pub system_shutdown_warn: Txt,
545
546    /// Initial payload for API extensions.
547    ///
548    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure` with the payload.
549    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
550}
551impl WindowRequest {
552    /// New request.
553    #[allow(clippy::too_many_arguments)]
554    pub fn new(
555        id: WindowId,
556        title: Txt,
557        state: WindowStateAll,
558        kiosk: bool,
559        default_position: bool,
560        video_mode: VideoMode,
561        visible: bool,
562        taskbar_visible: bool,
563        always_on_top: bool,
564        movable: bool,
565        resizable: bool,
566        icon: Option<ImageId>,
567        cursor: Option<CursorIcon>,
568        cursor_image: Option<(ImageId, PxPoint)>,
569        transparent: bool,
570        capture_mode: bool,
571        render_mode: RenderMode,
572        focus_indicator: Option<FocusIndicator>,
573        focus: bool,
574        ime_area: Option<DipRect>,
575        enabled_buttons: WindowButton,
576        system_shutdown_warn: Txt,
577        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
578    ) -> Self {
579        Self {
580            id,
581            title,
582            state,
583            kiosk,
584            default_position,
585            video_mode,
586            visible,
587            taskbar_visible,
588            always_on_top,
589            movable,
590            resizable,
591            icon,
592            cursor,
593            cursor_image,
594            transparent,
595            capture_mode,
596            render_mode,
597            focus_indicator,
598            focus,
599            extensions,
600            ime_area,
601            enabled_buttons,
602            system_shutdown_warn,
603        }
604    }
605
606    /// Corrects invalid values if [`kiosk`] is `true`.
607    ///
608    /// An error is logged for each invalid value.
609    ///
610    /// [`kiosk`]: Self::kiosk
611    pub fn enforce_kiosk(&mut self) {
612        if self.kiosk {
613            if !self.state.state.is_fullscreen() {
614                tracing::error!("window in `kiosk` mode did not request fullscreen");
615                self.state.state = WindowState::Exclusive;
616            }
617            if self.state.chrome_visible {
618                tracing::error!("window in `kiosk` mode request chrome");
619                self.state.chrome_visible = false;
620            }
621            if !self.visible {
622                tracing::error!("window in `kiosk` mode can only be visible");
623                self.visible = true;
624            }
625        }
626    }
627}
628
629/// Represents the properties of a window that affect its position, size and state.
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
631#[non_exhaustive]
632pub struct WindowStateAll {
633    /// The window state.
634    pub state: WindowState,
635
636    /// Position across monitors.
637    ///
638    /// This is mostly used to find a monitor to resolve the `restore_rect` in.
639    pub global_position: PxPoint,
640
641    /// Position and size of the window in the `Normal` state.
642    ///
643    /// The position is relative to the monitor.
644    pub restore_rect: DipRect,
645
646    /// What state the window goes too when "restored".
647    ///
648    /// The *restore* state that the window must be set to be restored, if the [current state] is [`Maximized`], [`Fullscreen`] or [`Exclusive`]
649    /// the restore state is [`Normal`], if the [current state] is [`Minimized`] the restore state is the previous state.
650    ///
651    /// When the restore state is [`Normal`] the [`restore_rect`] defines the window position and size.
652    ///
653    ///
654    /// [current state]: Self::state
655    /// [`Maximized`]: WindowState::Maximized
656    /// [`Fullscreen`]: WindowState::Fullscreen
657    /// [`Exclusive`]: WindowState::Exclusive
658    /// [`Normal`]: WindowState::Normal
659    /// [`Minimized`]: WindowState::Minimized
660    /// [`restore_rect`]: Self::restore_rect
661    pub restore_state: WindowState,
662
663    /// Minimal `Normal` size allowed.
664    pub min_size: DipSize,
665    /// Maximum `Normal` size allowed.
666    pub max_size: DipSize,
667
668    /// If the system provided outer-border and title-bar is visible.
669    ///
670    /// This is also called the "decoration" or "chrome" of the window.
671    pub chrome_visible: bool,
672}
673impl WindowStateAll {
674    /// New state.
675    pub fn new(
676        state: WindowState,
677        global_position: PxPoint,
678        restore_rect: DipRect,
679        restore_state: WindowState,
680        min_size: DipSize,
681        max_size: DipSize,
682        chrome_visible: bool,
683    ) -> Self {
684        Self {
685            state,
686            global_position,
687            restore_rect,
688            restore_state,
689            min_size,
690            max_size,
691            chrome_visible,
692        }
693    }
694
695    /// Clamp the `restore_rect.size` to `min_size` and `max_size`.
696    pub fn clamp_size(&mut self) {
697        self.restore_rect.size = self.restore_rect.size.min(self.max_size).max(self.min_size)
698    }
699
700    /// Compute a value for [`restore_state`] given the previous [`state`] in `self` and the `new_state` and update the [`state`].
701    ///
702    /// [`restore_state`]: Self::restore_state
703    /// [`state`]: Self::state
704    pub fn set_state(&mut self, new_state: WindowState) {
705        self.restore_state = Self::compute_restore_state(self.restore_state, self.state, new_state);
706        self.state = new_state;
707    }
708
709    /// Compute a value for [`restore_state`] given the previous `prev_state` and the new [`state`] in `self`.
710    ///
711    /// [`restore_state`]: Self::restore_state
712    /// [`state`]: Self::state
713    pub fn set_restore_state_from(&mut self, prev_state: WindowState) {
714        self.restore_state = Self::compute_restore_state(self.restore_state, prev_state, self.state);
715    }
716
717    fn compute_restore_state(restore_state: WindowState, prev_state: WindowState, new_state: WindowState) -> WindowState {
718        if new_state == WindowState::Minimized {
719            // restore to previous state from minimized.
720            if prev_state != WindowState::Minimized {
721                prev_state
722            } else {
723                WindowState::Normal
724            }
725        } else if new_state.is_fullscreen() && !prev_state.is_fullscreen() {
726            // restore to maximized or normal from fullscreen.
727            if prev_state == WindowState::Maximized {
728                WindowState::Maximized
729            } else {
730                WindowState::Normal
731            }
732        } else if new_state == WindowState::Maximized {
733            WindowState::Normal
734        } else {
735            // Fullscreen to/from Exclusive keeps the previous restore_state.
736            restore_state
737        }
738    }
739}
740
741/// Named system dependent cursor icon.
742#[non_exhaustive]
743#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
744pub enum CursorIcon {
745    /// The platform-dependent default cursor. Often rendered as arrow.
746    #[default]
747    Default,
748
749    /// A context menu is available for the object under the cursor. Often
750    /// rendered as an arrow with a small menu-like graphic next to it.
751    ContextMenu,
752
753    /// Help is available for the object under the cursor. Often rendered as a
754    /// question mark or a balloon.
755    Help,
756
757    /// The cursor is a pointer that indicates a link. Often rendered as the
758    /// backside of a hand with the index finger extended.
759    Pointer,
760
761    /// A progress indicator. The program is performing some processing, but is
762    /// different from [`CursorIcon::Wait`] in that the user may still interact
763    /// with the program.
764    Progress,
765
766    /// Indicates that the program is busy and the user should wait. Often
767    /// rendered as a watch or hourglass.
768    Wait,
769
770    /// Indicates that a cell or set of cells may be selected. Often rendered as
771    /// a thick plus-sign with a dot in the middle.
772    Cell,
773
774    /// A simple crosshair (e.g., short line segments resembling a "+" sign).
775    /// Often used to indicate a two dimensional bitmap selection mode.
776    Crosshair,
777
778    /// Indicates text that may be selected. Often rendered as an I-beam.
779    Text,
780
781    /// Indicates vertical-text that may be selected. Often rendered as a
782    /// horizontal I-beam.
783    VerticalText,
784
785    /// Indicates an alias of/shortcut to something is to be created. Often
786    /// rendered as an arrow with a small curved arrow next to it.
787    Alias,
788
789    /// Indicates something is to be copied. Often rendered as an arrow with a
790    /// small plus sign next to it.
791    Copy,
792
793    /// Indicates something is to be moved.
794    Move,
795
796    /// Indicates that the dragged item cannot be dropped at the current cursor
797    /// location. Often rendered as a hand or pointer with a small circle with a
798    /// line through it.
799    NoDrop,
800
801    /// Indicates that the requested action will not be carried out. Often
802    /// rendered as a circle with a line through it.
803    NotAllowed,
804
805    /// Indicates that something can be grabbed (dragged to be moved). Often
806    /// rendered as the backside of an open hand.
807    Grab,
808
809    /// Indicates that something is being grabbed (dragged to be moved). Often
810    /// rendered as the backside of a hand with fingers closed mostly out of
811    /// view.
812    Grabbing,
813
814    /// The east border to be moved.
815    EResize,
816
817    /// The north border to be moved.
818    NResize,
819
820    /// The north-east corner to be moved.
821    NeResize,
822
823    /// The north-west corner to be moved.
824    NwResize,
825
826    /// The south border to be moved.
827    SResize,
828
829    /// The south-east corner to be moved.
830    SeResize,
831
832    /// The south-west corner to be moved.
833    SwResize,
834
835    /// The west border to be moved.
836    WResize,
837
838    /// The east and west borders to be moved.
839    EwResize,
840
841    /// The south and north borders to be moved.
842    NsResize,
843
844    /// The north-east and south-west corners to be moved.
845    NeswResize,
846
847    /// The north-west and south-east corners to be moved.
848    NwseResize,
849
850    /// Indicates that the item/column can be resized horizontally. Often
851    /// rendered as arrows pointing left and right with a vertical bar
852    /// separating them.
853    ColResize,
854
855    /// Indicates that the item/row can be resized vertically. Often rendered as
856    /// arrows pointing up and down with a horizontal bar separating them.
857    RowResize,
858
859    /// Indicates that the something can be scrolled in any direction. Often
860    /// rendered as arrows pointing up, down, left, and right with a dot in the
861    /// middle.
862    AllScroll,
863
864    /// Indicates that something can be zoomed in. Often rendered as a
865    /// magnifying glass with a "+" in the center of the glass.
866    ZoomIn,
867
868    /// Indicates that something can be zoomed in. Often rendered as a
869    /// magnifying glass with a "-" in the center of the glass.
870    ZoomOut,
871}
872#[cfg(feature = "var")]
873zng_var::impl_from_and_into_var! {
874    fn from(some: CursorIcon) -> Option<CursorIcon>;
875}
876impl CursorIcon {
877    /// All cursor icons.
878    pub const ALL: &'static [CursorIcon] = {
879        use CursorIcon::*;
880        &[
881            Default,
882            ContextMenu,
883            Help,
884            Pointer,
885            Progress,
886            Wait,
887            Cell,
888            Crosshair,
889            Text,
890            VerticalText,
891            Alias,
892            Copy,
893            Move,
894            NoDrop,
895            NotAllowed,
896            Grab,
897            Grabbing,
898            EResize,
899            NResize,
900            NeResize,
901            NwResize,
902            SResize,
903            SeResize,
904            SwResize,
905            WResize,
906            EwResize,
907            NsResize,
908            NeswResize,
909            NwseResize,
910            ColResize,
911            RowResize,
912            AllScroll,
913            ZoomIn,
914            ZoomOut,
915        ]
916    };
917
918    /// Estimated icon size and click spot in that size.
919    pub fn size_and_spot(&self, scale_factor: Factor) -> (PxSize, PxPoint) {
920        fn splat(s: f32, rel_pt: f32) -> (DipSize, DipPoint) {
921            size(s, s, rel_pt, rel_pt)
922        }
923        fn size(w: f32, h: f32, rel_x: f32, rel_y: f32) -> (DipSize, DipPoint) {
924            (
925                DipSize::new(Dip::new_f32(w), Dip::new_f32(h)),
926                DipPoint::new(Dip::new_f32(w * rel_x), Dip::new_f32(h * rel_y)),
927            )
928        }
929
930        let (size, spot) = match self {
931            CursorIcon::Crosshair
932            | CursorIcon::Move
933            | CursorIcon::Wait
934            | CursorIcon::NotAllowed
935            | CursorIcon::NoDrop
936            | CursorIcon::Cell
937            | CursorIcon::Grab
938            | CursorIcon::Grabbing
939            | CursorIcon::AllScroll => splat(20.0, 0.5),
940            CursorIcon::Text | CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize => size(8.0, 20.0, 0.5, 0.5),
941            CursorIcon::VerticalText | CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize => size(20.0, 8.0, 0.5, 0.5),
942            _ => splat(20.0, 0.0),
943        };
944
945        (size.to_px(scale_factor), spot.to_px(scale_factor))
946    }
947}
948
949/// Defines a custom mouse cursor.
950#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
951#[non_exhaustive]
952pub struct CursorImage {
953    /// Cursor image.
954    pub img: ImageId,
955    /// Exact point in the image that is the mouse position.
956    ///
957    /// This value is only used if the image format does not contain a hotspot.
958    pub hotspot: PxPoint,
959}
960impl CursorImage {
961    /// New cursor.
962    pub fn new(img: ImageId, hotspot: PxPoint) -> Self {
963        Self { img, hotspot }
964    }
965}
966
967/// Defines the orientation that a window resize will be performed.
968#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
969pub enum ResizeDirection {
970    /// The east border will be moved.
971    East,
972    /// The north border will be moved.
973    North,
974    /// The north-east corner will be moved.
975    NorthEast,
976    /// The north-west corner will be moved.
977    NorthWest,
978    /// The south border will be moved.
979    South,
980    /// The south-east corner will be moved.
981    SouthEast,
982    /// The south-west corner will be moved.
983    SouthWest,
984    /// The west border will be moved.
985    West,
986}
987impl From<ResizeDirection> for CursorIcon {
988    fn from(direction: ResizeDirection) -> Self {
989        use ResizeDirection::*;
990        match direction {
991            East => CursorIcon::EResize,
992            North => CursorIcon::NResize,
993            NorthEast => CursorIcon::NeResize,
994            NorthWest => CursorIcon::NwResize,
995            South => CursorIcon::SResize,
996            SouthEast => CursorIcon::SeResize,
997            SouthWest => CursorIcon::SwResize,
998            West => CursorIcon::WResize,
999        }
1000    }
1001}
1002#[cfg(feature = "var")]
1003zng_var::impl_from_and_into_var! {
1004    fn from(some: ResizeDirection) -> Option<ResizeDirection>;
1005    fn from(some: ResizeDirection) -> Option<CursorIcon> {
1006        Some(some.into())
1007    }
1008}
1009impl ResizeDirection {
1010    /// All directions.
1011    pub const ALL: &'static [ResizeDirection] = {
1012        use ResizeDirection::*;
1013        &[East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West]
1014    };
1015
1016    /// Gets if this resize represents two directions.
1017    pub const fn is_corner(self) -> bool {
1018        matches!(self, Self::NorthEast | Self::NorthWest | Self::SouthEast | Self::SouthWest)
1019    }
1020
1021    /// Gets if this resize represents a single direction.
1022    pub const fn is_border(self) -> bool {
1023        !self.is_corner()
1024    }
1025}
1026
1027/// Window state.
1028#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
1029pub enum WindowState {
1030    /// Window is visible, but does not fill the screen.
1031    #[default]
1032    Normal,
1033    /// Window is only visible as an icon in the taskbar.
1034    Minimized,
1035    /// Window fills the screen, but not the parts reserved by the system, like the taskbar.
1036    Maximized,
1037    /// Window is chromeless and completely fills the screen, including over parts reserved by the system.
1038    ///
1039    /// Also called borderless fullscreen.
1040    Fullscreen,
1041    /// Window has exclusive access to the monitor's video output, so only the window content is visible.
1042    Exclusive,
1043}
1044impl WindowState {
1045    /// Returns `true` if `self` matches [`Fullscreen`] or [`Exclusive`].
1046    ///
1047    /// [`Fullscreen`]: WindowState::Fullscreen
1048    /// [`Exclusive`]: WindowState::Exclusive
1049    pub fn is_fullscreen(self) -> bool {
1050        matches!(self, Self::Fullscreen | Self::Exclusive)
1051    }
1052}
1053
1054/// [`Event::FrameRendered`] payload.
1055///
1056/// [`Event::FrameRendered`]: crate::Event::FrameRendered
1057#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1058#[non_exhaustive]
1059pub struct EventFrameRendered {
1060    /// Window that was rendered.
1061    pub window: WindowId,
1062    /// Frame that was rendered.
1063    pub frame: FrameId,
1064    /// Frame image, if one was requested with the frame request.
1065    pub frame_image: Option<ImageDecoded>,
1066}
1067impl EventFrameRendered {
1068    /// New response.
1069    pub fn new(window: WindowId, frame: FrameId, frame_image: Option<ImageDecoded>) -> Self {
1070        Self {
1071            window,
1072            frame,
1073            frame_image,
1074        }
1075    }
1076}
1077
1078/// [`Event::WindowChanged`] payload.
1079///
1080/// [`Event::WindowChanged`]: crate::Event::WindowChanged
1081#[derive(Debug, Clone, Serialize, Deserialize)]
1082#[non_exhaustive]
1083pub struct WindowChanged {
1084    // note that this payload is handled by `Event::coalesce`, add new fields there too.
1085    //
1086    /// Window that has changed state.
1087    pub window: WindowId,
1088
1089    /// Window new state, is `None` if the window state did not change.
1090    pub state: Option<WindowStateAll>,
1091
1092    /// Window new global position, is `None` if the window position did not change.
1093    ///
1094    /// The values are the global position and the position in the monitor.
1095    pub position: Option<(PxPoint, DipPoint)>,
1096
1097    /// Window new monitor.
1098    ///
1099    /// The window's monitor change when it is moved enough so that most of the
1100    /// client area is in the new monitor screen.
1101    pub monitor: Option<MonitorId>,
1102
1103    /// New scale factor.
1104    pub scale_factor: Option<Factor>,
1105
1106    /// New refresh rate, in millihertz.
1107    pub refresh_rate: Option<Frequency>,
1108
1109    /// The window new size, is `None` if the window size did not change.
1110    pub size: Option<DipSize>,
1111
1112    /// The window new safe padding, is `None` if the did not change.
1113    pub safe_padding: Option<DipSideOffsets>,
1114
1115    /// If the view-process is blocking the event loop for a time waiting for a frame for the new `size` this
1116    /// ID must be send with the frame to signal that it is the frame for the new size.
1117    ///
1118    /// Event loop implementations can use this to resize without visible artifacts
1119    /// like the clear color flashing on the window corners, there is a timeout to this delay but it
1120    /// can be a noticeable stutter, a [`render`] or [`render_update`] request for the window unblocks the loop early
1121    /// to continue the resize operation.
1122    ///
1123    /// [`render`]: crate::Api::render
1124    /// [`render_update`]: crate::Api::render_update
1125    pub frame_wait_id: Option<FrameWaitId>,
1126
1127    /// What caused the change, end-user/OS modifying the window or the app.
1128    pub cause: EventCause,
1129}
1130impl WindowChanged {
1131    /// New response.
1132    #[allow(clippy::too_many_arguments)] // already grouping stuff>
1133    pub fn new(
1134        window: WindowId,
1135        state: Option<WindowStateAll>,
1136        position: Option<(PxPoint, DipPoint)>,
1137        monitor: Option<MonitorId>,
1138        size: Option<DipSize>,
1139        safe_padding: Option<DipSideOffsets>,
1140        frame_wait_id: Option<FrameWaitId>,
1141        cause: EventCause,
1142    ) -> Self {
1143        Self {
1144            window,
1145            state,
1146            position,
1147            monitor,
1148            size,
1149            scale_factor: None,
1150            refresh_rate: None,
1151            safe_padding,
1152            frame_wait_id,
1153            cause,
1154        }
1155    }
1156
1157    /// Create an event that represents window move.
1158    pub fn moved(window: WindowId, global_position: PxPoint, position: DipPoint, cause: EventCause) -> Self {
1159        WindowChanged {
1160            window,
1161            state: None,
1162            position: Some((global_position, position)),
1163            monitor: None,
1164            size: None,
1165            safe_padding: None,
1166            scale_factor: None,
1167            refresh_rate: None,
1168            frame_wait_id: None,
1169            cause,
1170        }
1171    }
1172
1173    /// Create an event that represents window parent monitor change.
1174    pub fn monitor_changed(window: WindowId, monitor: MonitorId, cause: EventCause) -> Self {
1175        WindowChanged {
1176            window,
1177            state: None,
1178            position: None,
1179            monitor: Some(monitor),
1180            size: None,
1181            safe_padding: None,
1182            scale_factor: None,
1183            refresh_rate: None,
1184            frame_wait_id: None,
1185            cause,
1186        }
1187    }
1188
1189    /// Create an event that represents window resized.
1190    pub fn resized(window: WindowId, size: DipSize, cause: EventCause, frame_wait_id: Option<FrameWaitId>) -> Self {
1191        WindowChanged {
1192            window,
1193            state: None,
1194            position: None,
1195            monitor: None,
1196            size: Some(size),
1197            safe_padding: None,
1198            scale_factor: None,
1199            refresh_rate: None,
1200            frame_wait_id,
1201            cause,
1202        }
1203    }
1204
1205    /// Create an event that represents [`WindowStateAll`] change.
1206    pub fn state_changed(window: WindowId, state: WindowStateAll, cause: EventCause) -> Self {
1207        WindowChanged {
1208            window,
1209            state: Some(state),
1210            position: None,
1211            monitor: None,
1212            size: None,
1213            safe_padding: None,
1214            scale_factor: None,
1215            refresh_rate: None,
1216            frame_wait_id: None,
1217            cause,
1218        }
1219    }
1220}
1221
1222/// Identifier of a frame or frame update.
1223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, bytemuck::NoUninit)]
1224#[repr(C)]
1225pub struct FrameId(u32, u32);
1226impl FrameId {
1227    /// Dummy frame ID.
1228    pub const INVALID: FrameId = FrameId(u32::MAX, u32::MAX);
1229
1230    /// Create first frame id of a window.
1231    pub fn first() -> FrameId {
1232        FrameId(0, 0)
1233    }
1234
1235    /// Create the next full frame ID after the current one.
1236    pub fn next(self) -> FrameId {
1237        let mut id = self.0.wrapping_add(1);
1238        if id == u32::MAX {
1239            id = 0;
1240        }
1241        FrameId(id, 0)
1242    }
1243
1244    /// Create the next update frame ID after the current one.
1245    pub fn next_update(self) -> FrameId {
1246        let mut id = self.1.wrapping_add(1);
1247        if id == u32::MAX {
1248            id = 0;
1249        }
1250        FrameId(self.0, id)
1251    }
1252
1253    /// Get the raw ID.
1254    pub fn get(self) -> u64 {
1255        ((self.0 as u64) << 32) | (self.1 as u64)
1256    }
1257
1258    /// Get the full frame ID.
1259    pub fn epoch(self) -> u32 {
1260        self.0
1261    }
1262
1263    /// Get the frame update ID.
1264    pub fn update(self) -> u32 {
1265        self.1
1266    }
1267}
1268
1269/// Cause of a window state change.
1270#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1271#[non_exhaustive]
1272pub enum EventCause {
1273    /// Operating system or end-user affected the window.
1274    System,
1275    /// App affected the window.
1276    App,
1277}
1278
1279bitflags::bitflags! {
1280    /// Window chrome buttons.
1281    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1282    pub struct WindowButton: u32 {
1283        /// Close button.
1284        const CLOSE = 1 << 0;
1285        /// Minimize button.
1286        const MINIMIZE = 1 << 1;
1287        /// Maximize/restore button.
1288        const MAXIMIZE = 1 << 2;
1289    }
1290}
1291
1292bitflags::bitflags! {
1293    /// Window operations the view-process implements.
1294    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1295    pub struct WindowCapability: u64 {
1296        /// Can set title text.
1297        const SET_TITLE = 1 << 0;
1298        /// Can set window visible.
1299        const SET_VISIBLE = 1 << 1;
1300        /// Can make window "topmost".
1301        const SET_ALWAYS_ON_TOP = 1 << 2;
1302        /// Can change if window can be dragged by the user.
1303        const SET_MOVABLE = 1 << 3;
1304        /// Can change if window can be resized by the user.
1305        const SET_RESIZABLE = 1 << 4;
1306        /// Can make window icon not appear on the taskbar while the window remains visible.
1307        const SET_TASKBAR_VISIBLE = 1 << 5;
1308        /// Can force window to appear in front of other apps, without focusing input.
1309        const BRING_TO_TOP = 1 << 6;
1310        /// Can set the window icon.
1311        ///
1312        /// When this is not possible the system specific application metadata icon is used for all windows of the app.
1313        const SET_ICON = 1 << 7;
1314        /// Can set the window cursor to one of the named [`CursorIcon`].
1315        const SET_CURSOR = 1 << 8;
1316        /// Can set the window cursor to a custom image.
1317        const SET_CURSOR_IMAGE = 1 << 9;
1318        /// Can set attention indicator for the window.
1319        const SET_FOCUS_INDICATOR = 1 << 10;
1320        /// Can focus input on the window.
1321        ///
1322        /// This is also true if can the system only shows an attention indicator for the window some times.
1323        const FOCUS = 1 << 11;
1324        /// Can initiate a window move operation from a mouse press in the content area.
1325        const DRAG_MOVE = 1 << 12;
1326        /// Can initiate a window resize operation from a mouse press in the content area.
1327        const DRAG_RESIZE = 1 << 13;
1328        /// Can open the system context menu that usually shows on right click on the title bar.
1329        const OPEN_TITLE_BAR_CONTEXT_MENU = 1 << 14;
1330
1331        /// If operating system provides a window chrome (title bar, resize borders).
1332        const SYSTEM_CHROME = 1 << 15;
1333
1334        /// Can minimize window.
1335        const MINIMIZE = (1 << 16);
1336        /// Can restore window to *normal*.
1337        const RESTORE = (1 << 17);
1338        /// Can maximize window.
1339        const MAXIMIZE = (1 << 18);
1340        /// Can make window fullscreen.
1341        const FULLSCREEN = (1 << 19);
1342        /// Can takeover video output to show only the window content.
1343        const EXCLUSIVE = (1 << 20);
1344
1345        /// Can toggle if the system chrome (title bar, resize border) is visible.
1346        const SET_CHROME = (1 << 21) | Self::SYSTEM_CHROME.bits();
1347        /// Can programmatically move window after it is open.
1348        const SET_POSITION = (1 << 22);
1349
1350        /// Can programmatically resize window after it is open.
1351        const SET_SIZE = (1 << 23);
1352
1353        /// Can disable close button.
1354        const DISABLE_CLOSE_BUTTON = (1 << 24);
1355        /// Can disable minimize button.
1356        const DISABLE_MINIMIZE_BUTTON = (1 << 25);
1357        /// Can disable maximize button.
1358        const DISABLE_MAXIMIZE_BUTTON = (1 << 26);
1359
1360        /// Can set a system shutdown warning/blocker associated with the window.
1361        const SET_SYSTEM_SHUTDOWN_WARN = (1 << 27);
1362
1363        /// Can set the IME area, show virtual keyboard.
1364        const SET_IME_AREA = (1 << 28);
1365    }
1366}