zng_view_api/window.rs
1//! Window, surface and frame types.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zng_txt::Txt;
7
8use crate::{
9 api_extension::{ApiExtensionId, ApiExtensionPayload},
10 display_list::{DisplayList, FrameValueUpdate},
11 image::{ImageDecoded, ImageId, ImageMaskMode},
12};
13use zng_unit::{
14 Dip, DipPoint, DipRect, DipSideOffsets, DipSize, DipToPx as _, Factor, Frequency, Px, PxPoint, PxSize, PxToDip, PxTransform, Rgba,
15};
16
17crate::declare_id! {
18 /// Window ID in channel.
19 ///
20 /// In the View Process this is mapped to a system id.
21 ///
22 /// In the App Process this is an unique id that survives View crashes.
23 ///
24 /// The App Process defines the ID.
25 pub struct WindowId(_);
26
27 /// Monitor screen ID in channel.
28 ///
29 /// In the View Process this is mapped to a system id.
30 ///
31 /// In the App Process this is mapped to an unique id, but does not survived View crashes.
32 ///
33 /// The View Process defines the ID.
34 pub struct MonitorId(_);
35
36 /// Identifies a frame request for collaborative resize in [`WindowChanged`].
37 ///
38 /// The View Process defines the ID.
39 pub struct FrameWaitId(_);
40}
41
42/// Render backend preference.
43///
44/// This is mostly a trade-off between performance, power consumption and cold startup time.
45#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
46#[non_exhaustive]
47pub enum RenderMode {
48 /// Prefer the best dedicated GPU, probably the best performance after initialization, but also the
49 /// most power consumption.
50 ///
51 /// Falls back to `Integrated`, then `Software`.
52 Dedicated,
53
54 /// Prefer the integrated GPU (provided by the CPU), probably the best power consumption and good performance for most GUI applications,
55 /// this is the default value.
56 ///
57 /// Falls back to `Dedicated`, then `Software`.
58 Integrated,
59
60 /// Use a software render fallback, this has the best compatibility and best initialization time. This is probably the
61 /// best pick for one frame render tasks and small windows where the initialization time of a GPU context may not offset
62 /// the render time gains.
63 ///
64 /// If the view-process implementation has no software, falls back to `Integrated`, then `Dedicated`.
65 Software,
66}
67impl Default for RenderMode {
68 /// [`RenderMode::Integrated`].
69 fn default() -> Self {
70 RenderMode::Integrated
71 }
72}
73impl RenderMode {
74 /// Returns fallbacks that view-process implementers will try if `self` is not available.
75 pub fn fallbacks(self) -> [RenderMode; 2] {
76 use RenderMode::*;
77 match self {
78 Dedicated => [Integrated, Software],
79 Integrated => [Dedicated, Software],
80 Software => [Integrated, Dedicated],
81 }
82 }
83
84 /// Returns `self` plus [`fallbacks`].
85 ///
86 /// [`fallbacks`]: Self::fallbacks
87 pub fn with_fallbacks(self) -> [RenderMode; 3] {
88 let [f0, f1] = self.fallbacks();
89 [self, f0, f1]
90 }
91}
92
93#[cfg(feature = "var")]
94zng_var::impl_from_and_into_var! {
95 fn from(some: RenderMode) -> Option<RenderMode>;
96}
97
98/// Configuration of a new headless surface.
99///
100/// Headless surfaces are always [`capture_mode`] enabled.
101///
102/// [`capture_mode`]: WindowRequest::capture_mode
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[non_exhaustive]
105pub struct HeadlessRequest {
106 /// ID that will identify the new headless surface.
107 ///
108 /// The surface is identified by a [`WindowId`] so that some API methods
109 /// can apply to both windows or surfaces, no actual window is created.
110 pub id: WindowId,
111
112 /// Scale for the layout units in this config.
113 pub scale_factor: Factor,
114
115 /// Surface area (viewport size).
116 pub size: DipSize,
117
118 /// Render mode preference for this headless surface.
119 pub render_mode: RenderMode,
120 /// Cache compiled shaders to disk.
121 pub cache_shaders: bool,
122
123 /// Initial payload for API extensions.
124 ///
125 /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure`
126 /// with the payload.
127 pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
128}
129impl HeadlessRequest {
130 /// New request.
131 pub fn new(
132 id: WindowId,
133 scale_factor: Factor,
134 size: DipSize,
135 render_mode: RenderMode,
136 extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
137 ) -> Self {
138 Self {
139 id,
140 scale_factor,
141 size,
142 render_mode,
143 cache_shaders: true,
144 extensions,
145 }
146 }
147}
148
149/// Information about a monitor screen.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151#[non_exhaustive]
152pub struct MonitorInfo {
153 /// Readable name of the monitor.
154 pub name: Txt,
155 /// Top-left offset of the monitor region in the virtual screen, in pixels.
156 pub position: PxPoint,
157 /// Width/height of the monitor region in the virtual screen, in pixels.
158 pub size: PxSize,
159 /// The monitor scale factor.
160 pub scale_factor: Factor,
161 /// The refresh rate of this monitor in normal desktop.
162 ///
163 /// If a window is set to exclusive fullscreen use the [`VideoMode::refresh_rate`] instead.
164 pub refresh_rate: Frequency,
165
166 /// Exclusive fullscreen video modes.
167 pub video_modes: Vec<VideoMode>,
168
169 /// If could determine this monitor is the primary.
170 pub is_primary: bool,
171}
172impl MonitorInfo {
173 /// New info.
174 pub fn new(name: Txt, position: PxPoint, size: PxSize, scale_factor: Factor, video_modes: Vec<VideoMode>, is_primary: bool) -> Self {
175 Self {
176 name,
177 position,
178 size,
179 scale_factor,
180 video_modes,
181 is_primary,
182 refresh_rate: Frequency::from_hertz(60.0),
183 }
184 }
185
186 /// Returns the `size` descaled using the `scale_factor`.
187 pub fn dip_size(&self) -> DipSize {
188 self.size.to_dip(self.scale_factor)
189 }
190}
191
192/// Exclusive video mode info.
193///
194/// You can get the options for a monitor using [`MonitorInfo::video_modes`].
195///
196/// Note that actual system video mode is selected by approximation,
197/// closest `size`, then `bit_depth`, then `refresh_rate`.
198///
199/// [`MonitorInfo::video_modes`]: crate::window::MonitorInfo::video_modes
200#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
201#[non_exhaustive]
202pub struct VideoMode {
203 /// Resolution of this video mode.
204 pub size: PxSize,
205 /// The bit depth of this video mode.
206 /// This is generally 24 bits or 32 bits on modern systems,
207 /// depending on whether the alpha channel is counted or not.
208 pub bit_depth: u16,
209 /// The refresh rate of this video mode.
210 pub refresh_rate: Frequency,
211}
212impl Default for VideoMode {
213 fn default() -> Self {
214 Self::MAX
215 }
216}
217impl VideoMode {
218 /// New video mode.
219 pub fn new(size: PxSize, bit_depth: u16, refresh_rate: Frequency) -> Self {
220 Self {
221 size,
222 bit_depth,
223 refresh_rate,
224 }
225 }
226
227 /// Default value, matches with the largest size, greatest bit-depth and refresh rate.
228 pub const MAX: VideoMode = VideoMode {
229 size: PxSize::new(Px::MAX, Px::MAX),
230 bit_depth: u16::MAX,
231 refresh_rate: Frequency::from_millihertz(u64::MAX),
232 };
233}
234impl fmt::Display for VideoMode {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 if *self == Self::MAX {
237 write!(f, "MAX")
238 } else {
239 write!(
240 f,
241 "{}x{}, {}, {}",
242 self.size.width.0, self.size.height.0, self.bit_depth, self.refresh_rate
243 )
244 }
245 }
246}
247
248/// Information about a successfully opened window.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250#[non_exhaustive]
251pub struct WindowOpenData {
252 /// Window complete state.
253 pub state: WindowStateAll,
254
255 /// Monitor that contains the window, if any.
256 pub monitor: Option<MonitorId>,
257
258 /// Actual top-left offset of the window (excluding outer chrome).
259 ///
260 /// The values are the global position and the position in the monitor.
261 pub position: (PxPoint, DipPoint),
262 /// Actual dimensions of the client area of the window (excluding outer chrome).
263 pub size: DipSize,
264
265 /// Actual scale factor used for the window.
266 pub scale_factor: Factor,
267
268 /// Actual refresh rate used for the window, in millihertz.
269 pub refresh_rate: Frequency,
270
271 /// Actual render mode, can be different from the requested mode if it is not available.
272 pub render_mode: RenderMode,
273
274 /// Padding that must be applied to the window content so that it stays clear of screen obstructions
275 /// such as a camera notch cutout.
276 ///
277 /// Note that the *unsafe* area must still be rendered as it may be partially visible, just don't place nay
278 /// interactive or important content outside of this padding.
279 pub safe_padding: DipSideOffsets,
280}
281impl WindowOpenData {
282 /// New response.
283 pub fn new(
284 state: WindowStateAll,
285 monitor: Option<MonitorId>,
286 position: (PxPoint, DipPoint),
287 size: DipSize,
288 scale_factor: Factor,
289 render_mode: RenderMode,
290 safe_padding: DipSideOffsets,
291 ) -> Self {
292 Self {
293 state,
294 monitor,
295 position,
296 size,
297 scale_factor,
298 render_mode,
299 safe_padding,
300 refresh_rate: Frequency::from_hertz(60.0),
301 }
302 }
303}
304
305/// Information about a successfully opened headless surface.
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[non_exhaustive]
308pub struct HeadlessOpenData {
309 /// Actual render mode, can be different from the requested mode if it is not available.
310 pub render_mode: RenderMode,
311}
312impl HeadlessOpenData {
313 /// New response.
314 pub fn new(render_mode: RenderMode) -> Self {
315 Self { render_mode }
316 }
317}
318
319/// Represents a focus request indicator.
320#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
321#[non_exhaustive]
322pub enum FocusIndicator {
323 /// Activate critical focus request.
324 Critical,
325 /// Activate informational focus request.
326 Info,
327}
328
329/// Frame image capture request.
330#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331#[non_exhaustive]
332pub enum FrameCapture {
333 /// Don't capture the frame.
334 #[default]
335 None,
336 /// Captures a full BGRA8 image.
337 Full,
338 /// Captures an A8 mask image.
339 Mask(ImageMaskMode),
340}
341
342/// Data for rendering a new frame.
343#[derive(Debug, Clone, Serialize, Deserialize)]
344#[non_exhaustive]
345pub struct FrameRequest {
346 /// ID of the new frame.
347 pub id: FrameId,
348
349 /// Frame clear color.
350 pub clear_color: Rgba,
351
352 /// Display list.
353 pub display_list: DisplayList,
354
355 /// Create an image or mask from this rendered frame.
356 ///
357 /// The [`Event::FrameRendered`] will have the frame image.
358 ///
359 /// [`Event::FrameRendered`]: crate::Event::FrameRendered
360 pub capture: FrameCapture,
361
362 /// Identifies this frame as the response to the [`WindowChanged`] resized frame request.
363 pub wait_id: Option<FrameWaitId>,
364}
365impl FrameRequest {
366 /// New request.
367 pub fn new(id: FrameId, clear_color: Rgba, display_list: DisplayList, capture: FrameCapture, wait_id: Option<FrameWaitId>) -> Self {
368 Self {
369 id,
370 clear_color,
371 display_list,
372 capture,
373 wait_id,
374 }
375 }
376}
377
378/// Data for rendering a new frame that is derived from the current frame.
379#[derive(Clone, Serialize, Deserialize)]
380#[non_exhaustive]
381pub struct FrameUpdateRequest {
382 /// ID of the new frame.
383 pub id: FrameId,
384
385 /// Bound transforms.
386 pub transforms: Vec<FrameValueUpdate<PxTransform>>,
387 /// Bound floats.
388 pub floats: Vec<FrameValueUpdate<f32>>,
389 /// Bound colors.
390 pub colors: Vec<FrameValueUpdate<Rgba>>,
391
392 /// New clear color.
393 pub clear_color: Option<Rgba>,
394
395 /// Create an image or mask from this rendered frame.
396 ///
397 /// The [`Event::FrameRendered`] will have the image.
398 ///
399 /// [`Event::FrameRendered`]: crate::Event::FrameRendered
400 pub capture: FrameCapture,
401
402 /// Identifies this frame as the response to the [`WindowChanged`] resized frame request.
403 pub wait_id: Option<FrameWaitId>,
404
405 /// Update payload for API extensions.
406 ///
407 /// The `zng-view` crate implements this by calling `DisplayListExtension::update` with the payload.
408 pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
409}
410impl FrameUpdateRequest {
411 /// New request.
412 #[allow(clippy::too_many_arguments)] // already grouping stuff>
413 pub fn new(
414 id: FrameId,
415 transforms: Vec<FrameValueUpdate<PxTransform>>,
416 floats: Vec<FrameValueUpdate<f32>>,
417 colors: Vec<FrameValueUpdate<Rgba>>,
418 clear_color: Option<Rgba>,
419 capture: FrameCapture,
420 wait_id: Option<FrameWaitId>,
421 extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
422 ) -> Self {
423 Self {
424 id,
425 transforms,
426 floats,
427 colors,
428 extensions,
429 clear_color,
430 capture,
431 wait_id,
432 }
433 }
434
435 /// A request that does nothing, apart from re-rendering the frame.
436 pub fn empty(id: FrameId) -> FrameUpdateRequest {
437 FrameUpdateRequest {
438 id,
439 transforms: vec![],
440 floats: vec![],
441 colors: vec![],
442 extensions: vec![],
443 clear_color: None,
444 capture: FrameCapture::None,
445 wait_id: None,
446 }
447 }
448
449 /// If some property updates are requested.
450 pub fn has_bounds(&self) -> bool {
451 !(self.transforms.is_empty() && self.floats.is_empty() && self.colors.is_empty())
452 }
453
454 /// If this request does not do anything, apart from notifying
455 /// a new frame if send to the renderer.
456 pub fn is_empty(&self) -> bool {
457 !self.has_bounds() && self.extensions.is_empty() && self.clear_color.is_none() && self.capture != FrameCapture::None
458 }
459}
460impl fmt::Debug for FrameUpdateRequest {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 f.debug_struct("FrameUpdateRequest")
463 .field("id", &self.id)
464 .field("transforms", &self.transforms)
465 .field("floats", &self.floats)
466 .field("colors", &self.colors)
467 .field("clear_color", &self.clear_color)
468 .field("capture", &self.capture)
469 .finish()
470 }
471}
472
473/// Configuration of a new window.
474#[derive(Debug, Clone, Serialize, Deserialize)]
475#[non_exhaustive]
476pub struct WindowRequest {
477 /// ID that will identify the new window.
478 pub id: WindowId,
479 /// Title text.
480 pub title: Txt,
481
482 /// Window state, position, size and restore rectangle.
483 pub state: WindowStateAll,
484
485 /// Lock-in kiosk mode.
486 ///
487 /// If `true` the app-process will only set fullscreen states, never hide or minimize the window, never
488 /// make the window chrome visible and only request an opaque window. The view-process implementer is expected
489 /// to also never exit the fullscreen state, even temporally.
490 ///
491 /// The app-process does not expect the view-process to configure the operating system to run in kiosk mode, but
492 /// if possible to detect the view-process can assert that it is running in kiosk mode, logging an error if the assert fails.
493 pub kiosk: bool,
494
495 /// If the initial position should be provided the operating system,
496 /// if this is not possible the `state.restore_rect.origin` is used.
497 pub default_position: bool,
498
499 /// Video mode used when the window is in exclusive state.
500 pub video_mode: VideoMode,
501
502 /// Window visibility.
503 pub visible: bool,
504 /// Window taskbar icon visibility.
505 pub taskbar_visible: bool,
506 /// If the window is "top-most".
507 pub always_on_top: bool,
508 /// If the user can move the window.
509 pub movable: bool,
510 /// If the user can resize the window.
511 pub resizable: bool,
512 /// Window icon.
513 pub icon: Option<ImageId>,
514 /// Window cursor icon and visibility.
515 pub cursor: Option<CursorIcon>,
516 /// Window custom cursor with hotspot.
517 pub cursor_image: Option<(ImageId, PxPoint)>,
518 /// If the window is see-through in pixels that are not fully opaque.
519 pub transparent: bool,
520
521 /// If all or most frames will be *screen captured*.
522 ///
523 /// If `false` all resources for capturing frame images
524 /// are discarded after each screenshot request.
525 pub capture_mode: bool,
526
527 /// Render mode preference for this window.
528 pub render_mode: RenderMode,
529 /// Cache compiled shaders to disk.
530 pub cache_shaders: bool,
531
532 /// Focus request indicator on init.
533 pub focus_indicator: Option<FocusIndicator>,
534
535 /// Ensures the window is focused after open, if not set the initial focus is decided by
536 /// the windows manager, usually focusing the new window only if the process that causes the window has focus.
537 pub focus: bool,
538
539 /// IME cursor area, if IME is enabled.
540 pub ime_area: Option<DipRect>,
541
542 /// Enabled window chrome buttons.
543 pub enabled_buttons: WindowButton,
544
545 /// System shutdown warning associated with the window.
546 pub system_shutdown_warn: Txt,
547
548 /// Initial payload for API extensions.
549 ///
550 /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure` with the payload.
551 pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
552}
553impl WindowRequest {
554 /// New request.
555 #[allow(clippy::too_many_arguments)]
556 pub fn new(
557 id: WindowId,
558 title: Txt,
559 state: WindowStateAll,
560 kiosk: bool,
561 default_position: bool,
562 video_mode: VideoMode,
563 visible: bool,
564 taskbar_visible: bool,
565 always_on_top: bool,
566 movable: bool,
567 resizable: bool,
568 icon: Option<ImageId>,
569 cursor: Option<CursorIcon>,
570 cursor_image: Option<(ImageId, PxPoint)>,
571 transparent: bool,
572 capture_mode: bool,
573 render_mode: RenderMode,
574 focus_indicator: Option<FocusIndicator>,
575 focus: bool,
576 ime_area: Option<DipRect>,
577 enabled_buttons: WindowButton,
578 system_shutdown_warn: Txt,
579 extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
580 ) -> Self {
581 Self {
582 id,
583 title,
584 state,
585 kiosk,
586 default_position,
587 video_mode,
588 visible,
589 taskbar_visible,
590 always_on_top,
591 movable,
592 resizable,
593 icon,
594 cursor,
595 cursor_image,
596 transparent,
597 capture_mode,
598 render_mode,
599 cache_shaders: true,
600 focus_indicator,
601 focus,
602 extensions,
603 ime_area,
604 enabled_buttons,
605 system_shutdown_warn,
606 }
607 }
608
609 /// Corrects invalid values if [`kiosk`] is `true`.
610 ///
611 /// An error is logged for each invalid value.
612 ///
613 /// [`kiosk`]: Self::kiosk
614 pub fn enforce_kiosk(&mut self) {
615 if self.kiosk {
616 if !self.state.state.is_fullscreen() {
617 tracing::error!("window in `kiosk` mode did not request fullscreen");
618 self.state.state = WindowState::Exclusive;
619 }
620 if self.state.chrome_visible {
621 tracing::error!("window in `kiosk` mode request chrome");
622 self.state.chrome_visible = false;
623 }
624 if !self.visible {
625 tracing::error!("window in `kiosk` mode can only be visible");
626 self.visible = true;
627 }
628 }
629 }
630}
631
632/// Represents the properties of a window that affect its position, size and state.
633#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
634#[non_exhaustive]
635pub struct WindowStateAll {
636 /// The window state.
637 pub state: WindowState,
638
639 /// Position across monitors.
640 ///
641 /// This is mostly used to find a monitor to resolve the `restore_rect` in.
642 pub global_position: PxPoint,
643
644 /// Position and size of the window in the `Normal` state.
645 ///
646 /// The position is relative to the monitor.
647 pub restore_rect: DipRect,
648
649 /// What state the window goes too when "restored".
650 ///
651 /// The *restore* state that the window must be set to be restored, if the [current state] is [`Maximized`], [`Fullscreen`] or [`Exclusive`]
652 /// the restore state is [`Normal`], if the [current state] is [`Minimized`] the restore state is the previous state.
653 ///
654 /// When the restore state is [`Normal`] the [`restore_rect`] defines the window position and size.
655 ///
656 ///
657 /// [current state]: Self::state
658 /// [`Maximized`]: WindowState::Maximized
659 /// [`Fullscreen`]: WindowState::Fullscreen
660 /// [`Exclusive`]: WindowState::Exclusive
661 /// [`Normal`]: WindowState::Normal
662 /// [`Minimized`]: WindowState::Minimized
663 /// [`restore_rect`]: Self::restore_rect
664 pub restore_state: WindowState,
665
666 /// Minimal `Normal` size allowed.
667 pub min_size: DipSize,
668 /// Maximum `Normal` size allowed.
669 pub max_size: DipSize,
670
671 /// If the system provided outer-border and title-bar is visible.
672 ///
673 /// This is also called the "decoration" or "chrome" of the window.
674 pub chrome_visible: bool,
675}
676impl WindowStateAll {
677 /// New state.
678 pub fn new(
679 state: WindowState,
680 global_position: PxPoint,
681 restore_rect: DipRect,
682 restore_state: WindowState,
683 min_size: DipSize,
684 max_size: DipSize,
685 chrome_visible: bool,
686 ) -> Self {
687 Self {
688 state,
689 global_position,
690 restore_rect,
691 restore_state,
692 min_size,
693 max_size,
694 chrome_visible,
695 }
696 }
697
698 /// Clamp the `restore_rect.size` to `min_size` and `max_size`.
699 pub fn clamp_size(&mut self) {
700 self.restore_rect.size = self.restore_rect.size.min(self.max_size).max(self.min_size)
701 }
702
703 /// Compute a value for [`restore_state`] given the previous [`state`] in `self` and the `new_state` and update the [`state`].
704 ///
705 /// [`restore_state`]: Self::restore_state
706 /// [`state`]: Self::state
707 pub fn set_state(&mut self, new_state: WindowState) {
708 self.restore_state = Self::compute_restore_state(self.restore_state, self.state, new_state);
709 self.state = new_state;
710 }
711
712 /// Compute a value for [`restore_state`] given the previous `prev_state` and the new [`state`] in `self`.
713 ///
714 /// [`restore_state`]: Self::restore_state
715 /// [`state`]: Self::state
716 pub fn set_restore_state_from(&mut self, prev_state: WindowState) {
717 self.restore_state = Self::compute_restore_state(self.restore_state, prev_state, self.state);
718 }
719
720 fn compute_restore_state(restore_state: WindowState, prev_state: WindowState, new_state: WindowState) -> WindowState {
721 if new_state == WindowState::Minimized {
722 // restore to previous state from minimized.
723 if prev_state != WindowState::Minimized {
724 prev_state
725 } else {
726 WindowState::Normal
727 }
728 } else if new_state.is_fullscreen() && !prev_state.is_fullscreen() {
729 // restore to maximized or normal from fullscreen.
730 if prev_state == WindowState::Maximized {
731 WindowState::Maximized
732 } else {
733 WindowState::Normal
734 }
735 } else if new_state == WindowState::Maximized {
736 WindowState::Normal
737 } else {
738 // Fullscreen to/from Exclusive keeps the previous restore_state.
739 restore_state
740 }
741 }
742}
743
744/// Named system dependent cursor icon.
745#[non_exhaustive]
746#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
747pub enum CursorIcon {
748 /// The platform-dependent default cursor. Often rendered as arrow.
749 #[default]
750 Default,
751
752 /// A context menu is available for the object under the cursor. Often
753 /// rendered as an arrow with a small menu-like graphic next to it.
754 ContextMenu,
755
756 /// Help is available for the object under the cursor. Often rendered as a
757 /// question mark or a balloon.
758 Help,
759
760 /// The cursor is a pointer that indicates a link. Often rendered as the
761 /// backside of a hand with the index finger extended.
762 Pointer,
763
764 /// A progress indicator. The program is performing some processing, but is
765 /// different from [`CursorIcon::Wait`] in that the user may still interact
766 /// with the program.
767 Progress,
768
769 /// Indicates that the program is busy and the user should wait. Often
770 /// rendered as a watch or hourglass.
771 Wait,
772
773 /// Indicates that a cell or set of cells may be selected. Often rendered as
774 /// a thick plus-sign with a dot in the middle.
775 Cell,
776
777 /// A simple crosshair (e.g., short line segments resembling a "+" sign).
778 /// Often used to indicate a two dimensional bitmap selection mode.
779 Crosshair,
780
781 /// Indicates text that may be selected. Often rendered as an I-beam.
782 Text,
783
784 /// Indicates vertical-text that may be selected. Often rendered as a
785 /// horizontal I-beam.
786 VerticalText,
787
788 /// Indicates an alias of/shortcut to something is to be created. Often
789 /// rendered as an arrow with a small curved arrow next to it.
790 Alias,
791
792 /// Indicates something is to be copied. Often rendered as an arrow with a
793 /// small plus sign next to it.
794 Copy,
795
796 /// Indicates something is to be moved.
797 Move,
798
799 /// Indicates that the dragged item cannot be dropped at the current cursor
800 /// location. Often rendered as a hand or pointer with a small circle with a
801 /// line through it.
802 NoDrop,
803
804 /// Indicates that the requested action will not be carried out. Often
805 /// rendered as a circle with a line through it.
806 NotAllowed,
807
808 /// Indicates that something can be grabbed (dragged to be moved). Often
809 /// rendered as the backside of an open hand.
810 Grab,
811
812 /// Indicates that something is being grabbed (dragged to be moved). Often
813 /// rendered as the backside of a hand with fingers closed mostly out of
814 /// view.
815 Grabbing,
816
817 /// The east border to be moved.
818 EResize,
819
820 /// The north border to be moved.
821 NResize,
822
823 /// The north-east corner to be moved.
824 NeResize,
825
826 /// The north-west corner to be moved.
827 NwResize,
828
829 /// The south border to be moved.
830 SResize,
831
832 /// The south-east corner to be moved.
833 SeResize,
834
835 /// The south-west corner to be moved.
836 SwResize,
837
838 /// The west border to be moved.
839 WResize,
840
841 /// The east and west borders to be moved.
842 EwResize,
843
844 /// The south and north borders to be moved.
845 NsResize,
846
847 /// The north-east and south-west corners to be moved.
848 NeswResize,
849
850 /// The north-west and south-east corners to be moved.
851 NwseResize,
852
853 /// Indicates that the item/column can be resized horizontally. Often
854 /// rendered as arrows pointing left and right with a vertical bar
855 /// separating them.
856 ColResize,
857
858 /// Indicates that the item/row can be resized vertically. Often rendered as
859 /// arrows pointing up and down with a horizontal bar separating them.
860 RowResize,
861
862 /// Indicates that the something can be scrolled in any direction. Often
863 /// rendered as arrows pointing up, down, left, and right with a dot in the
864 /// middle.
865 AllScroll,
866
867 /// Indicates that something can be zoomed in. Often rendered as a
868 /// magnifying glass with a "+" in the center of the glass.
869 ZoomIn,
870
871 /// Indicates that something can be zoomed in. Often rendered as a
872 /// magnifying glass with a "-" in the center of the glass.
873 ZoomOut,
874}
875#[cfg(feature = "var")]
876zng_var::impl_from_and_into_var! {
877 fn from(some: CursorIcon) -> Option<CursorIcon>;
878}
879impl CursorIcon {
880 /// All cursor icons.
881 pub const ALL: &'static [CursorIcon] = {
882 use CursorIcon::*;
883 &[
884 Default,
885 ContextMenu,
886 Help,
887 Pointer,
888 Progress,
889 Wait,
890 Cell,
891 Crosshair,
892 Text,
893 VerticalText,
894 Alias,
895 Copy,
896 Move,
897 NoDrop,
898 NotAllowed,
899 Grab,
900 Grabbing,
901 EResize,
902 NResize,
903 NeResize,
904 NwResize,
905 SResize,
906 SeResize,
907 SwResize,
908 WResize,
909 EwResize,
910 NsResize,
911 NeswResize,
912 NwseResize,
913 ColResize,
914 RowResize,
915 AllScroll,
916 ZoomIn,
917 ZoomOut,
918 ]
919 };
920
921 /// Estimated icon size and click spot in that size.
922 pub fn size_and_spot(&self, scale_factor: Factor) -> (PxSize, PxPoint) {
923 fn splat(s: f32, rel_pt: f32) -> (DipSize, DipPoint) {
924 size(s, s, rel_pt, rel_pt)
925 }
926 fn size(w: f32, h: f32, rel_x: f32, rel_y: f32) -> (DipSize, DipPoint) {
927 (
928 DipSize::new(Dip::new_f32(w), Dip::new_f32(h)),
929 DipPoint::new(Dip::new_f32(w * rel_x), Dip::new_f32(h * rel_y)),
930 )
931 }
932
933 let (size, spot) = match self {
934 CursorIcon::Crosshair
935 | CursorIcon::Move
936 | CursorIcon::Wait
937 | CursorIcon::NotAllowed
938 | CursorIcon::NoDrop
939 | CursorIcon::Cell
940 | CursorIcon::Grab
941 | CursorIcon::Grabbing
942 | CursorIcon::AllScroll => splat(20.0, 0.5),
943 CursorIcon::Text | CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize => size(8.0, 20.0, 0.5, 0.5),
944 CursorIcon::VerticalText | CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize => size(20.0, 8.0, 0.5, 0.5),
945 _ => splat(20.0, 0.0),
946 };
947
948 (size.to_px(scale_factor), spot.to_px(scale_factor))
949 }
950}
951
952/// Defines a custom mouse cursor.
953#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
954#[non_exhaustive]
955pub struct CursorImage {
956 /// Cursor image.
957 pub img: ImageId,
958 /// Exact point in the image that is the mouse position.
959 ///
960 /// This value is only used if the image format does not contain a hotspot.
961 pub hotspot: PxPoint,
962}
963impl CursorImage {
964 /// New cursor.
965 pub fn new(img: ImageId, hotspot: PxPoint) -> Self {
966 Self { img, hotspot }
967 }
968}
969
970/// Defines the orientation that a window resize will be performed.
971#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
972pub enum ResizeDirection {
973 /// The east border will be moved.
974 East,
975 /// The north border will be moved.
976 North,
977 /// The north-east corner will be moved.
978 NorthEast,
979 /// The north-west corner will be moved.
980 NorthWest,
981 /// The south border will be moved.
982 South,
983 /// The south-east corner will be moved.
984 SouthEast,
985 /// The south-west corner will be moved.
986 SouthWest,
987 /// The west border will be moved.
988 West,
989}
990impl From<ResizeDirection> for CursorIcon {
991 fn from(direction: ResizeDirection) -> Self {
992 use ResizeDirection::*;
993 match direction {
994 East => CursorIcon::EResize,
995 North => CursorIcon::NResize,
996 NorthEast => CursorIcon::NeResize,
997 NorthWest => CursorIcon::NwResize,
998 South => CursorIcon::SResize,
999 SouthEast => CursorIcon::SeResize,
1000 SouthWest => CursorIcon::SwResize,
1001 West => CursorIcon::WResize,
1002 }
1003 }
1004}
1005#[cfg(feature = "var")]
1006zng_var::impl_from_and_into_var! {
1007 fn from(some: ResizeDirection) -> Option<ResizeDirection>;
1008 fn from(some: ResizeDirection) -> Option<CursorIcon> {
1009 Some(some.into())
1010 }
1011}
1012impl ResizeDirection {
1013 /// All directions.
1014 pub const ALL: &'static [ResizeDirection] = {
1015 use ResizeDirection::*;
1016 &[East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West]
1017 };
1018
1019 /// Gets if this resize represents two directions.
1020 pub const fn is_corner(self) -> bool {
1021 matches!(self, Self::NorthEast | Self::NorthWest | Self::SouthEast | Self::SouthWest)
1022 }
1023
1024 /// Gets if this resize represents a single direction.
1025 pub const fn is_border(self) -> bool {
1026 !self.is_corner()
1027 }
1028}
1029
1030/// Window state.
1031#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
1032pub enum WindowState {
1033 /// Window is visible, but does not fill the screen.
1034 #[default]
1035 Normal,
1036 /// Window is only visible as an icon in the taskbar.
1037 Minimized,
1038 /// Window fills the screen, but not the parts reserved by the system, like the taskbar.
1039 Maximized,
1040 /// Window is chromeless and completely fills the screen, including over parts reserved by the system.
1041 ///
1042 /// Also called borderless fullscreen.
1043 Fullscreen,
1044 /// Window has exclusive access to the monitor's video output, so only the window content is visible.
1045 Exclusive,
1046}
1047impl WindowState {
1048 /// Returns `true` if `self` matches [`Fullscreen`] or [`Exclusive`].
1049 ///
1050 /// [`Fullscreen`]: WindowState::Fullscreen
1051 /// [`Exclusive`]: WindowState::Exclusive
1052 pub fn is_fullscreen(self) -> bool {
1053 matches!(self, Self::Fullscreen | Self::Exclusive)
1054 }
1055}
1056
1057/// [`Event::FrameRendered`] payload.
1058///
1059/// [`Event::FrameRendered`]: crate::Event::FrameRendered
1060#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1061#[non_exhaustive]
1062pub struct EventFrameRendered {
1063 /// Window that was rendered.
1064 pub window: WindowId,
1065 /// Frame that was rendered.
1066 pub frame: FrameId,
1067 /// Frame image, if one was requested with the frame request.
1068 pub frame_image: Option<ImageDecoded>,
1069}
1070impl EventFrameRendered {
1071 /// New response.
1072 pub fn new(window: WindowId, frame: FrameId, frame_image: Option<ImageDecoded>) -> Self {
1073 Self {
1074 window,
1075 frame,
1076 frame_image,
1077 }
1078 }
1079}
1080
1081/// [`Event::WindowChanged`] payload.
1082///
1083/// [`Event::WindowChanged`]: crate::Event::WindowChanged
1084#[derive(Debug, Clone, Serialize, Deserialize)]
1085#[non_exhaustive]
1086pub struct WindowChanged {
1087 // note that this payload is handled by `Event::coalesce`, add new fields there too.
1088 //
1089 /// Window that has changed state.
1090 pub window: WindowId,
1091
1092 /// Window new state, is `None` if the window state did not change.
1093 pub state: Option<WindowStateAll>,
1094
1095 /// Window new global position, is `None` if the window position did not change.
1096 ///
1097 /// The values are the global position and the position in the monitor.
1098 pub position: Option<(PxPoint, DipPoint)>,
1099
1100 /// Window new monitor.
1101 ///
1102 /// The window's monitor change when it is moved enough so that most of the
1103 /// client area is in the new monitor screen.
1104 pub monitor: Option<MonitorId>,
1105
1106 /// New scale factor.
1107 pub scale_factor: Option<Factor>,
1108
1109 /// New refresh rate, in millihertz.
1110 pub refresh_rate: Option<Frequency>,
1111
1112 /// The window new size, is `None` if the window size did not change.
1113 pub size: Option<DipSize>,
1114
1115 /// The window new safe padding, is `None` if the did not change.
1116 pub safe_padding: Option<DipSideOffsets>,
1117
1118 /// If the view-process is blocking the event loop for a time waiting for a frame for the new `size` this
1119 /// ID must be send with the frame to signal that it is the frame for the new size.
1120 ///
1121 /// Event loop implementations can use this to resize without visible artifacts
1122 /// like the clear color flashing on the window corners, there is a timeout to this delay but it
1123 /// can be a noticeable stutter, a [`render`] or [`render_update`] request for the window unblocks the loop early
1124 /// to continue the resize operation.
1125 ///
1126 /// [`render`]: crate::Api::render
1127 /// [`render_update`]: crate::Api::render_update
1128 pub frame_wait_id: Option<FrameWaitId>,
1129
1130 /// What caused the change, end-user/OS modifying the window or the app.
1131 pub cause: EventCause,
1132}
1133impl WindowChanged {
1134 /// New response.
1135 #[allow(clippy::too_many_arguments)] // already grouping stuff>
1136 pub fn new(
1137 window: WindowId,
1138 state: Option<WindowStateAll>,
1139 position: Option<(PxPoint, DipPoint)>,
1140 monitor: Option<MonitorId>,
1141 size: Option<DipSize>,
1142 safe_padding: Option<DipSideOffsets>,
1143 frame_wait_id: Option<FrameWaitId>,
1144 cause: EventCause,
1145 ) -> Self {
1146 Self {
1147 window,
1148 state,
1149 position,
1150 monitor,
1151 size,
1152 scale_factor: None,
1153 refresh_rate: None,
1154 safe_padding,
1155 frame_wait_id,
1156 cause,
1157 }
1158 }
1159
1160 /// Create an event that represents window move.
1161 pub fn moved(window: WindowId, global_position: PxPoint, position: DipPoint, cause: EventCause) -> Self {
1162 WindowChanged {
1163 window,
1164 state: None,
1165 position: Some((global_position, position)),
1166 monitor: None,
1167 size: None,
1168 safe_padding: None,
1169 scale_factor: None,
1170 refresh_rate: None,
1171 frame_wait_id: None,
1172 cause,
1173 }
1174 }
1175
1176 /// Create an event that represents window parent monitor change.
1177 pub fn monitor_changed(window: WindowId, monitor: MonitorId, cause: EventCause) -> Self {
1178 WindowChanged {
1179 window,
1180 state: None,
1181 position: None,
1182 monitor: Some(monitor),
1183 size: None,
1184 safe_padding: None,
1185 scale_factor: None,
1186 refresh_rate: None,
1187 frame_wait_id: None,
1188 cause,
1189 }
1190 }
1191
1192 /// Create an event that represents window resized.
1193 pub fn resized(window: WindowId, size: DipSize, cause: EventCause, frame_wait_id: Option<FrameWaitId>) -> Self {
1194 WindowChanged {
1195 window,
1196 state: None,
1197 position: None,
1198 monitor: None,
1199 size: Some(size),
1200 safe_padding: None,
1201 scale_factor: None,
1202 refresh_rate: None,
1203 frame_wait_id,
1204 cause,
1205 }
1206 }
1207
1208 /// Create an event that represents [`WindowStateAll`] change.
1209 pub fn state_changed(window: WindowId, state: WindowStateAll, cause: EventCause) -> Self {
1210 WindowChanged {
1211 window,
1212 state: Some(state),
1213 position: None,
1214 monitor: None,
1215 size: None,
1216 safe_padding: None,
1217 scale_factor: None,
1218 refresh_rate: None,
1219 frame_wait_id: None,
1220 cause,
1221 }
1222 }
1223}
1224
1225/// Identifier of a frame or frame update.
1226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, bytemuck::NoUninit)]
1227#[repr(C)]
1228pub struct FrameId(u32, u32);
1229impl FrameId {
1230 /// Dummy frame ID.
1231 pub const INVALID: FrameId = FrameId(u32::MAX, u32::MAX);
1232
1233 /// Create first frame id of a window.
1234 pub fn first() -> FrameId {
1235 FrameId(0, 0)
1236 }
1237
1238 /// Create the next full frame ID after the current one.
1239 pub fn next(self) -> FrameId {
1240 let mut id = self.0.wrapping_add(1);
1241 if id == u32::MAX {
1242 id = 0;
1243 }
1244 FrameId(id, 0)
1245 }
1246
1247 /// Create the next update frame ID after the current one.
1248 pub fn next_update(self) -> FrameId {
1249 let mut id = self.1.wrapping_add(1);
1250 if id == u32::MAX {
1251 id = 0;
1252 }
1253 FrameId(self.0, id)
1254 }
1255
1256 /// Get the raw ID.
1257 pub fn get(self) -> u64 {
1258 ((self.0 as u64) << 32) | (self.1 as u64)
1259 }
1260
1261 /// Get the full frame ID.
1262 pub fn epoch(self) -> u32 {
1263 self.0
1264 }
1265
1266 /// Get the frame update ID.
1267 pub fn update(self) -> u32 {
1268 self.1
1269 }
1270}
1271
1272/// Cause of a window state change.
1273#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1274#[non_exhaustive]
1275pub enum EventCause {
1276 /// Operating system or end-user affected the window.
1277 System,
1278 /// App affected the window.
1279 App,
1280}
1281
1282bitflags::bitflags! {
1283 /// Window chrome buttons.
1284 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1285 pub struct WindowButton: u32 {
1286 /// Close button.
1287 const CLOSE = 1 << 0;
1288 /// Minimize button.
1289 const MINIMIZE = 1 << 1;
1290 /// Maximize/restore button.
1291 const MAXIMIZE = 1 << 2;
1292 }
1293}
1294
1295bitflags::bitflags! {
1296 /// Window operations the view-process implements.
1297 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1298 pub struct WindowCapability: u64 {
1299 /// Can set title text.
1300 const SET_TITLE = 1 << 0;
1301 /// Can set window visible.
1302 const SET_VISIBLE = 1 << 1;
1303 /// Can make window "topmost".
1304 const SET_ALWAYS_ON_TOP = 1 << 2;
1305 /// Can change if window can be dragged by the user.
1306 const SET_MOVABLE = 1 << 3;
1307 /// Can change if window can be resized by the user.
1308 const SET_RESIZABLE = 1 << 4;
1309 /// Can make window icon not appear on the taskbar while the window remains visible.
1310 const SET_TASKBAR_VISIBLE = 1 << 5;
1311 /// Can force window to appear in front of other apps, without focusing input.
1312 const BRING_TO_TOP = 1 << 6;
1313 /// Can set the window icon.
1314 ///
1315 /// When this is not possible the system specific application metadata icon is used for all windows of the app.
1316 const SET_ICON = 1 << 7;
1317 /// Can set the window cursor to one of the named [`CursorIcon`].
1318 const SET_CURSOR = 1 << 8;
1319 /// Can set the window cursor to a custom image.
1320 const SET_CURSOR_IMAGE = 1 << 9;
1321 /// Can set attention indicator for the window.
1322 const SET_FOCUS_INDICATOR = 1 << 10;
1323 /// Can focus input on the window.
1324 ///
1325 /// This is also true if can the system only shows an attention indicator for the window some times.
1326 const FOCUS = 1 << 11;
1327 /// Can initiate a window move operation from a mouse press in the content area.
1328 const DRAG_MOVE = 1 << 12;
1329 /// Can initiate a window resize operation from a mouse press in the content area.
1330 const DRAG_RESIZE = 1 << 13;
1331 /// Can open the system context menu that usually shows on right click on the title bar.
1332 const OPEN_TITLE_BAR_CONTEXT_MENU = 1 << 14;
1333
1334 /// If operating system provides a window chrome (title bar, resize borders).
1335 const SYSTEM_CHROME = 1 << 15;
1336
1337 /// Can minimize window.
1338 const MINIMIZE = (1 << 16);
1339 /// Can restore window to *normal*.
1340 const RESTORE = (1 << 17);
1341 /// Can maximize window.
1342 const MAXIMIZE = (1 << 18);
1343 /// Can make window fullscreen.
1344 const FULLSCREEN = (1 << 19);
1345 /// Can takeover video output to show only the window content.
1346 const EXCLUSIVE = (1 << 20);
1347
1348 /// Can toggle if the system chrome (title bar, resize border) is visible.
1349 const SET_CHROME = (1 << 21) | Self::SYSTEM_CHROME.bits();
1350 /// Can programmatically move window after it is open.
1351 const SET_POSITION = (1 << 22);
1352
1353 /// Can programmatically resize window after it is open.
1354 const SET_SIZE = (1 << 23);
1355
1356 /// Can disable close button.
1357 const DISABLE_CLOSE_BUTTON = (1 << 24);
1358 /// Can disable minimize button.
1359 const DISABLE_MINIMIZE_BUTTON = (1 << 25);
1360 /// Can disable maximize button.
1361 const DISABLE_MAXIMIZE_BUTTON = (1 << 26);
1362
1363 /// Can set a system shutdown warning/blocker associated with the window.
1364 const SET_SYSTEM_SHUTDOWN_WARN = (1 << 27);
1365
1366 /// Can set the IME area, show virtual keyboard.
1367 const SET_IME_AREA = (1 << 28);
1368 }
1369}