Skip to main content

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    /// Cache compiled shaders to disk.
121    pub cache_shaders: bool,
122
123    /// Initial payload for API extensions.
124    ///
125    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure`
126    /// with the payload.
127    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
128}
129impl HeadlessRequest {
130    /// New request.
131    pub fn new(
132        id: WindowId,
133        scale_factor: Factor,
134        size: DipSize,
135        render_mode: RenderMode,
136        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
137    ) -> Self {
138        Self {
139            id,
140            scale_factor,
141            size,
142            render_mode,
143            cache_shaders: true,
144            extensions,
145        }
146    }
147}
148
149/// Information about a monitor screen.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151#[non_exhaustive]
152pub struct MonitorInfo {
153    /// Readable name of the monitor.
154    pub name: Txt,
155    /// Top-left offset of the monitor region in the virtual screen, in pixels.
156    pub position: PxPoint,
157    /// Width/height of the monitor region in the virtual screen, in pixels.
158    pub size: PxSize,
159    /// The monitor scale factor.
160    pub scale_factor: Factor,
161    /// The refresh rate of this monitor in normal desktop.
162    ///
163    /// If a window is set to exclusive fullscreen use the [`VideoMode::refresh_rate`] instead.
164    pub refresh_rate: Frequency,
165
166    /// Exclusive fullscreen video modes.
167    pub video_modes: Vec<VideoMode>,
168
169    /// If could determine this monitor is the primary.
170    pub is_primary: bool,
171}
172impl MonitorInfo {
173    /// New info.
174    pub fn new(name: Txt, position: PxPoint, size: PxSize, scale_factor: Factor, video_modes: Vec<VideoMode>, is_primary: bool) -> Self {
175        Self {
176            name,
177            position,
178            size,
179            scale_factor,
180            video_modes,
181            is_primary,
182            refresh_rate: Frequency::from_hertz(60.0),
183        }
184    }
185
186    /// Returns the `size` descaled using the `scale_factor`.
187    pub fn dip_size(&self) -> DipSize {
188        self.size.to_dip(self.scale_factor)
189    }
190}
191
192/// Exclusive video mode info.
193///
194/// You can get the options for a monitor using [`MonitorInfo::video_modes`].
195///
196/// Note that actual system video mode is selected by approximation,
197/// closest `size`, then `bit_depth`, then `refresh_rate`.
198///
199/// [`MonitorInfo::video_modes`]: crate::window::MonitorInfo::video_modes
200#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
201#[non_exhaustive]
202pub struct VideoMode {
203    /// Resolution of this video mode.
204    pub size: PxSize,
205    /// The bit depth of this video mode.
206    /// This is generally 24 bits or 32 bits on modern systems,
207    /// depending on whether the alpha channel is counted or not.
208    pub bit_depth: u16,
209    /// The refresh rate of this video mode.
210    pub refresh_rate: Frequency,
211}
212impl Default for VideoMode {
213    fn default() -> Self {
214        Self::MAX
215    }
216}
217impl VideoMode {
218    /// New video mode.
219    pub fn new(size: PxSize, bit_depth: u16, refresh_rate: Frequency) -> Self {
220        Self {
221            size,
222            bit_depth,
223            refresh_rate,
224        }
225    }
226
227    /// Default value, matches with the largest size, greatest bit-depth and refresh rate.
228    pub const MAX: VideoMode = VideoMode {
229        size: PxSize::new(Px::MAX, Px::MAX),
230        bit_depth: u16::MAX,
231        refresh_rate: Frequency::from_millihertz(u64::MAX),
232    };
233}
234impl fmt::Display for VideoMode {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        if *self == Self::MAX {
237            write!(f, "MAX")
238        } else {
239            write!(
240                f,
241                "{}x{}, {}, {}",
242                self.size.width.0, self.size.height.0, self.bit_depth, self.refresh_rate
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    /// Cache compiled shaders to disk.
530    pub cache_shaders: bool,
531
532    /// Focus request indicator on init.
533    pub focus_indicator: Option<FocusIndicator>,
534
535    /// Ensures the window is focused after open, if not set the initial focus is decided by
536    /// the windows manager, usually focusing the new window only if the process that causes the window has focus.
537    pub focus: bool,
538
539    /// IME cursor area, if IME is enabled.
540    pub ime_area: Option<DipRect>,
541
542    /// Enabled window chrome buttons.
543    pub enabled_buttons: WindowButton,
544
545    /// System shutdown warning associated with the window.
546    pub system_shutdown_warn: Txt,
547
548    /// Initial payload for API extensions.
549    ///
550    /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure` with the payload.
551    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
552}
553impl WindowRequest {
554    /// New request.
555    #[allow(clippy::too_many_arguments)]
556    pub fn new(
557        id: WindowId,
558        title: Txt,
559        state: WindowStateAll,
560        kiosk: bool,
561        default_position: bool,
562        video_mode: VideoMode,
563        visible: bool,
564        taskbar_visible: bool,
565        always_on_top: bool,
566        movable: bool,
567        resizable: bool,
568        icon: Option<ImageId>,
569        cursor: Option<CursorIcon>,
570        cursor_image: Option<(ImageId, PxPoint)>,
571        transparent: bool,
572        capture_mode: bool,
573        render_mode: RenderMode,
574        focus_indicator: Option<FocusIndicator>,
575        focus: bool,
576        ime_area: Option<DipRect>,
577        enabled_buttons: WindowButton,
578        system_shutdown_warn: Txt,
579        extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
580    ) -> Self {
581        Self {
582            id,
583            title,
584            state,
585            kiosk,
586            default_position,
587            video_mode,
588            visible,
589            taskbar_visible,
590            always_on_top,
591            movable,
592            resizable,
593            icon,
594            cursor,
595            cursor_image,
596            transparent,
597            capture_mode,
598            render_mode,
599            cache_shaders: true,
600            focus_indicator,
601            focus,
602            extensions,
603            ime_area,
604            enabled_buttons,
605            system_shutdown_warn,
606        }
607    }
608
609    /// Corrects invalid values if [`kiosk`] is `true`.
610    ///
611    /// An error is logged for each invalid value.
612    ///
613    /// [`kiosk`]: Self::kiosk
614    pub fn enforce_kiosk(&mut self) {
615        if self.kiosk {
616            if !self.state.state.is_fullscreen() {
617                tracing::error!("window in `kiosk` mode did not request fullscreen");
618                self.state.state = WindowState::Exclusive;
619            }
620            if self.state.chrome_visible {
621                tracing::error!("window in `kiosk` mode request chrome");
622                self.state.chrome_visible = false;
623            }
624            if !self.visible {
625                tracing::error!("window in `kiosk` mode can only be visible");
626                self.visible = true;
627            }
628        }
629    }
630}
631
632/// Represents the properties of a window that affect its position, size and state.
633#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
634#[non_exhaustive]
635pub struct WindowStateAll {
636    /// The window state.
637    pub state: WindowState,
638
639    /// Position across monitors.
640    ///
641    /// This is mostly used to find a monitor to resolve the `restore_rect` in.
642    pub global_position: PxPoint,
643
644    /// Position and size of the window in the `Normal` state.
645    ///
646    /// The position is relative to the monitor.
647    pub restore_rect: DipRect,
648
649    /// Window restore state.
650    ///
651    /// Defines the state the window will return to when restored from [`Maximized`] or [`Minimized`].
652    ///
653    /// * If the [current state] is [`Maximized`], this is [`Normal`].
654    /// * If the [current state] is [`Minimized`], this is the pre-minimization state.
655    /// * If the [current state] is [`Fullscreen`] or [`Exclusive`], this retains the previous
656    ///   non-fullscreen state (e.g., [`Maximized`] or [`Normal`]) to restore to when exiting fullscreen.
657    ///
658    /// When this is [`Normal`], the [`restore_rect`] defines the window's position and size.
659    ///
660    /// Note that if a fullscreen window is minimized, [`restore_state_fullscreen`] is set and will
661    /// take precedence over this value upon restoration.
662    ///
663    /// [current state]: Self::state
664    /// [`Maximized`]: WindowState::Maximized
665    /// [`Fullscreen`]: WindowState::Fullscreen
666    /// [`Exclusive`]: WindowState::Exclusive
667    /// [`Normal`]: WindowState::Normal
668    /// [`Minimized`]: WindowState::Minimized
669    /// [`restore_rect`]: Self::restore_rect
670    /// [`restore_state_fullscreen`]: Self::restore_state_fullscreen
671    pub restore_state: WindowState,
672    /// Fullscreen restore state from minimized.
673    ///
674    /// Stores the fullscreen mode if the window was minimized while in [`Fullscreen`]
675    /// or [`Exclusive`].
676    ///
677    /// When this is `Some` and the window exits [`Minimized`], it restores directly back to
678    /// this fullscreen mode instead of [`restore_state`]. This variable resets to `None` once the window is restored.
679    ///
680    /// [`restore_state`]: Self::restore_state
681    /// [`Fullscreen`]: WindowState::Fullscreen
682    /// [`Exclusive`]: WindowState::Exclusive
683    /// [`Minimized`]: WindowState::Minimized
684    pub restore_state_fullscreen: Option<WindowState>,
685
686    /// Minimal `Normal` size allowed.
687    pub min_size: DipSize,
688    /// Maximum `Normal` size allowed.
689    pub max_size: DipSize,
690
691    /// If the system provided outer-border and title-bar is visible.
692    ///
693    /// This is also called the "decoration" or "chrome" of the window.
694    pub chrome_visible: bool,
695}
696impl WindowStateAll {
697    /// New state.
698    #[deprecated = "use new2"]
699    pub fn new(
700        state: WindowState,
701        global_position: PxPoint,
702        restore_rect: DipRect,
703        restore_state: WindowState,
704        min_size: DipSize,
705        max_size: DipSize,
706        chrome_visible: bool,
707    ) -> Self {
708        Self {
709            state,
710            global_position,
711            restore_rect,
712            restore_state,
713            restore_state_fullscreen: None,
714            min_size,
715            max_size,
716            chrome_visible,
717        }
718    }
719
720    /// New state.
721    #[allow(clippy::too_many_arguments)]
722    pub fn new2(
723        state: WindowState,
724        global_position: PxPoint,
725        restore_rect: DipRect,
726        restore_state: WindowState,
727        restore_state_fullscreen: Option<WindowState>,
728        min_size: DipSize,
729        max_size: DipSize,
730        chrome_visible: bool,
731    ) -> Self {
732        Self {
733            state,
734            global_position,
735            restore_rect,
736            restore_state_fullscreen,
737            restore_state,
738            min_size,
739            max_size,
740            chrome_visible,
741        }
742    }
743
744    /// Clamp the `restore_rect.size` to `min_size` and `max_size`.
745    pub fn clamp_size(&mut self) {
746        self.restore_rect.size = self.restore_rect.size.min(self.max_size).max(self.min_size)
747    }
748
749    /// Compute a value for [`restore_state`] and [`restore_state_fullscreen`] given the previous [`state`]
750    /// in `self` and the `new_state` and update the [`state`].
751    ///
752    /// [`restore_state`]: Self::restore_state
753    /// [`restore_state_fullscreen`]: Self::restore_state_fullscreen
754    /// [`state`]: Self::state
755    pub fn set_state(&mut self, new_state: WindowState) {
756        if new_state == self.state {
757            return;
758        }
759
760        match new_state {
761            WindowState::Normal => {
762                self.restore_state = WindowState::Normal;
763                self.restore_state_fullscreen = None;
764            }
765            WindowState::Minimized => {
766                if self.state.is_fullscreen() {
767                    self.restore_state_fullscreen = Some(self.state);
768                } else {
769                    self.restore_state_fullscreen = None;
770                    self.restore_state = self.state;
771                };
772            }
773            WindowState::Maximized => {
774                self.restore_state = WindowState::Normal;
775                self.restore_state_fullscreen = None;
776            }
777            WindowState::Fullscreen | WindowState::Exclusive => {
778                self.restore_state = self.state;
779                self.restore_state_fullscreen = None;
780            }
781        }
782        self.state = new_state;
783    }
784
785    /// Compute a value for [`restore_state`] and [`restore_state_fullscreen`]
786    //// given the assumed previous `prev_state` and the new [`state`] in `self`.
787    ///
788    /// [`restore_state`]: Self::restore_state
789    /// [`restore_state_fullscreen`]: Self::restore_state_fullscreen
790    /// [`state`]: Self::state
791    pub fn set_restore_state_from(&mut self, prev_state: WindowState) {
792        if let WindowState::Minimized = prev_state {
793            return;
794        }
795        let new_state = self.state;
796        self.state = prev_state;
797        self.set_state(new_state);
798    }
799
800    /// Apply restore state.
801    pub fn restore(&mut self) {
802        if let WindowState::Minimized = self.state
803            && let Some(s) = self.restore_state_fullscreen.take()
804        {
805            self.set_state(s);
806        }
807        self.set_state(self.restore_state)
808    }
809}
810
811/// Named system dependent cursor icon.
812#[non_exhaustive]
813#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
814pub enum CursorIcon {
815    /// The platform-dependent default cursor. Often rendered as arrow.
816    #[default]
817    Default,
818
819    /// A context menu is available for the object under the cursor. Often
820    /// rendered as an arrow with a small menu-like graphic next to it.
821    ContextMenu,
822
823    /// Help is available for the object under the cursor. Often rendered as a
824    /// question mark or a balloon.
825    Help,
826
827    /// The cursor is a pointer that indicates a link. Often rendered as the
828    /// backside of a hand with the index finger extended.
829    Pointer,
830
831    /// A progress indicator. The program is performing some processing, but is
832    /// different from [`CursorIcon::Wait`] in that the user may still interact
833    /// with the program.
834    Progress,
835
836    /// Indicates that the program is busy and the user should wait. Often
837    /// rendered as a watch or hourglass.
838    Wait,
839
840    /// Indicates that a cell or set of cells may be selected. Often rendered as
841    /// a thick plus-sign with a dot in the middle.
842    Cell,
843
844    /// A simple crosshair (e.g., short line segments resembling a "+" sign).
845    /// Often used to indicate a two dimensional bitmap selection mode.
846    Crosshair,
847
848    /// Indicates text that may be selected. Often rendered as an I-beam.
849    Text,
850
851    /// Indicates vertical-text that may be selected. Often rendered as a
852    /// horizontal I-beam.
853    VerticalText,
854
855    /// Indicates an alias of/shortcut to something is to be created. Often
856    /// rendered as an arrow with a small curved arrow next to it.
857    Alias,
858
859    /// Indicates something is to be copied. Often rendered as an arrow with a
860    /// small plus sign next to it.
861    Copy,
862
863    /// Indicates something is to be moved.
864    Move,
865
866    /// Indicates that the dragged item cannot be dropped at the current cursor
867    /// location. Often rendered as a hand or pointer with a small circle with a
868    /// line through it.
869    NoDrop,
870
871    /// Indicates that the requested action will not be carried out. Often
872    /// rendered as a circle with a line through it.
873    NotAllowed,
874
875    /// Indicates that something can be grabbed (dragged to be moved). Often
876    /// rendered as the backside of an open hand.
877    Grab,
878
879    /// Indicates that something is being grabbed (dragged to be moved). Often
880    /// rendered as the backside of a hand with fingers closed mostly out of
881    /// view.
882    Grabbing,
883
884    /// The east border to be moved.
885    EResize,
886
887    /// The north border to be moved.
888    NResize,
889
890    /// The north-east corner to be moved.
891    NeResize,
892
893    /// The north-west corner to be moved.
894    NwResize,
895
896    /// The south border to be moved.
897    SResize,
898
899    /// The south-east corner to be moved.
900    SeResize,
901
902    /// The south-west corner to be moved.
903    SwResize,
904
905    /// The west border to be moved.
906    WResize,
907
908    /// The east and west borders to be moved.
909    EwResize,
910
911    /// The south and north borders to be moved.
912    NsResize,
913
914    /// The north-east and south-west corners to be moved.
915    NeswResize,
916
917    /// The north-west and south-east corners to be moved.
918    NwseResize,
919
920    /// Indicates that the item/column can be resized horizontally. Often
921    /// rendered as arrows pointing left and right with a vertical bar
922    /// separating them.
923    ColResize,
924
925    /// Indicates that the item/row can be resized vertically. Often rendered as
926    /// arrows pointing up and down with a horizontal bar separating them.
927    RowResize,
928
929    /// Indicates that the something can be scrolled in any direction. Often
930    /// rendered as arrows pointing up, down, left, and right with a dot in the
931    /// middle.
932    AllScroll,
933
934    /// Indicates that something can be zoomed in. Often rendered as a
935    /// magnifying glass with a "+" in the center of the glass.
936    ZoomIn,
937
938    /// Indicates that something can be zoomed in. Often rendered as a
939    /// magnifying glass with a "-" in the center of the glass.
940    ZoomOut,
941}
942#[cfg(feature = "var")]
943zng_var::impl_from_and_into_var! {
944    fn from(some: CursorIcon) -> Option<CursorIcon>;
945}
946impl CursorIcon {
947    /// All cursor icons.
948    pub const ALL: &'static [CursorIcon] = {
949        use CursorIcon::*;
950        &[
951            Default,
952            ContextMenu,
953            Help,
954            Pointer,
955            Progress,
956            Wait,
957            Cell,
958            Crosshair,
959            Text,
960            VerticalText,
961            Alias,
962            Copy,
963            Move,
964            NoDrop,
965            NotAllowed,
966            Grab,
967            Grabbing,
968            EResize,
969            NResize,
970            NeResize,
971            NwResize,
972            SResize,
973            SeResize,
974            SwResize,
975            WResize,
976            EwResize,
977            NsResize,
978            NeswResize,
979            NwseResize,
980            ColResize,
981            RowResize,
982            AllScroll,
983            ZoomIn,
984            ZoomOut,
985        ]
986    };
987
988    /// Estimated icon size and click spot in that size.
989    pub fn size_and_spot(&self, scale_factor: Factor) -> (PxSize, PxPoint) {
990        fn splat(s: f32, rel_pt: f32) -> (DipSize, DipPoint) {
991            size(s, s, rel_pt, rel_pt)
992        }
993        fn size(w: f32, h: f32, rel_x: f32, rel_y: f32) -> (DipSize, DipPoint) {
994            (
995                DipSize::new(Dip::new_f32(w), Dip::new_f32(h)),
996                DipPoint::new(Dip::new_f32(w * rel_x), Dip::new_f32(h * rel_y)),
997            )
998        }
999
1000        let (size, spot) = match self {
1001            CursorIcon::Crosshair
1002            | CursorIcon::Move
1003            | CursorIcon::Wait
1004            | CursorIcon::NotAllowed
1005            | CursorIcon::NoDrop
1006            | CursorIcon::Cell
1007            | CursorIcon::Grab
1008            | CursorIcon::Grabbing
1009            | CursorIcon::AllScroll => splat(20.0, 0.5),
1010            CursorIcon::Text | CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize => size(8.0, 20.0, 0.5, 0.5),
1011            CursorIcon::VerticalText | CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize => size(20.0, 8.0, 0.5, 0.5),
1012            _ => splat(20.0, 0.0),
1013        };
1014
1015        (size.to_px(scale_factor), spot.to_px(scale_factor))
1016    }
1017}
1018
1019/// Defines a custom mouse cursor.
1020#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1021#[non_exhaustive]
1022pub struct CursorImage {
1023    /// Cursor image.
1024    pub img: ImageId,
1025    /// Exact point in the image that is the mouse position.
1026    ///
1027    /// This value is only used if the image format does not contain a hotspot.
1028    pub hotspot: PxPoint,
1029}
1030impl CursorImage {
1031    /// New cursor.
1032    pub fn new(img: ImageId, hotspot: PxPoint) -> Self {
1033        Self { img, hotspot }
1034    }
1035}
1036
1037/// Defines the orientation that a window resize will be performed.
1038#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1039pub enum ResizeDirection {
1040    /// The east border will be moved.
1041    East,
1042    /// The north border will be moved.
1043    North,
1044    /// The north-east corner will be moved.
1045    NorthEast,
1046    /// The north-west corner will be moved.
1047    NorthWest,
1048    /// The south border will be moved.
1049    South,
1050    /// The south-east corner will be moved.
1051    SouthEast,
1052    /// The south-west corner will be moved.
1053    SouthWest,
1054    /// The west border will be moved.
1055    West,
1056}
1057impl From<ResizeDirection> for CursorIcon {
1058    fn from(direction: ResizeDirection) -> Self {
1059        use ResizeDirection::*;
1060        match direction {
1061            East => CursorIcon::EResize,
1062            North => CursorIcon::NResize,
1063            NorthEast => CursorIcon::NeResize,
1064            NorthWest => CursorIcon::NwResize,
1065            South => CursorIcon::SResize,
1066            SouthEast => CursorIcon::SeResize,
1067            SouthWest => CursorIcon::SwResize,
1068            West => CursorIcon::WResize,
1069        }
1070    }
1071}
1072#[cfg(feature = "var")]
1073zng_var::impl_from_and_into_var! {
1074    fn from(some: ResizeDirection) -> Option<ResizeDirection>;
1075    fn from(some: ResizeDirection) -> Option<CursorIcon> {
1076        Some(some.into())
1077    }
1078}
1079impl ResizeDirection {
1080    /// All directions.
1081    pub const ALL: &'static [ResizeDirection] = {
1082        use ResizeDirection::*;
1083        &[East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West]
1084    };
1085
1086    /// Gets if this resize represents two directions.
1087    pub const fn is_corner(self) -> bool {
1088        matches!(self, Self::NorthEast | Self::NorthWest | Self::SouthEast | Self::SouthWest)
1089    }
1090
1091    /// Gets if this resize represents a single direction.
1092    pub const fn is_border(self) -> bool {
1093        !self.is_corner()
1094    }
1095}
1096
1097/// Window state.
1098#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
1099pub enum WindowState {
1100    /// Window is visible, but does not fill the screen.
1101    #[default]
1102    Normal,
1103    /// Window is only visible as an icon in the taskbar.
1104    Minimized,
1105    /// Window fills the screen, but not the parts reserved by the system, like the taskbar.
1106    Maximized,
1107    /// Window is chromeless and completely fills the screen, including over parts reserved by the system.
1108    ///
1109    /// Also called borderless fullscreen.
1110    Fullscreen,
1111    /// Window has exclusive access to the monitor's video output, so only the window content is visible.
1112    Exclusive,
1113}
1114impl WindowState {
1115    /// Returns `true` if `self` matches [`Fullscreen`] or [`Exclusive`].
1116    ///
1117    /// [`Fullscreen`]: WindowState::Fullscreen
1118    /// [`Exclusive`]: WindowState::Exclusive
1119    pub fn is_fullscreen(self) -> bool {
1120        matches!(self, Self::Fullscreen | Self::Exclusive)
1121    }
1122}
1123
1124/// [`Event::FrameRendered`] payload.
1125///
1126/// [`Event::FrameRendered`]: crate::Event::FrameRendered
1127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1128#[non_exhaustive]
1129pub struct EventFrameRendered {
1130    /// Window that was rendered.
1131    pub window: WindowId,
1132    /// Frame that was rendered.
1133    pub frame: FrameId,
1134    /// Frame image, if one was requested with the frame request.
1135    pub frame_image: Option<ImageDecoded>,
1136}
1137impl EventFrameRendered {
1138    /// New response.
1139    pub fn new(window: WindowId, frame: FrameId, frame_image: Option<ImageDecoded>) -> Self {
1140        Self {
1141            window,
1142            frame,
1143            frame_image,
1144        }
1145    }
1146}
1147
1148/// [`Event::WindowChanged`] payload.
1149///
1150/// [`Event::WindowChanged`]: crate::Event::WindowChanged
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1152#[non_exhaustive]
1153pub struct WindowChanged {
1154    // note that this payload is handled by `Event::coalesce`, add new fields there too.
1155    //
1156    /// Window that has changed state.
1157    pub window: WindowId,
1158
1159    /// Window new state, is `None` if the window state did not change.
1160    pub state: Option<WindowStateAll>,
1161
1162    /// Window new global position, is `None` if the window position did not change.
1163    ///
1164    /// The values are the global position and the position in the monitor.
1165    pub position: Option<(PxPoint, DipPoint)>,
1166
1167    /// Window new monitor.
1168    ///
1169    /// The window's monitor change when it is moved enough so that most of the
1170    /// client area is in the new monitor screen.
1171    pub monitor: Option<MonitorId>,
1172
1173    /// New scale factor.
1174    pub scale_factor: Option<Factor>,
1175
1176    /// New refresh rate, in millihertz.
1177    pub refresh_rate: Option<Frequency>,
1178
1179    /// The window new size, is `None` if the window size did not change.
1180    pub size: Option<DipSize>,
1181
1182    /// The window new safe padding, is `None` if the did not change.
1183    pub safe_padding: Option<DipSideOffsets>,
1184
1185    /// If the view-process is blocking the event loop for a time waiting for a frame for the new `size` this
1186    /// ID must be send with the frame to signal that it is the frame for the new size.
1187    ///
1188    /// Event loop implementations can use this to resize without visible artifacts
1189    /// like the clear color flashing on the window corners, there is a timeout to this delay but it
1190    /// can be a noticeable stutter, a [`render`] or [`render_update`] request for the window unblocks the loop early
1191    /// to continue the resize operation.
1192    ///
1193    /// [`render`]: crate::Api::render
1194    /// [`render_update`]: crate::Api::render_update
1195    pub frame_wait_id: Option<FrameWaitId>,
1196
1197    /// What caused the change, end-user/OS modifying the window or the app.
1198    pub cause: EventCause,
1199}
1200impl WindowChanged {
1201    /// New response.
1202    #[allow(clippy::too_many_arguments)] // already grouping stuff>
1203    pub fn new(
1204        window: WindowId,
1205        state: Option<WindowStateAll>,
1206        position: Option<(PxPoint, DipPoint)>,
1207        monitor: Option<MonitorId>,
1208        size: Option<DipSize>,
1209        safe_padding: Option<DipSideOffsets>,
1210        frame_wait_id: Option<FrameWaitId>,
1211        cause: EventCause,
1212    ) -> Self {
1213        Self {
1214            window,
1215            state,
1216            position,
1217            monitor,
1218            size,
1219            scale_factor: None,
1220            refresh_rate: None,
1221            safe_padding,
1222            frame_wait_id,
1223            cause,
1224        }
1225    }
1226
1227    /// Create an event that represents window move.
1228    pub fn moved(window: WindowId, global_position: PxPoint, position: DipPoint, cause: EventCause) -> Self {
1229        WindowChanged {
1230            window,
1231            state: None,
1232            position: Some((global_position, position)),
1233            monitor: None,
1234            size: None,
1235            safe_padding: None,
1236            scale_factor: None,
1237            refresh_rate: None,
1238            frame_wait_id: None,
1239            cause,
1240        }
1241    }
1242
1243    /// Create an event that represents window parent monitor change.
1244    pub fn monitor_changed(window: WindowId, monitor: MonitorId, cause: EventCause) -> Self {
1245        WindowChanged {
1246            window,
1247            state: None,
1248            position: None,
1249            monitor: Some(monitor),
1250            size: None,
1251            safe_padding: None,
1252            scale_factor: None,
1253            refresh_rate: None,
1254            frame_wait_id: None,
1255            cause,
1256        }
1257    }
1258
1259    /// Create an event that represents window resized.
1260    pub fn resized(window: WindowId, size: DipSize, cause: EventCause, frame_wait_id: Option<FrameWaitId>) -> Self {
1261        WindowChanged {
1262            window,
1263            state: None,
1264            position: None,
1265            monitor: None,
1266            size: Some(size),
1267            safe_padding: None,
1268            scale_factor: None,
1269            refresh_rate: None,
1270            frame_wait_id,
1271            cause,
1272        }
1273    }
1274
1275    /// Create an event that represents [`WindowStateAll`] change.
1276    pub fn state_changed(window: WindowId, state: WindowStateAll, cause: EventCause) -> Self {
1277        WindowChanged {
1278            window,
1279            state: Some(state),
1280            position: None,
1281            monitor: None,
1282            size: None,
1283            safe_padding: None,
1284            scale_factor: None,
1285            refresh_rate: None,
1286            frame_wait_id: None,
1287            cause,
1288        }
1289    }
1290}
1291
1292/// Identifier of a frame or frame update.
1293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, bytemuck::NoUninit)]
1294#[repr(C)]
1295pub struct FrameId(u32, u32);
1296impl FrameId {
1297    /// Dummy frame ID.
1298    pub const INVALID: FrameId = FrameId(u32::MAX, u32::MAX);
1299
1300    /// Create first frame id of a window.
1301    pub fn first() -> FrameId {
1302        FrameId(0, 0)
1303    }
1304
1305    /// Create the next full frame ID after the current one.
1306    pub fn next(self) -> FrameId {
1307        let mut id = self.0.wrapping_add(1);
1308        if id == u32::MAX {
1309            id = 0;
1310        }
1311        FrameId(id, 0)
1312    }
1313
1314    /// Create the next update frame ID after the current one.
1315    pub fn next_update(self) -> FrameId {
1316        let mut id = self.1.wrapping_add(1);
1317        if id == u32::MAX {
1318            id = 0;
1319        }
1320        FrameId(self.0, id)
1321    }
1322
1323    /// Get the raw ID.
1324    pub fn get(self) -> u64 {
1325        ((self.0 as u64) << 32) | (self.1 as u64)
1326    }
1327
1328    /// Get the full frame ID.
1329    pub fn epoch(self) -> u32 {
1330        self.0
1331    }
1332
1333    /// Get the frame update ID.
1334    pub fn update(self) -> u32 {
1335        self.1
1336    }
1337}
1338
1339/// Cause of a window state change.
1340#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1341#[non_exhaustive]
1342pub enum EventCause {
1343    /// Operating system or end-user affected the window.
1344    System,
1345    /// App affected the window.
1346    App,
1347}
1348
1349bitflags::bitflags! {
1350    /// Window chrome buttons.
1351    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1352    pub struct WindowButton: u32 {
1353        /// Close button.
1354        const CLOSE = 1 << 0;
1355        /// Minimize button.
1356        const MINIMIZE = 1 << 1;
1357        /// Maximize/restore button.
1358        const MAXIMIZE = 1 << 2;
1359    }
1360}
1361
1362bitflags::bitflags! {
1363    /// Window operations the view-process implements.
1364    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1365    pub struct WindowCapability: u64 {
1366        /// Can set title text.
1367        const SET_TITLE = 1 << 0;
1368        /// Can set window visible.
1369        const SET_VISIBLE = 1 << 1;
1370        /// Can make window "topmost".
1371        const SET_ALWAYS_ON_TOP = 1 << 2;
1372        /// Can change if window can be dragged by the user.
1373        const SET_MOVABLE = 1 << 3;
1374        /// Can change if window can be resized by the user.
1375        const SET_RESIZABLE = 1 << 4;
1376        /// Can make window icon not appear on the taskbar while the window remains visible.
1377        const SET_TASKBAR_VISIBLE = 1 << 5;
1378        /// Can force window to appear in front of other apps, without focusing input.
1379        const BRING_TO_TOP = 1 << 6;
1380        /// Can set the window icon.
1381        ///
1382        /// When this is not possible the system specific application metadata icon is used for all windows of the app.
1383        const SET_ICON = 1 << 7;
1384        /// Can set the window cursor to one of the named [`CursorIcon`].
1385        const SET_CURSOR = 1 << 8;
1386        /// Can set the window cursor to a custom image.
1387        const SET_CURSOR_IMAGE = 1 << 9;
1388        /// Can set attention indicator for the window.
1389        const SET_FOCUS_INDICATOR = 1 << 10;
1390        /// Can focus input on the window.
1391        ///
1392        /// This is also true if can the system only shows an attention indicator for the window some times.
1393        const FOCUS = 1 << 11;
1394        /// Can initiate a window move operation from a mouse press in the content area.
1395        const DRAG_MOVE = 1 << 12;
1396        /// Can initiate a window resize operation from a mouse press in the content area.
1397        const DRAG_RESIZE = 1 << 13;
1398        /// Can open the system context menu that usually shows on right click on the title bar.
1399        const OPEN_TITLE_BAR_CONTEXT_MENU = 1 << 14;
1400
1401        /// If operating system provides a window chrome (title bar, resize borders).
1402        const SYSTEM_CHROME = 1 << 15;
1403
1404        /// Can minimize window.
1405        const MINIMIZE = (1 << 16);
1406        /// Can restore window to *normal*.
1407        const RESTORE = (1 << 17);
1408        /// Can maximize window.
1409        const MAXIMIZE = (1 << 18);
1410        /// Can make window fullscreen.
1411        const FULLSCREEN = (1 << 19);
1412        /// Can takeover video output to show only the window content.
1413        const EXCLUSIVE = (1 << 20);
1414
1415        /// Can toggle if the system chrome (title bar, resize border) is visible.
1416        const SET_CHROME = (1 << 21) | Self::SYSTEM_CHROME.bits();
1417        /// Can programmatically move window after it is open.
1418        const SET_POSITION = (1 << 22);
1419
1420        /// Can programmatically resize window after it is open.
1421        const SET_SIZE = (1 << 23);
1422
1423        /// Can disable close button.
1424        const DISABLE_CLOSE_BUTTON = (1 << 24);
1425        /// Can disable minimize button.
1426        const DISABLE_MINIMIZE_BUTTON = (1 << 25);
1427        /// Can disable maximize button.
1428        const DISABLE_MAXIMIZE_BUTTON = (1 << 26);
1429
1430        /// Can set a system shutdown warning/blocker associated with the window.
1431        const SET_SYSTEM_SHUTDOWN_WARN = (1 << 27);
1432
1433        /// Can set the IME area, show virtual keyboard.
1434        const SET_IME_AREA = (1 << 28);
1435    }
1436}