1use crate::components::{
12 Fill, InlineStyle, LumenAttributes, LumenClasses, Opacity, Style, TextContent, Transform,
13 Visible, Visuals, ZIndex,
14};
15use bevy_ecs::world::EntityRef;
16
17pub type ComponentValueMap = Vec<(String, String)>;
19
20pub type ComponentReader = fn(EntityRef) -> Option<ComponentValueMap>;
23
24pub struct ComponentIntrospection {
27 readers: Vec<(&'static str, ComponentReader)>,
28}
29
30impl ComponentIntrospection {
31 pub fn with_defaults() -> Self {
34 Self {
35 readers: vec![
36 ("LayoutBox", read_layout_box),
37 ("Visuals", read_visuals),
38 ("Opacity", read_opacity),
39 ("ZIndex", read_z_index),
40 ("Visible", read_visible),
41 ("TextContent", read_text_content),
42 ("LumenClasses", read_classes),
43 ("LumenAttributes", read_attributes),
44 ("InlineStyle", read_inline_style),
45 ("Style", read_style),
46 ],
47 }
48 }
49
50 pub fn names(&self) -> Vec<&'static str> {
52 self.readers.iter().map(|(n, _)| *n).collect()
53 }
54
55 pub fn is_known(&self, name: &str) -> bool {
57 self.readers.iter().any(|(n, _)| *n == name)
58 }
59
60 pub fn read_all(&self, entity: EntityRef) -> Vec<(String, ComponentValueMap)> {
62 self.readers
63 .iter()
64 .filter_map(|(name, reader)| reader(entity).map(|map| (name.to_string(), map)))
65 .collect()
66 }
67}
68
69fn read_layout_box(e: EntityRef) -> Option<ComponentValueMap> {
70 let t = e.get::<Transform>()?;
71 let mut m = vec![
72 ("x".into(), t.absolute.x.to_string()),
73 ("y".into(), t.absolute.y.to_string()),
74 ("width".into(), t.size.x.to_string()),
75 ("height".into(), t.size.y.to_string()),
76 ];
77 if let Some(b) = t.baseline_y {
78 m.push(("baseline_y".into(), b.to_string()));
79 }
80 Some(m)
81}
82
83fn hex(c: &crate::components::Color) -> String {
84 let [r, g, b, a] = c.to_rgba8();
85 if a == 0xff {
86 format!("#{r:02x}{g:02x}{b:02x}")
87 } else {
88 format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
89 }
90}
91
92fn read_visuals(e: EntityRef) -> Option<ComponentValueMap> {
93 let v = e.get::<Visuals>()?;
94 let mut m = vec![("radius".into(), v.radius.to_string())];
95 match &v.fill {
96 Some(Fill::Solid(c)) => m.push(("fill".into(), hex(c))),
97 Some(_) => m.push(("fill".into(), "gradient".into())),
98 None => {}
99 }
100 if let Some(border) = &v.border {
101 m.push(("border_color".into(), hex(&border.color)));
102 m.push(("border_width".into(), border.widths.top.to_string()));
103 }
104 m.push(("shadows".into(), v.shadows.len().to_string()));
105 Some(m)
106}
107
108fn read_opacity(e: EntityRef) -> Option<ComponentValueMap> {
109 let o = e.get::<Opacity>()?;
110 Some(vec![("value".into(), o.0.to_string())])
111}
112
113fn read_z_index(e: EntityRef) -> Option<ComponentValueMap> {
114 let z = e.get::<ZIndex>()?;
115 Some(vec![("value".into(), z.0.to_string())])
116}
117
118fn read_visible(e: EntityRef) -> Option<ComponentValueMap> {
119 let v = e.get::<Visible>()?;
120 Some(vec![("value".into(), v.0.to_string())])
121}
122
123fn read_text_content(e: EntityRef) -> Option<ComponentValueMap> {
124 let t = e.get::<TextContent>()?;
125 Some(vec![("text".into(), t.0.clone())])
126}
127
128fn read_classes(e: EntityRef) -> Option<ComponentValueMap> {
129 let c = e.get::<LumenClasses>()?;
130 Some(vec![(
131 "classes".into(),
132 c.0.iter()
133 .map(|s| s.to_string())
134 .collect::<Vec<_>>()
135 .join(" "),
136 )])
137}
138
139fn read_attributes(e: EntityRef) -> Option<ComponentValueMap> {
140 let a = e.get::<LumenAttributes>()?;
141 let mut m: ComponentValueMap = a.0.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
142 m.sort();
143 Some(m)
144}
145
146fn read_inline_style(e: EntityRef) -> Option<ComponentValueMap> {
147 let s = e.get::<InlineStyle>()?;
148 Some(s.0.clone())
149}
150
151fn read_style(e: EntityRef) -> Option<ComponentValueMap> {
152 let s = e.get::<Style>()?;
153 Some(vec![
154 ("display".into(), format!("{:?}", s.display)),
155 ("width".into(), format!("{:?}", s.width)),
156 ("height".into(), format!("{:?}", s.height)),
157 ])
158}