Skip to main content

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#[non_exhaustive]
365#[cfg(feature = "image")]
366pub struct CursorImg {
367    /// Cursor image source.
368    ///
369    /// For better compatibility use a square image between 32 and 128 pixels.
370    pub source: ImageSource,
371    /// Pixel in the source image that is the exact mouse position.
372    ///
373    /// This value is ignored if the image source format already has hotspot information.
374    pub hotspot: Point,
375
376    /// Icon to use if the image cannot be displayed.
377    pub fallback: CursorIcon,
378}
379#[cfg(feature = "image")]
380impl_from_and_into_var! {
381    fn from(img: CursorImg) -> Option<CursorImg>;
382}
383#[cfg(feature = "image")]
384impl CursorImg {
385    /// New with default hotspot.
386    pub fn new(source: impl Into<ImageSource>, fallback: CursorIcon) -> Self {
387        Self {
388            source: source.into(),
389            hotspot: Point::zero(),
390            fallback,
391        }
392    }
393}
394
395/// Window cursor source.
396#[derive(Debug, Clone, PartialEq)]
397pub enum CursorSource {
398    /// Platform dependent named cursor icon.
399    Icon(CursorIcon),
400    /// Custom cursor image, with fallback.
401    #[cfg(feature = "image")]
402    Img(CursorImg),
403    /// Don't show cursor.
404    Hidden,
405}
406impl CursorSource {
407    /// Get the icon, image fallback or `None` if is hidden.
408    pub fn icon(&self) -> Option<CursorIcon> {
409        match self {
410            CursorSource::Icon(ico) => Some(*ico),
411            #[cfg(feature = "image")]
412            CursorSource::Img(img) => Some(img.fallback),
413            CursorSource::Hidden => None,
414        }
415    }
416
417    /// Custom icon image source.
418    #[cfg(feature = "image")]
419    pub fn img(&self) -> Option<&ImageSource> {
420        match self {
421            CursorSource::Img(img) => Some(&img.source),
422            _ => None,
423        }
424    }
425
426    /// Custom icon image click point, when the image data does not contain a hotspot.
427    #[cfg(feature = "image")]
428    pub fn hotspot(&self) -> Option<&Point> {
429        match self {
430            CursorSource::Img(img) => Some(&img.hotspot),
431            _ => None,
432        }
433    }
434}
435#[cfg(feature = "image")]
436impl_from_and_into_var! {
437    fn from(img: CursorImg) -> CursorSource {
438        CursorSource::Img(img)
439    }
440}
441impl_from_and_into_var! {
442    fn from(icon: CursorIcon) -> CursorSource {
443        CursorSource::Icon(icon)
444    }
445
446    /// Converts `true` to `CursorIcon::Default` and `false` to `CursorSource::Hidden`.
447    fn from(default_icon_or_hidden: bool) -> CursorSource {
448        if default_icon_or_hidden {
449            CursorIcon::Default.into()
450        } else {
451            CursorSource::Hidden
452        }
453    }
454}
455
456/// Frame image capture mode in a window.
457///
458/// You can set the capture mode using [`WindowVars::frame_capture_mode`].
459///
460/// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
461#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
462#[cfg(feature = "image")]
463pub enum FrameCaptureMode {
464    /// Frames are not automatically captured, but you can
465    /// use [`WINDOWS.frame_image`] to capture frames.
466    ///
467    /// [`WINDOWS.frame_image`]: crate::WINDOWS::frame_image
468    Sporadic,
469    /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
470    /// as a full BGRA8 image.
471    ///
472    /// After the frame is captured the mode changes to `Sporadic`.
473    Next,
474    /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
475    /// as an A8 mask image.
476    ///
477    /// After the frame is captured the mode changes to `Sporadic`.
478    NextMask(ImageMaskMode),
479    /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
480    /// as full BGRA8 images.
481    All,
482    /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
483    /// as A8 mask images.
484    AllMask(ImageMaskMode),
485}
486#[cfg(feature = "image")]
487impl Default for FrameCaptureMode {
488    /// [`Sporadic`]: FrameCaptureMode::Sporadic
489    fn default() -> Self {
490        Self::Sporadic
491    }
492}
493
494event_args! {
495    /// [`WINDOW_OPEN_EVENT`] args.
496    pub struct WindowOpenArgs {
497        /// Id of window that has opened.
498        pub window_id: WindowId,
499
500        ..
501
502        /// Broadcast to all widgets.
503        fn is_in_target(&self, _id: WidgetId) -> bool {
504            true
505        }
506    }
507
508    /// [`WINDOW_CLOSE_EVENT`] args.
509    pub struct WindowCloseArgs {
510        /// IDs of windows that have closed.
511        ///
512        /// This is at least one window, is multiple if the close operation was requested as group.
513        pub windows: IdSet<WindowId>,
514
515        ..
516
517        /// Broadcast to all widgets.
518        fn is_in_target(&self, _id: WidgetId) -> bool {
519            true
520        }
521    }
522
523    /// [`WINDOW_CHANGED_EVENT`] args.
524    pub struct WindowChangedArgs {
525        /// Window that has moved, resized or has a state change.
526        pub window_id: WindowId,
527
528        /// Window state change, if it has changed the values are `(prev, new)` states.
529        pub state: Option<(WindowState, WindowState)>,
530
531        /// New window position if it was moved.
532        ///
533        /// The values are `(global_position, actual_position)` where the global position is
534        /// in the virtual space that encompasses all monitors and actual position is in the monitor space.
535        pub position: Option<(PxPoint, DipPoint)>,
536
537        /// New window size if it was resized.
538        pub size: Option<DipSize>,
539
540        /// If the app or operating system caused the change.
541        pub cause: EventCause,
542
543        ..
544
545        /// Broadcast to all widgets.
546        fn is_in_target(&self, _id: WidgetId) -> bool {
547            true
548        }
549    }
550
551    /// [`WINDOW_FOCUS_CHANGED_EVENT`] args.
552    pub struct WindowFocusChangedArgs {
553        /// Previously focused window.
554        pub prev_focus: Option<WindowId>,
555
556        /// Newly focused window.
557        pub new_focus: Option<WindowId>,
558
559        /// If the focus changed because the previously focused window closed.
560        pub closed: bool,
561
562        ..
563
564        /// Broadcast to all widgets.
565        fn is_in_target(&self, _id: WidgetId) -> bool {
566            true
567        }
568    }
569
570    /// [`WINDOW_CLOSE_REQUESTED_EVENT`] args.
571    ///
572    /// Requesting `propagation().stop()` on this event cancels the window close.
573    pub struct WindowCloseRequestedArgs {
574        /// Windows closing, headed and headless.
575        ///
576        /// This is at least one window, is multiple if the close operation was requested as group, cancelling the request
577        /// cancels close for all windows.
578        pub windows: IdSet<WindowId>,
579
580        ..
581
582        /// Broadcast to all widgets.
583        fn is_in_target(&self, _id: WidgetId) -> bool {
584            true
585        }
586    }
587}
588#[cfg(feature = "image")]
589event_args! {
590    /// [`FRAME_IMAGE_READY_EVENT`] args.
591    pub struct FrameImageReadyArgs {
592        /// Window ID.
593        pub window_id: WindowId,
594
595        /// Frame that finished rendering.
596        ///
597        /// This is *probably* the ID of frame pixels if they are requested immediately.
598        pub frame_id: FrameId,
599
600        /// The frame pixels.
601        ///
602        /// See [`WindowVars::frame_capture_mode`] for more details. The image reference will hold only for the hooks, you must upgrade it
603        /// to keep it, this is done to avoid retaining the image in the last event.
604        ///
605        /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
606        pub frame_image: WeakVarEq<ImageEntry>,
607
608        ..
609
610        /// Broadcast to all widgets.
611        fn is_in_target(&self, _id: WidgetId) -> bool {
612            true
613        }
614    }
615}
616impl WindowChangedArgs {
617    /// Returns `true` if this event represents a window state change.
618    pub fn is_state_changed(&self) -> bool {
619        self.state.is_some()
620    }
621
622    /// Returns the previous window state if it has changed.
623    pub fn prev_state(&self) -> Option<WindowState> {
624        self.state.map(|(p, _)| p)
625    }
626
627    /// Returns the new window state if it has changed.
628    pub fn new_state(&self) -> Option<WindowState> {
629        self.state.map(|(_, n)| n)
630    }
631
632    /// Returns `true` if [`new_state`] is `state` and [`prev_state`] is not.
633    ///
634    /// [`new_state`]: Self::new_state
635    /// [`prev_state`]: Self::prev_state
636    pub fn entered_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 [`prev_state`] is `state` and [`new_state`] is not.
645    ///
646    /// [`new_state`]: Self::new_state
647    /// [`prev_state`]: Self::prev_state
648    pub fn exited_state(&self, state: WindowState) -> bool {
649        if let Some((prev, new)) = self.state {
650            prev == state && new != state
651        } else {
652            false
653        }
654    }
655
656    /// Returns `true` if [`new_state`] is one of the fullscreen states and [`prev_state`] is not.
657    ///
658    /// [`new_state`]: Self::new_state
659    /// [`prev_state`]: Self::prev_state
660    pub fn entered_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 [`prev_state`] is one of the fullscreen states and [`new_state`] is not.
669    ///
670    /// [`new_state`]: Self::new_state
671    /// [`prev_state`]: Self::prev_state
672    pub fn exited_fullscreen(&self) -> bool {
673        if let Some((prev, new)) = self.state {
674            prev.is_fullscreen() && !new.is_fullscreen()
675        } else {
676            false
677        }
678    }
679
680    /// Returns `true` if this event represents a window move.
681    pub fn is_moved(&self) -> bool {
682        self.position.is_some()
683    }
684
685    /// Returns `true` if this event represents a window resize.
686    pub fn is_resized(&self) -> bool {
687        self.size.is_some()
688    }
689}
690impl WindowFocusChangedArgs {
691    /// If `window_id` got focus.
692    pub fn is_focus(&self, window_id: WindowId) -> bool {
693        self.new_focus == Some(window_id)
694    }
695
696    /// If `window_id` lost focus.
697    pub fn is_blur(&self, window_id: WindowId) -> bool {
698        self.prev_focus == Some(window_id)
699    }
700
701    /// If `window_id` lost focus because it was closed.
702    pub fn is_close(&self, window_id: WindowId) -> bool {
703        self.closed && self.is_blur(window_id)
704    }
705
706    /// Gets the previous focused window if it was closed.
707    pub fn closed(&self) -> Option<WindowId> {
708        if self.closed { self.prev_focus } else { None }
709    }
710}
711impl WindowCloseRequestedArgs {
712    /// Gets only headed windows that will close.
713    pub fn headed(&self) -> impl Iterator<Item = WindowId> + '_ {
714        self.windows
715            .iter()
716            .copied()
717            .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headed()).unwrap_or(false))
718    }
719
720    /// Gets only headless windows that will close.
721    pub fn headless(&self) -> impl Iterator<Item = WindowId> + '_ {
722        self.windows
723            .iter()
724            .copied()
725            .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headless()).unwrap_or(false))
726    }
727}
728
729event! {
730    /// Window moved, resized or other state changed.
731    ///
732    /// This event aggregates events moves, resizes and other state changes into a
733    /// single event to simplify tracking composite changes, for example, the window changes size and position
734    /// when maximized, this can be trivially observed with this event.
735    pub static WINDOW_CHANGED_EVENT: WindowChangedArgs;
736
737    /// New window has inited.
738    pub static WINDOW_OPEN_EVENT: WindowOpenArgs;
739
740    /// Window instance state has changed to [`WindowInstanceState::Loaded`].
741    ///
742    /// If the window has renderer this event notifies only after the window is loaded with renderer.
743    pub static WINDOW_LOAD_EVENT: WindowOpenArgs;
744
745    /// Window focus/blur event.
746    pub static WINDOW_FOCUS_CHANGED_EVENT: WindowFocusChangedArgs;
747
748    /// Window close requested event.
749    ///
750    /// Calling `propagation().stop()` on this event cancels the window close.
751    pub static WINDOW_CLOSE_REQUESTED_EVENT: WindowCloseRequestedArgs;
752
753    /// Window closed event.
754    ///
755    /// The closed windows deinit after this event notifies, so the window content can subscribe to it.
756    pub static WINDOW_CLOSE_EVENT: WindowCloseArgs;
757}
758#[cfg(feature = "image")]
759event! {
760    /// A window frame has finished rendering and frame pixels where copied due to [`WindowVars::frame_capture_mode`].
761    ///
762    /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
763    pub static FRAME_IMAGE_READY_EVENT: FrameImageReadyArgs;
764}
765
766/// Response message of [`close`] and [`close_together`].
767///
768/// [`close`]: crate::WINDOWS::close
769/// [`close_together`]: crate::WINDOWS::close_together
770#[derive(Clone, Copy, Debug, Eq, PartialEq)]
771pub enum CloseWindowResult {
772    /// Operation completed, all requested windows closed.
773    Closed,
774
775    /// Operation canceled, no window closed.
776    Cancel,
777}
778
779/// Represents a handle that stops the window from loading while the handle is alive.
780///
781/// A handle can be retrieved using [`WINDOWS.loading_handle`] or [`WINDOW.loading_handle`], the window does not
782/// open until all handles expire or are dropped.
783///
784/// [`WINDOWS.loading_handle`]: WINDOWS::loading_handle
785/// [`WINDOW.loading_handle`]: WINDOW::loading_handle
786#[derive(Clone, Debug)]
787#[must_use = "the window does not await loading if the handle is dropped"]
788pub struct WindowLoadingHandle(pub(crate) DeadlineVar);
789impl WindowLoadingHandle {
790    /// Handle expiration deadline.
791    pub fn deadline(&self) -> Deadline {
792        self.0.get()
793    }
794}
795
796/// Error calling a view-process API extension associated with a window or renderer.
797#[derive(Debug)]
798#[non_exhaustive]
799pub enum ViewExtensionError {
800    /// Window is not open in the `WINDOWS` service.
801    WindowNotFound(WindowId),
802    /// Window must be headed to call window extensions.
803    WindowNotHeaded(WindowId),
804    /// Window is not open in the view-process.
805    ///
806    /// If the window is headless without renderer it will never open in view-process, if the window is headed
807    /// headless with renderer the window opens in the view-process after the first layout.
808    NotOpenInViewProcess(WindowId),
809    /// Window is already open in view-process. Extensions init must be set before first layout.
810    AlreadyOpenInViewProcess(WindowId),
811    /// View-process is not running.
812    Disconnected,
813    /// Api Error.
814    Api(zng_view_api::api_extension::ApiExtensionRecvError),
815}
816impl fmt::Display for ViewExtensionError {
817    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
818        match self {
819            Self::WindowNotFound(id) => write!(f, "window `{id}` not found"),
820            Self::WindowNotHeaded(id) => write!(f, "window `{id}` is not headed"),
821            Self::NotOpenInViewProcess(id) => write!(f, "window/renderer `{id}` not open in the view-process"),
822            Self::AlreadyOpenInViewProcess(id) => write!(f, "window/renderer `{id}` already open in the view-process"),
823            Self::Disconnected => fmt::Display::fmt(&DISCONNECTED_SOURCE, f),
824            Self::Api(e) => fmt::Display::fmt(e, f),
825        }
826    }
827}
828impl std::error::Error for ViewExtensionError {
829    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
830        match self {
831            Self::WindowNotFound(_) => None,
832            Self::WindowNotHeaded(_) => None,
833            Self::NotOpenInViewProcess(_) => None,
834            Self::AlreadyOpenInViewProcess(_) => None,
835            Self::Disconnected => Some(&DISCONNECTED_SOURCE),
836            Self::Api(e) => Some(e),
837        }
838    }
839}
840static DISCONNECTED_SOURCE: ChannelError = ChannelError::Disconnected { cause: None };
841
842bitflags! {
843    /// Defines what window operations can run in parallel, between windows.
844    ///
845    /// Note that this does not define parallelism inside the window, see [`WINDOWS.parallel`] for more details.
846    ///
847    /// [`WINDOWS.parallel`]: WINDOWS::parallel
848    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
849    #[serde(transparent)]
850    pub struct ParallelWin: u8 {
851        /// Windows can init, deinit, update and rebuild info in parallel.
852        const UPDATE = 0b0001;
853        /// Windows can layout in parallel.
854        const LAYOUT = 0b0100;
855        /// Windows with pending render or render update generate display lists in parallel.
856        const RENDER = 0b1000;
857    }
858}
859impl Default for ParallelWin {
860    /// Is all by default.
861    fn default() -> Self {
862        Self::all()
863    }
864}
865impl_from_and_into_var! {
866    fn from(all: bool) -> ParallelWin {
867        if all { ParallelWin::all() } else { ParallelWin::empty() }
868    }
869}
870
871/// Represents stages of a window instance lifetime.
872#[derive(Debug, PartialEq, Clone)]
873pub enum WindowInstanceState {
874    /// The new window closure is running.
875    ///
876    /// The [`WINDOWS.vars`] is available.
877    ///
878    /// [`WINDOWS.vars`]: WINDOWS::vars
879    Building,
880    /// Some [`WindowLoadingHandle`] are still held.
881    ///
882    /// The [`WINDOWS.widget_tree`] is available, the window is awaiting load to show.
883    ///
884    /// [`WINDOWS.widget_tree`]: WINDOWS::widget_tree
885    Loading,
886    /// Window is fully loaded or load handles have timeout.
887    Loaded {
888        /// If the window is headed or headless with renderer and it is represented in the view-process.
889        has_view: bool,
890    },
891    /// Window content and view has deinited and dropped.
892    ///
893    /// The [`WindowVars::instance_state`] variable will not update again.
894    ///
895    /// [`WindowVars::instance_state`]: crate::WindowVars::instance_state
896    Closed,
897}
898impl WindowInstanceState {
899    /// If matches [`Loaded`] with and without view.
900    ///
901    /// [`Loaded`]: WindowInstanceState::Loaded
902    pub fn is_loaded(&self) -> bool {
903        matches!(self, Self::Loaded { .. })
904    }
905}