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