1use alloc::string::String;
4use core::{fmt, num::ParseFloatError};
5
6use crate::props::{
7 basic::{
8 angle::{
9 parse_angle_value, AngleValue, CssAngleValueParseError, CssAngleValueParseErrorOwned,
10 },
11 geometry::{LayoutPoint, LayoutRect},
12 },
13 formatter::PrintAsCssValue,
14};
15
16#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(C)]
18pub enum DirectionCorner {
19 Right,
20 Left,
21 Top,
22 Bottom,
23 TopRight,
24 TopLeft,
25 BottomRight,
26 BottomLeft,
27}
28
29impl fmt::Display for DirectionCorner {
30 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31 write!(
32 f,
33 "{}",
34 match self {
35 DirectionCorner::Right => "right",
36 DirectionCorner::Left => "left",
37 DirectionCorner::Top => "top",
38 DirectionCorner::Bottom => "bottom",
39 DirectionCorner::TopRight => "top right",
40 DirectionCorner::TopLeft => "top left",
41 DirectionCorner::BottomRight => "bottom right",
42 DirectionCorner::BottomLeft => "bottom left",
43 }
44 )
45 }
46}
47
48impl PrintAsCssValue for DirectionCorner {
49 fn print_as_css_value(&self) -> String {
50 format!("{}", self)
51 }
52}
53
54impl DirectionCorner {
55 pub const fn opposite(&self) -> Self {
56 use self::DirectionCorner::*;
57 match *self {
58 Right => Left,
59 Left => Right,
60 Top => Bottom,
61 Bottom => Top,
62 TopRight => BottomLeft,
63 BottomLeft => TopRight,
64 TopLeft => BottomRight,
65 BottomRight => TopLeft,
66 }
67 }
68
69 pub const fn combine(&self, other: &Self) -> Option<Self> {
70 use self::DirectionCorner::*;
71 match (*self, *other) {
72 (Right, Top) | (Top, Right) => Some(TopRight),
73 (Left, Top) | (Top, Left) => Some(TopLeft),
74 (Right, Bottom) | (Bottom, Right) => Some(BottomRight),
75 (Left, Bottom) | (Bottom, Left) => Some(BottomLeft),
76 _ => None,
77 }
78 }
79
80 pub const fn to_point(&self, rect: &LayoutRect) -> LayoutPoint {
81 use self::DirectionCorner::*;
82 match *self {
83 Right => LayoutPoint {
84 x: rect.size.width,
85 y: rect.size.height / 2,
86 },
87 Left => LayoutPoint {
88 x: 0,
89 y: rect.size.height / 2,
90 },
91 Top => LayoutPoint {
92 x: rect.size.width / 2,
93 y: 0,
94 },
95 Bottom => LayoutPoint {
96 x: rect.size.width / 2,
97 y: rect.size.height,
98 },
99 TopRight => LayoutPoint {
100 x: rect.size.width,
101 y: 0,
102 },
103 TopLeft => LayoutPoint { x: 0, y: 0 },
104 BottomRight => LayoutPoint {
105 x: rect.size.width,
106 y: rect.size.height,
107 },
108 BottomLeft => LayoutPoint {
109 x: 0,
110 y: rect.size.height,
111 },
112 }
113 }
114}
115
116#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
117#[repr(C)]
118pub struct DirectionCorners {
119 pub dir_from: DirectionCorner,
120 pub dir_to: DirectionCorner,
121}
122
123#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
126#[repr(C, u8)]
127pub enum Direction {
128 Angle(AngleValue),
129 FromTo(DirectionCorners),
130}
131
132impl Default for Direction {
133 fn default() -> Self {
134 Direction::FromTo(DirectionCorners {
135 dir_from: DirectionCorner::Top,
136 dir_to: DirectionCorner::Bottom,
137 })
138 }
139}
140
141impl PrintAsCssValue for Direction {
142 fn print_as_css_value(&self) -> String {
143 match self {
144 Direction::Angle(a) => format!("{}", a),
145 Direction::FromTo(d) => format!("to {}", d.dir_to), }
147 }
148}
149
150impl Direction {
151 pub fn to_points(&self, rect: &LayoutRect) -> (LayoutPoint, LayoutPoint) {
152 match self {
153 Direction::Angle(angle_value) => {
154 let deg = -angle_value.to_degrees();
157 let width_half = rect.size.width as f32 / 2.0;
158 let height_half = rect.size.height as f32 / 2.0;
159 let hypotenuse_len = libm::hypotf(width_half, height_half);
160 let angle_to_corner = libm::atanf(height_half / width_half).to_degrees();
161 let corner_angle = if deg < 90.0 {
162 90.0 - angle_to_corner
163 } else if deg < 180.0 {
164 90.0 + angle_to_corner
165 } else if deg < 270.0 {
166 270.0 - angle_to_corner
167 } else {
168 270.0 + angle_to_corner
169 };
170 let angle_diff = corner_angle - deg;
171 let line_length = libm::fabsf(hypotenuse_len * libm::cosf(angle_diff.to_radians()));
172 let dx = libm::sinf(deg.to_radians()) * line_length;
173 let dy = libm::cosf(deg.to_radians()) * line_length;
174 (
175 LayoutPoint::new(
176 libm::roundf(width_half - dx) as isize,
177 libm::roundf(height_half + dy) as isize,
178 ),
179 LayoutPoint::new(
180 libm::roundf(width_half + dx) as isize,
181 libm::roundf(height_half - dy) as isize,
182 ),
183 )
184 }
185 Direction::FromTo(ft) => (ft.dir_from.to_point(rect), ft.dir_to.to_point(rect)),
186 }
187 }
188}
189
190#[derive(Debug, Copy, Clone, PartialEq)]
193pub enum CssDirectionCornerParseError<'a> {
194 InvalidDirection(&'a str),
195}
196
197impl_display! { CssDirectionCornerParseError<'a>, {
198 InvalidDirection(val) => format!("Invalid direction: \"{}\"", val),
199}}
200
201#[derive(Debug, Clone, PartialEq)]
202pub enum CssDirectionCornerParseErrorOwned {
203 InvalidDirection(String),
204}
205
206impl<'a> CssDirectionCornerParseError<'a> {
207 pub fn to_contained(&self) -> CssDirectionCornerParseErrorOwned {
208 match self {
209 CssDirectionCornerParseError::InvalidDirection(s) => {
210 CssDirectionCornerParseErrorOwned::InvalidDirection(s.to_string())
211 }
212 }
213 }
214}
215
216impl CssDirectionCornerParseErrorOwned {
217 pub fn to_shared<'a>(&'a self) -> CssDirectionCornerParseError<'a> {
218 match self {
219 CssDirectionCornerParseErrorOwned::InvalidDirection(s) => {
220 CssDirectionCornerParseError::InvalidDirection(s.as_str())
221 }
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq)]
227pub enum CssDirectionParseError<'a> {
228 Error(&'a str),
229 InvalidArguments(&'a str),
230 ParseFloat(ParseFloatError),
231 CornerError(CssDirectionCornerParseError<'a>),
232 AngleError(CssAngleValueParseError<'a>),
233}
234
235impl_display! {CssDirectionParseError<'a>, {
236 Error(e) => e,
237 InvalidArguments(val) => format!("Invalid arguments: \"{}\"", val),
238 ParseFloat(e) => format!("Invalid value: {}", e),
239 CornerError(e) => format!("Invalid corner value: {}", e),
240 AngleError(e) => format!("Invalid angle value: {}", e),
241}}
242
243impl<'a> From<ParseFloatError> for CssDirectionParseError<'a> {
244 fn from(e: ParseFloatError) -> Self {
245 CssDirectionParseError::ParseFloat(e)
246 }
247}
248impl_from! { CssDirectionCornerParseError<'a>, CssDirectionParseError::CornerError }
249impl_from! { CssAngleValueParseError<'a>, CssDirectionParseError::AngleError }
250
251#[derive(Debug, Clone, PartialEq)]
252pub enum CssDirectionParseErrorOwned {
253 Error(String),
254 InvalidArguments(String),
255 ParseFloat(ParseFloatError),
256 CornerError(CssDirectionCornerParseErrorOwned),
257 AngleError(CssAngleValueParseErrorOwned),
258}
259
260impl<'a> CssDirectionParseError<'a> {
261 pub fn to_contained(&self) -> CssDirectionParseErrorOwned {
262 match self {
263 CssDirectionParseError::Error(s) => CssDirectionParseErrorOwned::Error(s.to_string()),
264 CssDirectionParseError::InvalidArguments(s) => {
265 CssDirectionParseErrorOwned::InvalidArguments(s.to_string())
266 }
267 CssDirectionParseError::ParseFloat(e) => {
268 CssDirectionParseErrorOwned::ParseFloat(e.clone())
269 }
270 CssDirectionParseError::CornerError(e) => {
271 CssDirectionParseErrorOwned::CornerError(e.to_contained())
272 }
273 CssDirectionParseError::AngleError(e) => {
274 CssDirectionParseErrorOwned::AngleError(e.to_contained())
275 }
276 }
277 }
278}
279
280impl CssDirectionParseErrorOwned {
281 pub fn to_shared<'a>(&'a self) -> CssDirectionParseError<'a> {
282 match self {
283 CssDirectionParseErrorOwned::Error(s) => CssDirectionParseError::Error(s.as_str()),
284 CssDirectionParseErrorOwned::InvalidArguments(s) => {
285 CssDirectionParseError::InvalidArguments(s.as_str())
286 }
287 CssDirectionParseErrorOwned::ParseFloat(e) => {
288 CssDirectionParseError::ParseFloat(e.clone())
289 }
290 CssDirectionParseErrorOwned::CornerError(e) => {
291 CssDirectionParseError::CornerError(e.to_shared())
292 }
293 CssDirectionParseErrorOwned::AngleError(e) => {
294 CssDirectionParseError::AngleError(e.to_shared())
295 }
296 }
297 }
298}
299
300#[cfg(feature = "parser")]
301pub fn parse_direction_corner<'a>(
302 input: &'a str,
303) -> Result<DirectionCorner, CssDirectionCornerParseError<'a>> {
304 match input {
305 "right" => Ok(DirectionCorner::Right),
306 "left" => Ok(DirectionCorner::Left),
307 "top" => Ok(DirectionCorner::Top),
308 "bottom" => Ok(DirectionCorner::Bottom),
309 _ => Err(CssDirectionCornerParseError::InvalidDirection(input)),
310 }
311}
312
313#[cfg(feature = "parser")]
314pub fn parse_direction<'a>(input: &'a str) -> Result<Direction, CssDirectionParseError<'a>> {
315 let mut input_iter = input.split_whitespace();
316 let first_input = input_iter
317 .next()
318 .ok_or(CssDirectionParseError::Error(input))?;
319
320 if let Ok(angle) = parse_angle_value(first_input) {
321 return Ok(Direction::Angle(angle));
322 }
323
324 if first_input != "to" {
325 return Err(CssDirectionParseError::InvalidArguments(input));
326 }
327
328 let mut components = input_iter.collect::<Vec<_>>();
329 if components.is_empty() || components.len() > 2 {
330 return Err(CssDirectionParseError::InvalidArguments(input));
331 }
332
333 let first_corner = parse_direction_corner(components[0])?;
334 let end = if components.len() == 2 {
335 let second_corner = parse_direction_corner(components[1])?;
336 first_corner
337 .combine(&second_corner)
338 .ok_or(CssDirectionParseError::InvalidArguments(input))?
339 } else {
340 first_corner
341 };
342
343 Ok(Direction::FromTo(DirectionCorners {
344 dir_from: end.opposite(),
345 dir_to: end,
346 }))
347}
348
349#[cfg(all(test, feature = "parser"))]
350mod tests {
351 use super::*;
352 use crate::props::basic::angle::AngleValue;
353
354 #[test]
355 fn test_parse_direction_angle() {
356 assert_eq!(
357 parse_direction("45deg").unwrap(),
358 Direction::Angle(AngleValue::deg(45.0))
359 );
360 assert_eq!(
361 parse_direction(" -0.25turn ").unwrap(),
362 Direction::Angle(AngleValue::turn(-0.25))
363 );
364 }
365
366 #[test]
367 fn test_parse_direction_corners() {
368 assert_eq!(
369 parse_direction("to right").unwrap(),
370 Direction::FromTo(DirectionCorners {
371 dir_from: DirectionCorner::Left,
372 dir_to: DirectionCorner::Right,
373 })
374 );
375 assert_eq!(
376 parse_direction("to top left").unwrap(),
377 Direction::FromTo(DirectionCorners {
378 dir_from: DirectionCorner::BottomRight,
379 dir_to: DirectionCorner::TopLeft,
380 })
381 );
382 assert_eq!(
383 parse_direction("to left top").unwrap(),
384 Direction::FromTo(DirectionCorners {
385 dir_from: DirectionCorner::BottomRight,
386 dir_to: DirectionCorner::TopLeft,
387 })
388 );
389 }
390
391 #[test]
392 fn test_parse_direction_errors() {
393 assert!(parse_direction("").is_err());
394 assert!(parse_direction("to").is_err());
395 assert!(parse_direction("right").is_err());
396 assert!(parse_direction("to center").is_err());
397 assert!(parse_direction("to top right bottom").is_err());
398 assert!(parse_direction("to top top").is_err());
399 }
400}