1use indexmap::IndexMap;
4use nemo_plugin_api::PluginValue;
5
6pub trait Builder {
8 fn build(self) -> PluginValue;
10}
11
12pub struct LayoutBuilder {
14 attributes: IndexMap<String, PluginValue>,
15 children: Option<IndexMap<String, PluginValue>>,
16}
17
18impl LayoutBuilder {
19 pub fn new(component_type: impl Into<String>) -> Self {
21 let mut attributes = IndexMap::new();
22 attributes.insert(
23 "type".to_string(),
24 PluginValue::String(component_type.into()),
25 );
26
27 Self {
28 attributes,
29 children: None,
30 }
31 }
32
33 pub fn attr(mut self, key: impl Into<String>, value: impl Into<PluginValue>) -> Self {
35 self.attributes.insert(key.into(), value.into());
36 self
37 }
38
39 pub fn attrs(mut self, attrs: &[(&str, PluginValue)]) -> Self {
41 for (k, v) in attrs {
42 self.attributes.insert(k.to_string(), v.clone());
43 }
44 self
45 }
46
47 pub fn child(mut self, id: impl Into<String>, child: impl Builder) -> Self {
49 let children = self.children.get_or_insert_with(IndexMap::new);
50 children.insert(id.into(), child.build());
51 self
52 }
53
54 pub fn children(mut self, children: impl IntoIterator<Item = (String, PluginValue)>) -> Self {
56 let child_map = self.children.get_or_insert_with(IndexMap::new);
57 for (id, value) in children {
58 child_map.insert(id, value);
59 }
60 self
61 }
62
63 pub fn with_children(mut self, children: IndexMap<String, PluginValue>) -> Self {
65 self.children = Some(children);
66 self
67 }
68}
69
70impl Builder for LayoutBuilder {
71 fn build(mut self) -> PluginValue {
72 if let Some(children) = self.children {
73 self.attributes
74 .insert("component".to_string(), PluginValue::Object(children));
75 }
76 PluginValue::Object(self.attributes)
77 }
78}
79
80impl LayoutBuilder {
82 pub fn width(self, width: i64) -> Self {
84 self.attr("width", PluginValue::Integer(width))
85 }
86
87 pub fn height(self, height: i64) -> Self {
89 self.attr("height", PluginValue::Integer(height))
90 }
91
92 pub fn padding(self, padding: i64) -> Self {
94 self.attr("padding", PluginValue::Integer(padding))
95 }
96
97 pub fn margin(self, margin: i64) -> Self {
99 self.attr("margin", PluginValue::Integer(margin))
100 }
101
102 pub fn border(self, border: i64) -> Self {
104 self.attr("border", PluginValue::Integer(border))
105 }
106
107 pub fn border_color(self, color: impl Into<String>) -> Self {
109 self.attr("border_color", PluginValue::String(color.into()))
110 }
111
112 pub fn bg_color(self, color: impl Into<String>) -> Self {
114 self.attr("bg_color", PluginValue::String(color.into()))
115 }
116
117 pub fn shadow(self, shadow: impl Into<String>) -> Self {
119 self.attr("shadow", PluginValue::String(shadow.into()))
120 }
121
122 pub fn bind(self, property: impl Into<String>, path: impl Into<String>) -> Self {
124 let bind_key = format!("bind_{}", property.into());
125 self.attr(bind_key, PluginValue::String(path.into()))
126 }
127
128 pub fn on(self, event: impl Into<String>, handler: impl Into<String>) -> Self {
130 let event_key = format!("on_{}", event.into());
131 self.attr(event_key, PluginValue::String(handler.into()))
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_layout_builder_basic() {
141 let layout = LayoutBuilder::new("button")
142 .attr("label", PluginValue::String("Click".into()))
143 .build();
144
145 match layout {
146 PluginValue::Object(map) => {
147 assert_eq!(map.get("type"), Some(&PluginValue::String("button".into())));
148 assert_eq!(map.get("label"), Some(&PluginValue::String("Click".into())));
149 }
150 _ => panic!("Expected Object"),
151 }
152 }
153
154 #[test]
155 fn test_layout_builder_with_children() {
156 let child = LayoutBuilder::new("label")
157 .attr("text", PluginValue::String("Child".into()))
158 .build();
159
160 let mut children = IndexMap::new();
161 children.insert("child1".to_string(), child);
162
163 let layout = LayoutBuilder::new("panel").with_children(children).build();
164
165 match layout {
166 PluginValue::Object(map) => {
167 assert!(map.contains_key("component"));
168 }
169 _ => panic!("Expected Object"),
170 }
171 }
172
173 #[test]
174 fn test_layout_builder_common_attrs() {
175 let layout = LayoutBuilder::new("panel")
176 .width(100)
177 .height(200)
178 .padding(10)
179 .margin(5)
180 .border(2)
181 .build();
182
183 match layout {
184 PluginValue::Object(map) => {
185 assert_eq!(map.get("width"), Some(&PluginValue::Integer(100)));
186 assert_eq!(map.get("height"), Some(&PluginValue::Integer(200)));
187 assert_eq!(map.get("padding"), Some(&PluginValue::Integer(10)));
188 assert_eq!(map.get("margin"), Some(&PluginValue::Integer(5)));
189 assert_eq!(map.get("border"), Some(&PluginValue::Integer(2)));
190 }
191 _ => panic!("Expected Object"),
192 }
193 }
194
195 #[test]
196 fn test_event_handler() {
197 let layout = LayoutBuilder::new("button")
198 .on("click", "handle_click")
199 .build();
200
201 match layout {
202 PluginValue::Object(map) => {
203 assert_eq!(
204 map.get("on_click"),
205 Some(&PluginValue::String("handle_click".into()))
206 );
207 }
208 _ => panic!("Expected Object"),
209 }
210 }
211
212 #[test]
213 fn test_data_binding() {
214 let layout = LayoutBuilder::new("label")
215 .bind("text", "data.message")
216 .build();
217
218 match layout {
219 PluginValue::Object(map) => {
220 assert_eq!(
221 map.get("bind_text"),
222 Some(&PluginValue::String("data.message".into()))
223 );
224 }
225 _ => panic!("Expected Object"),
226 }
227 }
228}