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, 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]
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]
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]
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: Rect = other.abs();
255 Rect::new(
256 x0:other.x0 - self.x0,
257 y0:other.y0 - self.y0,
258 x1:other.x1 + self.x1,
259 y1: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 Sub<Insets> for Rect {
283 type Output = Rect;
284
285 #[inline]
286 fn sub(self, other: Insets) -> Rect {
287 other - self
288 }
289}
290
291impl From<f64> for Insets {
292 fn from(src: f64) -> Insets {
293 Insets::uniform(src)
294 }
295}
296
297impl From<(f64, f64)> for Insets {
298 fn from(src: (f64, f64)) -> Insets {
299 Insets::uniform_xy(x:src.0, y:src.1)
300 }
301}
302
303impl From<(f64, f64, f64, f64)> for Insets {
304 fn from(src: (f64, f64, f64, f64)) -> Insets {
305 Insets::new(x0:src.0, y0:src.1, x1:src.2, y1:src.3)
306 }
307}
308