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