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