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