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