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