1use std::fmt;
5
6use bevy::text::{FontWeight, Justify, LineBreak};
7use bevy::ui::{
8 AlignContent, AlignItems, AlignSelf, BoxSizing, Display, FlexDirection, FlexWrap, FocusPolicy,
9 GridAutoFlow, JustifyContent, JustifyItems, JustifySelf, OverflowAxis, PositionType,
10};
11
12use serde::de::{self, Deserializer, Visitor};
13
14use super::background_image::BackgroundImageMode;
15use super::decode_warn;
16use super::style::LayerCache;
17
18macro_rules! keyword_fields {
25 ( $(
26 $(#[$meta:meta])*
27 fn $fn_name:ident($kind:literal) -> $ty:ty {
28 $( $($kw:literal)|+ => $variant:ident ),+ $(,)?
29 }
30 )+ ) => { $(
31 $(#[$meta])*
32 pub(crate) fn $fn_name<'de, D: Deserializer<'de>>(d: D) -> Result<Option<$ty>, D::Error> {
33 struct V;
34 impl<'de> Visitor<'de> for V {
35 type Value = Option<$ty>;
36 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 f.write_str(concat!("a `", $kind, "` keyword string"))
38 }
39 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
40 Ok(Some(match s {
41 $( $($kw)|+ => <$ty>::$variant, )+
42 _ => {
43 decode_warn(
44 $kind,
45 s,
46 &format!("unrecognized {} {s:?}", $kind),
47 );
48 <$ty>::default()
49 }
50 }))
51 }
52 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
53 Ok(None)
54 }
55 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
56 Ok(None)
57 }
58 }
59 d.deserialize_any(V)
60 }
61 )+ };
62}
63
64keyword_fields! {
65 fn de_display("display") -> Display {
66 "flex" => Flex, "grid" => Grid, "block" => Block, "none" => None,
67 }
68 fn de_layer_cache("cache") -> LayerCache {
69 "auto" => Auto, "always" => Always, "never" => Never,
70 }
71 fn de_box_sizing("boxSizing") -> BoxSizing {
72 "borderBox" | "border-box" => BorderBox,
73 "contentBox" | "content-box" => ContentBox,
74 }
75 fn de_position_type("positionType") -> PositionType {
76 "absolute" => Absolute, "relative" => Relative,
77 }
78 fn de_overflow_axis("overflow") -> OverflowAxis {
79 "visible" => Visible, "clip" => Clip, "hidden" => Hidden, "scroll" => Scroll,
80 }
81 fn de_align_items("alignItems") -> AlignItems {
87 "start" => Start, "end" => End,
88 "flexStart" => FlexStart, "flexEnd" => FlexEnd,
89 "center" => Center, "baseline" => Baseline, "stretch" => Stretch,
90 }
91 fn de_justify_items("justifyItems") -> JustifyItems {
92 "start" => Start, "end" => End,
93 "center" => Center, "baseline" => Baseline, "stretch" => Stretch,
94 }
95 fn de_align_self("alignSelf") -> AlignSelf {
96 "auto" => Auto, "start" => Start, "end" => End,
97 "flexStart" => FlexStart, "flexEnd" => FlexEnd,
98 "center" => Center, "baseline" => Baseline, "stretch" => Stretch,
99 }
100 fn de_justify_self("justifySelf") -> JustifySelf {
101 "auto" => Auto, "start" => Start, "end" => End,
102 "center" => Center, "baseline" => Baseline, "stretch" => Stretch,
103 }
104 fn de_align_content("alignContent") -> AlignContent {
105 "start" => Start, "end" => End,
106 "flexStart" => FlexStart, "flexEnd" => FlexEnd,
107 "center" => Center, "stretch" => Stretch,
108 "spaceBetween" => SpaceBetween, "spaceEvenly" => SpaceEvenly,
109 "spaceAround" => SpaceAround,
110 }
111 fn de_justify_content("justifyContent") -> JustifyContent {
112 "start" => Start, "end" => End,
113 "flexStart" => FlexStart, "flexEnd" => FlexEnd,
114 "center" => Center, "stretch" => Stretch,
115 "spaceBetween" => SpaceBetween, "spaceEvenly" => SpaceEvenly,
116 "spaceAround" => SpaceAround,
117 }
118 fn de_flex_direction("flexDirection") -> FlexDirection {
119 "row" => Row, "column" => Column,
120 "rowReverse" => RowReverse, "columnReverse" => ColumnReverse,
121 }
122 fn de_flex_wrap("flexWrap") -> FlexWrap {
123 "nowrap" | "noWrap" => NoWrap, "wrap" => Wrap, "wrapReverse" => WrapReverse,
124 }
125 fn de_grid_auto_flow("gridAutoFlow") -> GridAutoFlow {
126 "row" => Row, "column" => Column,
127 "rowDense" => RowDense, "columnDense" => ColumnDense,
128 }
129 fn de_focus_policy("focusPolicy") -> FocusPolicy {
132 "block" => Block, "pass" => Pass,
133 }
134 fn de_text_align("textAlign") -> Justify {
135 "left" => Left, "center" => Center, "right" => Right,
136 "justify" => Justified, "start" => Start, "end" => End,
137 }
138 fn de_line_break("lineBreak") -> LineBreak {
139 "wordBoundary" => WordBoundary, "anyCharacter" => AnyCharacter,
140 "wordOrCharacter" => WordOrCharacter, "noWrap" => NoWrap,
141 }
142 fn de_bg_image_mode("backgroundImage") -> BackgroundImageMode {
145 "stretch" => Stretch, "repeat" => Repeat,
146 "repeatX" => RepeatX, "repeatY" => RepeatY,
147 }
148}
149
150pub(crate) fn de_font_weight<'de, D: Deserializer<'de>>(
154 d: D,
155) -> Result<Option<FontWeight>, D::Error> {
156 struct V;
157 impl<'de> Visitor<'de> for V {
158 type Value = Option<FontWeight>;
159 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 f.write_str("a `fontWeight` keyword or numeric weight string")
161 }
162 fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
163 Ok(Some(match s {
164 "thin" => FontWeight::THIN,
165 "light" => FontWeight(300),
166 "normal" => FontWeight::NORMAL,
167 "medium" => FontWeight(500),
168 "semibold" => FontWeight(600),
169 "bold" => FontWeight::BOLD,
170 "black" => FontWeight::BLACK,
171 other => other.parse::<u16>().map(FontWeight).unwrap_or_else(|_| {
172 decode_warn(
173 "fontWeight",
174 other,
175 &format!("unrecognized fontWeight {other:?}"),
176 );
177 FontWeight::NORMAL
178 }),
179 }))
180 }
181 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
182 Ok(None)
183 }
184 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
185 Ok(None)
186 }
187 }
188 d.deserialize_any(V)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::protocol::style::Style;
195
196 #[test]
202 fn keyword_fields_decode_to_bevy_enums() {
203 let s: Style = serde_json::from_value(serde_json::json!({
204 "display": "grid",
205 "alignItems": "start",
206 "alignSelf": "flexStart",
207 "alignContent": "spaceBetween",
208 "justifyContent": "flexEnd",
209 "flexWrap": "nowrap",
210 "focusPolicy": "block",
211 "textAlign": "justify",
212 "lineBreak": "anyCharacter",
213 }))
214 .expect("keyword style decodes");
215 assert_eq!(s.display, Some(Display::Grid));
216 assert_eq!(s.align_items, Some(AlignItems::Start));
217 assert_eq!(s.align_self, Some(AlignSelf::FlexStart));
218 assert_eq!(s.align_content, Some(AlignContent::SpaceBetween));
219 assert_eq!(s.justify_content, Some(JustifyContent::FlexEnd));
220 assert_eq!(s.flex_wrap, Some(FlexWrap::NoWrap));
221 assert_eq!(s.focus_policy, Some(FocusPolicy::Block));
222 assert_eq!(s.text_align, Some(Justify::Justified));
223 assert_eq!(s.line_break, Some(LineBreak::AnyCharacter));
224
225 let s: Style = serde_json::from_value(serde_json::json!({
226 "alignItems": "flexStart",
227 "justifyContent": "start",
228 "boxSizing": "border-box",
230 "flexWrap": "noWrap",
231 }))
232 .expect("alias keywords decode");
233 assert_eq!(s.align_items, Some(AlignItems::FlexStart));
234 assert_eq!(s.justify_content, Some(JustifyContent::Start));
235 assert_eq!(s.box_sizing, Some(BoxSizing::BorderBox));
236 assert_eq!(s.flex_wrap, Some(FlexWrap::NoWrap));
237 }
238
239 #[test]
243 fn unknown_enum_keywords_fall_back_to_default() {
244 let s: Style = serde_json::from_value(serde_json::json!({
245 "display": "flx",
246 "alignItems": "centre",
247 "flexDirection": "sideways",
248 "textAlign": "middle",
249 "fontWeight": "heavyish",
250 "focusPolicy": "weird",
251 "lineBreak": "wordBoundary",
253 }))
254 .expect("bad keywords must not abort deserialization");
255 assert_eq!(s.display, Some(Display::default()));
256 assert_eq!(s.align_items, Some(AlignItems::default()));
257 assert_eq!(s.flex_direction, Some(FlexDirection::default()));
258 assert_eq!(s.text_align, Some(Justify::default()));
259 assert_eq!(s.font_weight, Some(FontWeight::NORMAL));
260 assert_eq!(s.focus_policy, Some(FocusPolicy::Pass));
261 assert_eq!(s.line_break, Some(LineBreak::WordBoundary));
262 }
263
264 #[test]
266 fn font_weight_keywords_and_numeric() {
267 let fw = |v: serde_json::Value| {
268 serde_json::from_value::<Style>(serde_json::json!({ "fontWeight": v }))
269 .expect("fontWeight decodes")
270 .font_weight
271 };
272 assert_eq!(fw("bold".into()), Some(FontWeight::BOLD));
273 assert_eq!(fw("600".into()), Some(FontWeight(600)));
274 assert_eq!(fw("thin".into()), Some(FontWeight::THIN));
275 }
276}