1 | //! UI scaling is important, so read the docs for this module if you don't want to be confused. |
2 | //! |
3 | //! ## Why should I care about UI scaling? |
4 | //! |
5 | //! Modern computer screens don't have a consistent relationship between resolution and size. |
6 | //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens |
7 | //! typically being less than a quarter the size of their desktop counterparts. Moreover, neither |
8 | //! desktop nor mobile screens have consistent resolutions within their own size classes - common |
9 | //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K |
10 | //! and beyond. |
11 | //! |
12 | //! Given that, it's a mistake to assume that 2D content will only be displayed on screens with |
13 | //! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and |
14 | //! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up |
15 | //! about a quarter of the physical space as it did on the 1080p screen. That issue is especially |
16 | //! problematic with text rendering, where quarter-sized text becomes a significant legibility |
17 | //! problem. |
18 | //! |
19 | //! Failure to account for the scale factor can create a significantly degraded user experience. |
20 | //! Most notably, it can make users feel like they have bad eyesight, which will potentially cause |
21 | //! them to think about growing elderly, resulting in them having an existential crisis. Once users |
22 | //! enter that state, they will no longer be focused on your application. |
23 | //! |
24 | //! ## How should I handle it? |
25 | //! |
26 | //! The solution to this problem is to account for the device's *scale factor*. The scale factor is |
27 | //! the factor UI elements should be scaled by to be consistent with the rest of the user's system - |
28 | //! for example, a button that's usually 50 pixels across would be 100 pixels across on a device |
29 | //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. |
30 | //! |
31 | //! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's |
32 | //! usually a mistake since there's no consistent mapping between the scale factor and the screen's |
33 | //! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather |
34 | //! than any DPI-dependent units. |
35 | //! |
36 | //! ### Position and Size types |
37 | //! |
38 | //! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the |
39 | //! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels |
40 | //! divided by the scale factor. |
41 | //! All of Winit's functions return physical types, but can take either logical or physical |
42 | //! coordinates as input, allowing you to use the most convenient coordinate system for your |
43 | //! particular application. |
44 | //! |
45 | //! Winit's position and size types are generic over their exact pixel type, `P`, to allow the |
46 | //! API to have integer precision where appropriate (e.g. most window manipulation functions) and |
47 | //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch |
48 | //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so |
49 | //! will truncate the fractional part of the float rather than properly round to the nearest |
50 | //! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the |
51 | //! rounding properly. Note that precision loss will still occur when rounding from a float to an |
52 | //! int, although rounding lessens the problem. |
53 | //! |
54 | //! ### Events |
55 | //! |
56 | //! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. |
57 | //! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI |
58 | //! monitor or if the user changes their DPI settings. This allows you to rescale your application's |
59 | //! UI elements and adjust how the platform changes the window's size to reflect the new scale |
60 | //! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor |
61 | //! can be found by calling [`window.scale_factor()`]. |
62 | //! |
63 | //! ## How is the scale factor calculated? |
64 | //! |
65 | //! The scale factor is calculated differently on different platforms: |
66 | //! |
67 | //! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the |
68 | //! display settings. While users are free to select any option they want, they're only given a |
69 | //! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is |
70 | //! global and changing it requires logging out. See [this article][windows_1] for technical |
71 | //! details. |
72 | //! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific |
73 | //! displays. When available, the user may pick a per-monitor scaling factor from a set of |
74 | //! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default, |
75 | //! but the specific value varies across devices. |
76 | //! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit |
77 | //! currently uses a three-pronged approach: |
78 | //! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present. |
79 | //! + If not present, use the value set in `Xft.dpi` in Xresources. |
80 | //! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR. |
81 | //! |
82 | //! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the |
83 | //! XRandR scaling method. Generally speaking, you should try to configure the standard system |
84 | //! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. |
85 | //! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The |
86 | //! monitor scale factor may differ from the window scale factor. |
87 | //! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range |
88 | //! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more |
89 | //! information. |
90 | //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the |
91 | //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. |
92 | //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. |
93 | //! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by |
94 | //! both the screen scaling and the browser zoom level and can go below `1.0`. |
95 | //! |
96 | //! |
97 | //! [points]: https://en.wikipedia.org/wiki/Point_(typography) |
98 | //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) |
99 | //! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged |
100 | //! [`window.scale_factor()`]: crate::window::Window::scale_factor |
101 | //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows |
102 | //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html |
103 | //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ |
104 | //! [android_1]: https://developer.android.com/training/multiscreen/screendensities |
105 | //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio |
106 | |
107 | pub trait Pixel: Copy + Into<f64> { |
108 | fn from_f64(f: f64) -> Self; |
109 | fn cast<P: Pixel>(self) -> P { |
110 | P::from_f64(self.into()) |
111 | } |
112 | } |
113 | |
114 | impl Pixel for u8 { |
115 | fn from_f64(f: f64) -> Self { |
116 | f.round() as u8 |
117 | } |
118 | } |
119 | impl Pixel for u16 { |
120 | fn from_f64(f: f64) -> Self { |
121 | f.round() as u16 |
122 | } |
123 | } |
124 | impl Pixel for u32 { |
125 | fn from_f64(f: f64) -> Self { |
126 | f.round() as u32 |
127 | } |
128 | } |
129 | impl Pixel for i8 { |
130 | fn from_f64(f: f64) -> Self { |
131 | f.round() as i8 |
132 | } |
133 | } |
134 | impl Pixel for i16 { |
135 | fn from_f64(f: f64) -> Self { |
136 | f.round() as i16 |
137 | } |
138 | } |
139 | impl Pixel for i32 { |
140 | fn from_f64(f: f64) -> Self { |
141 | f.round() as i32 |
142 | } |
143 | } |
144 | impl Pixel for f32 { |
145 | fn from_f64(f: f64) -> Self { |
146 | f as f32 |
147 | } |
148 | } |
149 | impl Pixel for f64 { |
150 | fn from_f64(f: f64) -> Self { |
151 | f |
152 | } |
153 | } |
154 | |
155 | /// Checks that the scale factor is a normal positive `f64`. |
156 | /// |
157 | /// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from |
158 | /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; |
159 | /// otherwise, you risk panics. |
160 | #[inline ] |
161 | pub fn validate_scale_factor(scale_factor: f64) -> bool { |
162 | scale_factor.is_sign_positive() && scale_factor.is_normal() |
163 | } |
164 | |
165 | /// A position represented in logical pixels. |
166 | /// |
167 | /// The position is stored as floats, so please be careful. Casting floats to integers truncates the |
168 | /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` |
169 | /// implementation is provided which does the rounding for you. |
170 | #[derive (Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] |
171 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
172 | pub struct LogicalPosition<P> { |
173 | pub x: P, |
174 | pub y: P, |
175 | } |
176 | |
177 | impl<P> LogicalPosition<P> { |
178 | #[inline ] |
179 | pub const fn new(x: P, y: P) -> Self { |
180 | LogicalPosition { x, y } |
181 | } |
182 | } |
183 | |
184 | impl<P: Pixel> LogicalPosition<P> { |
185 | #[inline ] |
186 | pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>( |
187 | physical: T, |
188 | scale_factor: f64, |
189 | ) -> Self { |
190 | physical.into().to_logical(scale_factor) |
191 | } |
192 | |
193 | #[inline ] |
194 | pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<X> { |
195 | assert!(validate_scale_factor(scale_factor)); |
196 | let x = self.x.into() * scale_factor; |
197 | let y = self.y.into() * scale_factor; |
198 | PhysicalPosition::new(x, y).cast() |
199 | } |
200 | |
201 | #[inline ] |
202 | pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> { |
203 | LogicalPosition { |
204 | x: self.x.cast(), |
205 | y: self.y.cast(), |
206 | } |
207 | } |
208 | } |
209 | |
210 | impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> { |
211 | fn from((x: X, y: X): (X, X)) -> LogicalPosition<P> { |
212 | LogicalPosition::new(x:x.cast(), y:y.cast()) |
213 | } |
214 | } |
215 | |
216 | impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) { |
217 | fn from(p: LogicalPosition<P>) -> (X, X) { |
218 | (p.x.cast(), p.y.cast()) |
219 | } |
220 | } |
221 | |
222 | impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> { |
223 | fn from([x: X, y: X]: [X; 2]) -> LogicalPosition<P> { |
224 | LogicalPosition::new(x:x.cast(), y:y.cast()) |
225 | } |
226 | } |
227 | |
228 | impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] { |
229 | fn from(p: LogicalPosition<P>) -> [X; 2] { |
230 | [p.x.cast(), p.y.cast()] |
231 | } |
232 | } |
233 | |
234 | #[cfg (feature = "mint" )] |
235 | impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> { |
236 | fn from(p: mint::Point2<P>) -> Self { |
237 | Self::new(p.x, p.y) |
238 | } |
239 | } |
240 | |
241 | #[cfg (feature = "mint" )] |
242 | impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> { |
243 | fn from(p: LogicalPosition<P>) -> Self { |
244 | mint::Point2 { x: p.x, y: p.y } |
245 | } |
246 | } |
247 | |
248 | /// A position represented in physical pixels. |
249 | #[derive (Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] |
250 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
251 | pub struct PhysicalPosition<P> { |
252 | pub x: P, |
253 | pub y: P, |
254 | } |
255 | |
256 | impl<P> PhysicalPosition<P> { |
257 | #[inline ] |
258 | pub const fn new(x: P, y: P) -> Self { |
259 | PhysicalPosition { x, y } |
260 | } |
261 | } |
262 | |
263 | impl<P: Pixel> PhysicalPosition<P> { |
264 | #[inline ] |
265 | pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>( |
266 | logical: T, |
267 | scale_factor: f64, |
268 | ) -> Self { |
269 | logical.into().to_physical(scale_factor) |
270 | } |
271 | |
272 | #[inline ] |
273 | pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalPosition<X> { |
274 | assert!(validate_scale_factor(scale_factor)); |
275 | let x = self.x.into() / scale_factor; |
276 | let y = self.y.into() / scale_factor; |
277 | LogicalPosition::new(x, y).cast() |
278 | } |
279 | |
280 | #[inline ] |
281 | pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> { |
282 | PhysicalPosition { |
283 | x: self.x.cast(), |
284 | y: self.y.cast(), |
285 | } |
286 | } |
287 | } |
288 | |
289 | impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> { |
290 | fn from((x: X, y: X): (X, X)) -> PhysicalPosition<P> { |
291 | PhysicalPosition::new(x:x.cast(), y:y.cast()) |
292 | } |
293 | } |
294 | |
295 | impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) { |
296 | fn from(p: PhysicalPosition<P>) -> (X, X) { |
297 | (p.x.cast(), p.y.cast()) |
298 | } |
299 | } |
300 | |
301 | impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> { |
302 | fn from([x: X, y: X]: [X; 2]) -> PhysicalPosition<P> { |
303 | PhysicalPosition::new(x:x.cast(), y:y.cast()) |
304 | } |
305 | } |
306 | |
307 | impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] { |
308 | fn from(p: PhysicalPosition<P>) -> [X; 2] { |
309 | [p.x.cast(), p.y.cast()] |
310 | } |
311 | } |
312 | |
313 | #[cfg (feature = "mint" )] |
314 | impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> { |
315 | fn from(p: mint::Point2<P>) -> Self { |
316 | Self::new(p.x, p.y) |
317 | } |
318 | } |
319 | |
320 | #[cfg (feature = "mint" )] |
321 | impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> { |
322 | fn from(p: PhysicalPosition<P>) -> Self { |
323 | mint::Point2 { x: p.x, y: p.y } |
324 | } |
325 | } |
326 | |
327 | /// A size represented in logical pixels. |
328 | #[derive (Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] |
329 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
330 | pub struct LogicalSize<P> { |
331 | pub width: P, |
332 | pub height: P, |
333 | } |
334 | |
335 | impl<P> LogicalSize<P> { |
336 | #[inline ] |
337 | pub const fn new(width: P, height: P) -> Self { |
338 | LogicalSize { width, height } |
339 | } |
340 | } |
341 | |
342 | impl<P: Pixel> LogicalSize<P> { |
343 | #[inline ] |
344 | pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>( |
345 | physical: T, |
346 | scale_factor: f64, |
347 | ) -> Self { |
348 | physical.into().to_logical(scale_factor) |
349 | } |
350 | |
351 | #[inline ] |
352 | pub fn to_physical<X: Pixel>(&self, scale_factor: f64) -> PhysicalSize<X> { |
353 | assert!(validate_scale_factor(scale_factor)); |
354 | let width = self.width.into() * scale_factor; |
355 | let height = self.height.into() * scale_factor; |
356 | PhysicalSize::new(width, height).cast() |
357 | } |
358 | |
359 | #[inline ] |
360 | pub fn cast<X: Pixel>(&self) -> LogicalSize<X> { |
361 | LogicalSize { |
362 | width: self.width.cast(), |
363 | height: self.height.cast(), |
364 | } |
365 | } |
366 | } |
367 | |
368 | impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> { |
369 | fn from((x: X, y: X): (X, X)) -> LogicalSize<P> { |
370 | LogicalSize::new(width:x.cast(), height:y.cast()) |
371 | } |
372 | } |
373 | |
374 | impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) { |
375 | fn from(s: LogicalSize<P>) -> (X, X) { |
376 | (s.width.cast(), s.height.cast()) |
377 | } |
378 | } |
379 | |
380 | impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> { |
381 | fn from([x: X, y: X]: [X; 2]) -> LogicalSize<P> { |
382 | LogicalSize::new(width:x.cast(), height:y.cast()) |
383 | } |
384 | } |
385 | |
386 | impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] { |
387 | fn from(s: LogicalSize<P>) -> [X; 2] { |
388 | [s.width.cast(), s.height.cast()] |
389 | } |
390 | } |
391 | |
392 | #[cfg (feature = "mint" )] |
393 | impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> { |
394 | fn from(v: mint::Vector2<P>) -> Self { |
395 | Self::new(v.x, v.y) |
396 | } |
397 | } |
398 | |
399 | #[cfg (feature = "mint" )] |
400 | impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> { |
401 | fn from(s: LogicalSize<P>) -> Self { |
402 | mint::Vector2 { |
403 | x: s.width, |
404 | y: s.height, |
405 | } |
406 | } |
407 | } |
408 | |
409 | /// A size represented in physical pixels. |
410 | #[derive (Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] |
411 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
412 | pub struct PhysicalSize<P> { |
413 | pub width: P, |
414 | pub height: P, |
415 | } |
416 | |
417 | impl<P> PhysicalSize<P> { |
418 | #[inline ] |
419 | pub const fn new(width: P, height: P) -> Self { |
420 | PhysicalSize { width, height } |
421 | } |
422 | } |
423 | |
424 | impl<P: Pixel> PhysicalSize<P> { |
425 | #[inline ] |
426 | pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, scale_factor: f64) -> Self { |
427 | logical.into().to_physical(scale_factor) |
428 | } |
429 | |
430 | #[inline ] |
431 | pub fn to_logical<X: Pixel>(&self, scale_factor: f64) -> LogicalSize<X> { |
432 | assert!(validate_scale_factor(scale_factor)); |
433 | let width = self.width.into() / scale_factor; |
434 | let height = self.height.into() / scale_factor; |
435 | LogicalSize::new(width, height).cast() |
436 | } |
437 | |
438 | #[inline ] |
439 | pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> { |
440 | PhysicalSize { |
441 | width: self.width.cast(), |
442 | height: self.height.cast(), |
443 | } |
444 | } |
445 | } |
446 | |
447 | impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> { |
448 | fn from((x: X, y: X): (X, X)) -> PhysicalSize<P> { |
449 | PhysicalSize::new(width:x.cast(), height:y.cast()) |
450 | } |
451 | } |
452 | |
453 | impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) { |
454 | fn from(s: PhysicalSize<P>) -> (X, X) { |
455 | (s.width.cast(), s.height.cast()) |
456 | } |
457 | } |
458 | |
459 | impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> { |
460 | fn from([x: X, y: X]: [X; 2]) -> PhysicalSize<P> { |
461 | PhysicalSize::new(width:x.cast(), height:y.cast()) |
462 | } |
463 | } |
464 | |
465 | impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] { |
466 | fn from(s: PhysicalSize<P>) -> [X; 2] { |
467 | [s.width.cast(), s.height.cast()] |
468 | } |
469 | } |
470 | |
471 | #[cfg (feature = "mint" )] |
472 | impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> { |
473 | fn from(v: mint::Vector2<P>) -> Self { |
474 | Self::new(v.x, v.y) |
475 | } |
476 | } |
477 | |
478 | #[cfg (feature = "mint" )] |
479 | impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> { |
480 | fn from(s: PhysicalSize<P>) -> Self { |
481 | mint::Vector2 { |
482 | x: s.width, |
483 | y: s.height, |
484 | } |
485 | } |
486 | } |
487 | |
488 | /// A size that's either physical or logical. |
489 | #[derive (Debug, Copy, Clone, PartialEq)] |
490 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
491 | pub enum Size { |
492 | Physical(PhysicalSize<u32>), |
493 | Logical(LogicalSize<f64>), |
494 | } |
495 | |
496 | impl Size { |
497 | pub fn new<S: Into<Size>>(size: S) -> Size { |
498 | size.into() |
499 | } |
500 | |
501 | pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalSize<P> { |
502 | match *self { |
503 | Size::Physical(size) => size.to_logical(scale_factor), |
504 | Size::Logical(size) => size.cast(), |
505 | } |
506 | } |
507 | |
508 | pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalSize<P> { |
509 | match *self { |
510 | Size::Physical(size) => size.cast(), |
511 | Size::Logical(size) => size.to_physical(scale_factor), |
512 | } |
513 | } |
514 | |
515 | pub fn clamp<S: Into<Size>>(input: S, min: S, max: S, scale_factor: f64) -> Size { |
516 | let (input, min, max) = ( |
517 | input.into().to_physical::<f64>(scale_factor), |
518 | min.into().to_physical::<f64>(scale_factor), |
519 | max.into().to_physical::<f64>(scale_factor), |
520 | ); |
521 | |
522 | let width = input.width.clamp(min.width, max.width); |
523 | let height = input.height.clamp(min.height, max.height); |
524 | |
525 | PhysicalSize::new(width, height).into() |
526 | } |
527 | } |
528 | |
529 | impl<P: Pixel> From<PhysicalSize<P>> for Size { |
530 | #[inline ] |
531 | fn from(size: PhysicalSize<P>) -> Size { |
532 | Size::Physical(size.cast()) |
533 | } |
534 | } |
535 | |
536 | impl<P: Pixel> From<LogicalSize<P>> for Size { |
537 | #[inline ] |
538 | fn from(size: LogicalSize<P>) -> Size { |
539 | Size::Logical(size.cast()) |
540 | } |
541 | } |
542 | |
543 | /// A position that's either physical or logical. |
544 | #[derive (Debug, Copy, Clone, PartialEq)] |
545 | #[cfg_attr (feature = "serde" , derive(Serialize, Deserialize))] |
546 | pub enum Position { |
547 | Physical(PhysicalPosition<i32>), |
548 | Logical(LogicalPosition<f64>), |
549 | } |
550 | |
551 | impl Position { |
552 | pub fn new<S: Into<Position>>(position: S) -> Position { |
553 | position.into() |
554 | } |
555 | |
556 | pub fn to_logical<P: Pixel>(&self, scale_factor: f64) -> LogicalPosition<P> { |
557 | match *self { |
558 | Position::Physical(position: PhysicalPosition) => position.to_logical(scale_factor), |
559 | Position::Logical(position: LogicalPosition) => position.cast(), |
560 | } |
561 | } |
562 | |
563 | pub fn to_physical<P: Pixel>(&self, scale_factor: f64) -> PhysicalPosition<P> { |
564 | match *self { |
565 | Position::Physical(position: PhysicalPosition) => position.cast(), |
566 | Position::Logical(position: LogicalPosition) => position.to_physical(scale_factor), |
567 | } |
568 | } |
569 | } |
570 | |
571 | impl<P: Pixel> From<PhysicalPosition<P>> for Position { |
572 | #[inline ] |
573 | fn from(position: PhysicalPosition<P>) -> Position { |
574 | Position::Physical(position.cast()) |
575 | } |
576 | } |
577 | |
578 | impl<P: Pixel> From<LogicalPosition<P>> for Position { |
579 | #[inline ] |
580 | fn from(position: LogicalPosition<P>) -> Position { |
581 | Position::Logical(position.cast()) |
582 | } |
583 | } |
584 | |
585 | #[cfg (test)] |
586 | mod tests { |
587 | use crate::dpi; |
588 | use std::collections::HashSet; |
589 | |
590 | macro_rules! test_pixel_int_impl { |
591 | ($($name:ident => $ty:ty),*) => {$( |
592 | #[test] |
593 | fn $name() { |
594 | use dpi::Pixel; |
595 | |
596 | assert_eq!( |
597 | <$ty as Pixel>::from_f64(37.0), |
598 | 37, |
599 | ); |
600 | assert_eq!( |
601 | <$ty as Pixel>::from_f64(37.4), |
602 | 37, |
603 | ); |
604 | assert_eq!( |
605 | <$ty as Pixel>::from_f64(37.5), |
606 | 38, |
607 | ); |
608 | assert_eq!( |
609 | <$ty as Pixel>::from_f64(37.9), |
610 | 38, |
611 | ); |
612 | |
613 | assert_eq!( |
614 | <$ty as Pixel>::cast::<u8>(37), |
615 | 37, |
616 | ); |
617 | assert_eq!( |
618 | <$ty as Pixel>::cast::<u16>(37), |
619 | 37, |
620 | ); |
621 | assert_eq!( |
622 | <$ty as Pixel>::cast::<u32>(37), |
623 | 37, |
624 | ); |
625 | assert_eq!( |
626 | <$ty as Pixel>::cast::<i8>(37), |
627 | 37, |
628 | ); |
629 | assert_eq!( |
630 | <$ty as Pixel>::cast::<i16>(37), |
631 | 37, |
632 | ); |
633 | assert_eq!( |
634 | <$ty as Pixel>::cast::<i32>(37), |
635 | 37, |
636 | ); |
637 | } |
638 | )*}; |
639 | } |
640 | |
641 | test_pixel_int_impl! { |
642 | test_pixel_int_u8 => u8, |
643 | test_pixel_int_u16 => u16, |
644 | test_pixel_int_u32 => u32, |
645 | test_pixel_int_i8 => i8, |
646 | test_pixel_int_i16 => i16 |
647 | } |
648 | |
649 | macro_rules! assert_approx_eq { |
650 | ($a:expr, $b:expr $(,)?) => { |
651 | assert!( |
652 | ($a - $b).abs() < 0.001, |
653 | "{} is not approximately equal to {}" , |
654 | $a, |
655 | $b |
656 | ); |
657 | }; |
658 | } |
659 | |
660 | macro_rules! test_pixel_float_impl { |
661 | ($($name:ident => $ty:ty),*) => {$( |
662 | #[test] |
663 | fn $name() { |
664 | use dpi::Pixel; |
665 | |
666 | assert_approx_eq!( |
667 | <$ty as Pixel>::from_f64(37.0), |
668 | 37.0, |
669 | ); |
670 | assert_approx_eq!( |
671 | <$ty as Pixel>::from_f64(37.4), |
672 | 37.4, |
673 | ); |
674 | assert_approx_eq!( |
675 | <$ty as Pixel>::from_f64(37.5), |
676 | 37.5, |
677 | ); |
678 | assert_approx_eq!( |
679 | <$ty as Pixel>::from_f64(37.9), |
680 | 37.9, |
681 | ); |
682 | |
683 | assert_eq!( |
684 | <$ty as Pixel>::cast::<u8>(37.0), |
685 | 37, |
686 | ); |
687 | assert_eq!( |
688 | <$ty as Pixel>::cast::<u8>(37.4), |
689 | 37, |
690 | ); |
691 | assert_eq!( |
692 | <$ty as Pixel>::cast::<u8>(37.5), |
693 | 38, |
694 | ); |
695 | |
696 | assert_eq!( |
697 | <$ty as Pixel>::cast::<u16>(37.0), |
698 | 37, |
699 | ); |
700 | assert_eq!( |
701 | <$ty as Pixel>::cast::<u16>(37.4), |
702 | 37, |
703 | ); |
704 | assert_eq!( |
705 | <$ty as Pixel>::cast::<u16>(37.5), |
706 | 38, |
707 | ); |
708 | |
709 | assert_eq!( |
710 | <$ty as Pixel>::cast::<u32>(37.0), |
711 | 37, |
712 | ); |
713 | assert_eq!( |
714 | <$ty as Pixel>::cast::<u32>(37.4), |
715 | 37, |
716 | ); |
717 | assert_eq!( |
718 | <$ty as Pixel>::cast::<u32>(37.5), |
719 | 38, |
720 | ); |
721 | |
722 | assert_eq!( |
723 | <$ty as Pixel>::cast::<i8>(37.0), |
724 | 37, |
725 | ); |
726 | assert_eq!( |
727 | <$ty as Pixel>::cast::<i8>(37.4), |
728 | 37, |
729 | ); |
730 | assert_eq!( |
731 | <$ty as Pixel>::cast::<i8>(37.5), |
732 | 38, |
733 | ); |
734 | |
735 | assert_eq!( |
736 | <$ty as Pixel>::cast::<i16>(37.0), |
737 | 37, |
738 | ); |
739 | assert_eq!( |
740 | <$ty as Pixel>::cast::<i16>(37.4), |
741 | 37, |
742 | ); |
743 | assert_eq!( |
744 | <$ty as Pixel>::cast::<i16>(37.5), |
745 | 38, |
746 | ); |
747 | } |
748 | )*}; |
749 | } |
750 | |
751 | test_pixel_float_impl! { |
752 | test_pixel_float_f32 => f32, |
753 | test_pixel_float_f64 => f64 |
754 | } |
755 | |
756 | #[test ] |
757 | fn test_validate_scale_factor() { |
758 | assert!(dpi::validate_scale_factor(1.0)); |
759 | assert!(dpi::validate_scale_factor(2.0)); |
760 | assert!(dpi::validate_scale_factor(3.0)); |
761 | assert!(dpi::validate_scale_factor(1.5)); |
762 | assert!(dpi::validate_scale_factor(0.5)); |
763 | |
764 | assert!(!dpi::validate_scale_factor(0.0)); |
765 | assert!(!dpi::validate_scale_factor(-1.0)); |
766 | assert!(!dpi::validate_scale_factor(f64::INFINITY)); |
767 | assert!(!dpi::validate_scale_factor(f64::NAN)); |
768 | assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY)); |
769 | } |
770 | |
771 | #[test ] |
772 | fn test_logical_position() { |
773 | let log_pos = dpi::LogicalPosition::new(1.0, 2.0); |
774 | assert_eq!( |
775 | log_pos.to_physical::<u32>(1.0), |
776 | dpi::PhysicalPosition::new(1, 2) |
777 | ); |
778 | assert_eq!( |
779 | log_pos.to_physical::<u32>(2.0), |
780 | dpi::PhysicalPosition::new(2, 4) |
781 | ); |
782 | assert_eq!(log_pos.cast::<u32>(), dpi::LogicalPosition::new(1, 2)); |
783 | assert_eq!( |
784 | log_pos, |
785 | dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0) |
786 | ); |
787 | assert_eq!( |
788 | log_pos, |
789 | dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0) |
790 | ); |
791 | assert_eq!( |
792 | dpi::LogicalPosition::from((2.0, 2.0)), |
793 | dpi::LogicalPosition::new(2.0, 2.0) |
794 | ); |
795 | assert_eq!( |
796 | dpi::LogicalPosition::from([2.0, 3.0]), |
797 | dpi::LogicalPosition::new(2.0, 3.0) |
798 | ); |
799 | |
800 | let x: (f64, f64) = log_pos.into(); |
801 | assert_eq!(x, (1.0, 2.0)); |
802 | let x: [f64; 2] = log_pos.into(); |
803 | assert_eq!(x, [1.0, 2.0]); |
804 | } |
805 | |
806 | #[test ] |
807 | fn test_physical_position() { |
808 | assert_eq!( |
809 | dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0), |
810 | dpi::PhysicalPosition::new(1, 2) |
811 | ); |
812 | assert_eq!( |
813 | dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5), |
814 | dpi::PhysicalPosition::new(1, 2) |
815 | ); |
816 | assert_eq!( |
817 | dpi::PhysicalPosition::from((2.0, 2.0)), |
818 | dpi::PhysicalPosition::new(2.0, 2.0) |
819 | ); |
820 | assert_eq!( |
821 | dpi::PhysicalPosition::from([2.0, 3.0]), |
822 | dpi::PhysicalPosition::new(2.0, 3.0) |
823 | ); |
824 | |
825 | let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into(); |
826 | assert_eq!(x, (1.0, 2.0)); |
827 | let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into(); |
828 | assert_eq!(x, [1.0, 2.0]); |
829 | } |
830 | |
831 | #[test ] |
832 | fn test_logical_size() { |
833 | let log_size = dpi::LogicalSize::new(1.0, 2.0); |
834 | assert_eq!( |
835 | log_size.to_physical::<u32>(1.0), |
836 | dpi::PhysicalSize::new(1, 2) |
837 | ); |
838 | assert_eq!( |
839 | log_size.to_physical::<u32>(2.0), |
840 | dpi::PhysicalSize::new(2, 4) |
841 | ); |
842 | assert_eq!(log_size.cast::<u32>(), dpi::LogicalSize::new(1, 2)); |
843 | assert_eq!( |
844 | log_size, |
845 | dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0) |
846 | ); |
847 | assert_eq!( |
848 | log_size, |
849 | dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0) |
850 | ); |
851 | assert_eq!( |
852 | dpi::LogicalSize::from((2.0, 2.0)), |
853 | dpi::LogicalSize::new(2.0, 2.0) |
854 | ); |
855 | assert_eq!( |
856 | dpi::LogicalSize::from([2.0, 3.0]), |
857 | dpi::LogicalSize::new(2.0, 3.0) |
858 | ); |
859 | |
860 | let x: (f64, f64) = log_size.into(); |
861 | assert_eq!(x, (1.0, 2.0)); |
862 | let x: [f64; 2] = log_size.into(); |
863 | assert_eq!(x, [1.0, 2.0]); |
864 | } |
865 | |
866 | #[test ] |
867 | fn test_physical_size() { |
868 | assert_eq!( |
869 | dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0), |
870 | dpi::PhysicalSize::new(1, 2) |
871 | ); |
872 | assert_eq!( |
873 | dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5), |
874 | dpi::PhysicalSize::new(1, 2) |
875 | ); |
876 | assert_eq!( |
877 | dpi::PhysicalSize::from((2.0, 2.0)), |
878 | dpi::PhysicalSize::new(2.0, 2.0) |
879 | ); |
880 | assert_eq!( |
881 | dpi::PhysicalSize::from([2.0, 3.0]), |
882 | dpi::PhysicalSize::new(2.0, 3.0) |
883 | ); |
884 | |
885 | let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into(); |
886 | assert_eq!(x, (1.0, 2.0)); |
887 | let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into(); |
888 | assert_eq!(x, [1.0, 2.0]); |
889 | } |
890 | |
891 | #[test ] |
892 | fn test_size() { |
893 | assert_eq!( |
894 | dpi::Size::new(dpi::PhysicalSize::new(1, 2)), |
895 | dpi::Size::Physical(dpi::PhysicalSize::new(1, 2)) |
896 | ); |
897 | assert_eq!( |
898 | dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)), |
899 | dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0)) |
900 | ); |
901 | |
902 | assert_eq!( |
903 | dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(1.0), |
904 | dpi::LogicalSize::new(1.0, 2.0) |
905 | ); |
906 | assert_eq!( |
907 | dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::<f64>(2.0), |
908 | dpi::LogicalSize::new(0.5, 1.0) |
909 | ); |
910 | assert_eq!( |
911 | dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::<f64>(1.0), |
912 | dpi::LogicalSize::new(1.0, 2.0) |
913 | ); |
914 | |
915 | assert_eq!( |
916 | dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(1.0), |
917 | dpi::PhysicalSize::new(1, 2) |
918 | ); |
919 | assert_eq!( |
920 | dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::<u32>(2.0), |
921 | dpi::PhysicalSize::new(1, 2) |
922 | ); |
923 | assert_eq!( |
924 | dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(1.0), |
925 | dpi::PhysicalSize::new(1, 2) |
926 | ); |
927 | assert_eq!( |
928 | dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::<u32>(2.0), |
929 | dpi::PhysicalSize::new(2, 4) |
930 | ); |
931 | |
932 | let small = dpi::Size::Physical((1, 2).into()); |
933 | let medium = dpi::Size::Logical((3, 4).into()); |
934 | let medium_physical = dpi::Size::new(medium.to_physical::<u32>(1.0)); |
935 | let large = dpi::Size::Physical((5, 6).into()); |
936 | assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical); |
937 | assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical); |
938 | assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical); |
939 | } |
940 | |
941 | #[test ] |
942 | fn test_position() { |
943 | assert_eq!( |
944 | dpi::Position::new(dpi::PhysicalPosition::new(1, 2)), |
945 | dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2)) |
946 | ); |
947 | assert_eq!( |
948 | dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)), |
949 | dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0)) |
950 | ); |
951 | |
952 | assert_eq!( |
953 | dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(1.0), |
954 | dpi::LogicalPosition::new(1.0, 2.0) |
955 | ); |
956 | assert_eq!( |
957 | dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::<f64>(2.0), |
958 | dpi::LogicalPosition::new(0.5, 1.0) |
959 | ); |
960 | assert_eq!( |
961 | dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::<f64>(1.0), |
962 | dpi::LogicalPosition::new(1.0, 2.0) |
963 | ); |
964 | |
965 | assert_eq!( |
966 | dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(1.0), |
967 | dpi::PhysicalPosition::new(1, 2) |
968 | ); |
969 | assert_eq!( |
970 | dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::<u32>(2.0), |
971 | dpi::PhysicalPosition::new(1, 2) |
972 | ); |
973 | assert_eq!( |
974 | dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(1.0), |
975 | dpi::PhysicalPosition::new(1, 2) |
976 | ); |
977 | assert_eq!( |
978 | dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::<u32>(2.0), |
979 | dpi::PhysicalPosition::new(2, 4) |
980 | ); |
981 | } |
982 | |
983 | // Eat coverage for the Debug impls et al |
984 | #[test ] |
985 | fn ensure_attrs_do_not_panic() { |
986 | let _ = format!(" {:?}" , dpi::LogicalPosition::<u32>::default().clone()); |
987 | HashSet::new().insert(dpi::LogicalPosition::<u32>::default()); |
988 | |
989 | let _ = format!(" {:?}" , dpi::PhysicalPosition::<u32>::default().clone()); |
990 | HashSet::new().insert(dpi::PhysicalPosition::<u32>::default()); |
991 | |
992 | let _ = format!(" {:?}" , dpi::LogicalSize::<u32>::default().clone()); |
993 | HashSet::new().insert(dpi::LogicalSize::<u32>::default()); |
994 | |
995 | let _ = format!(" {:?}" , dpi::PhysicalSize::<u32>::default().clone()); |
996 | HashSet::new().insert(dpi::PhysicalSize::<u32>::default()); |
997 | |
998 | let _ = format!(" {:?}" , dpi::Size::Physical((1, 2).into()).clone()); |
999 | let _ = format!(" {:?}" , dpi::Position::Physical((1, 2).into()).clone()); |
1000 | } |
1001 | } |
1002 | |