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
107pub 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
114impl Pixel for u8 {
115 fn from_f64(f: f64) -> Self {
116 f.round() as u8
117 }
118}
119impl Pixel for u16 {
120 fn from_f64(f: f64) -> Self {
121 f.round() as u16
122 }
123}
124impl Pixel for u32 {
125 fn from_f64(f: f64) -> Self {
126 f.round() as u32
127 }
128}
129impl Pixel for i8 {
130 fn from_f64(f: f64) -> Self {
131 f.round() as i8
132 }
133}
134impl Pixel for i16 {
135 fn from_f64(f: f64) -> Self {
136 f.round() as i16
137 }
138}
139impl Pixel for i32 {
140 fn from_f64(f: f64) -> Self {
141 f.round() as i32
142 }
143}
144impl Pixel for f32 {
145 fn from_f64(f: f64) -> Self {
146 f as f32
147 }
148}
149impl 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]
161pub 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))]
172pub struct LogicalPosition<P> {
173 pub x: P,
174 pub y: P,
175}
176
177impl<P> LogicalPosition<P> {
178 #[inline]
179 pub const fn new(x: P, y: P) -> Self {
180 LogicalPosition { x, y }
181 }
182}
183
184impl<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
210impl<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
216impl<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
222impl<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
228impl<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")]
235impl<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")]
242impl<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))]
251pub struct PhysicalPosition<P> {
252 pub x: P,
253 pub y: P,
254}
255
256impl<P> PhysicalPosition<P> {
257 #[inline]
258 pub const fn new(x: P, y: P) -> Self {
259 PhysicalPosition { x, y }
260 }
261}
262
263impl<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
289impl<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
295impl<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
301impl<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
307impl<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")]
314impl<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")]
321impl<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))]
330pub struct LogicalSize<P> {
331 pub width: P,
332 pub height: P,
333}
334
335impl<P> LogicalSize<P> {
336 #[inline]
337 pub const fn new(width: P, height: P) -> Self {
338 LogicalSize { width, height }
339 }
340}
341
342impl<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
368impl<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
374impl<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
380impl<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
386impl<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")]
393impl<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")]
400impl<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))]
412pub struct PhysicalSize<P> {
413 pub width: P,
414 pub height: P,
415}
416
417impl<P> PhysicalSize<P> {
418 #[inline]
419 pub const fn new(width: P, height: P) -> Self {
420 PhysicalSize { width, height }
421 }
422}
423
424impl<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
447impl<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
453impl<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
459impl<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
465impl<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")]
472impl<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")]
479impl<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))]
491pub enum Size {
492 Physical(PhysicalSize<u32>),
493 Logical(LogicalSize<f64>),
494}
495
496impl 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
529impl<P: Pixel> From<PhysicalSize<P>> for Size {
530 #[inline]
531 fn from(size: PhysicalSize<P>) -> Size {
532 Size::Physical(size.cast())
533 }
534}
535
536impl<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))]
546pub enum Position {
547 Physical(PhysicalPosition<i32>),
548 Logical(LogicalPosition<f64>),
549}
550
551impl 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
571impl<P: Pixel> From<PhysicalPosition<P>> for Position {
572 #[inline]
573 fn from(position: PhysicalPosition<P>) -> Position {
574 Position::Physical(position.cast())
575 }
576}
577
578impl<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)]
586mod 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