zng_ext_window/
types.rs

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