zng_layout/unit/
side_offsets.rs

1use std::{fmt, ops};
2
3use zng_unit::DipSideOffsets;
4use zng_var::{animation::Transitionable, impl_from_and_into_var};
5
6use crate::unit::{LengthCompositeParser, ParseCompositeError};
7
8use super::{Factor, FactorPercent, FactorSideOffsets, Layout1d, LayoutMask, Length, PxSideOffsets, impl_length_comp_conversions};
9
10/// 2D size offsets in [`Length`] units.
11///
12/// This unit defines spacing around all four sides of a box, a widget margin can be defined using a value of this type.
13#[derive(Clone, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
14pub struct SideOffsets {
15    /// Spacing above, in length units.
16    pub top: Length,
17    /// Spacing to the right, in length units.
18    pub right: Length,
19    /// Spacing bellow, in length units.
20    pub bottom: Length,
21    /// Spacing to the left ,in length units.
22    pub left: Length,
23}
24impl fmt::Debug for SideOffsets {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        if f.alternate() {
27            f.debug_struct("SideOffsets")
28                .field("top", &self.top)
29                .field("right", &self.right)
30                .field("bottom", &self.bottom)
31                .field("left", &self.left)
32                .finish()
33        } else if self.all_eq() {
34            write!(f, "{:?}", self.top)
35        } else if self.dimensions_eq() {
36            write!(f, "({:?}, {:?})", self.top, self.left)
37        } else {
38            write!(f, "({:?}, {:?}, {:?}, {:?})", self.top, self.right, self.bottom, self.left)
39        }
40    }
41}
42impl std::str::FromStr for SideOffsets {
43    type Err = ParseCompositeError;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        let mut parser = LengthCompositeParser::new(s)?;
47        let a = parser.next()?;
48        if parser.has_ended() {
49            return Ok(Self::new_all(a));
50        }
51        let b = parser.next()?;
52        if parser.has_ended() {
53            return Ok(Self::new_vh(a, b));
54        }
55        let c = parser.next()?;
56        let d = parser.expect_last()?;
57        Ok(Self::new(a, b, c, d))
58    }
59}
60impl SideOffsets {
61    /// New top, right, bottom left offsets. From any [`Length`] type.
62    pub fn new<T: Into<Length>, R: Into<Length>, B: Into<Length>, L: Into<Length>>(top: T, right: R, bottom: B, left: L) -> Self {
63        SideOffsets {
64            top: top.into(),
65            right: right.into(),
66            bottom: bottom.into(),
67            left: left.into(),
68        }
69    }
70
71    /// Top-bottom and left-right equal. From any [`Length`] type.
72    pub fn new_vh<TB: Into<Length>, LR: Into<Length>>(top_bottom: TB, left_right: LR) -> Self {
73        let top_bottom = top_bottom.into();
74        let left_right = left_right.into();
75        SideOffsets {
76            top: top_bottom.clone(),
77            bottom: top_bottom,
78            left: left_right.clone(),
79            right: left_right,
80        }
81    }
82
83    /// All sides equal. From any [`Length`] type.
84    pub fn new_all<T: Into<Length>>(all_sides: T) -> Self {
85        let all_sides = all_sides.into();
86        SideOffsets {
87            top: all_sides.clone(),
88            right: all_sides.clone(),
89            bottom: all_sides.clone(),
90            left: all_sides,
91        }
92    }
93
94    /// All sides [zero](Length::zero).
95    pub fn zero() -> Self {
96        Self::new_all(Length::zero())
97    }
98
99    /// If all sides are equal.
100    pub fn all_eq(&self) -> bool {
101        self.top == self.bottom && self.top == self.left && self.top == self.right
102    }
103
104    /// If top and bottom are equal; and left and right are equal.
105    pub fn dimensions_eq(&self) -> bool {
106        self.top == self.bottom && self.left == self.right
107    }
108}
109impl super::Layout2d for SideOffsets {
110    type Px = PxSideOffsets;
111
112    fn layout_dft(&self, default: Self::Px) -> Self::Px {
113        PxSideOffsets::new(
114            self.top.layout_dft_y(default.top),
115            self.right.layout_dft_x(default.right),
116            self.bottom.layout_dft_y(default.bottom),
117            self.left.layout_dft_x(default.left),
118        )
119    }
120
121    fn affect_mask(&self) -> LayoutMask {
122        self.top.affect_mask() | self.right.affect_mask() | self.bottom.affect_mask() | self.left.affect_mask()
123    }
124}
125impl_from_and_into_var! {
126    /// All sides equal.
127    fn from(all: Length) -> SideOffsets {
128        SideOffsets::new_all(all)
129    }
130
131    /// All sides equal relative length.
132    fn from(percent: FactorPercent) -> SideOffsets {
133        SideOffsets::new_all(percent)
134    }
135    /// All sides equal relative length.
136    fn from(norm: Factor) -> SideOffsets {
137        SideOffsets::new_all(norm)
138    }
139
140    /// All sides equal exact length.
141    fn from(f: f32) -> SideOffsets {
142        SideOffsets::new_all(f)
143    }
144    /// All sides equal exact length.
145    fn from(i: i32) -> SideOffsets {
146        SideOffsets::new_all(i)
147    }
148
149    /// From exact lengths.
150    fn from(offsets: PxSideOffsets) -> SideOffsets {
151        SideOffsets::new(offsets.top, offsets.right, offsets.bottom, offsets.left)
152    }
153
154    // From exact lengths.
155    fn from(offsets: DipSideOffsets) -> SideOffsets {
156        SideOffsets::new(offsets.top, offsets.right, offsets.bottom, offsets.left)
157    }
158}
159
160impl_length_comp_conversions! {
161    /// (top-bottom, left-right)
162    fn from(top_bottom: TB, left_right: LR) -> SideOffsets {
163        SideOffsets::new_vh(top_bottom, left_right)
164    }
165
166    /// (top, right, bottom, left)
167    fn from(top: T, right: R, bottom: B, left: L) -> SideOffsets {
168        SideOffsets::new(top, right, bottom, left)
169    }
170}
171
172impl ops::Add for SideOffsets {
173    type Output = Self;
174
175    fn add(mut self, rhs: Self) -> Self {
176        self += rhs;
177        self
178    }
179}
180impl ops::AddAssign for SideOffsets {
181    fn add_assign(&mut self, rhs: Self) {
182        self.top += rhs.top;
183        self.right += rhs.right;
184        self.bottom += rhs.bottom;
185        self.left += rhs.left;
186    }
187}
188impl ops::Sub for SideOffsets {
189    type Output = Self;
190
191    fn sub(mut self, rhs: Self) -> Self {
192        self -= rhs;
193        self
194    }
195}
196impl ops::SubAssign for SideOffsets {
197    fn sub_assign(&mut self, rhs: Self) {
198        self.top -= rhs.top;
199        self.right -= rhs.right;
200        self.bottom -= rhs.bottom;
201        self.left -= rhs.left;
202    }
203}
204impl<S: Into<FactorSideOffsets>> ops::Mul<S> for SideOffsets {
205    type Output = Self;
206
207    fn mul(mut self, rhs: S) -> Self {
208        self *= rhs;
209        self
210    }
211}
212impl<S: Into<FactorSideOffsets>> ops::Mul<S> for &SideOffsets {
213    type Output = SideOffsets;
214
215    fn mul(self, rhs: S) -> Self::Output {
216        self.clone() * rhs
217    }
218}
219impl<S: Into<FactorSideOffsets>> ops::MulAssign<S> for SideOffsets {
220    fn mul_assign(&mut self, rhs: S) {
221        let rhs = rhs.into();
222        self.top *= rhs.top;
223        self.right *= rhs.right;
224        self.bottom *= rhs.bottom;
225        self.left *= rhs.left;
226    }
227}
228impl<S: Into<FactorSideOffsets>> ops::Div<S> for SideOffsets {
229    type Output = Self;
230
231    fn div(mut self, rhs: S) -> Self {
232        self /= rhs;
233        self
234    }
235}
236impl<S: Into<FactorSideOffsets>> ops::Div<S> for &SideOffsets {
237    type Output = SideOffsets;
238
239    fn div(self, rhs: S) -> Self::Output {
240        self.clone() / rhs
241    }
242}
243impl<S: Into<FactorSideOffsets>> ops::DivAssign<S> for SideOffsets {
244    fn div_assign(&mut self, rhs: S) {
245        let rhs = rhs.into();
246        self.top /= rhs.top;
247        self.right /= rhs.right;
248        self.bottom /= rhs.bottom;
249        self.left /= rhs.left;
250    }
251}