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