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, in millihertz.
207 pub refresh_rate: u32, // TODO(breaking) use Frequency unit
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: u32) -> 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: u32::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{}, {}, {}hz",
239 self.size.width.0,
240 self.size.height.0,
241 self.bit_depth,
242 (self.refresh_rate as f32 * 0.001).round()
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
530 /// Focus request indicator on init.
531 pub focus_indicator: Option<FocusIndicator>,
532
533 /// Ensures the window is focused after open, if not set the initial focus is decided by
534 /// the windows manager, usually focusing the new window only if the process that causes the window has focus.
535 pub focus: bool,
536
537 /// IME cursor area, if IME is enabled.
538 pub ime_area: Option<DipRect>,
539
540 /// Enabled window chrome buttons.
541 pub enabled_buttons: WindowButton,
542
543 /// System shutdown warning associated with the window.
544 pub system_shutdown_warn: Txt,
545
546 /// Initial payload for API extensions.
547 ///
548 /// The `zng-view` crate implements this by calling `WindowExtension::configure` and `RendererExtension::configure` with the payload.
549 pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
550}
551impl WindowRequest {
552 /// New request.
553 #[allow(clippy::too_many_arguments)]
554 pub fn new(
555 id: WindowId,
556 title: Txt,
557 state: WindowStateAll,
558 kiosk: bool,
559 default_position: bool,
560 video_mode: VideoMode,
561 visible: bool,
562 taskbar_visible: bool,
563 always_on_top: bool,
564 movable: bool,
565 resizable: bool,
566 icon: Option<ImageId>,
567 cursor: Option<CursorIcon>,
568 cursor_image: Option<(ImageId, PxPoint)>,
569 transparent: bool,
570 capture_mode: bool,
571 render_mode: RenderMode,
572 focus_indicator: Option<FocusIndicator>,
573 focus: bool,
574 ime_area: Option<DipRect>,
575 enabled_buttons: WindowButton,
576 system_shutdown_warn: Txt,
577 extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
578 ) -> Self {
579 Self {
580 id,
581 title,
582 state,
583 kiosk,
584 default_position,
585 video_mode,
586 visible,
587 taskbar_visible,
588 always_on_top,
589 movable,
590 resizable,
591 icon,
592 cursor,
593 cursor_image,
594 transparent,
595 capture_mode,
596 render_mode,
597 focus_indicator,
598 focus,
599 extensions,
600 ime_area,
601 enabled_buttons,
602 system_shutdown_warn,
603 }
604 }
605
606 /// Corrects invalid values if [`kiosk`] is `true`.
607 ///
608 /// An error is logged for each invalid value.
609 ///
610 /// [`kiosk`]: Self::kiosk
611 pub fn enforce_kiosk(&mut self) {
612 if self.kiosk {
613 if !self.state.state.is_fullscreen() {
614 tracing::error!("window in `kiosk` mode did not request fullscreen");
615 self.state.state = WindowState::Exclusive;
616 }
617 if self.state.chrome_visible {
618 tracing::error!("window in `kiosk` mode request chrome");
619 self.state.chrome_visible = false;
620 }
621 if !self.visible {
622 tracing::error!("window in `kiosk` mode can only be visible");
623 self.visible = true;
624 }
625 }
626 }
627}
628
629/// Represents the properties of a window that affect its position, size and state.
630#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
631#[non_exhaustive]
632pub struct WindowStateAll {
633 /// The window state.
634 pub state: WindowState,
635
636 /// Position across monitors.
637 ///
638 /// This is mostly used to find a monitor to resolve the `restore_rect` in.
639 pub global_position: PxPoint,
640
641 /// Position and size of the window in the `Normal` state.
642 ///
643 /// The position is relative to the monitor.
644 pub restore_rect: DipRect,
645
646 /// What state the window goes too when "restored".
647 ///
648 /// The *restore* state that the window must be set to be restored, if the [current state] is [`Maximized`], [`Fullscreen`] or [`Exclusive`]
649 /// the restore state is [`Normal`], if the [current state] is [`Minimized`] the restore state is the previous state.
650 ///
651 /// When the restore state is [`Normal`] the [`restore_rect`] defines the window position and size.
652 ///
653 ///
654 /// [current state]: Self::state
655 /// [`Maximized`]: WindowState::Maximized
656 /// [`Fullscreen`]: WindowState::Fullscreen
657 /// [`Exclusive`]: WindowState::Exclusive
658 /// [`Normal`]: WindowState::Normal
659 /// [`Minimized`]: WindowState::Minimized
660 /// [`restore_rect`]: Self::restore_rect
661 pub restore_state: WindowState,
662
663 /// Minimal `Normal` size allowed.
664 pub min_size: DipSize,
665 /// Maximum `Normal` size allowed.
666 pub max_size: DipSize,
667
668 /// If the system provided outer-border and title-bar is visible.
669 ///
670 /// This is also called the "decoration" or "chrome" of the window.
671 pub chrome_visible: bool,
672}
673impl WindowStateAll {
674 /// New state.
675 pub fn new(
676 state: WindowState,
677 global_position: PxPoint,
678 restore_rect: DipRect,
679 restore_state: WindowState,
680 min_size: DipSize,
681 max_size: DipSize,
682 chrome_visible: bool,
683 ) -> Self {
684 Self {
685 state,
686 global_position,
687 restore_rect,
688 restore_state,
689 min_size,
690 max_size,
691 chrome_visible,
692 }
693 }
694
695 /// Clamp the `restore_rect.size` to `min_size` and `max_size`.
696 pub fn clamp_size(&mut self) {
697 self.restore_rect.size = self.restore_rect.size.min(self.max_size).max(self.min_size)
698 }
699
700 /// Compute a value for [`restore_state`] given the previous [`state`] in `self` and the `new_state` and update the [`state`].
701 ///
702 /// [`restore_state`]: Self::restore_state
703 /// [`state`]: Self::state
704 pub fn set_state(&mut self, new_state: WindowState) {
705 self.restore_state = Self::compute_restore_state(self.restore_state, self.state, new_state);
706 self.state = new_state;
707 }
708
709 /// Compute a value for [`restore_state`] given the previous `prev_state` and the new [`state`] in `self`.
710 ///
711 /// [`restore_state`]: Self::restore_state
712 /// [`state`]: Self::state
713 pub fn set_restore_state_from(&mut self, prev_state: WindowState) {
714 self.restore_state = Self::compute_restore_state(self.restore_state, prev_state, self.state);
715 }
716
717 fn compute_restore_state(restore_state: WindowState, prev_state: WindowState, new_state: WindowState) -> WindowState {
718 if new_state == WindowState::Minimized {
719 // restore to previous state from minimized.
720 if prev_state != WindowState::Minimized {
721 prev_state
722 } else {
723 WindowState::Normal
724 }
725 } else if new_state.is_fullscreen() && !prev_state.is_fullscreen() {
726 // restore to maximized or normal from fullscreen.
727 if prev_state == WindowState::Maximized {
728 WindowState::Maximized
729 } else {
730 WindowState::Normal
731 }
732 } else if new_state == WindowState::Maximized {
733 WindowState::Normal
734 } else {
735 // Fullscreen to/from Exclusive keeps the previous restore_state.
736 restore_state
737 }
738 }
739}
740
741/// Named system dependent cursor icon.
742#[non_exhaustive]
743#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
744pub enum CursorIcon {
745 /// The platform-dependent default cursor. Often rendered as arrow.
746 #[default]
747 Default,
748
749 /// A context menu is available for the object under the cursor. Often
750 /// rendered as an arrow with a small menu-like graphic next to it.
751 ContextMenu,
752
753 /// Help is available for the object under the cursor. Often rendered as a
754 /// question mark or a balloon.
755 Help,
756
757 /// The cursor is a pointer that indicates a link. Often rendered as the
758 /// backside of a hand with the index finger extended.
759 Pointer,
760
761 /// A progress indicator. The program is performing some processing, but is
762 /// different from [`CursorIcon::Wait`] in that the user may still interact
763 /// with the program.
764 Progress,
765
766 /// Indicates that the program is busy and the user should wait. Often
767 /// rendered as a watch or hourglass.
768 Wait,
769
770 /// Indicates that a cell or set of cells may be selected. Often rendered as
771 /// a thick plus-sign with a dot in the middle.
772 Cell,
773
774 /// A simple crosshair (e.g., short line segments resembling a "+" sign).
775 /// Often used to indicate a two dimensional bitmap selection mode.
776 Crosshair,
777
778 /// Indicates text that may be selected. Often rendered as an I-beam.
779 Text,
780
781 /// Indicates vertical-text that may be selected. Often rendered as a
782 /// horizontal I-beam.
783 VerticalText,
784
785 /// Indicates an alias of/shortcut to something is to be created. Often
786 /// rendered as an arrow with a small curved arrow next to it.
787 Alias,
788
789 /// Indicates something is to be copied. Often rendered as an arrow with a
790 /// small plus sign next to it.
791 Copy,
792
793 /// Indicates something is to be moved.
794 Move,
795
796 /// Indicates that the dragged item cannot be dropped at the current cursor
797 /// location. Often rendered as a hand or pointer with a small circle with a
798 /// line through it.
799 NoDrop,
800
801 /// Indicates that the requested action will not be carried out. Often
802 /// rendered as a circle with a line through it.
803 NotAllowed,
804
805 /// Indicates that something can be grabbed (dragged to be moved). Often
806 /// rendered as the backside of an open hand.
807 Grab,
808
809 /// Indicates that something is being grabbed (dragged to be moved). Often
810 /// rendered as the backside of a hand with fingers closed mostly out of
811 /// view.
812 Grabbing,
813
814 /// The east border to be moved.
815 EResize,
816
817 /// The north border to be moved.
818 NResize,
819
820 /// The north-east corner to be moved.
821 NeResize,
822
823 /// The north-west corner to be moved.
824 NwResize,
825
826 /// The south border to be moved.
827 SResize,
828
829 /// The south-east corner to be moved.
830 SeResize,
831
832 /// The south-west corner to be moved.
833 SwResize,
834
835 /// The west border to be moved.
836 WResize,
837
838 /// The east and west borders to be moved.
839 EwResize,
840
841 /// The south and north borders to be moved.
842 NsResize,
843
844 /// The north-east and south-west corners to be moved.
845 NeswResize,
846
847 /// The north-west and south-east corners to be moved.
848 NwseResize,
849
850 /// Indicates that the item/column can be resized horizontally. Often
851 /// rendered as arrows pointing left and right with a vertical bar
852 /// separating them.
853 ColResize,
854
855 /// Indicates that the item/row can be resized vertically. Often rendered as
856 /// arrows pointing up and down with a horizontal bar separating them.
857 RowResize,
858
859 /// Indicates that the something can be scrolled in any direction. Often
860 /// rendered as arrows pointing up, down, left, and right with a dot in the
861 /// middle.
862 AllScroll,
863
864 /// Indicates that something can be zoomed in. Often rendered as a
865 /// magnifying glass with a "+" in the center of the glass.
866 ZoomIn,
867
868 /// Indicates that something can be zoomed in. Often rendered as a
869 /// magnifying glass with a "-" in the center of the glass.
870 ZoomOut,
871}
872#[cfg(feature = "var")]
873zng_var::impl_from_and_into_var! {
874 fn from(some: CursorIcon) -> Option<CursorIcon>;
875}
876impl CursorIcon {
877 /// All cursor icons.
878 pub const ALL: &'static [CursorIcon] = {
879 use CursorIcon::*;
880 &[
881 Default,
882 ContextMenu,
883 Help,
884 Pointer,
885 Progress,
886 Wait,
887 Cell,
888 Crosshair,
889 Text,
890 VerticalText,
891 Alias,
892 Copy,
893 Move,
894 NoDrop,
895 NotAllowed,
896 Grab,
897 Grabbing,
898 EResize,
899 NResize,
900 NeResize,
901 NwResize,
902 SResize,
903 SeResize,
904 SwResize,
905 WResize,
906 EwResize,
907 NsResize,
908 NeswResize,
909 NwseResize,
910 ColResize,
911 RowResize,
912 AllScroll,
913 ZoomIn,
914 ZoomOut,
915 ]
916 };
917
918 /// Estimated icon size and click spot in that size.
919 pub fn size_and_spot(&self, scale_factor: Factor) -> (PxSize, PxPoint) {
920 fn splat(s: f32, rel_pt: f32) -> (DipSize, DipPoint) {
921 size(s, s, rel_pt, rel_pt)
922 }
923 fn size(w: f32, h: f32, rel_x: f32, rel_y: f32) -> (DipSize, DipPoint) {
924 (
925 DipSize::new(Dip::new_f32(w), Dip::new_f32(h)),
926 DipPoint::new(Dip::new_f32(w * rel_x), Dip::new_f32(h * rel_y)),
927 )
928 }
929
930 let (size, spot) = match self {
931 CursorIcon::Crosshair
932 | CursorIcon::Move
933 | CursorIcon::Wait
934 | CursorIcon::NotAllowed
935 | CursorIcon::NoDrop
936 | CursorIcon::Cell
937 | CursorIcon::Grab
938 | CursorIcon::Grabbing
939 | CursorIcon::AllScroll => splat(20.0, 0.5),
940 CursorIcon::Text | CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize => size(8.0, 20.0, 0.5, 0.5),
941 CursorIcon::VerticalText | CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize => size(20.0, 8.0, 0.5, 0.5),
942 _ => splat(20.0, 0.0),
943 };
944
945 (size.to_px(scale_factor), spot.to_px(scale_factor))
946 }
947}
948
949/// Defines a custom mouse cursor.
950#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
951#[non_exhaustive]
952pub struct CursorImage {
953 /// Cursor image.
954 pub img: ImageId,
955 /// Exact point in the image that is the mouse position.
956 ///
957 /// This value is only used if the image format does not contain a hotspot.
958 pub hotspot: PxPoint,
959}
960impl CursorImage {
961 /// New cursor.
962 pub fn new(img: ImageId, hotspot: PxPoint) -> Self {
963 Self { img, hotspot }
964 }
965}
966
967/// Defines the orientation that a window resize will be performed.
968#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
969pub enum ResizeDirection {
970 /// The east border will be moved.
971 East,
972 /// The north border will be moved.
973 North,
974 /// The north-east corner will be moved.
975 NorthEast,
976 /// The north-west corner will be moved.
977 NorthWest,
978 /// The south border will be moved.
979 South,
980 /// The south-east corner will be moved.
981 SouthEast,
982 /// The south-west corner will be moved.
983 SouthWest,
984 /// The west border will be moved.
985 West,
986}
987impl From<ResizeDirection> for CursorIcon {
988 fn from(direction: ResizeDirection) -> Self {
989 use ResizeDirection::*;
990 match direction {
991 East => CursorIcon::EResize,
992 North => CursorIcon::NResize,
993 NorthEast => CursorIcon::NeResize,
994 NorthWest => CursorIcon::NwResize,
995 South => CursorIcon::SResize,
996 SouthEast => CursorIcon::SeResize,
997 SouthWest => CursorIcon::SwResize,
998 West => CursorIcon::WResize,
999 }
1000 }
1001}
1002#[cfg(feature = "var")]
1003zng_var::impl_from_and_into_var! {
1004 fn from(some: ResizeDirection) -> Option<ResizeDirection>;
1005 fn from(some: ResizeDirection) -> Option<CursorIcon> {
1006 Some(some.into())
1007 }
1008}
1009impl ResizeDirection {
1010 /// All directions.
1011 pub const ALL: &'static [ResizeDirection] = {
1012 use ResizeDirection::*;
1013 &[East, North, NorthEast, NorthWest, South, SouthEast, SouthWest, West]
1014 };
1015
1016 /// Gets if this resize represents two directions.
1017 pub const fn is_corner(self) -> bool {
1018 matches!(self, Self::NorthEast | Self::NorthWest | Self::SouthEast | Self::SouthWest)
1019 }
1020
1021 /// Gets if this resize represents a single direction.
1022 pub const fn is_border(self) -> bool {
1023 !self.is_corner()
1024 }
1025}
1026
1027/// Window state.
1028#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
1029pub enum WindowState {
1030 /// Window is visible, but does not fill the screen.
1031 #[default]
1032 Normal,
1033 /// Window is only visible as an icon in the taskbar.
1034 Minimized,
1035 /// Window fills the screen, but not the parts reserved by the system, like the taskbar.
1036 Maximized,
1037 /// Window is chromeless and completely fills the screen, including over parts reserved by the system.
1038 ///
1039 /// Also called borderless fullscreen.
1040 Fullscreen,
1041 /// Window has exclusive access to the monitor's video output, so only the window content is visible.
1042 Exclusive,
1043}
1044impl WindowState {
1045 /// Returns `true` if `self` matches [`Fullscreen`] or [`Exclusive`].
1046 ///
1047 /// [`Fullscreen`]: WindowState::Fullscreen
1048 /// [`Exclusive`]: WindowState::Exclusive
1049 pub fn is_fullscreen(self) -> bool {
1050 matches!(self, Self::Fullscreen | Self::Exclusive)
1051 }
1052}
1053
1054/// [`Event::FrameRendered`] payload.
1055///
1056/// [`Event::FrameRendered`]: crate::Event::FrameRendered
1057#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1058#[non_exhaustive]
1059pub struct EventFrameRendered {
1060 /// Window that was rendered.
1061 pub window: WindowId,
1062 /// Frame that was rendered.
1063 pub frame: FrameId,
1064 /// Frame image, if one was requested with the frame request.
1065 pub frame_image: Option<ImageDecoded>,
1066}
1067impl EventFrameRendered {
1068 /// New response.
1069 pub fn new(window: WindowId, frame: FrameId, frame_image: Option<ImageDecoded>) -> Self {
1070 Self {
1071 window,
1072 frame,
1073 frame_image,
1074 }
1075 }
1076}
1077
1078/// [`Event::WindowChanged`] payload.
1079///
1080/// [`Event::WindowChanged`]: crate::Event::WindowChanged
1081#[derive(Debug, Clone, Serialize, Deserialize)]
1082#[non_exhaustive]
1083pub struct WindowChanged {
1084 // note that this payload is handled by `Event::coalesce`, add new fields there too.
1085 //
1086 /// Window that has changed state.
1087 pub window: WindowId,
1088
1089 /// Window new state, is `None` if the window state did not change.
1090 pub state: Option<WindowStateAll>,
1091
1092 /// Window new global position, is `None` if the window position did not change.
1093 ///
1094 /// The values are the global position and the position in the monitor.
1095 pub position: Option<(PxPoint, DipPoint)>,
1096
1097 /// Window new monitor.
1098 ///
1099 /// The window's monitor change when it is moved enough so that most of the
1100 /// client area is in the new monitor screen.
1101 pub monitor: Option<MonitorId>,
1102
1103 /// New scale factor.
1104 pub scale_factor: Option<Factor>,
1105
1106 /// New refresh rate, in millihertz.
1107 pub refresh_rate: Option<Frequency>,
1108
1109 /// The window new size, is `None` if the window size did not change.
1110 pub size: Option<DipSize>,
1111
1112 /// The window new safe padding, is `None` if the did not change.
1113 pub safe_padding: Option<DipSideOffsets>,
1114
1115 /// If the view-process is blocking the event loop for a time waiting for a frame for the new `size` this
1116 /// ID must be send with the frame to signal that it is the frame for the new size.
1117 ///
1118 /// Event loop implementations can use this to resize without visible artifacts
1119 /// like the clear color flashing on the window corners, there is a timeout to this delay but it
1120 /// can be a noticeable stutter, a [`render`] or [`render_update`] request for the window unblocks the loop early
1121 /// to continue the resize operation.
1122 ///
1123 /// [`render`]: crate::Api::render
1124 /// [`render_update`]: crate::Api::render_update
1125 pub frame_wait_id: Option<FrameWaitId>,
1126
1127 /// What caused the change, end-user/OS modifying the window or the app.
1128 pub cause: EventCause,
1129}
1130impl WindowChanged {
1131 /// New response.
1132 #[allow(clippy::too_many_arguments)] // already grouping stuff>
1133 pub fn new(
1134 window: WindowId,
1135 state: Option<WindowStateAll>,
1136 position: Option<(PxPoint, DipPoint)>,
1137 monitor: Option<MonitorId>,
1138 size: Option<DipSize>,
1139 safe_padding: Option<DipSideOffsets>,
1140 frame_wait_id: Option<FrameWaitId>,
1141 cause: EventCause,
1142 ) -> Self {
1143 Self {
1144 window,
1145 state,
1146 position,
1147 monitor,
1148 size,
1149 scale_factor: None,
1150 refresh_rate: None,
1151 safe_padding,
1152 frame_wait_id,
1153 cause,
1154 }
1155 }
1156
1157 /// Create an event that represents window move.
1158 pub fn moved(window: WindowId, global_position: PxPoint, position: DipPoint, cause: EventCause) -> Self {
1159 WindowChanged {
1160 window,
1161 state: None,
1162 position: Some((global_position, position)),
1163 monitor: None,
1164 size: None,
1165 safe_padding: None,
1166 scale_factor: None,
1167 refresh_rate: None,
1168 frame_wait_id: None,
1169 cause,
1170 }
1171 }
1172
1173 /// Create an event that represents window parent monitor change.
1174 pub fn monitor_changed(window: WindowId, monitor: MonitorId, cause: EventCause) -> Self {
1175 WindowChanged {
1176 window,
1177 state: None,
1178 position: None,
1179 monitor: Some(monitor),
1180 size: None,
1181 safe_padding: None,
1182 scale_factor: None,
1183 refresh_rate: None,
1184 frame_wait_id: None,
1185 cause,
1186 }
1187 }
1188
1189 /// Create an event that represents window resized.
1190 pub fn resized(window: WindowId, size: DipSize, cause: EventCause, frame_wait_id: Option<FrameWaitId>) -> Self {
1191 WindowChanged {
1192 window,
1193 state: None,
1194 position: None,
1195 monitor: None,
1196 size: Some(size),
1197 safe_padding: None,
1198 scale_factor: None,
1199 refresh_rate: None,
1200 frame_wait_id,
1201 cause,
1202 }
1203 }
1204
1205 /// Create an event that represents [`WindowStateAll`] change.
1206 pub fn state_changed(window: WindowId, state: WindowStateAll, cause: EventCause) -> Self {
1207 WindowChanged {
1208 window,
1209 state: Some(state),
1210 position: None,
1211 monitor: None,
1212 size: None,
1213 safe_padding: None,
1214 scale_factor: None,
1215 refresh_rate: None,
1216 frame_wait_id: None,
1217 cause,
1218 }
1219 }
1220}
1221
1222/// Identifier of a frame or frame update.
1223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, bytemuck::NoUninit)]
1224#[repr(C)]
1225pub struct FrameId(u32, u32);
1226impl FrameId {
1227 /// Dummy frame ID.
1228 pub const INVALID: FrameId = FrameId(u32::MAX, u32::MAX);
1229
1230 /// Create first frame id of a window.
1231 pub fn first() -> FrameId {
1232 FrameId(0, 0)
1233 }
1234
1235 /// Create the next full frame ID after the current one.
1236 pub fn next(self) -> FrameId {
1237 let mut id = self.0.wrapping_add(1);
1238 if id == u32::MAX {
1239 id = 0;
1240 }
1241 FrameId(id, 0)
1242 }
1243
1244 /// Create the next update frame ID after the current one.
1245 pub fn next_update(self) -> FrameId {
1246 let mut id = self.1.wrapping_add(1);
1247 if id == u32::MAX {
1248 id = 0;
1249 }
1250 FrameId(self.0, id)
1251 }
1252
1253 /// Get the raw ID.
1254 pub fn get(self) -> u64 {
1255 ((self.0 as u64) << 32) | (self.1 as u64)
1256 }
1257
1258 /// Get the full frame ID.
1259 pub fn epoch(self) -> u32 {
1260 self.0
1261 }
1262
1263 /// Get the frame update ID.
1264 pub fn update(self) -> u32 {
1265 self.1
1266 }
1267}
1268
1269/// Cause of a window state change.
1270#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1271#[non_exhaustive]
1272pub enum EventCause {
1273 /// Operating system or end-user affected the window.
1274 System,
1275 /// App affected the window.
1276 App,
1277}
1278
1279bitflags::bitflags! {
1280 /// Window chrome buttons.
1281 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1282 pub struct WindowButton: u32 {
1283 /// Close button.
1284 const CLOSE = 1 << 0;
1285 /// Minimize button.
1286 const MINIMIZE = 1 << 1;
1287 /// Maximize/restore button.
1288 const MAXIMIZE = 1 << 2;
1289 }
1290}
1291
1292bitflags::bitflags! {
1293 /// Window operations the view-process implements.
1294 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1295 pub struct WindowCapability: u64 {
1296 /// Can set title text.
1297 const SET_TITLE = 1 << 0;
1298 /// Can set window visible.
1299 const SET_VISIBLE = 1 << 1;
1300 /// Can make window "topmost".
1301 const SET_ALWAYS_ON_TOP = 1 << 2;
1302 /// Can change if window can be dragged by the user.
1303 const SET_MOVABLE = 1 << 3;
1304 /// Can change if window can be resized by the user.
1305 const SET_RESIZABLE = 1 << 4;
1306 /// Can make window icon not appear on the taskbar while the window remains visible.
1307 const SET_TASKBAR_VISIBLE = 1 << 5;
1308 /// Can force window to appear in front of other apps, without focusing input.
1309 const BRING_TO_TOP = 1 << 6;
1310 /// Can set the window icon.
1311 ///
1312 /// When this is not possible the system specific application metadata icon is used for all windows of the app.
1313 const SET_ICON = 1 << 7;
1314 /// Can set the window cursor to one of the named [`CursorIcon`].
1315 const SET_CURSOR = 1 << 8;
1316 /// Can set the window cursor to a custom image.
1317 const SET_CURSOR_IMAGE = 1 << 9;
1318 /// Can set attention indicator for the window.
1319 const SET_FOCUS_INDICATOR = 1 << 10;
1320 /// Can focus input on the window.
1321 ///
1322 /// This is also true if can the system only shows an attention indicator for the window some times.
1323 const FOCUS = 1 << 11;
1324 /// Can initiate a window move operation from a mouse press in the content area.
1325 const DRAG_MOVE = 1 << 12;
1326 /// Can initiate a window resize operation from a mouse press in the content area.
1327 const DRAG_RESIZE = 1 << 13;
1328 /// Can open the system context menu that usually shows on right click on the title bar.
1329 const OPEN_TITLE_BAR_CONTEXT_MENU = 1 << 14;
1330
1331 /// If operating system provides a window chrome (title bar, resize borders).
1332 const SYSTEM_CHROME = 1 << 15;
1333
1334 /// Can minimize window.
1335 const MINIMIZE = (1 << 16);
1336 /// Can restore window to *normal*.
1337 const RESTORE = (1 << 17);
1338 /// Can maximize window.
1339 const MAXIMIZE = (1 << 18);
1340 /// Can make window fullscreen.
1341 const FULLSCREEN = (1 << 19);
1342 /// Can takeover video output to show only the window content.
1343 const EXCLUSIVE = (1 << 20);
1344
1345 /// Can toggle if the system chrome (title bar, resize border) is visible.
1346 const SET_CHROME = (1 << 21) | Self::SYSTEM_CHROME.bits();
1347 /// Can programmatically move window after it is open.
1348 const SET_POSITION = (1 << 22);
1349
1350 /// Can programmatically resize window after it is open.
1351 const SET_SIZE = (1 << 23);
1352
1353 /// Can disable close button.
1354 const DISABLE_CLOSE_BUTTON = (1 << 24);
1355 /// Can disable minimize button.
1356 const DISABLE_MINIMIZE_BUTTON = (1 << 25);
1357 /// Can disable maximize button.
1358 const DISABLE_MAXIMIZE_BUTTON = (1 << 26);
1359
1360 /// Can set a system shutdown warning/blocker associated with the window.
1361 const SET_SYSTEM_SHUTDOWN_WARN = (1 << 27);
1362
1363 /// Can set the IME area, show virtual keyboard.
1364 const SET_IME_AREA = (1 << 28);
1365 }
1366}