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 DirectionCorner::Right => "right",
39 DirectionCorner::Left => "left",
40 DirectionCorner::Top => "top",
41 DirectionCorner::Bottom => "bottom",
42 DirectionCorner::TopRight => "top right",
43 DirectionCorner::TopLeft => "top left",
44 DirectionCorner::BottomRight => "bottom right",
45 DirectionCorner::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 pub const fn opposite(&self) -> Self {
59 use self::DirectionCorner::*;
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 pub const fn combine(&self, other: &Self) -> Option<Self> {
73 use self::DirectionCorner::*;
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 pub const fn to_point(&self, rect: &LayoutRect) -> LayoutPoint {
84 use self::DirectionCorner::*;
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 Direction::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 Direction::Angle(a) => format!("{}", a),
151 Direction::FromTo(d) => format!("to {}", d.dir_to), }
153 }
154}
155
156impl Direction {
157 pub fn to_points(&self, rect: &LayoutRect) -> (LayoutPoint, LayoutPoint) {
158 match self {
159 Direction::Angle(angle_value) => {
160 let deg = -angle_value.to_degrees();
163 let width_half = rect.size.width as f32 / 2.0;
164 let height_half = rect.size.height as f32 / 2.0;
165 let hypotenuse_len = libm::hypotf(width_half, height_half);
166 let angle_to_corner = libm::atanf(height_half / width_half).to_degrees();
167 let corner_angle = if deg < 90.0 {
168 90.0 - angle_to_corner
169 } else if deg < 180.0 {
170 90.0 + angle_to_corner
171 } else if deg < 270.0 {
172 270.0 - angle_to_corner
173 } else {
174 270.0 + angle_to_corner
175 };
176 let angle_diff = corner_angle - deg;
177 let line_length = libm::fabsf(hypotenuse_len * libm::cosf(angle_diff.to_radians()));
178 let dx = libm::sinf(deg.to_radians()) * line_length;
179 let dy = libm::cosf(deg.to_radians()) * line_length;
180 (
181 LayoutPoint::new(
182 libm::roundf(width_half - dx) as isize,
183 libm::roundf(height_half + dy) as isize,
184 ),
185 LayoutPoint::new(
186 libm::roundf(width_half + dx) as isize,
187 libm::roundf(height_half - dy) as isize,
188 ),
189 )
190 }
191 Direction::FromTo(ft) => (ft.dir_from.to_point(rect), ft.dir_to.to_point(rect)),
192 }
193 }
194}
195
196#[derive(Debug, Copy, Clone, PartialEq)]
199pub enum CssDirectionCornerParseError<'a> {
200 InvalidDirection(&'a str),
201}
202
203impl_display! { CssDirectionCornerParseError<'a>, {
204 InvalidDirection(val) => format!("Invalid direction: \"{}\"", val),
205}}
206
207#[derive(Debug, Clone, PartialEq)]
208#[repr(C, u8)]
209pub enum CssDirectionCornerParseErrorOwned {
210 InvalidDirection(AzString),
211}
212
213impl<'a> CssDirectionCornerParseError<'a> {
214 pub fn to_contained(&self) -> CssDirectionCornerParseErrorOwned {
215 match self {
216 CssDirectionCornerParseError::InvalidDirection(s) => {
217 CssDirectionCornerParseErrorOwned::InvalidDirection(s.to_string().into())
218 }
219 }
220 }
221}
222
223impl CssDirectionCornerParseErrorOwned {
224 pub fn to_shared<'a>(&'a self) -> CssDirectionCornerParseError<'a> {
225 match self {
226 CssDirectionCornerParseErrorOwned::InvalidDirection(s) => {
227 CssDirectionCornerParseError::InvalidDirection(s.as_str())
228 }
229 }
230 }
231}
232
233#[derive(Debug, Clone, PartialEq)]
234pub enum CssDirectionParseError<'a> {
235 Error(&'a str),
236 InvalidArguments(&'a str),
237 ParseFloat(ParseFloatError),
238 CornerError(CssDirectionCornerParseError<'a>),
239 AngleError(CssAngleValueParseError<'a>),
240}
241
242impl_display! {CssDirectionParseError<'a>, {
243 Error(e) => e,
244 InvalidArguments(val) => format!("Invalid arguments: \"{}\"", val),
245 ParseFloat(e) => format!("Invalid value: {}", e),
246 CornerError(e) => format!("Invalid corner value: {}", e),
247 AngleError(e) => format!("Invalid angle value: {}", e),
248}}
249
250impl<'a> From<ParseFloatError> for CssDirectionParseError<'a> {
251 fn from(e: ParseFloatError) -> Self {
252 CssDirectionParseError::ParseFloat(e)
253 }
254}
255impl_from! { CssDirectionCornerParseError<'a>, CssDirectionParseError::CornerError }
256impl_from! { CssAngleValueParseError<'a>, CssDirectionParseError::AngleError }
257
258#[derive(Debug, Clone, PartialEq)]
259#[repr(C, u8)]
260pub enum CssDirectionParseErrorOwned {
261 Error(AzString),
262 InvalidArguments(AzString),
263 ParseFloat(crate::props::basic::error::ParseFloatError),
264 CornerError(CssDirectionCornerParseErrorOwned),
265 AngleError(CssAngleValueParseErrorOwned),
266}
267
268impl<'a> CssDirectionParseError<'a> {
269 pub fn to_contained(&self) -> CssDirectionParseErrorOwned {
270 match self {
271 CssDirectionParseError::Error(s) => CssDirectionParseErrorOwned::Error(s.to_string().into()),
272 CssDirectionParseError::InvalidArguments(s) => {
273 CssDirectionParseErrorOwned::InvalidArguments(s.to_string().into())
274 }
275 CssDirectionParseError::ParseFloat(e) => {
276 CssDirectionParseErrorOwned::ParseFloat(e.clone().into())
277 }
278 CssDirectionParseError::CornerError(e) => {
279 CssDirectionParseErrorOwned::CornerError(e.to_contained())
280 }
281 CssDirectionParseError::AngleError(e) => {
282 CssDirectionParseErrorOwned::AngleError(e.to_contained())
283 }
284 }
285 }
286}
287
288impl CssDirectionParseErrorOwned {
289 pub fn to_shared<'a>(&'a self) -> CssDirectionParseError<'a> {
290 match self {
291 CssDirectionParseErrorOwned::Error(s) => CssDirectionParseError::Error(s.as_str()),
292 CssDirectionParseErrorOwned::InvalidArguments(s) => {
293 CssDirectionParseError::InvalidArguments(s.as_str())
294 }
295 CssDirectionParseErrorOwned::ParseFloat(e) => {
296 CssDirectionParseError::ParseFloat(e.to_std())
297 }
298 CssDirectionParseErrorOwned::CornerError(e) => {
299 CssDirectionParseError::CornerError(e.to_shared())
300 }
301 CssDirectionParseErrorOwned::AngleError(e) => {
302 CssDirectionParseError::AngleError(e.to_shared())
303 }
304 }
305 }
306}
307
308#[cfg(feature = "parser")]
309fn parse_direction_corner<'a>(
310 input: &'a str,
311) -> Result<DirectionCorner, CssDirectionCornerParseError<'a>> {
312 match input {
313 "right" => Ok(DirectionCorner::Right),
314 "left" => Ok(DirectionCorner::Left),
315 "top" => Ok(DirectionCorner::Top),
316 "bottom" => Ok(DirectionCorner::Bottom),
317 _ => Err(CssDirectionCornerParseError::InvalidDirection(input)),
318 }
319}
320
321#[cfg(feature = "parser")]
322pub fn parse_direction<'a>(input: &'a str) -> Result<Direction, CssDirectionParseError<'a>> {
323 let mut input_iter = input.split_whitespace();
324 let first_input = input_iter
325 .next()
326 .ok_or(CssDirectionParseError::Error(input))?;
327
328 if let Ok(angle) = parse_angle_value(first_input) {
329 return Ok(Direction::Angle(angle));
330 }
331
332 if first_input != "to" {
333 return Err(CssDirectionParseError::InvalidArguments(input));
334 }
335
336 let components = input_iter.collect::<Vec<_>>();
337 if components.is_empty() || components.len() > 2 {
338 return Err(CssDirectionParseError::InvalidArguments(input));
339 }
340
341 let first_corner = parse_direction_corner(components[0])?;
342 let end = if components.len() == 2 {
343 let second_corner = parse_direction_corner(components[1])?;
344 first_corner
345 .combine(&second_corner)
346 .ok_or(CssDirectionParseError::InvalidArguments(input))?
347 } else {
348 first_corner
349 };
350
351 Ok(Direction::FromTo(DirectionCorners {
352 dir_from: end.opposite(),
353 dir_to: end,
354 }))
355}
356
357#[cfg(all(test, feature = "parser"))]
358mod tests {
359 use super::*;
360 use crate::props::basic::angle::AngleValue;
361
362 #[test]
363 fn test_parse_direction_angle() {
364 assert_eq!(
365 parse_direction("45deg").unwrap(),
366 Direction::Angle(AngleValue::deg(45.0))
367 );
368 assert_eq!(
369 parse_direction(" -0.25turn ").unwrap(),
370 Direction::Angle(AngleValue::turn(-0.25))
371 );
372 }
373
374 #[test]
375 fn test_parse_direction_corners() {
376 assert_eq!(
377 parse_direction("to right").unwrap(),
378 Direction::FromTo(DirectionCorners {
379 dir_from: DirectionCorner::Left,
380 dir_to: DirectionCorner::Right,
381 })
382 );
383 assert_eq!(
384 parse_direction("to top left").unwrap(),
385 Direction::FromTo(DirectionCorners {
386 dir_from: DirectionCorner::BottomRight,
387 dir_to: DirectionCorner::TopLeft,
388 })
389 );
390 assert_eq!(
391 parse_direction("to left top").unwrap(),
392 Direction::FromTo(DirectionCorners {
393 dir_from: DirectionCorner::BottomRight,
394 dir_to: DirectionCorner::TopLeft,
395 })
396 );
397 }
398
399 #[test]
400 fn test_parse_direction_errors() {
401 assert!(parse_direction("").is_err());
402 assert!(parse_direction("to").is_err());
403 assert!(parse_direction("right").is_err());
404 assert!(parse_direction("to center").is_err());
405 assert!(parse_direction("to top right bottom").is_err());
406 assert!(parse_direction("to top top").is_err());
407 }
408}