kurbo/insets.rs
1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A description of the distances between the edges of two rectangles.
5
6use core::ops::{Add, Div, Mul, Neg, Sub};
7
8use crate::{Rect, Size};
9
10/// Insets from the edges of a rectangle.
11///
12///
13/// The inset value for each edge can be thought of as a delta computed from
14/// the center of the rect to that edge. For instance, with an inset of `2.0` on
15/// the x-axis, a rectangle with the origin `(0.0, 0.0)` with that inset added
16/// will have the new origin at `(-2.0, 0.0)`.
17///
18/// Put alternatively, a positive inset represents increased distance from center,
19/// and a negative inset represents decreased distance from center.
20///
21/// # Examples
22///
23/// Positive insets added to a [`Rect`] produce a larger [`Rect`]:
24/// ```
25/// # use kurbo::{Insets, Rect};
26/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.,));
27/// let insets = Insets::uniform_xy(3., 0.,);
28///
29/// let inset_rect = rect + insets;
30/// assert_eq!(inset_rect.width(), 16.0, "10.0 + 3.0 × 2");
31/// assert_eq!(inset_rect.x0, -3.0);
32/// ```
33///
34/// Negative insets added to a [`Rect`] produce a smaller [`Rect`]:
35///
36/// ```
37/// # use kurbo::{Insets, Rect};
38/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.,));
39/// let insets = Insets::uniform_xy(-3., 0.,);
40///
41/// let inset_rect = rect + insets;
42/// assert_eq!(inset_rect.width(), 4.0, "10.0 - 3.0 × 2");
43/// assert_eq!(inset_rect.x0, 3.0);
44/// ```
45///
46/// [`Insets`] operate on the absolute rectangle [`Rect::abs`], and so ignore
47/// existing negative widths and heights.
48///
49/// ```
50/// # use kurbo::{Insets, Rect};
51/// let rect = Rect::new(7., 11., 0., 0.,);
52/// let insets = Insets::uniform_xy(0., 1.,);
53///
54/// assert_eq!(rect.width(), -7.0);
55///
56/// let inset_rect = rect + insets;
57/// assert_eq!(inset_rect.width(), 7.0);
58/// assert_eq!(inset_rect.x0, 0.0);
59/// assert_eq!(inset_rect.height(), 13.0);
60/// ```
61///
62/// The width and height of an inset operation can still be negative if the
63/// [`Insets`]' dimensions are greater than the dimensions of the original [`Rect`].
64///
65/// ```
66/// # use kurbo::{Insets, Rect};
67/// let rect = Rect::new(0., 0., 3., 5.);
68/// let insets = Insets::uniform_xy(0., 7.,);
69///
70/// let inset_rect = rect - insets;
71/// assert_eq!(inset_rect.height(), -9., "5 - 7 × 2")
72/// ```
73///
74/// `Rect - Rect = Insets`:
75///
76///
77/// ```
78/// # use kurbo::{Insets, Rect};
79/// let rect = Rect::new(0., 0., 5., 11.);
80/// let insets = Insets::uniform_xy(1., 7.,);
81///
82/// let inset_rect = rect + insets;
83/// let insets2 = inset_rect - rect;
84///
85/// assert_eq!(insets2.x0, insets.x0);
86/// assert_eq!(insets2.y1, insets.y1);
87/// assert_eq!(insets2.x_value(), insets.x_value());
88/// assert_eq!(insets2.y_value(), insets.y_value());
89/// ```
90#[derive(Clone, Copy, Default, Debug, PartialEq)]
91#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct Insets {
94 /// The minimum x coordinate (left edge).
95 pub x0: f64,
96 /// The minimum y coordinate (top edge in y-down spaces).
97 pub y0: f64,
98 /// The maximum x coordinate (right edge).
99 pub x1: f64,
100 /// The maximum y coordinate (bottom edge in y-down spaces).
101 pub y1: f64,
102}
103
104impl Insets {
105 /// Zeroed insets.
106 pub const ZERO: Insets = Insets::uniform(0.);
107
108 /// New uniform insets.
109 #[inline(always)]
110 pub const fn uniform(d: f64) -> Insets {
111 Insets {
112 x0: d,
113 y0: d,
114 x1: d,
115 y1: d,
116 }
117 }
118
119 /// New insets with uniform values along each axis.
120 #[inline(always)]
121 pub const fn uniform_xy(x: f64, y: f64) -> Insets {
122 Insets {
123 x0: x,
124 y0: y,
125 x1: x,
126 y1: y,
127 }
128 }
129
130 /// New insets. The ordering of the arguments is "left, top, right, bottom",
131 /// assuming a y-down coordinate space.
132 #[inline(always)]
133 pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Insets {
134 Insets { x0, y0, x1, y1 }
135 }
136
137 /// The total delta on the x-axis represented by these insets.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use kurbo::Insets;
143 ///
144 /// let insets = Insets::uniform_xy(3., 8.);
145 /// assert_eq!(insets.x_value(), 6.);
146 ///
147 /// let insets = Insets::new(5., 0., -12., 0.,);
148 /// assert_eq!(insets.x_value(), -7.);
149 /// ```
150 #[inline]
151 pub fn x_value(self) -> f64 {
152 self.x0 + self.x1
153 }
154
155 /// The total delta on the y-axis represented by these insets.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// use kurbo::Insets;
161 ///
162 /// let insets = Insets::uniform_xy(3., 7.);
163 /// assert_eq!(insets.y_value(), 14.);
164 ///
165 /// let insets = Insets::new(5., 10., -12., 4.,);
166 /// assert_eq!(insets.y_value(), 14.);
167 /// ```
168 #[inline]
169 pub fn y_value(self) -> f64 {
170 self.y0 + self.y1
171 }
172
173 /// Returns the total delta represented by these insets as a [`Size`].
174 ///
175 /// This is equivalent to creating a [`Size`] from the values returned by
176 /// [`x_value`] and [`y_value`].
177 ///
178 /// This function may return a size with negative values.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use kurbo::{Insets, Size};
184 ///
185 /// let insets = Insets::new(11.1, -43.3, 3.333, -0.0);
186 /// assert_eq!(insets.size(), Size::new(insets.x_value(), insets.y_value()));
187 /// ```
188 ///
189 /// [`x_value`]: Insets::x_value
190 /// [`y_value`]: Insets::y_value
191 pub fn size(self) -> Size {
192 Size::new(self.x_value(), self.y_value())
193 }
194
195 /// Return `true` iff all values are nonnegative.
196 pub fn are_nonnegative(self) -> bool {
197 let Insets { x0, y0, x1, y1 } = self;
198 x0 >= 0.0 && y0 >= 0.0 && x1 >= 0.0 && y1 >= 0.0
199 }
200
201 /// Return new `Insets` with all negative values replaced with `0.0`.
202 ///
203 /// This is provided as a convenience for applications where negative insets
204 /// are not meaningful.
205 ///
206 /// # Examples
207 ///
208 /// ```
209 /// use kurbo::Insets;
210 ///
211 /// let insets = Insets::new(-10., 3., -0.2, 4.);
212 /// let nonnegative = insets.nonnegative();
213 /// assert_eq!(nonnegative.x_value(), 0.0);
214 /// assert_eq!(nonnegative.y_value(), 7.0);
215 /// ```
216 pub fn nonnegative(self) -> Insets {
217 let Insets { x0, y0, x1, y1 } = self;
218 Insets {
219 x0: x0.max(0.0),
220 y0: y0.max(0.0),
221 x1: x1.max(0.0),
222 y1: y1.max(0.0),
223 }
224 }
225
226 /// Are these insets finite?
227 #[inline]
228 pub fn is_finite(&self) -> bool {
229 self.x0.is_finite() && self.y0.is_finite() && self.x1.is_finite() && self.y1.is_finite()
230 }
231
232 /// Are these insets NaN?
233 #[inline]
234 pub fn is_nan(&self) -> bool {
235 self.x0.is_nan() || self.y0.is_nan() || self.x1.is_nan() || self.y1.is_nan()
236 }
237}
238
239impl Neg for Insets {
240 type Output = Insets;
241
242 #[inline]
243 fn neg(self) -> Insets {
244 Insets::new(-self.x0, -self.y0, -self.x1, -self.y1)
245 }
246}
247
248impl Add<Rect> for Insets {
249 type Output = Rect;
250
251 #[inline]
252 #[allow(clippy::suspicious_arithmetic_impl)]
253 fn add(self, other: Rect) -> Rect {
254 let other = other.abs();
255 Rect::new(
256 other.x0 - self.x0,
257 other.y0 - self.y0,
258 other.x1 + self.x1,
259 other.y1 + self.y1,
260 )
261 }
262}
263
264impl Add<Insets> for Rect {
265 type Output = Rect;
266
267 #[inline]
268 fn add(self, other: Insets) -> Rect {
269 other + self
270 }
271}
272
273impl Sub<Rect> for Insets {
274 type Output = Rect;
275
276 #[inline]
277 fn sub(self, other: Rect) -> Rect {
278 other + -self
279 }
280}
281
282impl Mul<f64> for Insets {
283 type Output = Insets;
284
285 fn mul(self, rhs: f64) -> Self::Output {
286 Self {
287 x0: self.x0 * rhs,
288 y0: self.y0 * rhs,
289 x1: self.x1 * rhs,
290 y1: self.y1 * rhs,
291 }
292 }
293}
294
295impl Div<f64> for Insets {
296 type Output = Insets;
297
298 fn div(self, rhs: f64) -> Self::Output {
299 Self {
300 x0: self.x0 / rhs,
301 y0: self.y0 / rhs,
302 x1: self.x1 / rhs,
303 y1: self.y1 / rhs,
304 }
305 }
306}
307
308impl Sub<Insets> for Rect {
309 type Output = Rect;
310
311 #[inline]
312 fn sub(self, other: Insets) -> Rect {
313 other - self
314 }
315}
316
317impl From<f64> for Insets {
318 #[inline(always)]
319 fn from(src: f64) -> Insets {
320 Insets::uniform(src)
321 }
322}
323
324impl From<(f64, f64)> for Insets {
325 #[inline(always)]
326 fn from(src: (f64, f64)) -> Insets {
327 Insets::uniform_xy(src.0, src.1)
328 }
329}
330
331impl From<(f64, f64, f64, f64)> for Insets {
332 #[inline(always)]
333 fn from(src: (f64, f64, f64, f64)) -> Insets {
334 Insets::new(src.0, src.1, src.2, src.3)
335 }
336}