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