bevy_flair_css_parser/reflect/
image.rs1use 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
72fn 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 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}