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