zng_ext_window/
types.rs

1use std::{
2    fmt,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use zng_app::{
8    AppEventSender, Deadline,
9    event::{event, event_args},
10    update::UpdateOp,
11    widget::{
12        WidgetId,
13        node::{BoxedUiNode, UiNode},
14    },
15    window::{WINDOW, WindowId},
16};
17use zng_ext_image::{ImageSource, ImageVar, Img};
18use zng_layout::unit::{DipPoint, DipSize, Point, PxPoint};
19use zng_txt::Txt;
20use zng_unique_id::IdSet;
21use zng_var::impl_from_and_into_var;
22use zng_view_api::{
23    ViewProcessOffline,
24    image::{ImageDataFormat, ImageMaskMode},
25    window::{CursorIcon, EventCause, FrameId},
26};
27
28pub use zng_view_api::window::{FocusIndicator, RenderMode, VideoMode, WindowButton, WindowState};
29
30use crate::{HeadlessMonitor, WINDOW_Ext as _, WINDOWS};
31
32/// Window root node and values.
33///
34/// The `Window!` widget instantiates this type.
35///
36/// This struct contains the window config that does not change, other window config is available in [`WINDOW.vars()`].
37///
38/// [`WINDOW.vars()`]: crate::WINDOW_Ext::vars
39pub struct WindowRoot {
40    pub(super) id: WidgetId,
41    pub(super) start_position: StartPosition,
42    pub(super) kiosk: bool,
43    pub(super) transparent: bool,
44    pub(super) render_mode: Option<RenderMode>,
45    pub(super) headless_monitor: HeadlessMonitor,
46    pub(super) start_focused: bool,
47    pub(super) child: BoxedUiNode,
48}
49impl WindowRoot {
50    /// New window from a `root` node that forms the window root widget.
51    ///
52    /// * `root_id` - Widget ID of `root`.
53    /// * `start_position` - Position of the window when it first opens.
54    /// * `kiosk` - Only allow fullscreen mode. Note this does not configure the windows manager, only blocks the app itself
55    ///   from accidentally exiting fullscreen. Also causes subsequent open windows to be child of this window.
56    /// * `transparent` - If the window should be created in a compositor mode that renders semi-transparent pixels as "see-through".
57    /// * `render_mode` - Render mode preference overwrite for this window, note that the actual render mode selected can be different.
58    /// * `headless_monitor` - "Monitor" configuration used in [headless mode](zng_app::window::WindowMode::is_headless).
59    /// * `start_focused` - If the window is forced to be the foreground keyboard focus after opening.
60    /// * `root` - The root widget's outermost `CONTEXT` node, the window uses this and the `root_id` to form the root widget.
61    #[expect(clippy::too_many_arguments)]
62    pub fn new(
63        root_id: WidgetId,
64        start_position: StartPosition,
65        kiosk: bool,
66        transparent: bool,
67        render_mode: Option<RenderMode>,
68        headless_monitor: HeadlessMonitor,
69        start_focused: bool,
70        root: impl UiNode,
71    ) -> Self {
72        WindowRoot {
73            id: root_id,
74            start_position,
75            kiosk,
76            transparent,
77            render_mode,
78            headless_monitor,
79            start_focused,
80            child: root.boxed(),
81        }
82    }
83
84    /// New window from a `child` node that becomes the child of the window root widget.
85    ///
86    /// The `child` parameter is a node that is the window's content, if it is a full widget the `root_id` is the id of
87    /// an internal container widget that is the parent of `child`, if it is not a widget it will still be placed in the inner
88    /// nest group of the root widget.
89    ///
90    /// See [`new`] for other parameters.
91    ///
92    /// [`new`]: Self::new
93    #[expect(clippy::too_many_arguments)]
94    pub fn new_container(
95        root_id: WidgetId,
96        start_position: StartPosition,
97        kiosk: bool,
98        transparent: bool,
99        render_mode: Option<RenderMode>,
100        headless_monitor: HeadlessMonitor,
101        start_focused: bool,
102        child: impl UiNode,
103    ) -> Self {
104        WindowRoot::new(
105            root_id,
106            start_position,
107            kiosk,
108            transparent,
109            render_mode,
110            headless_monitor,
111            start_focused,
112            zng_app::widget::base::node::widget_inner(child),
113        )
114    }
115
116    /// New test window.
117    #[cfg(any(test, doc, feature = "test_util"))]
118    pub fn new_test(child: impl UiNode) -> Self {
119        WindowRoot::new_container(
120            WidgetId::named("test-window-root"),
121            StartPosition::Default,
122            false,
123            false,
124            None,
125            HeadlessMonitor::default(),
126            false,
127            child,
128        )
129    }
130}
131
132bitflags! {
133    /// Window auto-size config.
134    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
135    #[serde(transparent)]
136    pub struct AutoSize: u8 {
137        /// Does not automatically adjust size.
138        const DISABLED = 0;
139        /// Uses the content desired width.
140        const CONTENT_WIDTH = 0b01;
141        /// Uses the content desired height.
142        const CONTENT_HEIGHT = 0b10;
143        /// Uses the content desired width and height.
144        const CONTENT = Self::CONTENT_WIDTH.bits() | Self::CONTENT_HEIGHT.bits();
145    }
146}
147impl_from_and_into_var! {
148    /// Returns [`AutoSize::CONTENT`] if `content` is `true`, otherwise
149    // returns [`AutoSize::DISABLED`].
150    fn from(content: bool) -> AutoSize {
151        if content {
152            AutoSize::CONTENT
153        } else {
154            AutoSize::DISABLED
155        }
156    }
157}
158
159/// Window startup position.
160///
161/// The startup position affects the window once, at the moment the window
162/// is open just after the first [`UiNode::layout`] call.
163///
164///  [`UiNode::layout`]: zng_app::widget::node::UiNode::layout
165#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
166pub enum StartPosition {
167    /// Resolves to [`position`](crate::WindowVars::position).
168    Default,
169
170    /// Centralizes the window in the monitor screen, defined by the [`monitor`](crate::WindowVars::monitor).
171    ///
172    /// Uses the `headless_monitor` in headless windows and falls back to not centering if no
173    /// monitor was found in headed windows.
174    CenterMonitor,
175    /// Centralizes the window in the parent window, defined by the [`parent`](crate::WindowVars::parent).
176    ///
177    /// Falls back to center on the monitor if the window has no parent.
178    CenterParent,
179}
180impl Default for StartPosition {
181    fn default() -> Self {
182        Self::Default
183    }
184}
185impl fmt::Debug for StartPosition {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        if f.alternate() {
188            write!(f, "StartPosition::")?;
189        }
190        match self {
191            StartPosition::Default => write!(f, "Default"),
192            StartPosition::CenterMonitor => write!(f, "CenterMonitor"),
193            StartPosition::CenterParent => write!(f, "CenterParent"),
194        }
195    }
196}
197
198bitflags! {
199    /// Mask of allowed [`WindowState`] states of a window.
200    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
201    #[serde(transparent)]
202    pub struct WindowStateAllowed: u8 {
203        /// Enable minimize.
204        const MINIMIZE = 0b0001;
205        /// Enable maximize.
206        const MAXIMIZE = 0b0010;
207        /// Enable fullscreen, but only windowed not exclusive video.
208        const FULLSCREEN_WN_ONLY = 0b0100;
209        /// Allow fullscreen windowed or exclusive video.
210        const FULLSCREEN = 0b1100;
211    }
212}
213
214/// Window icon.
215#[derive(Clone, PartialEq)]
216pub enum WindowIcon {
217    /// The operating system's default icon.
218    Default,
219    /// Image is requested from [`IMAGES`].
220    ///
221    /// [`IMAGES`]: zng_ext_image::IMAGES
222    Image(ImageSource),
223}
224impl fmt::Debug for WindowIcon {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        if f.alternate() {
227            write!(f, "WindowIcon::")?;
228        }
229        match self {
230            WindowIcon::Default => write!(f, "Default"),
231            WindowIcon::Image(r) => write!(f, "Image({r:?})"),
232        }
233    }
234}
235impl Default for WindowIcon {
236    /// [`WindowIcon::Default`]
237    fn default() -> Self {
238        Self::Default
239    }
240}
241impl WindowIcon {
242    /// New window icon from a closure that generates a new icon [`UiNode`] for the window.
243    ///
244    /// The closure is called once on init and every time the window icon property changes,
245    /// the closure runs in a headless window context, it must return a node to be rendered as an icon.
246    ///
247    /// The icon node is deinited and dropped after the first render, you can enable [`image::render_retain`] on it
248    /// to cause the icon to continue rendering on updates.
249    ///
250    /// [`image::render_retain`]: fn@zng_ext_image::render_retain
251    ///
252    /// # Examples
253    ///
254    /// ```no_run
255    /// # use zng_ext_window::WindowIcon;
256    /// # macro_rules! Container { ($($tt:tt)*) => { zng_app::widget::node::FillUiNode } }
257    /// # let _ =
258    /// WindowIcon::render(
259    ///     || Container! {
260    ///         // image::render_retain = true;
261    ///         size = (36, 36);
262    ///         background_gradient = Line::to_bottom_right(), stops![colors::MIDNIGHT_BLUE, 70.pct(), colors::CRIMSON];
263    ///         corner_radius = 6;
264    ///         font_size = 28;
265    ///         font_weight = FontWeight::BOLD;
266    ///         child = Text!("A");
267    ///     }
268    /// )
269    /// # ;
270    /// ```
271    ///
272    /// [`UiNode`]: zng_app::widget::node::UiNode
273    pub fn render<I, F>(new_icon: F) -> Self
274    where
275        I: UiNode,
276        F: Fn() -> I + Send + Sync + 'static,
277    {
278        Self::Image(ImageSource::render_node(RenderMode::Software, move |args| {
279            let node = new_icon();
280            WINDOW.vars().parent().set(args.parent);
281            node
282        }))
283    }
284}
285#[cfg(feature = "http")]
286impl_from_and_into_var! {
287    fn from(uri: zng_task::http::Uri) -> WindowIcon {
288        ImageSource::from(uri).into()
289    }
290}
291impl_from_and_into_var! {
292    fn from(source: ImageSource) -> WindowIcon {
293        WindowIcon::Image(source)
294    }
295    fn from(image: ImageVar) -> WindowIcon {
296        ImageSource::Image(image).into()
297    }
298    fn from(path: PathBuf) -> WindowIcon {
299        ImageSource::from(path).into()
300    }
301    fn from(path: &Path) -> WindowIcon {
302        ImageSource::from(path).into()
303    }
304    /// See [`ImageSource`] conversion from `&str`
305    fn from(s: &str) -> WindowIcon {
306        ImageSource::from(s).into()
307    }
308    /// Same as conversion from `&str`.
309    fn from(s: String) -> WindowIcon {
310        ImageSource::from(s).into()
311    }
312    /// Same as conversion from `&str`.
313    fn from(s: Txt) -> WindowIcon {
314        ImageSource::from(s).into()
315    }
316    /// From encoded data of [`Unknown`] format.
317    ///
318    /// [`Unknown`]: ImageDataFormat::Unknown
319    fn from(data: &'static [u8]) -> WindowIcon {
320        ImageSource::from(data).into()
321    }
322    /// From encoded data of [`Unknown`] format.
323    ///
324    /// [`Unknown`]: ImageDataFormat::Unknown
325    fn from<const N: usize>(data: &'static [u8; N]) -> WindowIcon {
326        ImageSource::from(data).into()
327    }
328    /// From encoded data of [`Unknown`] format.
329    ///
330    /// [`Unknown`]: ImageDataFormat::Unknown
331    fn from(data: Arc<Vec<u8>>) -> WindowIcon {
332        ImageSource::from(data).into()
333    }
334    /// From encoded data of [`Unknown`] format.
335    ///
336    /// [`Unknown`]: ImageDataFormat::Unknown
337    fn from(data: Vec<u8>) -> WindowIcon {
338        ImageSource::from(data).into()
339    }
340    /// From encoded data of known format.
341    fn from<F: Into<ImageDataFormat>>((data, format): (&'static [u8], F)) -> WindowIcon {
342        ImageSource::from((data, format)).into()
343    }
344    /// From encoded data of known format.
345    fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&'static [u8; N], F)) -> WindowIcon {
346        ImageSource::from((data, format)).into()
347    }
348    /// From encoded data of known format.
349    fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> WindowIcon {
350        ImageSource::from((data, format)).into()
351    }
352    /// From encoded data of known format.
353    fn from<F: Into<ImageDataFormat>>((data, format): (Arc<Vec<u8>>, F)) -> WindowIcon {
354        ImageSource::from((data, format)).into()
355    }
356}
357
358/// Window custom cursor.
359#[derive(Debug, Clone, PartialEq)]
360pub struct CursorImg {
361    /// Cursor image source.
362    ///
363    /// For better compatibility use a square image between 32 and 128 pixels.
364    pub source: ImageSource,
365    /// Pixel in the source image that is the exact mouse position.
366    ///
367    /// This value is ignored if the image source format already has hotspot information.
368    pub hotspot: Point,
369
370    /// Icon to use if the image cannot be displayed.
371    pub fallback: CursorIcon,
372}
373impl_from_and_into_var! {
374    fn from(img: CursorImg) -> Option<CursorImg>;
375}
376
377/// Window cursor source.
378#[derive(Debug, Clone, PartialEq)]
379pub enum CursorSource {
380    /// Platform dependent named cursor icon.
381    Icon(CursorIcon),
382    /// Custom cursor image, with fallback.
383    Img(CursorImg),
384    /// Don't show cursor.
385    Hidden,
386}
387impl CursorSource {
388    /// Get the icon, image fallback or `None` if is hidden.
389    pub fn icon(&self) -> Option<CursorIcon> {
390        match self {
391            CursorSource::Icon(ico) => Some(*ico),
392            CursorSource::Img(img) => Some(img.fallback),
393            CursorSource::Hidden => None,
394        }
395    }
396
397    /// Custom icon image source.
398    pub fn img(&self) -> Option<&ImageSource> {
399        match self {
400            CursorSource::Img(img) => Some(&img.source),
401            _ => None,
402        }
403    }
404
405    /// Custom icon image click point, when the image data does not contain a hotspot.
406    pub fn hotspot(&self) -> Option<&Point> {
407        match self {
408            CursorSource::Img(img) => Some(&img.hotspot),
409            _ => None,
410        }
411    }
412}
413impl_from_and_into_var! {
414    fn from(icon: CursorIcon) -> CursorSource {
415        CursorSource::Icon(icon)
416    }
417    fn from(img: CursorImg) -> CursorSource {
418        CursorSource::Img(img)
419    }
420    /// Converts `true` to `CursorIcon::Default` and `false` to `CursorSource::Hidden`.
421    fn from(default_icon_or_hidden: bool) -> CursorSource {
422        if default_icon_or_hidden {
423            CursorIcon::Default.into()
424        } else {
425            CursorSource::Hidden
426        }
427    }
428}
429
430/// Frame image capture mode in a window.
431///
432/// You can set the capture mode using [`WindowVars::frame_capture_mode`].
433///
434/// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
435#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
436pub enum FrameCaptureMode {
437    /// Frames are not automatically captured, but you can
438    /// use [`WINDOWS.frame_image`] to capture frames.
439    ///
440    /// [`WINDOWS.frame_image`]: crate::WINDOWS::frame_image
441    Sporadic,
442    /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
443    /// as a full BGRA8 image.
444    ///
445    /// After the frame is captured the mode changes to `Sporadic`.
446    Next,
447    /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
448    /// as an A8 mask image.
449    ///
450    /// After the frame is captured the mode changes to `Sporadic`.
451    NextMask(ImageMaskMode),
452    /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
453    /// as full BGRA8 images.
454    All,
455    /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
456    /// as A8 mask images.
457    AllMask(ImageMaskMode),
458}
459impl Default for FrameCaptureMode {
460    /// [`Sporadic`]: FrameCaptureMode::Sporadic
461    fn default() -> Self {
462        Self::Sporadic
463    }
464}
465
466event_args! {
467    /// [`WINDOW_OPEN_EVENT`] args.
468    pub struct WindowOpenArgs {
469        /// Id of window that has opened.
470        pub window_id: WindowId,
471
472        ..
473
474        /// Broadcast to all widgets.
475        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
476            list.search_all()
477        }
478    }
479
480    /// [`WINDOW_CLOSE_EVENT`] args.
481    pub struct WindowCloseArgs {
482        /// IDs of windows that have closed.
483        ///
484        /// This is at least one window, is multiple if the close operation was requested as group.
485        pub windows: IdSet<WindowId>,
486
487        ..
488
489        /// Broadcast to all widgets.
490        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
491            list.search_all()
492        }
493    }
494
495    /// [`WINDOW_CHANGED_EVENT`] args.
496    pub struct WindowChangedArgs {
497        /// Window that has moved, resized or has a state change.
498        pub window_id: WindowId,
499
500        /// Window state change, if it has changed the values are `(prev, new)` states.
501        pub state: Option<(WindowState, WindowState)>,
502
503        /// New window position if it was moved.
504        ///
505        /// The values are `(global_position, actual_position)` where the global position is
506        /// in the virtual space that encompasses all monitors and actual position is in the monitor space.
507        pub position: Option<(PxPoint, DipPoint)>,
508
509        /// New window size if it was resized.
510        pub size: Option<DipSize>,
511
512        /// If the app or operating system caused the change.
513        pub cause: EventCause,
514
515        ..
516
517        /// Broadcast to all widgets.
518        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
519            list.search_all()
520        }
521    }
522
523    /// [`WINDOW_FOCUS_CHANGED_EVENT`] args.
524    pub struct WindowFocusChangedArgs {
525        /// Previously focused window.
526        pub prev_focus: Option<WindowId>,
527
528        /// Newly focused window.
529        pub new_focus: Option<WindowId>,
530
531        /// If the focus changed because the previously focused window closed.
532        pub closed: bool,
533
534        ..
535
536        /// Broadcast to all widgets.
537        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
538            list.search_all()
539        }
540    }
541
542    /// [`FRAME_IMAGE_READY_EVENT`] args.
543    pub struct FrameImageReadyArgs {
544        /// Window ID.
545        pub window_id: WindowId,
546
547        /// Frame that finished rendering.
548        ///
549        /// This is *probably* the ID of frame pixels if they are requested immediately.
550        pub frame_id: FrameId,
551
552        /// The frame pixels if it was requested when the frame request was sent to the view-process.
553        ///
554        /// See [`WindowVars::frame_capture_mode`] for more details.
555        ///
556        /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
557        pub frame_image: Option<Img>,
558
559        ..
560
561        /// Broadcast to all widgets.
562        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
563            list.search_all()
564        }
565    }
566
567    /// [`WINDOW_CLOSE_REQUESTED_EVENT`] args.
568    ///
569    /// Requesting `propagation().stop()` on this event cancels the window close.
570    pub struct WindowCloseRequestedArgs {
571        /// Windows closing, headed and headless.
572        ///
573        /// This is at least one window, is multiple if the close operation was requested as group, cancelling the request
574        /// cancels close for all windows.
575        pub windows: IdSet<WindowId>,
576
577        ..
578
579        /// Broadcast to all widgets.
580        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
581            list.search_all()
582        }
583    }
584}
585impl WindowChangedArgs {
586    /// Returns `true` if this event represents a window state change.
587    pub fn is_state_changed(&self) -> bool {
588        self.state.is_some()
589    }
590
591    /// Returns the previous window state if it has changed.
592    pub fn prev_state(&self) -> Option<WindowState> {
593        self.state.map(|(p, _)| p)
594    }
595
596    /// Returns the new window state if it has changed.
597    pub fn new_state(&self) -> Option<WindowState> {
598        self.state.map(|(_, n)| n)
599    }
600
601    /// Returns `true` if [`new_state`] is `state` and [`prev_state`] is not.
602    ///
603    /// [`new_state`]: Self::new_state
604    /// [`prev_state`]: Self::prev_state
605    pub fn entered_state(&self, state: WindowState) -> bool {
606        if let Some((prev, new)) = self.state {
607            prev != state && new == state
608        } else {
609            false
610        }
611    }
612
613    /// Returns `true` if [`prev_state`] is `state` and [`new_state`] is not.
614    ///
615    /// [`new_state`]: Self::new_state
616    /// [`prev_state`]: Self::prev_state
617    pub fn exited_state(&self, state: WindowState) -> bool {
618        if let Some((prev, new)) = self.state {
619            prev == state && new != state
620        } else {
621            false
622        }
623    }
624
625    /// Returns `true` if [`new_state`] is one of the fullscreen states and [`prev_state`] is not.
626    ///
627    /// [`new_state`]: Self::new_state
628    /// [`prev_state`]: Self::prev_state
629    pub fn entered_fullscreen(&self) -> bool {
630        if let Some((prev, new)) = self.state {
631            !prev.is_fullscreen() && new.is_fullscreen()
632        } else {
633            false
634        }
635    }
636
637    /// Returns `true` if [`prev_state`] is one of the fullscreen states and [`new_state`] is not.
638    ///
639    /// [`new_state`]: Self::new_state
640    /// [`prev_state`]: Self::prev_state
641    pub fn exited_fullscreen(&self) -> bool {
642        if let Some((prev, new)) = self.state {
643            prev.is_fullscreen() && !new.is_fullscreen()
644        } else {
645            false
646        }
647    }
648
649    /// Returns `true` if this event represents a window move.
650    pub fn is_moved(&self) -> bool {
651        self.position.is_some()
652    }
653
654    /// Returns `true` if this event represents a window resize.
655    pub fn is_resized(&self) -> bool {
656        self.size.is_some()
657    }
658}
659impl WindowFocusChangedArgs {
660    /// If `window_id` got focus.
661    pub fn is_focus(&self, window_id: WindowId) -> bool {
662        self.new_focus == Some(window_id)
663    }
664
665    /// If `window_id` lost focus.
666    pub fn is_blur(&self, window_id: WindowId) -> bool {
667        self.prev_focus == Some(window_id)
668    }
669
670    /// If `window_id` lost focus because it was closed.
671    pub fn is_close(&self, window_id: WindowId) -> bool {
672        self.closed && self.is_blur(window_id)
673    }
674
675    /// Gets the previous focused window if it was closed.
676    pub fn closed(&self) -> Option<WindowId> {
677        if self.closed { self.prev_focus } else { None }
678    }
679}
680impl WindowCloseRequestedArgs {
681    /// Gets only headed windows that will close.
682    pub fn headed(&self) -> impl Iterator<Item = WindowId> + '_ {
683        self.windows
684            .iter()
685            .copied()
686            .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headed()).unwrap_or(false))
687    }
688
689    /// Gets only headless windows that will close.
690    pub fn headless(&self) -> impl Iterator<Item = WindowId> + '_ {
691        self.windows
692            .iter()
693            .copied()
694            .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headless()).unwrap_or(false))
695    }
696}
697
698event! {
699    /// Window moved, resized or other state changed.
700    ///
701    /// This event aggregates events moves, resizes and other state changes into a
702    /// single event to simplify tracking composite changes, for example, the window changes size and position
703    /// when maximized, this can be trivially observed with this event.
704    pub static WINDOW_CHANGED_EVENT: WindowChangedArgs;
705
706    /// New window has inited.
707    pub static WINDOW_OPEN_EVENT: WindowOpenArgs;
708
709    /// Window finished loading and has opened in the view-process.
710    pub static WINDOW_LOAD_EVENT: WindowOpenArgs;
711
712    /// Window focus/blur event.
713    pub static WINDOW_FOCUS_CHANGED_EVENT: WindowFocusChangedArgs;
714
715    /// Window close requested event.
716    ///
717    /// Calling `propagation().stop()` on this event cancels the window close.
718    pub static WINDOW_CLOSE_REQUESTED_EVENT: WindowCloseRequestedArgs;
719
720    /// Window closed event.
721    ///
722    /// The closed windows deinit after this event notifies, so the window content can subscribe to it.
723    pub static WINDOW_CLOSE_EVENT: WindowCloseArgs;
724
725    /// A window frame has finished rendering.
726    ///
727    /// You can request a copy of the pixels using [`WINDOWS.frame_image`] or by setting the [`WindowVars::frame_capture_mode`].
728    ///
729    /// [`WINDOWS.frame_image`]: crate::WINDOWS::frame_image
730    /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
731    pub static FRAME_IMAGE_READY_EVENT: FrameImageReadyArgs;
732}
733
734/// Response message of [`close`] and [`close_together`].
735///
736/// [`close`]: crate::WINDOWS::close
737/// [`close_together`]: crate::WINDOWS::close_together
738#[derive(Clone, Copy, Debug, Eq, PartialEq)]
739pub enum CloseWindowResult {
740    /// Operation completed, all requested windows closed.
741    Closed,
742
743    /// Operation canceled, no window closed.
744    Cancel,
745}
746
747/// Error when a [`WindowId`] is not opened by the [`WINDOWS`] service.
748///
749/// [`WINDOWS`]: crate::WINDOWS
750/// [`WindowId`]: crate::WindowId
751#[derive(Debug, PartialEq, Eq, Clone, Copy)]
752pub struct WindowNotFound(pub WindowId);
753impl fmt::Display for WindowNotFound {
754    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755        write!(f, "window `{}` not found", self.0)
756    }
757}
758impl std::error::Error for WindowNotFound {}
759
760/// Represents a handle that stops the window from loading while the handle is alive.
761///
762/// A handle can be retrieved using [`WINDOWS.loading_handle`] or [`WINDOW.loading_handle`], the window does not
763/// open until all handles expire or are dropped.
764///
765/// [`WINDOWS.loading_handle`]: WINDOWS::loading_handle
766/// [`WINDOW.loading_handle`]: WINDOW::loading_handle
767#[derive(Clone)]
768#[must_use = "the window does not await loading if the handle is dropped"]
769pub struct WindowLoadingHandle(pub(crate) Arc<WindowLoadingHandleData>);
770impl WindowLoadingHandle {
771    /// Handle expiration deadline.
772    pub fn deadline(&self) -> Deadline {
773        self.0.deadline
774    }
775}
776pub(crate) struct WindowLoadingHandleData {
777    pub(crate) update: AppEventSender,
778    pub(crate) deadline: Deadline,
779}
780impl Drop for WindowLoadingHandleData {
781    fn drop(&mut self) {
782        let _ = self.update.send_update(UpdateOp::Update, None);
783    }
784}
785impl PartialEq for WindowLoadingHandle {
786    fn eq(&self, other: &Self) -> bool {
787        Arc::ptr_eq(&self.0, &other.0)
788    }
789}
790impl Eq for WindowLoadingHandle {}
791impl std::hash::Hash for WindowLoadingHandle {
792    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
793        (Arc::as_ptr(&self.0) as usize).hash(state);
794    }
795}
796impl fmt::Debug for WindowLoadingHandle {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798        write!(f, "WindowLoadingHandle(_)")
799    }
800}
801
802/// Error calling a view-process API extension associated with a window or renderer.
803#[derive(Debug)]
804pub enum ViewExtensionError {
805    /// Window is not open in the `WINDOWS` service.
806    WindowNotFound(WindowNotFound),
807    /// Window must be headed to call window extensions.
808    WindowNotHeaded(WindowId),
809    /// Window is not open in the view-process.
810    ///
811    /// If the window is headless without renderer it will never open in view-process, if the window is headed
812    /// headless with renderer the window opens in the view-process after the first layout.
813    NotOpenInViewProcess(WindowId),
814    /// View-process is not running.
815    ViewProcessOffline(ViewProcessOffline),
816    /// Api Error.
817    Api(zng_view_api::api_extension::ApiExtensionRecvError),
818}
819impl fmt::Display for ViewExtensionError {
820    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821        match self {
822            Self::WindowNotFound(e) => fmt::Display::fmt(e, f),
823            Self::WindowNotHeaded(id) => write!(f, "window `{id}` is not headed"),
824            Self::NotOpenInViewProcess(id) => write!(f, "window/renderer `{id}` not open in the view-process"),
825            Self::ViewProcessOffline(e) => fmt::Display::fmt(e, f),
826            Self::Api(e) => fmt::Display::fmt(e, f),
827        }
828    }
829}
830impl std::error::Error for ViewExtensionError {
831    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
832        match self {
833            Self::WindowNotFound(e) => Some(e),
834            Self::WindowNotHeaded(_) => None,
835            Self::NotOpenInViewProcess(_) => None,
836            Self::ViewProcessOffline(e) => Some(e),
837            Self::Api(e) => Some(e),
838        }
839    }
840}