euclid/
side_offsets.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! A group of side offsets, which correspond to top/left/bottom/right for borders, padding,
11//! and margins in CSS.
12
13use crate::length::Length;
14use crate::num::Zero;
15use crate::scale::Scale;
16use crate::Vector2D;
17use core::cmp::{Eq, PartialEq};
18use core::fmt;
19use core::hash::Hash;
20use core::marker::PhantomData;
21use core::ops::{Add, Div, DivAssign, Mul, MulAssign, Neg};
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24
25/// A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding,
26/// and margins in CSS, optionally tagged with a unit.
27#[repr(C)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29#[cfg_attr(
30    feature = "serde",
31    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
32)]
33pub struct SideOffsets2D<T, U> {
34    pub top: T,
35    pub right: T,
36    pub bottom: T,
37    pub left: T,
38    #[doc(hidden)]
39    pub _unit: PhantomData<U>,
40}
41
42impl<T: Copy, U> Copy for SideOffsets2D<T, U> {}
43
44impl<T: Clone, U> Clone for SideOffsets2D<T, U> {
45    fn clone(&self) -> Self {
46        SideOffsets2D {
47            top: self.top.clone(),
48            right: self.right.clone(),
49            bottom: self.bottom.clone(),
50            left: self.left.clone(),
51            _unit: PhantomData,
52        }
53    }
54}
55
56impl<T, U> Eq for SideOffsets2D<T, U> where T: Eq {}
57
58impl<T, U> PartialEq for SideOffsets2D<T, U>
59where
60    T: PartialEq,
61{
62    fn eq(&self, other: &Self) -> bool {
63        self.top == other.top
64            && self.right == other.right
65            && self.bottom == other.bottom
66            && self.left == other.left
67    }
68}
69
70impl<T, U> Hash for SideOffsets2D<T, U>
71where
72    T: Hash,
73{
74    fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
75        self.top.hash(h);
76        self.right.hash(h);
77        self.bottom.hash(h);
78        self.left.hash(h);
79    }
80}
81
82impl<T: fmt::Debug, U> fmt::Debug for SideOffsets2D<T, U> {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        write!(
85            f,
86            "({:?},{:?},{:?},{:?})",
87            self.top, self.right, self.bottom, self.left
88        )
89    }
90}
91
92impl<T: Default, U> Default for SideOffsets2D<T, U> {
93    fn default() -> Self {
94        SideOffsets2D {
95            top: Default::default(),
96            right: Default::default(),
97            bottom: Default::default(),
98            left: Default::default(),
99            _unit: PhantomData,
100        }
101    }
102}
103
104impl<T, U> SideOffsets2D<T, U> {
105    /// Constructor taking a scalar for each side.
106    ///
107    /// Sides are specified in top-right-bottom-left order following
108    /// CSS's convention.
109    pub const fn new(top: T, right: T, bottom: T, left: T) -> Self {
110        SideOffsets2D {
111            top,
112            right,
113            bottom,
114            left,
115            _unit: PhantomData,
116        }
117    }
118
119    /// Constructor taking a typed Length for each side.
120    ///
121    /// Sides are specified in top-right-bottom-left order following
122    /// CSS's convention.
123    pub fn from_lengths(
124        top: Length<T, U>,
125        right: Length<T, U>,
126        bottom: Length<T, U>,
127        left: Length<T, U>,
128    ) -> Self {
129        SideOffsets2D::new(top.0, right.0, bottom.0, left.0)
130    }
131
132    /// Construct side offsets from min and a max vector offsets.
133    ///
134    /// The outer rect of the resulting side offsets is equivalent to translating
135    /// a rectangle's upper-left corner with the min vector and translating the
136    /// bottom-right corner with the max vector.
137    pub fn from_vectors_outer(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
138    where
139        T: Neg<Output = T>,
140    {
141        SideOffsets2D {
142            left: -min.x,
143            top: -min.y,
144            right: max.x,
145            bottom: max.y,
146            _unit: PhantomData,
147        }
148    }
149
150    /// Construct side offsets from min and a max vector offsets.
151    ///
152    /// The inner rect of the resulting side offsets is equivalent to translating
153    /// a rectangle's upper-left corner with the min vector and translating the
154    /// bottom-right corner with the max vector.
155    pub fn from_vectors_inner(min: Vector2D<T, U>, max: Vector2D<T, U>) -> Self
156    where
157        T: Neg<Output = T>,
158    {
159        SideOffsets2D {
160            left: min.x,
161            top: min.y,
162            right: -max.x,
163            bottom: -max.y,
164            _unit: PhantomData,
165        }
166    }
167
168    /// Constructor, setting all sides to zero.
169    pub fn zero() -> Self
170        where T: Zero,
171    {
172        SideOffsets2D::new(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero())
173    }
174
175    /// Returns `true` if all side offsets are zero.
176    pub fn is_zero(&self) -> bool
177    where
178        T: Zero + PartialEq,
179    {
180        let zero = T::zero();
181        self.top == zero && self.right == zero && self.bottom == zero && self.left == zero
182    }
183
184    /// Constructor setting the same value to all sides, taking a scalar value directly.
185    pub fn new_all_same(all: T) -> Self
186        where T : Copy
187    {
188        SideOffsets2D::new(all, all, all, all)
189    }
190
191    /// Constructor setting the same value to all sides, taking a typed Length.
192    pub fn from_length_all_same(all: Length<T, U>) -> Self
193        where T : Copy
194    {
195        SideOffsets2D::new_all_same(all.0)
196    }
197
198    pub fn horizontal(&self) -> T
199        where T: Copy + Add<T, Output = T>
200    {
201        self.left + self.right
202    }
203
204    pub fn vertical(&self) -> T
205        where T: Copy + Add<T, Output = T>
206    {
207        self.top + self.bottom
208    }
209}
210
211impl<T, U> Add for SideOffsets2D<T, U>
212where
213    T: Add<T, Output = T>,
214{
215    type Output = Self;
216    fn add(self, other: Self) -> Self {
217        SideOffsets2D::new(
218            self.top + other.top,
219            self.right + other.right,
220            self.bottom + other.bottom,
221            self.left + other.left,
222        )
223    }
224}
225
226impl<T: Copy + Mul, U> Mul<T> for SideOffsets2D<T, U> {
227    type Output = SideOffsets2D<T::Output, U>;
228
229    #[inline]
230    fn mul(self, scale: T) -> Self::Output {
231        SideOffsets2D::new(
232            self.top * scale,
233            self.right * scale,
234            self.bottom * scale,
235            self.left * scale,
236        )
237    }
238}
239
240impl<T: Copy + MulAssign, U> MulAssign<T> for SideOffsets2D<T, U> {
241    #[inline]
242    fn mul_assign(&mut self, other: T) {
243        self.top *= other;
244        self.right *= other;
245        self.bottom *= other;
246        self.left *= other;
247    }
248}
249
250impl<T: Copy + Mul, U1, U2> Mul<Scale<T, U1, U2>> for SideOffsets2D<T, U1> {
251    type Output = SideOffsets2D<T::Output, U2>;
252
253    #[inline]
254    fn mul(self, scale: Scale<T, U1, U2>) -> Self::Output {
255        SideOffsets2D::new(
256            self.top * scale.0,
257            self.right * scale.0,
258            self.bottom * scale.0,
259            self.left * scale.0,
260        )
261    }
262}
263
264impl<T: Copy + MulAssign, U> MulAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
265    #[inline]
266    fn mul_assign(&mut self, other: Scale<T, U, U>) {
267        *self *= other.0;
268    }
269}
270
271impl<T: Copy + Div, U> Div<T> for SideOffsets2D<T, U> {
272    type Output = SideOffsets2D<T::Output, U>;
273
274    #[inline]
275    fn div(self, scale: T) -> Self::Output {
276        SideOffsets2D::new(
277            self.top / scale,
278            self.right / scale,
279            self.bottom / scale,
280            self.left / scale,
281        )
282    }
283}
284
285impl<T: Copy + DivAssign, U> DivAssign<T> for SideOffsets2D<T, U> {
286    #[inline]
287    fn div_assign(&mut self, other: T) {
288        self.top /= other;
289        self.right /= other;
290        self.bottom /= other;
291        self.left /= other;
292    }
293}
294
295impl<T: Copy + Div, U1, U2> Div<Scale<T, U1, U2>> for SideOffsets2D<T, U2> {
296    type Output = SideOffsets2D<T::Output, U1>;
297
298    #[inline]
299    fn div(self, scale: Scale<T, U1, U2>) -> Self::Output {
300        SideOffsets2D::new(
301            self.top / scale.0,
302            self.right / scale.0,
303            self.bottom / scale.0,
304            self.left / scale.0,
305        )
306    }
307}
308
309impl<T: Copy + DivAssign, U> DivAssign<Scale<T, U, U>> for SideOffsets2D<T, U> {
310    fn div_assign(&mut self, other: Scale<T, U, U>) {
311        *self /= other.0;
312    }
313}
314
315#[test]
316fn from_vectors() {
317    use crate::{point2, vec2};
318    type Box2D = crate::default::Box2D<i32>;
319
320    let b = Box2D {
321        min: point2(10, 10),
322        max: point2(20, 20),
323    };
324
325    let outer = b.outer_box(SideOffsets2D::from_vectors_outer(vec2(-1, -2), vec2(3, 4)));
326    let inner = b.inner_box(SideOffsets2D::from_vectors_inner(vec2(1, 2), vec2(-3, -4)));
327
328    assert_eq!(
329        outer,
330        Box2D {
331            min: point2(9, 8),
332            max: point2(23, 24)
333        }
334    );
335    assert_eq!(
336        inner,
337        Box2D {
338            min: point2(11, 12),
339            max: point2(17, 16)
340        }
341    );
342}
343
344#[test]
345fn test_is_zero() {
346    let s1: SideOffsets2D<f32, ()> = SideOffsets2D::new_all_same(0.0);
347    assert!(s1.is_zero());
348
349    let s2: SideOffsets2D<f32, ()> = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
350    assert!(!s2.is_zero());
351}
352
353#[cfg(test)]
354mod ops {
355    use crate::Scale;
356
357    pub enum Mm {}
358    pub enum Cm {}
359
360    type SideOffsets2D<T> = crate::default::SideOffsets2D<T>;
361    type SideOffsets2DMm<T> = crate::SideOffsets2D<T, Mm>;
362    type SideOffsets2DCm<T> = crate::SideOffsets2D<T, Cm>;
363
364    #[test]
365    fn test_mul_scalar() {
366        let s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
367
368        let result = s * 3.0;
369
370        assert_eq!(result, SideOffsets2D::new(3.0, 6.0, 9.0, 12.0));
371    }
372
373    #[test]
374    fn test_mul_assign_scalar() {
375        let mut s = SideOffsets2D::new(1.0, 2.0, 3.0, 4.0);
376
377        s *= 2.0;
378
379        assert_eq!(s, SideOffsets2D::new(2.0, 4.0, 6.0, 8.0));
380    }
381
382    #[test]
383    fn test_mul_scale() {
384        let s = SideOffsets2DMm::new(0.0, 1.0, 3.0, 2.0);
385        let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
386
387        let result = s * cm_per_mm;
388
389        assert_eq!(result, SideOffsets2DCm::new(0.0, 0.1, 0.3, 0.2));
390    }
391
392    #[test]
393    fn test_mul_assign_scale() {
394        let mut s = SideOffsets2DMm::new(2.0, 4.0, 6.0, 8.0);
395        let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
396
397        s *= scale;
398
399        assert_eq!(s, SideOffsets2DMm::new(0.2, 0.4, 0.6, 0.8));
400    }
401
402    #[test]
403    fn test_div_scalar() {
404        let s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
405
406        let result = s / 10.0;
407
408        assert_eq!(result, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
409    }
410
411    #[test]
412    fn test_div_assign_scalar() {
413        let mut s = SideOffsets2D::new(10.0, 20.0, 30.0, 40.0);
414
415        s /= 10.0;
416
417        assert_eq!(s, SideOffsets2D::new(1.0, 2.0, 3.0, 4.0));
418    }
419
420    #[test]
421    fn test_div_scale() {
422        let s = SideOffsets2DCm::new(0.1, 0.2, 0.3, 0.4);
423        let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
424
425        let result = s / cm_per_mm;
426
427        assert_eq!(result, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
428    }
429
430    #[test]
431    fn test_div_assign_scale() {
432        let mut s = SideOffsets2DMm::new(0.1, 0.2, 0.3, 0.4);
433        let scale: Scale<f32, Mm, Mm> = Scale::new(0.1);
434
435        s /= scale;
436
437        assert_eq!(s, SideOffsets2DMm::new(1.0, 2.0, 3.0, 4.0));
438    }
439}