1use crate::{DisplayId, Fullscreen, PhysicalScreenPosition, PhysicalSize};
4use incular_core::Size;
5use std::{fmt, sync::Arc};
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
9pub enum WindowLevel {
10 #[default]
11 Normal,
12 AlwaysOnTop,
13}
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
17pub enum UserAttentionType {
18 #[default]
19 Informational,
20 Critical,
21}
22
23#[derive(Clone, PartialEq, Eq)]
29pub struct WindowIcon {
30 rgba: Arc<[u8]>,
31 width: u32,
32 height: u32,
33}
34
35impl WindowIcon {
36 pub fn from_rgba(
37 rgba: impl Into<Vec<u8>>,
38 width: u32,
39 height: u32,
40 ) -> Result<Self, WindowIconError> {
41 let rgba = rgba.into();
42 if width == 0 || height == 0 {
43 return Err(WindowIconError::ZeroDimension);
44 }
45 let pixels = usize::try_from(width)
46 .ok()
47 .and_then(|width| {
48 usize::try_from(height)
49 .ok()
50 .and_then(|height| width.checked_mul(height))
51 })
52 .and_then(|pixels| pixels.checked_mul(4))
53 .ok_or(WindowIconError::DimensionsOverflow)?;
54 if rgba.len() != pixels {
55 return Err(WindowIconError::InvalidByteCount {
56 expected: pixels,
57 actual: rgba.len(),
58 });
59 }
60 Ok(Self {
61 rgba: rgba.into(),
62 width,
63 height,
64 })
65 }
66
67 #[must_use]
68 pub fn rgba(&self) -> &[u8] {
69 &self.rgba
70 }
71
72 #[must_use]
73 pub const fn width(&self) -> u32 {
74 self.width
75 }
76
77 #[must_use]
78 pub const fn height(&self) -> u32 {
79 self.height
80 }
81}
82
83impl fmt::Debug for WindowIcon {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter
86 .debug_struct("WindowIcon")
87 .field("width", &self.width)
88 .field("height", &self.height)
89 .field("rgba_bytes", &self.rgba.len())
90 .finish()
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
95pub enum WindowIconError {
96 #[error("window icon dimensions must be non-zero")]
97 ZeroDimension,
98 #[error("window icon dimensions overflow addressable memory")]
99 DimensionsOverflow,
100 #[error("window icon RGBA byte count is {actual}, expected {expected}")]
101 InvalidByteCount { expected: usize, actual: usize },
102}
103
104#[derive(Clone, Copy, Debug, Default, PartialEq)]
106pub struct LogicalSizeLimits {
107 minimum: Option<Size>,
108 maximum: Option<Size>,
109}
110
111impl LogicalSizeLimits {
112 pub fn new(
113 minimum: Option<Size>,
114 maximum: Option<Size>,
115 ) -> Result<Self, LogicalSizeLimitsError> {
116 if minimum.is_some_and(|size| !is_positive_size(size)) {
117 return Err(LogicalSizeLimitsError::InvalidMinimum);
118 }
119 if maximum.is_some_and(|size| !is_positive_size(size)) {
120 return Err(LogicalSizeLimitsError::InvalidMaximum);
121 }
122 if let (Some(minimum), Some(maximum)) = (minimum, maximum)
123 && (minimum.width > maximum.width || minimum.height > maximum.height)
124 {
125 return Err(LogicalSizeLimitsError::MinimumExceedsMaximum);
126 }
127 Ok(Self { minimum, maximum })
128 }
129
130 #[must_use]
131 pub const fn minimum(self) -> Option<Size> {
132 self.minimum
133 }
134
135 #[must_use]
136 pub const fn maximum(self) -> Option<Size> {
137 self.maximum
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
142pub enum LogicalSizeLimitsError {
143 #[error("minimum logical size must be finite and positive")]
144 InvalidMinimum,
145 #[error("maximum logical size must be finite and positive")]
146 InvalidMaximum,
147 #[error("minimum logical size cannot exceed maximum logical size")]
148 MinimumExceedsMaximum,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq)]
157pub struct WindowRequestedState {
158 pub visible: bool,
159 pub minimized: bool,
160 pub maximized: bool,
161 pub fullscreen: Option<Fullscreen>,
162 pub resizable: bool,
163 pub decorations: bool,
164 pub size_limits: LogicalSizeLimits,
165 pub level: WindowLevel,
166}
167
168#[derive(Clone, Copy, Debug, Default, PartialEq)]
173pub struct WindowObservedState {
174 pub visible: Option<bool>,
175 pub minimized: Option<bool>,
176 pub maximized: Option<bool>,
177 pub fullscreen: Option<bool>,
178 pub resizable: Option<bool>,
179 pub decorations: Option<bool>,
180 pub outer_position: Option<PhysicalScreenPosition>,
181 pub outer_size: Option<PhysicalSize>,
182 pub current_display: Option<DisplayId>,
183}
184
185const fn is_positive_size(size: Size) -> bool {
186 size.width.is_finite() && size.height.is_finite() && size.width > 0.0 && size.height > 0.0
187}