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}