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