Skip to main content

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}