1
//! CSS property types for direction (for gradients).
2

            
3
use alloc::string::String;
4
use core::{fmt, num::ParseFloatError};
5
use crate::corety::AzString;
6

            
7
use 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
/// Corner or side of a rectangle, used to specify CSS gradient directions
18
/// (e.g. `to top right`).
19
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20
#[repr(C)]
21
pub enum DirectionCorner {
22
    Right,
23
    Left,
24
    Top,
25
    Bottom,
26
    TopRight,
27
    TopLeft,
28
    BottomRight,
29
    BottomLeft,
30
}
31

            
32
impl 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

            
51
impl PrintAsCssValue for DirectionCorner {
52
    fn print_as_css_value(&self) -> String {
53
        format!("{}", self)
54
    }
55
}
56

            
57
impl DirectionCorner {
58
54
    pub const fn opposite(&self) -> Self {
59
        use self::DirectionCorner::*;
60
54
        match *self {
61
51
            Right => Left,
62
            Left => Right,
63
            Top => Bottom,
64
            Bottom => Top,
65
1
            TopRight => BottomLeft,
66
            BottomLeft => TopRight,
67
2
            TopLeft => BottomRight,
68
            BottomRight => TopLeft,
69
        }
70
54
    }
71

            
72
4
    pub const fn combine(&self, other: &Self) -> Option<Self> {
73
        use self::DirectionCorner::*;
74
4
        match (*self, *other) {
75
1
            (Right, Top) | (Top, Right) => Some(TopRight),
76
2
            (Left, Top) | (Top, Left) => Some(TopLeft),
77
            (Right, Bottom) | (Bottom, Right) => Some(BottomRight),
78
            (Left, Bottom) | (Bottom, Left) => Some(BottomLeft),
79
1
            _ => None,
80
        }
81
4
    }
82

            
83
84
    pub const fn to_point(&self, rect: &LayoutRect) -> LayoutPoint {
84
        use self::DirectionCorner::*;
85
84
        match *self {
86
42
            Right => LayoutPoint {
87
42
                x: rect.size.width,
88
42
                y: rect.size.height / 2,
89
42
            },
90
42
            Left => LayoutPoint {
91
42
                x: 0,
92
42
                y: rect.size.height / 2,
93
42
            },
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
84
    }
117
}
118

            
119
/// A pair of corners representing the start and end of a CSS gradient direction.
120
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
121
#[repr(C)]
122
pub struct DirectionCorners {
123
    /// The corner or side from which the gradient starts.
124
    pub dir_from: DirectionCorner,
125
    /// The corner or side at which the gradient ends.
126
    pub dir_to: DirectionCorner,
127
}
128

            
129
/// CSS direction (necessary for gradients). Can either be a fixed angle or
130
/// a direction ("to right" / "to left", etc.).
131
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132
#[repr(C, u8)]
133
pub enum Direction {
134
    Angle(AngleValue),
135
    FromTo(DirectionCorners),
136
}
137

            
138
impl Default for Direction {
139
63
    fn default() -> Self {
140
63
        Direction::FromTo(DirectionCorners {
141
63
            dir_from: DirectionCorner::Top,
142
63
            dir_to: DirectionCorner::Bottom,
143
63
        })
144
63
    }
145
}
146

            
147
impl 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), // simplified "from X to Y"
152
        }
153
    }
