Skip to main content

tessera_ui/
dp.rs

1//! Density-independent layout units.
2//!
3//! ## Usage
4//!
5//! Size public UI in Dp; conversions use the active runtime session density.
6
7use std::{
8    fmt::Display,
9    ops::{Div, Mul},
10};
11
12use crate::{Px, RuntimeSession};
13
14/// Sets the positive finite density scale for the active UI session.
15///
16/// # Examples
17///
18/// ```
19/// use tessera_ui::{RuntimeSession, dp::{scale_factor, set_scale_factor}};
20/// let session = RuntimeSession::new();
21/// session.run(|| {
22///     set_scale_factor(2.0);
23///     assert_eq!(scale_factor(), 2.0);
24/// });
25/// ```
26pub fn set_scale_factor(scale_factor: f64) {
27    debug_assert!(
28        scale_factor.is_finite() && scale_factor > 0.0,
29        "set_scale_factor called with a non-positive or non-finite value \
30         ({scale_factor}); keeping the previous scale factor. Expected a \
31         positive finite value."
32    );
33    if scale_factor.is_finite() && scale_factor > 0.0 {
34        RuntimeSession::current()
35            .expect("density changes require an active UI session")
36            .set_density(scale_factor);
37    }
38}
39
40/// Returns the active session density, or 1.0 outside a session.
41pub fn scale_factor() -> f64 {
42    RuntimeSession::current().map_or(1.0, |session| session.density())
43}
44
45/// Density-independent layout units, converted using the active session's scale.
46///
47/// ```
48/// use tessera_ui::{Dp, RuntimeSession, dp::set_scale_factor};
49/// let session = RuntimeSession::new();
50/// let _scope = session.enter();
51/// set_scale_factor(1.5);
52/// assert_eq!(Dp(8.0).to_pixels_f64(), 12.0);
53/// assert_eq!(Dp::from_pixels_f64(12.0), Dp(8.0));
54/// ```
55#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
56pub struct Dp(pub f64);
57
58impl Display for Dp {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{:.2}dp", self.0)
61    }
62}
63
64impl Dp {
65    /// A constant representing zero density-independent pixels.
66    pub const ZERO: Dp = Dp(0.0);
67
68    /// Creates a new `Dp` instance with the specified value.
69    pub const fn new(value: f64) -> Self {
70        Dp(value)
71    }
72
73    /// Scales to physical pixels using the active session's density.
74    pub fn to_pixels_f64(&self) -> f64 {
75        let scale_factor = scale_factor();
76        self.0 * scale_factor
77    }
78
79    /// Converts physical pixels using the active session's density.
80    pub fn from_pixels_f64(value: f64) -> Self {
81        let scale_factor = scale_factor();
82        Dp(value / scale_factor)
83    }
84
85    /// Scales to physical pixels, truncating toward zero and saturating to `u32`.
86    pub fn to_pixels_u32(&self) -> u32 {
87        let scale_factor = scale_factor();
88        (self.0 * scale_factor) as u32
89    }
90
91    /// Converts physical pixels using the active session's density.
92    pub fn from_pixels_u32(value: u32) -> Self {
93        let scale_factor = scale_factor();
94        Dp((value as f64) / scale_factor)
95    }
96
97    /// Scales to physical pixels with `f32` precision.
98    pub fn to_pixels_f32(&self) -> f32 {
99        let scale_factor = scale_factor();
100        (self.0 * scale_factor) as f32
101    }
102
103    /// Converts physical pixels using the active session's density.
104    pub fn from_pixels_f32(value: f32) -> Self {
105        let scale_factor = scale_factor();
106        Dp((value as f64) / scale_factor)
107    }
108
109    /// Scales to whole physical pixels, truncating the fractional part.
110    pub fn to_px(&self) -> Px {
111        Px::from_f32(self.to_pixels_f32())
112    }
113}
114
115impl From<f64> for Dp {
116    /// Creates a `Dp` instance from an `f64` value.
117    fn from(value: f64) -> Self {
118        Dp(value)
119    }
120}
121
122impl From<Px> for Dp {
123    /// Creates a `Dp` instance from a `Px` (physical pixels) value.
124    fn from(px: Px) -> Self {
125        px.to_dp()
126    }
127}
128
129impl Mul<f32> for Dp {
130    type Output = Dp;
131
132    fn mul(self, rhs: f32) -> Self::Output {
133        Dp(self.0 * (rhs as f64))
134    }
135}
136
137impl Div<f32> for Dp {
138    type Output = Dp;
139
140    fn div(self, rhs: f32) -> Self::Output {
141        Dp(self.0 / (rhs as f64))
142    }
143}
144
145impl Mul<f64> for Dp {
146    type Output = Dp;
147
148    fn mul(self, rhs: f64) -> Self::Output {
149        Dp(self.0 * rhs)
150    }
151}
152
153impl Div<f64> for Dp {
154    type Output = Dp;
155
156    fn div(self, rhs: f64) -> Self::Output {
157        Dp(self.0 / rhs)
158    }
159}
160
161#[cfg(test)]
162mod set_scale_factor_tests {
163    use std::panic::catch_unwind;
164
165    use super::*;
166
167    #[test]
168    fn density_rejects_invalid_values_without_replacing_the_last_valid_scale() {
169        let session = crate::RuntimeSession::new();
170        let _scope = session.enter();
171        set_scale_factor(2.5);
172        assert_eq!(scale_factor(), 2.5);
173        assert_eq!(Dp(10.0).to_pixels_f64(), 25.0);
174        for invalid in [f64::NAN, 0.0, -1.0, f64::INFINITY, f64::NEG_INFINITY] {
175            let rejected = catch_unwind(|| set_scale_factor(invalid));
176            assert_eq!(rejected.is_err(), cfg!(debug_assertions));
177            assert_eq!(scale_factor(), 2.5);
178            assert_eq!(Dp(10.0).to_pixels_f64(), 25.0);
179            assert_eq!(Dp(10.0).to_pixels_f32(), 25.0);
180            assert_eq!(Dp(10.0).to_px(), Px(25));
181        }
182    }
183}