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