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 const 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 const 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 const 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 const 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 const 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 const 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 const 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 /// Returns the component-wise minimum of `self` and `other`.
239 ///
240 /// # Examples
241 ///
242 /// ```
243 /// use kurbo::Insets;
244 ///
245 /// let this = Insets::new(1., 2., 3., 4.);
246 /// let other = Insets::new(4., 3., 2., 1.);
247 ///
248 /// assert_eq!(this.min(other), Insets::new(1., 2., 2., 1.));
249 /// ```
250 pub const fn min(self, other: Insets) -> Self {
251 Self {
252 x0: self.x0.min(other.x0),
253 x1: self.x1.min(other.x1),
254 y0: self.y0.min(other.y0),
255 y1: self.y1.min(other.y1),
256 }
257 }
258
259 /// Returns the component-wise maximum of `self` and `other`.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// use kurbo::Insets;
265 ///
266 /// let this = Insets::new(1., 2., 3., 4.);
267 /// let other = Insets::new(4., 3., 2., 1.);
268 ///
269 /// assert_eq!(this.max(other), Insets::new(4., 3., 3., 4.));
270 /// ```
271 pub const fn max(self, other: Insets) -> Self {
272 Self {
273 x0: self.x0.max(other.x0),
274 x1: self.x1.max(other.x1),
275 y0: self.y0.max(other.y0),
276 y1: self.y1.max(other.y1),
277 }
278 }
279}
280
281impl Neg for Insets {
282 type Output = Insets;
283
284 #[inline]
285 fn neg(self) -> Insets {
286 Insets::new(-self.x0, -self.y0, -self.x1, -self.y1)
287 }
288}
289
290impl Add<Insets> for Insets {
291 type Output = Insets;
292
293 fn add(self, other: Insets) -> Self::Output {
294 Insets {
295 x0: self.x0 + other.x0,
296 x1: self.x1 + other.x1,
297 y0: self.y0 + other.y0,
298 y1: self.y1 + other.y1,
299 }
300 }
301}
302
303impl Sub<Insets> for Insets {
304 type Output = Insets;
305
306 fn sub(self, other: Insets) -> Self::Output {
307 Insets {
308 x0: self.x0 - other.x0,
309 x1: self.x1 - other.x1,
310 y0: self.y0 - other.y0,
311 y1: self.y1 - other.y1,
312 }
313 }
314}
315
316impl Add<Rect> for Insets {
317 type Output = Rect;
318
319 #[inline]
320 #[allow(clippy::suspicious_arithmetic_impl)]
321 fn add(self, other: Rect) -> Rect {
322 let other = other.abs();
323 Rect::new(
324 other.x0 - self.x0,
325 other.y0 - self.y0,
326 other.x1 + self.x1,
327 other.y1 + self.y1,
328 )
329 }
330}
331
332impl Add<Insets> for Rect {
333 type Output = Rect;
334
335 #[inline]
336 fn add(self, other: Insets) -> Rect {
337 other + self
338 }
339}
340
341impl Sub<Rect> for Insets {
342 type Output = Rect;
343
344 #[inline]
345 fn sub(self, other: Rect) -> Rect {
346 other + -self
347 }
348}
349
350impl Mul<f64> for Insets {
351 type Output = Insets;
352
353 fn mul(self, rhs: f64) -> Self::Output {
354 Self {
355 x0: self.x0 * rhs,
356 y0: self.y0 * rhs,
357 x1: self.x1 * rhs,
358 y1: self.y1 * rhs,
359 }
360 }
361}
362
363impl Div<f64> for Insets {
364 type Output = Insets;
365
366 fn div(self, rhs: f64) -> Self::Output {
367 Self {
368 x0: self.x0 / rhs,
369 y0: self.y0 / rhs,
370 x1: self.x1 / rhs,
371 y1: self.y1 / rhs,
372 }
373 }
374}
375
376impl Sub<Insets> for Rect {
377 type Output = Rect;
378
379 #[inline]
380 fn sub(self, other: Insets) -> Rect {
381 other - self
382 }
383}
384
385impl From<f64> for Insets {
386 #[inline(always)]
387 fn from(src: f64) -> Insets {
388 Insets::uniform(src)
389 }
390}
391
392impl From<(f64, f64)> for Insets {
393 #[inline(always)]
394 fn from(src: (f64, f64)) -> Insets {
395 Insets::uniform_xy(src.0, src.1)
396 }
397}
398
399impl From<(f64, f64, f64, f64)> for Insets {
400 #[inline(always)]
401 fn from(src: (f64, f64, f64, f64)) -> Insets {
402 Insets::new(src.0, src.1, src.2, src.3)
403 }
404}