druid_shell/
scale.rs

1// Copyright 2020 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Resolution scale related helpers.
16
17use crate::kurbo::{Insets, Line, Point, Rect, Size, Vec2};
18
19/// Coordinate scaling between pixels and display points.
20///
21/// This holds the platform scale factors.
22///
23/// ## Pixels and Display Points
24///
25/// A pixel (**px**) represents the smallest controllable area of color on the platform.
26/// A display point (**dp**) is a resolution independent logical unit.
27/// When developing your application you should primarily be thinking in display points.
28/// These display points will be automatically converted into pixels under the hood.
29/// One pixel is equal to one display point when the platform scale factor is `1.0`.
30///
31/// Read more about pixels and display points [in the Druid book].
32///
33/// ## Converting with `Scale`
34///
35/// To translate coordinates between pixels and display points you should use one of the
36/// helper conversion methods of `Scale` or for manual conversion [`x`] / [`y`].
37///
38/// `Scale` is designed for responsive applications, including responding to platform scale changes.
39/// The platform scale can change quickly, e.g. when moving a window from one monitor to another.
40///
41/// A copy of `Scale` will be stale as soon as the platform scale changes.
42///
43/// [`x`]: #method.x
44/// [`y`]: #method.y
45/// [in the Druid book]: https://linebender.org/druid/07_resolution_independence.html
46#[derive(Copy, Clone, PartialEq, Debug)]
47pub struct Scale {
48    /// The scale factor on the x axis.
49    x: f64,
50    /// The scale factor on the y axis.
51    y: f64,
52}
53
54/// A specific area scaling state.
55///
56/// This holds the platform area size in pixels and the logical area size in display points.
57///
58/// The platform area size in pixels tends to be limited to integers and `ScaledArea` works
59/// under that assumption.
60///
61/// The logical area size in display points is an unrounded conversion, which means that it is
62/// often not limited to integers. This allows for accurate calculations of
63/// the platform area pixel boundaries from the logical area using a [`Scale`].
64///
65/// Even though the logical area size can be fractional, the integer boundaries of that logical area
66/// will still match up with the platform area pixel boundaries as often as the scale factor allows.
67///
68/// A copy of `ScaledArea` will be stale as soon as the platform area size changes.
69///
70/// [`Scale`]: struct.Scale.html
71#[derive(Copy, Clone, PartialEq, Debug)]
72pub struct ScaledArea {
73    /// The size of the scaled area in display points.
74    size_dp: Size,
75    /// The size of the scaled area in pixels.
76    size_px: Size,
77}
78
79/// The `Scalable` trait describes how coordinates should be translated
80/// from display points into pixels and vice versa using a [`Scale`].
81///
82/// [`Scale`]: struct.Scale.html
83pub trait Scalable {
84    /// Converts the scalable item from display points into pixels,
85    /// using the x axis scale factor for coordinates on the x axis
86    /// and the y axis scale factor for coordinates on the y axis.
87    fn to_px(&self, scale: Scale) -> Self;
88
89    /// Converts the scalable item from pixels into display points,
90    /// using the x axis scale factor for coordinates on the x axis
91    /// and the y axis scale factor for coordinates on the y axis.
92    fn to_dp(&self, scale: Scale) -> Self;
93}
94
95impl Default for Scale {
96    fn default() -> Scale {
97        Scale { x: 1.0, y: 1.0 }
98    }
99}
100
101impl Scale {
102    /// Create a new `Scale` based on the specified axis factors.
103    ///
104    /// Units: none (scale relative to "standard" scale)
105    pub fn new(x: f64, y: f64) -> Scale {
106        Scale { x, y }
107    }
108
109    /// Returns the x axis scale factor.
110    #[inline]
111    pub fn x(self) -> f64 {
112        self.x
113    }
114
115    /// Returns the y axis scale factor.
116    #[inline]
117    pub fn y(self) -> f64 {
118        self.y
119    }
120
121    /// Converts from pixels into display points, using the x axis scale factor.
122    #[inline]
123    pub fn px_to_dp_x<T: Into<f64>>(self, x: T) -> f64 {
124        x.into() / self.x
125    }
126
127    /// Converts from pixels into display points, using the y axis scale factor.
128    #[inline]
129    pub fn px_to_dp_y<T: Into<f64>>(self, y: T) -> f64 {
130        y.into() / self.y
131    }
132
133    /// Converts from pixels into display points,
134    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
135    #[inline]
136    pub fn px_to_dp_xy<T: Into<f64>>(self, x: T, y: T) -> (f64, f64) {
137        (x.into() / self.x, y.into() / self.y)
138    }
139}
140
141impl Scalable for Vec2 {
142    /// Converts a `Vec2` from display points into pixels,
143    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
144    #[inline]
145    fn to_px(&self, scale: Scale) -> Vec2 {
146        Vec2::new(self.x * scale.x, self.y * scale.y)
147    }
148
149    /// Converts a `Vec2` from pixels into display points,
150    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
151    #[inline]
152    fn to_dp(&self, scale: Scale) -> Vec2 {
153        Vec2::new(self.x / scale.x, self.y / scale.y)
154    }
155}
156
157impl Scalable for Point {
158    /// Converts a `Point` from display points into pixels,
159    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
160    #[inline]
161    fn to_px(&self, scale: Scale) -> Point {
162        Point::new(self.x * scale.x, self.y * scale.y)
163    }
164
165    /// Converts a `Point` from pixels into display points,
166    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
167    #[inline]
168    fn to_dp(&self, scale: Scale) -> Point {
169        Point::new(self.x / scale.x, self.y / scale.y)
170    }
171}
172
173impl Scalable for Line {
174    /// Converts a `Line` from display points into pixels,
175    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
176    #[inline]
177    fn to_px(&self, scale: Scale) -> Line {
178        Line::new(self.p0.to_px(scale), self.p1.to_px(scale))
179    }
180
181    /// Converts a `Line` from pixels into display points,
182    /// using the x axis scale factor for `x` and the y axis scale factor for `y`.
183    #[inline]
184    fn to_dp(&self, scale: Scale) -> Line {
185        Line::new(self.p0.to_dp(scale), self.p1.to_dp(scale))
186    }
187}
188
189impl Scalable for Size {
190    /// Converts a `Size` from display points into pixels,
191    /// using the x axis scale factor for `width`
192    /// and the y axis scale factor for `height`.
193    #[inline]
194    fn to_px(&self, scale: Scale) -> Size {
195        Size::new(self.width * scale.x, self.height * scale.y)
196    }
197
198    /// Converts a `Size` from pixels into points,
199    /// using the x axis scale factor for `width`
200    /// and the y axis scale factor for `height`.
201    #[inline]
202    fn to_dp(&self, scale: Scale) -> Size {
203        Size::new(self.width / scale.x, self.height / scale.y)
204    }
205}
206
207impl Scalable for Rect {
208    /// Converts a `Rect` from display points into pixels,
209    /// using the x axis scale factor for `x0` and `x1`
210    /// and the y axis scale factor for `y0` and `y1`.
211    #[inline]
212    fn to_px(&self, scale: Scale) -> Rect {
213        Rect::new(
214            self.x0 * scale.x,
215            self.y0 * scale.y,
216            self.x1 * scale.x,
217            self.y1 * scale.y,
218        )
219    }
220
221    /// Converts a `Rect` from pixels into display points,
222    /// using the x axis scale factor for `x0` and `x1`
223    /// and the y axis scale factor for `y0` and `y1`.
224    #[inline]
225    fn to_dp(&self, scale: Scale) -> Rect {
226        Rect::new(
227            self.x0 / scale.x,
228            self.y0 / scale.y,
229            self.x1 / scale.x,
230            self.y1 / scale.y,
231        )
232    }
233}
234
235impl Scalable for Insets {
236    /// Converts `Insets` from display points into pixels,
237    /// using the x axis scale factor for `x0` and `x1`
238    /// and the y axis scale factor for `y0` and `y1`.
239    #[inline]
240    fn to_px(&self, scale: Scale) -> Insets {
241        Insets::new(
242            self.x0 * scale.x,
243            self.y0 * scale.y,
244            self.x1 * scale.x,
245            self.y1 * scale.y,
246        )
247    }
248
249    /// Converts `Insets` from pixels into display points,
250    /// using the x axis scale factor for `x0` and `x1`
251    /// and the y axis scale factor for `y0` and `y1`.
252    #[inline]
253    fn to_dp(&self, scale: Scale) -> Insets {
254        Insets::new(
255            self.x0 / scale.x,
256            self.y0 / scale.y,
257            self.x1 / scale.x,
258            self.y1 / scale.y,
259        )
260    }
261}
262
263impl Default for ScaledArea {
264    fn default() -> ScaledArea {
265        ScaledArea {
266            size_dp: Size::ZERO,
267            size_px: Size::ZERO,
268        }
269    }
270}
271
272impl ScaledArea {
273    /// Create a new scaled area from pixels.
274    pub fn from_px<T: Into<Size>>(size: T, scale: Scale) -> ScaledArea {
275        let size_px = size.into();
276        let size_dp = size_px.to_dp(scale);
277        ScaledArea { size_dp, size_px }
278    }
279
280    /// Create a new scaled area from display points.
281    ///
282    /// The calculated size in pixels is rounded away from zero to integers.
283    /// That means that the scaled area size in display points isn't always the same
284    /// as the `size` given to this function. To find out the new size in points use [`size_dp`].
285    ///
286    /// [`size_dp`]: #method.size_dp
287    pub fn from_dp<T: Into<Size>>(size: T, scale: Scale) -> ScaledArea {
288        let size_px = size.into().to_px(scale).expand();
289        let size_dp = size_px.to_dp(scale);
290        ScaledArea { size_dp, size_px }
291    }
292
293    /// Returns the scaled area size in display points.
294    #[inline]
295    pub fn size_dp(&self) -> Size {
296        self.size_dp
297    }
298
299    /// Returns the scaled area size in pixels.
300    #[inline]
301    pub fn size_px(&self) -> Size {
302        self.size_px
303    }
304}