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