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