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