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#[cfg(feature = "image")]
365pub struct CursorImg {
366 /// Cursor image source.
367 ///
368 /// For better compatibility use a square image between 32 and 128 pixels.
369 pub source: ImageSource,
370 /// Pixel in the source image that is the exact mouse position.
371 ///
372 /// This value is ignored if the image source format already has hotspot information.
373 pub hotspot: Point,
374
375 /// Icon to use if the image cannot be displayed.
376 pub fallback: CursorIcon,
377}
378#[cfg(feature = "image")]
379impl_from_and_into_var! {
380 fn from(img: CursorImg) -> Option<CursorImg>;
381}
382
383/// Window cursor source.
384#[derive(Debug, Clone, PartialEq)]
385pub enum CursorSource {
386 /// Platform dependent named cursor icon.
387 Icon(CursorIcon),
388 /// Custom cursor image, with fallback.
389 #[cfg(feature = "image")]
390 Img(CursorImg),
391 /// Don't show cursor.
392 Hidden,
393}
394impl CursorSource {
395 /// Get the icon, image fallback or `None` if is hidden.
396 pub fn icon(&self) -> Option<CursorIcon> {
397 match self {
398 CursorSource::Icon(ico) => Some(*ico),
399 #[cfg(feature = "image")]
400 CursorSource::Img(img) => Some(img.fallback),
401 CursorSource::Hidden => None,
402 }
403 }
404
405 /// Custom icon image source.
406 #[cfg(feature = "image")]
407 pub fn img(&self) -> Option<&ImageSource> {
408 match self {
409 CursorSource::Img(img) => Some(&img.source),
410 _ => None,
411 }
412 }
413
414 /// Custom icon image click point, when the image data does not contain a hotspot.
415 #[cfg(feature = "image")]
416 pub fn hotspot(&self) -> Option<&Point> {
417 match self {
418 CursorSource::Img(img) => Some(&img.hotspot),
419 _ => None,
420 }
421 }
422}
423#[cfg(feature = "image")]
424impl_from_and_into_var! {
425 fn from(img: CursorImg) -> CursorSource {
426 CursorSource::Img(img)
427 }
428}
429impl_from_and_into_var! {
430 fn from(icon: CursorIcon) -> CursorSource {
431 CursorSource::Icon(icon)
432 }
433
434 /// Converts `true` to `CursorIcon::Default` and `false` to `CursorSource::Hidden`.
435 fn from(default_icon_or_hidden: bool) -> CursorSource {
436 if default_icon_or_hidden {
437 CursorIcon::Default.into()
438 } else {
439 CursorSource::Hidden
440 }
441 }
442}
443
444/// Frame image capture mode in a window.
445///
446/// You can set the capture mode using [`WindowVars::frame_capture_mode`].
447///
448/// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
449#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
450#[cfg(feature = "image")]
451pub enum FrameCaptureMode {
452 /// Frames are not automatically captured, but you can
453 /// use [`WINDOWS.frame_image`] to capture frames.
454 ///
455 /// [`WINDOWS.frame_image`]: crate::WINDOWS::frame_image
456 Sporadic,
457 /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
458 /// as a full BGRA8 image.
459 ///
460 /// After the frame is captured the mode changes to `Sporadic`.
461 Next,
462 /// The next rendered frame will be captured and available in [`FrameImageReadyArgs::frame_image`]
463 /// as an A8 mask image.
464 ///
465 /// After the frame is captured the mode changes to `Sporadic`.
466 NextMask(ImageMaskMode),
467 /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
468 /// as full BGRA8 images.
469 All,
470 /// All subsequent frames rendered will be captured and available in [`FrameImageReadyArgs::frame_image`]
471 /// as A8 mask images.
472 AllMask(ImageMaskMode),
473}
474#[cfg(feature = "image")]
475impl Default for FrameCaptureMode {
476 /// [`Sporadic`]: FrameCaptureMode::Sporadic
477 fn default() -> Self {
478 Self::Sporadic
479 }
480}
481
482event_args! {
483 /// [`WINDOW_OPEN_EVENT`] args.
484 pub struct WindowOpenArgs {
485 /// Id of window that has opened.
486 pub window_id: WindowId,
487
488 ..
489
490 /// Broadcast to all widgets.
491 fn is_in_target(&self, _id: WidgetId) -> bool {
492 true
493 }
494 }
495
496 /// [`WINDOW_CLOSE_EVENT`] args.
497 pub struct WindowCloseArgs {
498 /// IDs of windows that have closed.
499 ///
500 /// This is at least one window, is multiple if the close operation was requested as group.
501 pub windows: IdSet<WindowId>,
502
503 ..
504
505 /// Broadcast to all widgets.
506 fn is_in_target(&self, _id: WidgetId) -> bool {
507 true
508 }
509 }
510
511 /// [`WINDOW_CHANGED_EVENT`] args.
512 pub struct WindowChangedArgs {
513 /// Window that has moved, resized or has a state change.
514 pub window_id: WindowId,
515
516 /// Window state change, if it has changed the values are `(prev, new)` states.
517 pub state: Option<(WindowState, WindowState)>,
518
519 /// New window position if it was moved.
520 ///
521 /// The values are `(global_position, actual_position)` where the global position is
522 /// in the virtual space that encompasses all monitors and actual position is in the monitor space.
523 pub position: Option<(PxPoint, DipPoint)>,
524
525 /// New window size if it was resized.
526 pub size: Option<DipSize>,
527
528 /// If the app or operating system caused the change.
529 pub cause: EventCause,
530
531 ..
532
533 /// Broadcast to all widgets.
534 fn is_in_target(&self, _id: WidgetId) -> bool {
535 true
536 }
537 }
538
539 /// [`WINDOW_FOCUS_CHANGED_EVENT`] args.
540 pub struct WindowFocusChangedArgs {
541 /// Previously focused window.
542 pub prev_focus: Option<WindowId>,
543
544 /// Newly focused window.
545 pub new_focus: Option<WindowId>,
546
547 /// If the focus changed because the previously focused window closed.
548 pub closed: bool,
549
550 ..
551
552 /// Broadcast to all widgets.
553 fn is_in_target(&self, _id: WidgetId) -> bool {
554 true
555 }
556 }
557
558 /// [`WINDOW_CLOSE_REQUESTED_EVENT`] args.
559 ///
560 /// Requesting `propagation().stop()` on this event cancels the window close.
561 pub struct WindowCloseRequestedArgs {
562 /// Windows closing, headed and headless.
563 ///
564 /// This is at least one window, is multiple if the close operation was requested as group, cancelling the request
565 /// cancels close for all windows.
566 pub windows: IdSet<WindowId>,
567
568 ..
569
570 /// Broadcast to all widgets.
571 fn is_in_target(&self, _id: WidgetId) -> bool {
572 true
573 }
574 }
575}
576#[cfg(feature = "image")]
577event_args! {
578 /// [`FRAME_IMAGE_READY_EVENT`] args.
579 pub struct FrameImageReadyArgs {
580 /// Window ID.
581 pub window_id: WindowId,
582
583 /// Frame that finished rendering.
584 ///
585 /// This is *probably* the ID of frame pixels if they are requested immediately.
586 pub frame_id: FrameId,
587
588 /// The frame pixels.
589 ///
590 /// See [`WindowVars::frame_capture_mode`] for more details. The image reference will hold only for the hooks, you must upgrade it
591 /// to keep it, this is done to avoid retaining the image in the last event.
592 ///
593 /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
594 pub frame_image: WeakVarEq<ImageEntry>,
595
596 ..
597
598 /// Broadcast to all widgets.
599 fn is_in_target(&self, _id: WidgetId) -> bool {
600 true
601 }
602 }
603}
604impl WindowChangedArgs {
605 /// Returns `true` if this event represents a window state change.
606 pub fn is_state_changed(&self) -> bool {
607 self.state.is_some()
608 }
609
610 /// Returns the previous window state if it has changed.
611 pub fn prev_state(&self) -> Option<WindowState> {
612 self.state.map(|(p, _)| p)
613 }
614
615 /// Returns the new window state if it has changed.
616 pub fn new_state(&self) -> Option<WindowState> {
617 self.state.map(|(_, n)| n)
618 }
619
620 /// Returns `true` if [`new_state`] is `state` and [`prev_state`] is not.
621 ///
622 /// [`new_state`]: Self::new_state
623 /// [`prev_state`]: Self::prev_state
624 pub fn entered_state(&self, state: WindowState) -> bool {
625 if let Some((prev, new)) = self.state {
626 prev != state && new == state
627 } else {
628 false
629 }
630 }
631
632 /// Returns `true` if [`prev_state`] is `state` and [`new_state`] is not.
633 ///
634 /// [`new_state`]: Self::new_state
635 /// [`prev_state`]: Self::prev_state
636 pub fn exited_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 [`new_state`] is one of the fullscreen states and [`prev_state`] is not.
645 ///
646 /// [`new_state`]: Self::new_state
647 /// [`prev_state`]: Self::prev_state
648 pub fn entered_fullscreen(&self) -> bool {
649 if let Some((prev, new)) = self.state {
650 !prev.is_fullscreen() && new.is_fullscreen()
651 } else {
652 false
653 }
654 }
655
656 /// Returns `true` if [`prev_state`] is one of the fullscreen states and [`new_state`] is not.
657 ///
658 /// [`new_state`]: Self::new_state
659 /// [`prev_state`]: Self::prev_state
660 pub fn exited_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 this event represents a window move.
669 pub fn is_moved(&self) -> bool {
670 self.position.is_some()
671 }
672
673 /// Returns `true` if this event represents a window resize.
674 pub fn is_resized(&self) -> bool {
675 self.size.is_some()
676 }
677}
678impl WindowFocusChangedArgs {
679 /// If `window_id` got focus.
680 pub fn is_focus(&self, window_id: WindowId) -> bool {
681 self.new_focus == Some(window_id)
682 }
683
684 /// If `window_id` lost focus.
685 pub fn is_blur(&self, window_id: WindowId) -> bool {
686 self.prev_focus == Some(window_id)
687 }
688
689 /// If `window_id` lost focus because it was closed.
690 pub fn is_close(&self, window_id: WindowId) -> bool {
691 self.closed && self.is_blur(window_id)
692 }
693
694 /// Gets the previous focused window if it was closed.
695 pub fn closed(&self) -> Option<WindowId> {
696 if self.closed { self.prev_focus } else { None }
697 }
698}
699impl WindowCloseRequestedArgs {
700 /// Gets only headed windows that will close.
701 pub fn headed(&self) -> impl Iterator<Item = WindowId> + '_ {
702 self.windows
703 .iter()
704 .copied()
705 .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headed()).unwrap_or(false))
706 }
707
708 /// Gets only headless windows that will close.
709 pub fn headless(&self) -> impl Iterator<Item = WindowId> + '_ {
710 self.windows
711 .iter()
712 .copied()
713 .filter(|&id| WINDOWS.mode(id).map(|m| m.is_headless()).unwrap_or(false))
714 }
715}
716
717event! {
718 /// Window moved, resized or other state changed.
719 ///
720 /// This event aggregates events moves, resizes and other state changes into a
721 /// single event to simplify tracking composite changes, for example, the window changes size and position
722 /// when maximized, this can be trivially observed with this event.
723 pub static WINDOW_CHANGED_EVENT: WindowChangedArgs;
724
725 /// New window has inited.
726 pub static WINDOW_OPEN_EVENT: WindowOpenArgs;
727
728 /// Window instance state has changed to [`WindowInstanceState::Loaded`].
729 ///
730 /// If the window has renderer this event notifies only after the window is loaded with renderer.
731 pub static WINDOW_LOAD_EVENT: WindowOpenArgs;
732
733 /// Window focus/blur event.
734 pub static WINDOW_FOCUS_CHANGED_EVENT: WindowFocusChangedArgs;
735
736 /// Window close requested event.
737 ///
738 /// Calling `propagation().stop()` on this event cancels the window close.
739 pub static WINDOW_CLOSE_REQUESTED_EVENT: WindowCloseRequestedArgs;
740
741 /// Window closed event.
742 ///
743 /// The closed windows deinit after this event notifies, so the window content can subscribe to it.
744 pub static WINDOW_CLOSE_EVENT: WindowCloseArgs;
745}
746#[cfg(feature = "image")]
747event! {
748 /// A window frame has finished rendering and frame pixels where copied due to [`WindowVars::frame_capture_mode`].
749 ///
750 /// [`WindowVars::frame_capture_mode`]: crate::WindowVars::frame_capture_mode
751 pub static FRAME_IMAGE_READY_EVENT: FrameImageReadyArgs;
752}
753
754/// Response message of [`close`] and [`close_together`].
755///
756/// [`close`]: crate::WINDOWS::close
757/// [`close_together`]: crate::WINDOWS::close_together
758#[derive(Clone, Copy, Debug, Eq, PartialEq)]
759pub enum CloseWindowResult {
760 /// Operation completed, all requested windows closed.
761 Closed,
762
763 /// Operation canceled, no window closed.
764 Cancel,
765}
766
767/// Represents a handle that stops the window from loading while the handle is alive.
768///
769/// A handle can be retrieved using [`WINDOWS.loading_handle`] or [`WINDOW.loading_handle`], the window does not
770/// open until all handles expire or are dropped.
771///
772/// [`WINDOWS.loading_handle`]: WINDOWS::loading_handle
773/// [`WINDOW.loading_handle`]: WINDOW::loading_handle
774#[derive(Clone, Debug)]
775#[must_use = "the window does not await loading if the handle is dropped"]
776pub struct WindowLoadingHandle(pub(crate) DeadlineVar);
777impl WindowLoadingHandle {
778 /// Handle expiration deadline.
779 pub fn deadline(&self) -> Deadline {
780 self.0.get()
781 }
782}
783
784/// Error calling a view-process API extension associated with a window or renderer.
785#[derive(Debug)]
786#[non_exhaustive]
787pub enum ViewExtensionError {
788 /// Window is not open in the `WINDOWS` service.
789 WindowNotFound(WindowId),
790 /// Window must be headed to call window extensions.
791 WindowNotHeaded(WindowId),
792 /// Window is not open in the view-process.
793 ///
794 /// If the window is headless without renderer it will never open in view-process, if the window is headed
795 /// headless with renderer the window opens in the view-process after the first layout.
796 NotOpenInViewProcess(WindowId),
797 /// Window is already open in view-process. Extensions init must be set before first layout.
798 AlreadyOpenInViewProcess(WindowId),
799 /// View-process is not running.
800 Disconnected,
801 /// Api Error.
802 Api(zng_view_api::api_extension::ApiExtensionRecvError),
803}
804impl fmt::Display for ViewExtensionError {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 match self {
807 Self::WindowNotFound(id) => write!(f, "window `{id}` not found"),
808 Self::WindowNotHeaded(id) => write!(f, "window `{id}` is not headed"),
809 Self::NotOpenInViewProcess(id) => write!(f, "window/renderer `{id}` not open in the view-process"),
810 Self::AlreadyOpenInViewProcess(id) => write!(f, "window/renderer `{id}` already open in the view-process"),
811 Self::Disconnected => fmt::Display::fmt(&DISCONNECTED_SOURCE, f),
812 Self::Api(e) => fmt::Display::fmt(e, f),
813 }
814 }
815}
816impl std::error::Error for ViewExtensionError {
817 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
818 match self {
819 Self::WindowNotFound(_) => None,
820 Self::WindowNotHeaded(_) => None,
821 Self::NotOpenInViewProcess(_) => None,
822 Self::AlreadyOpenInViewProcess(_) => None,
823 Self::Disconnected => Some(&DISCONNECTED_SOURCE),
824 Self::Api(e) => Some(e),
825 }
826 }
827}
828static DISCONNECTED_SOURCE: ChannelError = ChannelError::Disconnected { cause: None };
829
830bitflags! {
831 /// Defines what window operations can run in parallel, between windows.
832 ///
833 /// Note that this does not define parallelism inside the window, see [`WINDOWS.parallel`] for more details.
834 ///
835 /// [`WINDOWS.parallel`]: WINDOWS::parallel
836 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
837 #[serde(transparent)]
838 pub struct ParallelWin: u8 {
839 /// Windows can init, deinit, update and rebuild info in parallel.
840 const UPDATE = 0b0001;
841 /// Windows can layout in parallel.
842 const LAYOUT = 0b0100;
843 /// Windows with pending render or render update generate display lists in parallel.
844 const RENDER = 0b1000;
845 }
846}
847impl Default for ParallelWin {
848 /// Is all by default.
849 fn default() -> Self {
850 Self::all()
851 }
852}
853impl_from_and_into_var! {
854 fn from(all: bool) -> ParallelWin {
855 if all { ParallelWin::all() } else { ParallelWin::empty() }
856 }
857}
858
859/// Represents stages of a window instance lifetime.
860#[derive(Debug, PartialEq, Clone)]
861pub enum WindowInstanceState {
862 /// The new window closure is running.
863 ///
864 /// The [`WINDOWS.vars`] is available.
865 ///
866 /// [`WINDOWS.vars`]: WINDOWS::vars
867 Building,
868 /// Some [`WindowLoadingHandle`] are still held.
869 ///
870 /// The [`WINDOWS.widget_tree`] is available, the window is awaiting load to show.
871 ///
872 /// [`WINDOWS.widget_tree`]: WINDOWS::widget_tree
873 Loading,
874 /// Window is fully loaded or load handles have timeout.
875 Loaded {
876 /// If the window is headed or headless with renderer and it is represented in the view-process.
877 has_view: bool,
878 },
879 /// Window content and view has deinited and dropped.
880 ///
881 /// The [`WindowVars::instance_state`] variable will not update again.
882 ///
883 /// [`WindowVars::instance_state`]: crate::WindowVars::instance_state
884 Closed,
885}
886impl WindowInstanceState {
887 /// If matches [`Loaded`] with and without view.
888 ///
889 /// [`Loaded`]: WindowInstanceState::Loaded
890 pub fn is_loaded(&self) -> bool {
891 matches!(self, Self::Loaded { .. })
892 }
893}