zng_ext_window/
types.rs

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