1use crate::PhysicalSize;
9use std::fmt;
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct DisplayId {
16 index: u32,
17 generation: u32,
18}
19
20impl DisplayId {
21 #[must_use]
22 pub const fn from_parts(index: u32, generation: u32) -> Self {
23 Self { index, generation }
24 }
25
26 #[must_use]
27 pub const fn index(self) -> u32 {
28 self.index
29 }
30
31 #[must_use]
32 pub const fn generation(self) -> u32 {
33 self.generation
34 }
35}
36
37impl fmt::Debug for DisplayId {
38 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(formatter, "DisplayId({}, {})", self.index, self.generation)
40 }
41}
42
43impl fmt::Display for DisplayId {
44 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(formatter, "Display#{}@{}", self.index + 1, self.generation)
46 }
47}
48
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
51pub struct PhysicalScreenPosition {
52 pub x: i32,
53 pub y: i32,
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
62pub struct PhysicalDisplayPosition {
63 pub x: i32,
64 pub y: i32,
65}
66
67impl PhysicalDisplayPosition {
68 #[must_use]
69 pub const fn new(x: i32, y: i32) -> Self {
70 Self { x, y }
71 }
72}
73
74impl PhysicalScreenPosition {
75 #[must_use]
76 pub const fn new(x: i32, y: i32) -> Self {
77 Self { x, y }
78 }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83pub struct PhysicalScreenRect {
84 pub origin: PhysicalScreenPosition,
85 pub size: PhysicalSize,
86}
87
88impl PhysicalScreenRect {
89 #[must_use]
90 pub const fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
91 Self {
92 origin: PhysicalScreenPosition::new(x, y),
93 size: PhysicalSize::new(width, height),
94 }
95 }
96
97 #[must_use]
98 pub fn centered_position(self, outer_size: PhysicalSize) -> PhysicalScreenPosition {
99 let x = i64::from(self.origin.x)
100 + (i64::from(self.size.width) - i64::from(outer_size.width)) / 2;
101 let y = i64::from(self.origin.y)
102 + (i64::from(self.size.height) - i64::from(outer_size.height)) / 2;
103 PhysicalScreenPosition::new(clamp_i64_to_i32(x), clamp_i64_to_i32(y))
104 }
105
106 #[must_use]
107 pub fn intersection(self, other: Self) -> Option<Self> {
108 let left = i64::from(self.origin.x).max(i64::from(other.origin.x));
109 let top = i64::from(self.origin.y).max(i64::from(other.origin.y));
110 let right = (i64::from(self.origin.x) + i64::from(self.size.width))
111 .min(i64::from(other.origin.x) + i64::from(other.size.width));
112 let bottom = (i64::from(self.origin.y) + i64::from(self.size.height))
113 .min(i64::from(other.origin.y) + i64::from(other.size.height));
114 (right > left && bottom > top).then(|| {
115 Self::new(
116 clamp_i64_to_i32(left),
117 clamp_i64_to_i32(top),
118 u32::try_from(right - left).unwrap_or(u32::MAX),
119 u32::try_from(bottom - top).unwrap_or(u32::MAX),
120 )
121 })
122 }
123}
124
125#[derive(Clone, Copy, Debug, Default, PartialEq)]
128pub struct LogicalScreenPosition {
129 pub x: f64,
130 pub y: f64,
131}
132
133#[derive(Clone, Copy, Debug, Default, PartialEq)]
136pub struct LogicalDisplayPosition {
137 pub x: f64,
138 pub y: f64,
139}
140
141impl LogicalDisplayPosition {
142 #[must_use]
143 pub const fn new(x: f64, y: f64) -> Self {
144 Self { x, y }
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq)]
152pub struct LogicalScreenRect {
153 pub origin: LogicalScreenPosition,
154 pub width: f64,
155 pub height: f64,
156}
157
158#[derive(Clone, Debug, PartialEq)]
160pub struct DisplaySnapshot {
161 pub id: DisplayId,
162 pub name: Option<String>,
163 pub scale_factor: f64,
164 pub physical_size: PhysicalSize,
167 pub physical_bounds: Option<PhysicalScreenRect>,
168 pub logical_bounds: Option<LogicalScreenRect>,
169 pub physical_work_area: Option<PhysicalScreenRect>,
170 pub logical_work_area: Option<LogicalScreenRect>,
171 pub is_primary: bool,
172}
173
174impl DisplaySnapshot {
175 #[must_use]
176 pub fn logical_size(&self) -> Option<(f64, f64)> {
177 (self.scale_factor.is_finite() && self.scale_factor > 0.0).then(|| {
178 (
179 f64::from(self.physical_size.width) / self.scale_factor,
180 f64::from(self.physical_size.height) / self.scale_factor,
181 )
182 })
183 }
184
185 #[must_use]
186 pub fn placement_rect(&self, area: DisplayPlacementArea) -> Option<PhysicalScreenRect> {
187 match area {
188 DisplayPlacementArea::WorkArea => self.physical_work_area,
189 DisplayPlacementArea::FullBounds => self.physical_bounds,
190 }
191 }
192
193 #[must_use]
197 pub fn logical_to_physical_local(
198 &self,
199 position: LogicalDisplayPosition,
200 ) -> Option<PhysicalDisplayPosition> {
201 if !self.scale_factor.is_finite()
202 || self.scale_factor <= 0.0
203 || !position.x.is_finite()
204 || !position.y.is_finite()
205 {
206 return None;
207 }
208 let x = position.x * self.scale_factor;
209 let y = position.y * self.scale_factor;
210 Some(PhysicalDisplayPosition::new(
211 rounded_f64_to_i32(x)?,
212 rounded_f64_to_i32(y)?,
213 ))
214 }
215
216 #[must_use]
218 pub fn physical_to_logical_local(
219 &self,
220 position: PhysicalDisplayPosition,
221 ) -> Option<LogicalDisplayPosition> {
222 (self.scale_factor.is_finite() && self.scale_factor > 0.0).then(|| {
223 LogicalDisplayPosition::new(
224 f64::from(position.x) / self.scale_factor,
225 f64::from(position.y) / self.scale_factor,
226 )
227 })
228 }
229
230 #[must_use]
233 pub fn screen_position(
234 &self,
235 position: PhysicalDisplayPosition,
236 ) -> Option<PhysicalScreenPosition> {
237 let bounds = self.physical_bounds?;
238 Some(PhysicalScreenPosition::new(
239 checked_i64_to_i32(i64::from(bounds.origin.x) + i64::from(position.x))?,
240 checked_i64_to_i32(i64::from(bounds.origin.y) + i64::from(position.y))?,
241 ))
242 }
243}
244
245#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
246pub enum DisplayPlacementArea {
247 #[default]
248 WorkArea,
249 FullBounds,
250}
251
252const fn clamp_i64_to_i32(value: i64) -> i32 {
253 if value < i32::MIN as i64 {
254 i32::MIN
255 } else if value > i32::MAX as i64 {
256 i32::MAX
257 } else {
258 value as i32
259 }
260}
261
262fn checked_i64_to_i32(value: i64) -> Option<i32> {
263 i32::try_from(value).ok()
264}
265
266fn rounded_f64_to_i32(value: f64) -> Option<i32> {
267 if !value.is_finite() || value < f64::from(i32::MIN) || value > f64::from(i32::MAX) {
268 return None;
269 }
270 Some(value.round() as i32)
271}