bevy_flair_css_parser/reflect/
image.rs

1use crate::error::CssError;
2use crate::error_codes::image as error_codes;
3use crate::reflect::ui::{parse_f32, parse_four_values};
4use crate::utils::parse_property_value_with;
5use crate::{ParserExt, ReflectParseCss};
6use bevy_flair_core::ReflectValue;
7use bevy_reflect::FromType;
8use bevy_ui::prelude::{BorderRect, TextureSlicer};
9use bevy_ui::widget::NodeImageMode;
10use cssparser::{Parser, Token};
11
12struct TiledParams {
13    tile_x: bool,
14    tile_y: bool,
15    stretch_value: f32,
16}
17
18impl Default for TiledParams {
19    fn default() -> Self {
20        TiledParams {
21            tile_x: false,
22            tile_y: false,
23            stretch_value: 1.0,
24        }
25    }
26}
27
28fn parse_tiled_params(parser: &mut Parser) -> Result<TiledParams, CssError> {
29    let mut result = TiledParams::default();
30
31    while !parser.is_exhausted() {
32        let next = parser.located_next()?;
33        match &*next {
34            Token::Ident(ident) if ident.as_ref() == "tile_x" => {
35                result.tile_x = true;
36            }
37            Token::Ident(ident) if ident.as_ref() == "tile_y" => {
38                result.tile_y = true;
39            }
40            Token::Number { value, .. } => {
41                result.stretch_value = *value;
42            }
43            _ => {
44                return Err(CssError::new_located(
45                    &next,
46                    error_codes::UNEXPECTED_RILED_TOKEN,
47                    "This is not valid tiled token. 'tile_x', 'tile_y', 3.2 are valid tokens",
48                ));
49            }
50        }
51    }
52
53    Ok(result)
54}
55
56fn parse_sliced_params(parser: &mut Parser) -> Result<TextureSlicer, CssError> {
57    let [top, right, bottom, left] = parse_four_values(parser, parse_f32)?;
58
59    let border = BorderRect {
60        left,
61        right,
62        top,
63        bottom,
64    };
65
66    Ok(TextureSlicer {
67        border,
68        ..Default::default()
69    })
70}
71
72// This is completely custom parsing, since there is nothing in css similar
73fn parse_image_mode(parser: &mut Parser) -> Result<NodeImageMode, CssError> {
74    let next = parser.located_next()?;
75    Ok(match &*next {
76        Token::Ident(ident) if ident.as_ref() == "auto" => NodeImageMode::Auto,
77        Token::Ident(ident) if ident.as_ref() == "stretch" => NodeImageMode::Stretch,
78        Token::Function(name) if name.eq_ignore_ascii_case("tiled") => {
79            let TiledParams {
80                tile_x,
81                tile_y,
82                stretch_value,
83            } = parser.parse_nested_block_with(parse_tiled_params)?;
84
85            NodeImageMode::Tiled {
86                tile_x,
87                tile_y,
88                stretch_value,
89            }
90        }
91        Token::Function(name) if name.eq_ignore_ascii_case("sliced") => {
92            let texture_slicer = parser.parse_nested_block_with(parse_sliced_params)?;
93
94            NodeImageMode::Sliced(texture_slicer)
95        }
96        _ => {
97            return Err(CssError::new_located(
98                &next,
99                error_codes::UNEXPECTED_IMAGE_MODE_TOKEN,
100                "This is not valid ImageMode token. 'auto', 'stretch', tiled(tile_x), sliced(20px) are valid examples",
101            ));
102        }
103    })
104}
105
106impl FromType<NodeImageMode> for ReflectParseCss {
107    fn from_type() -> Self {
108        ReflectParseCss(|parser| {
109            parse_property_value_with(parser, |parser| {
110                parse_image_mode(parser).map(ReflectValue::new)
111            })
112        })
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use crate::reflect::testing::test_parse_reflect;
119    use bevy_ui::prelude::{BorderRect, TextureSlicer};
120
121    use bevy_ui::widget::NodeImageMode;
122
123    #[test]
124    fn test_image_mode() {
125        // TODO: NodeImageMode does not implement PartialEq. Try to upstream it to bevy.
126        assert!(matches!(
127            test_parse_reflect::<NodeImageMode>("auto"),
128            NodeImageMode::Auto
129        ));
130
131        assert!(matches!(
132            test_parse_reflect::<NodeImageMode>("stretch"),
133            NodeImageMode::Stretch
134        ));
135
136        assert!(matches!(
137            test_parse_reflect::<NodeImageMode>("tiled()"),
138            NodeImageMode::Tiled { .. }
139        ));
140
141        let expected_slicer = TextureSlicer {
142            border: BorderRect::all(20.0),
143            ..Default::default()
144        };
145
146        assert!(matches!(
147            test_parse_reflect::<NodeImageMode>("sliced(20px)"),
148            NodeImageMode::Sliced(_)
149        ));
150
151        let NodeImageMode::Sliced(slicer) = test_parse_reflect::<NodeImageMode>("sliced(20px)")
152        else {
153            unreachable!();
154        };
155
156        assert_eq!(expected_slicer, slicer);
157    }
158}