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