1use std::fmt;
4use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities;
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::Size;
11
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Thickness {
15 pub left: f64,
17 pub top: f64,
19 pub right: f64,
21 pub bottom: f64,
23}
24
25impl Thickness {
26 #[inline]
28 pub const fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
29 Self {
30 left,
31 top,
32 right,
33 bottom,
34 }
35 }
36
37 #[inline]
39 pub const fn uniform(uniform_length: f64) -> Self {
40 Self::new(
41 uniform_length,
42 uniform_length,
43 uniform_length,
44 uniform_length,
45 )
46 }
47
48 #[inline]
51 pub const fn symmetric(horizontal: f64, vertical: f64) -> Self {
52 Self::new(horizontal, vertical, horizontal, vertical)
53 }
54
55 #[inline]
57 pub fn is_uniform(&self) -> bool {
58 math_utilities::equals(self.left, self.right)
59 && math_utilities::equals(self.top, self.bottom)
60 && math_utilities::equals(self.right, self.bottom)
61 }
62
63 pub fn parse(s: &str) -> Result<Self, FormatError> {
65 const EXCEPTION_MESSAGE: &str = "Invalid Thickness.";
66
67 SpanStringTokenizer::with_message(s, EXCEPTION_MESSAGE).scope(|t| {
68 if let Some(a) = t.try_read_double()? {
69 if let Some(b) = t.try_read_double()? {
70 if let Some(c) = t.try_read_double()? {
71 return Ok(Thickness::new(a, b, c, t.read_double()?));
72 }
73
74 return Ok(Thickness::symmetric(a, b));
75 }
76
77 return Ok(Thickness::uniform(a));
78 }
79
80 Err(FormatError::new(EXCEPTION_MESSAGE))
81 })
82 }
83
84 #[inline]
87 pub fn equals(&self, other: Thickness) -> bool {
88 self.left == other.left
89 && self.top == other.top
90 && self.right == other.right
91 && self.bottom == other.bottom
92 }
93
94 #[inline]
96 pub const fn deconstruct(&self) -> (f64, f64, f64, f64) {
97 (self.left, self.top, self.right, self.bottom)
98 }
99}
100
101impl From<Thickness> for (f64, f64, f64, f64) {
102 #[inline]
103 fn from(t: Thickness) -> Self {
104 (t.left, t.top, t.right, t.bottom)
105 }
106}
107
108impl Add for Thickness {
109 type Output = Thickness;
110 #[inline]
111 fn add(self, b: Thickness) -> Thickness {
112 Thickness::new(
113 self.left + b.left,
114 self.top + b.top,
115 self.right + b.right,
116 self.bottom + b.bottom,
117 )
118 }
119}
120
121impl Sub for Thickness {
122 type Output = Thickness;
123 #[inline]
124 fn sub(self, b: Thickness) -> Thickness {
125 Thickness::new(
126 self.left - b.left,
127 self.top - b.top,
128 self.right - b.right,
129 self.bottom - b.bottom,
130 )
131 }
132}
133
134impl Mul<f64> for Thickness {
135 type Output = Thickness;
136 #[inline]
137 fn mul(self, b: f64) -> Thickness {
138 Thickness::new(self.left * b, self.top * b, self.right * b, self.bottom * b)
139 }
140}
141
142impl Add<Thickness> for Size {
144 type Output = Size;
145 #[inline]
146 fn add(self, thickness: Thickness) -> Size {
147 Size::new(
148 self.width + thickness.left + thickness.right,
149 self.height + thickness.top + thickness.bottom,
150 )
151 }
152}
153
154impl Sub<Thickness> for Size {
156 type Output = Size;
157 #[inline]
158 fn sub(self, thickness: Thickness) -> Size {
159 Size::new(
160 self.width - (thickness.left + thickness.right),
161 self.height - (thickness.top + thickness.bottom),
162 )
163 }
164}
165
166impl AddAssign for Thickness {
167 #[inline]
168 fn add_assign(&mut self, b: Thickness) {
169 *self = *self + b;
170 }
171}
172
173impl SubAssign for Thickness {
174 #[inline]
175 fn sub_assign(&mut self, b: Thickness) {
176 *self = *self - b;
177 }
178}
179
180impl MulAssign<f64> for Thickness {
181 #[inline]
182 fn mul_assign(&mut self, b: f64) {
183 *self = *self * b;
184 }
185}
186
187impl AddAssign<Thickness> for Size {
188 #[inline]
189 fn add_assign(&mut self, thickness: Thickness) {
190 *self = *self + thickness;
191 }
192}
193
194impl SubAssign<Thickness> for Size {
195 #[inline]
196 fn sub_assign(&mut self, thickness: Thickness) {
197 *self = *self - thickness;
198 }
199}
200
201impl FromStr for Thickness {
202 type Err = FormatError;
203 #[inline]
204 fn from_str(s: &str) -> Result<Self, FormatError> {
205 Thickness::parse(s)
206 }
207}
208
209impl fmt::Display for Thickness {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(
212 f,
213 "{},{},{},{}",
214 InvariantF64(self.left),
215 InvariantF64(self.top),
216 InvariantF64(self.right),
217 InvariantF64(self.bottom)
218 )
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn parse_parses_single_uniform_size() {
228 let result = Thickness::parse("1.2").unwrap();
229
230 assert_eq!(Thickness::uniform(1.2), result);
231 }
232
233 #[test]
234 fn parse_parses_horizontal_vertical() {
235 let result = Thickness::parse("1.2,3.4").unwrap();
236
237 assert_eq!(Thickness::symmetric(1.2, 3.4), result);
238 }
239
240 #[test]
241 fn parse_parses_left_top_right_bottom() {
242 let result = Thickness::parse("1.2, 3.4, 5, 6").unwrap();
243
244 assert_eq!(Thickness::new(1.2, 3.4, 5.0, 6.0), result);
245 }
246
247 #[test]
248 fn parse_accepts_spaces() {
249 let result = Thickness::parse("1.2 3.4 5 6").unwrap();
250
251 assert_eq!(Thickness::new(1.2, 3.4, 5.0, 6.0), result);
252 }
253
254 #[test]
257 fn parse_errors_display_and_operators() {
258 assert_eq!(
259 Thickness::parse("").unwrap_err().message(),
260 "Invalid Thickness."
261 );
262 assert!(Thickness::parse("1,2,3").is_err());
263 assert!(Thickness::parse("1,2,3,4,5").is_err());
264 assert_eq!(Thickness::new(1.0, 2.5, 3.0, 4.0).to_string(), "1,2.5,3,4");
265 assert!(Thickness::uniform(2.0).is_uniform());
266 assert!(Thickness::uniform(f64::NAN).is_uniform());
267 assert!(!Thickness::symmetric(1.0, 2.0).is_uniform());
268 let t = Thickness::new(1.0, 2.0, 3.0, 4.0);
269 assert_eq!(t + t, t * 2.0);
270 assert_eq!(t - t, Thickness::default());
271 assert_eq!(Size::new(10.0, 10.0) + t, Size::new(14.0, 16.0));
272 assert_eq!(Size::new(10.0, 10.0) - t, Size::new(6.0, 4.0));
273 }
274}