154
}
155

            
156
impl Direction {
157
42
    pub fn to_points(&self, rect: &LayoutRect) -> (LayoutPoint, LayoutPoint) {
158
42
        match self {
159
            Direction::Angle(angle_value) => {
160
                // Convert the angle to start/end points on the rectangle.
161
                // TODO: does not handle negative angles or angles >= 360 correctly.
162
                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
42
            Direction::FromTo(ft) => (ft.dir_from.to_point(rect), ft.dir_to.to_point(rect)),
192
        }
193
42
    }
194
}
195

            
196
// -- Parser
197

            
198
#[derive(Debug, Copy, Clone, PartialEq)]
199
pub enum CssDirectionCornerParseError<'a> {
200
    InvalidDirection(&'a str),
201
}
202

            
203
impl_display! { CssDirectionCornerParseError<'a>, {
204
    InvalidDirection(val) => format!("Invalid direction: \"{}\"", val),
205
}}
206

            
207
#[derive(Debug, Clone, PartialEq)]
208
#[repr(C, u8)]
209
pub enum CssDirectionCornerParseErrorOwned {
210
    InvalidDirection(AzString),
211
}
212

            
213
impl<'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

            
223
impl 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)]
234
pub enum CssDirectionParseError<'a> {
235
    Error(&'a str),
236
    InvalidArguments(&'a str),
237
    ParseFloat(ParseFloatError),
238
    CornerError(CssDirectionCornerParseError<'a>),
239
    AngleError(CssAngleValueParseError<'a>),
240
}
241

            
242
impl_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

            
250
impl<'a> From<ParseFloatError> for CssDirectionParseError<'a> {
251
    fn from(e: ParseFloatError) -> Self {
252
        CssDirectionParseError::ParseFloat(e)
253
    }
254
}
255
impl_from! { CssDirectionCornerParseError<'a>, CssDirectionParseError::CornerError }
256
impl_from! { CssAngleValueParseError<'a>, CssDirectionParseError::AngleError }
257

            
258
#[derive(Debug, Clone, PartialEq)]
259
#[repr(C, u8)]
260
pub enum CssDirectionParseErrorOwned {
261
    Error(AzString),
262
    InvalidArguments(AzString),
263
    ParseFloat(crate::props::basic::error::ParseFloatError),
264
    CornerError(CssDirectionCornerParseErrorOwned),
265
    AngleError(CssAngleValueParseErrorOwned),
266
}
267

            
268
impl<'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

            
288
impl 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")]
309
60
fn parse_direction_corner<'a>(
310
60
    input: &'a str,
311
60
) -> Result<DirectionCorner, CssDirectionCornerParseError<'a>> {
312
60
    match input {
313
60
        "right" => Ok(DirectionCorner::Right),
314
8
        "left" => Ok(DirectionCorner::Left),
315
6
        "top" => Ok(DirectionCorner::Top),
316
1
        "bottom" => Ok(DirectionCorner::Bottom),
317
1
        _ => Err(CssDirectionCornerParseError::InvalidDirection(input)),
318
    }
319
60
}
320

            
321
#[cfg(feature = "parser")]
322
79
pub fn parse_direction<'a>(input: &'a str) -> Result<Direction, CssDirectionParseError<'a>> {
323
79
    let mut input_iter = input.split_whitespace();
324
79
    let first_input = input_iter
325
79
        .next()
326
79
        .ok_or(CssDirectionParseError::Error(input))?;
327

            
328
78
    if let Ok(angle) = parse_angle_value(first_input) {
329
8
        return Ok(Direction::Angle(angle));
330
70
    }
331

            
332
70
    if first_input != "to" {
333
12
        return Err(CssDirectionParseError::InvalidArguments(input));
334
58
    }
335

            
336
58
    let components = input_iter.collect::<Vec<_>>();
337
58
    if components.is_empty() || components.len() > 2 {
338
2
        return Err(CssDirectionParseError::InvalidArguments(input));
339
56
    }
340

            
341
56
    let first_corner = parse_direction_corner(components[0])?;
342
55
    let end = if components.len() == 2 {
343
4
        let second_corner = parse_direction_corner(components[1])?;
344
4
        first_corner
345
4
            .combine(&second_corner)
346
4
            .ok_or(CssDirectionParseError::InvalidArguments(input))?
347
    } else {
348
51
        first_corner
349
    };
350

            
351
54
    Ok(Direction::FromTo(DirectionCorners {
352
54
        dir_from: end.opposite(),
353
54
        dir_to: end,
354
54
    }))
355
79
}
356

            
357
#[cfg(all(test, feature = "parser"))]
358
mod tests {
359
    use super::*;
360
    use crate::props::basic::angle::AngleValue;
361

            
362
    #[test]
363
1
    fn test_parse_direction_angle() {
364
1
        assert_eq!(
365
1
            parse_direction("45deg").unwrap(),
366
1
            Direction::Angle(AngleValue::deg(45.0))
367
        );
368
1
        assert_eq!(
369
1
            parse_direction("  -0.25turn  ").unwrap(),
370
1
            Direction::Angle(AngleValue::turn(-0.25))
371
        );
372
1
    }
373

            
374
    #[test]
375
1
    fn test_parse_direction_corners() {
376
1
        assert_eq!(
377
1
            parse_direction("to right").unwrap(),
378
            Direction::FromTo(DirectionCorners {
379
                dir_from: DirectionCorner::Left,
380
                dir_to: DirectionCorner::Right,
381
            })
382
        );
383
1
        assert_eq!(
384
1
            parse_direction("to top left").unwrap(),
385
            Direction::FromTo(DirectionCorners {
386
                dir_from: DirectionCorner::BottomRight,
387
                dir_to: DirectionCorner::TopLeft,
388
            })
389
        );
390
1
        assert_eq!(
391
1
            parse_direction("to left top").unwrap(),
392
            Direction::FromTo(DirectionCorners {
393
                dir_from: DirectionCorner::BottomRight,
394
                dir_to: DirectionCorner::TopLeft,
395
            })
396
        );
397
1
    }
398

            
399
    #[test]
400
1
    fn test_parse_direction_errors() {
401
1
        assert!(parse_direction("").is_err());
402
1
        assert!(parse_direction("to").is_err());
403
1
        assert!(parse_direction("right").is_err());
404
1
        assert!(parse_direction("to center").is_err());
405
1
        assert!(parse_direction("to top right bottom").is_err());
406
1
        assert!(parse_direction("to top top").is_err());
407
1
    }
408
}