1use crate::ir::WidgetKind;
14use std::collections::HashSet;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct WidgetSchema {
37 pub required: &'static [&'static str],
39 pub optional: &'static [&'static str],
41 pub events: &'static [&'static str],
43 pub style_attributes: &'static [&'static str],
45 pub layout_attributes: &'static [&'static str],
47}
48
49pub const COMMON_STYLE_ATTRIBUTES: &[&str] = &[
51 "background",
52 "color",
53 "border_color",
54 "border_width",
55 "border_radius",
56 "border_style",
57 "shadow",
58 "opacity",
59 "transform",
60 "style",
61 "text_color",
62 "shadow_color",
63 "shadow_offset",
64 "shadow_blur_radius",
65];
66
67pub const COMMON_LAYOUT_ATTRIBUTES: &[&str] = &[
69 "id",
70 "width",
71 "height",
72 "min_width",
73 "max_width",
74 "min_height",
75 "max_height",
76 "padding",
77 "spacing",
78 "align_items",
79 "justify_content",
80 "align",
81 "align_x",
82 "align_y",
83 "align_self",
84 "direction",
85 "position",
86 "top",
87 "right",
88 "bottom",
89 "left",
90 "z_index",
91 "class",
92 "theme",
93 "theme_ref",
94];
95
96pub const COMMON_EVENTS: &[&str] = &[
98 "on_click",
99 "on_press",
100 "on_release",
101 "on_change",
102 "on_input",
103 "on_submit",
104 "on_select",
105 "on_toggle",
106 "on_scroll",
107];
108
109impl WidgetSchema {
110 pub fn all_valid(&self) -> HashSet<&'static str> {
114 let mut set = HashSet::new();
115 set.extend(self.required.iter().cloned());
116 set.extend(self.optional.iter().cloned());
117 set.extend(self.events.iter().cloned());
118 set.extend(self.style_attributes.iter().cloned());
119 set.extend(self.layout_attributes.iter().cloned());
120 set
121 }
122
123 pub fn all_valid_names(&self) -> Vec<&'static str> {
125 let mut names = Vec::new();
126 names.extend_from_slice(self.required);
127 names.extend_from_slice(self.optional);
128 names.extend_from_slice(self.events);
129 names.extend_from_slice(self.style_attributes);
130 names.extend_from_slice(self.layout_attributes);
131 names
132 }
133}
134
135pub fn get_widget_schema(kind: &WidgetKind) -> WidgetSchema {
147 match kind {
148 WidgetKind::Text => WidgetSchema {
149 required: &["value"],
150 optional: &["size", "weight", "color"],
151 events: COMMON_EVENTS,
152 style_attributes: COMMON_STYLE_ATTRIBUTES,
153 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
154 },
155 WidgetKind::Image => WidgetSchema {
156 required: &["src"],
157 optional: &["width", "height", "fit", "filter_method", "path"],
158 events: COMMON_EVENTS,
159 style_attributes: COMMON_STYLE_ATTRIBUTES,
160 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
161 },
162 WidgetKind::Button => WidgetSchema {
163 required: &[],
164 optional: &["label", "enabled"],
165 events: &["on_click", "on_press", "on_release"],
166 style_attributes: COMMON_STYLE_ATTRIBUTES,
167 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
168 },
169 WidgetKind::TextInput => WidgetSchema {
170 required: &[],
171 optional: &["placeholder", "value", "password", "icon", "size"],
172 events: &["on_input", "on_submit", "on_change", "on_paste"],
173 style_attributes: COMMON_STYLE_ATTRIBUTES,
174 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
175 },
176 WidgetKind::Checkbox => WidgetSchema {
177 required: &[],
178 optional: &["checked", "label", "icon", "size"],
179 events: &["on_toggle"],
180 style_attributes: COMMON_STYLE_ATTRIBUTES,
181 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
182 },
183 WidgetKind::Radio => WidgetSchema {
184 required: &["label", "value"],
185 optional: &[
186 "id",
187 "selected",
188 "disabled",
189 "size",
190 "text_size",
191 "text_line_height",
192 "text_shaping",
193 ],
194 events: &["on_select"],
195 style_attributes: COMMON_STYLE_ATTRIBUTES,
196 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
197 },
198 WidgetKind::Slider => WidgetSchema {
199 required: &[],
200 optional: &["min", "max", "value", "step"],
201 events: &["on_change", "on_release"],
202 style_attributes: COMMON_STYLE_ATTRIBUTES,
203 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
204 },
205 WidgetKind::Column | WidgetKind::Row | WidgetKind::Container => WidgetSchema {
206 required: &[],
207 optional: &[],
208 events: COMMON_EVENTS,
209 style_attributes: COMMON_STYLE_ATTRIBUTES,
210 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
211 },
212 WidgetKind::Scrollable => WidgetSchema {
213 required: &[],
214 optional: &[],
215 events: &["on_scroll"],
216 style_attributes: COMMON_STYLE_ATTRIBUTES,
217 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
218 },
219 WidgetKind::Stack => WidgetSchema {
220 required: &[],
221 optional: &[],
222 events: COMMON_EVENTS,
223 style_attributes: COMMON_STYLE_ATTRIBUTES,
224 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
225 },
226 WidgetKind::Svg => WidgetSchema {
227 required: &["src"],
228 optional: &["width", "height", "path"],
229 events: COMMON_EVENTS,
230 style_attributes: COMMON_STYLE_ATTRIBUTES,
231 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
232 },
233 WidgetKind::PickList => WidgetSchema {
234 required: &[],
235 optional: &["placeholder", "selected", "options"],
236 events: &["on_select"],
237 style_attributes: COMMON_STYLE_ATTRIBUTES,
238 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
239 },
240 WidgetKind::Toggler => WidgetSchema {
241 required: &[],
242 optional: &["checked", "active", "toggled", "label"],
243 events: &["on_toggle"],
244 style_attributes: COMMON_STYLE_ATTRIBUTES,
245 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
246 },
247 WidgetKind::Space | WidgetKind::Rule => WidgetSchema {
248 required: &[],
249 optional: &[],
250 events: &[],
251 style_attributes: COMMON_STYLE_ATTRIBUTES,
252 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
253 },
254 WidgetKind::ComboBox => WidgetSchema {
255 required: &[],
256 optional: &["placeholder", "value", "selected", "options"],
257 events: &["on_input", "on_select"],
258 style_attributes: COMMON_STYLE_ATTRIBUTES,
259 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
260 },
261 WidgetKind::ProgressBar => WidgetSchema {
262 required: &[],
263 optional: &["value", "min", "max", "style"],
264 events: &[],
265 style_attributes: COMMON_STYLE_ATTRIBUTES,
266 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
267 },
268 WidgetKind::Tooltip => WidgetSchema {
269 required: &[],
270 optional: &["message", "position", "delay"],
271 events: COMMON_EVENTS,
272 style_attributes: COMMON_STYLE_ATTRIBUTES,
273 layout_attributes: &[],
276 },
277 WidgetKind::Grid => WidgetSchema {
278 required: &[],
279 optional: &["columns"],
280 events: COMMON_EVENTS,
281 style_attributes: COMMON_STYLE_ATTRIBUTES,
282 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
283 },
284 WidgetKind::Canvas => WidgetSchema {
285 required: &[],
286 optional: &["width", "height", "program", "cache"],
287 events: &["on_click", "on_drag", "on_move", "on_release"],
288 style_attributes: COMMON_STYLE_ATTRIBUTES,
289 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
290 },
291 WidgetKind::CanvasRect => WidgetSchema {
292 required: &["x", "y", "width", "height"],
293 optional: &["fill", "stroke", "stroke_width", "radius"],
294 events: &[],
295 style_attributes: &[],
296 layout_attributes: &[],
297 },
298 WidgetKind::CanvasCircle => WidgetSchema {
299 required: &["cx", "cy", "radius"],
300 optional: &["fill", "stroke", "stroke_width"],
301 events: &[],
302 style_attributes: &[],
303 layout_attributes: &[],
304 },
305 WidgetKind::CanvasLine => WidgetSchema {
306 required: &["x1", "y1", "x2", "y2"],
307 optional: &["stroke", "stroke_width"],
308 events: &[],
309 style_attributes: &[],
310 layout_attributes: &[],
311 },
312 WidgetKind::CanvasText => WidgetSchema {
313 required: &["x", "y", "content"],
314 optional: &["size", "color"],
315 events: &[],
316 style_attributes: &[],
317 layout_attributes: &[],
318 },
319 WidgetKind::CanvasGroup => WidgetSchema {
320 required: &[],
321 optional: &["transform"],
322 events: &[],
323 style_attributes: &[],
324 layout_attributes: &[],
325 },
326 WidgetKind::DatePicker => WidgetSchema {
327 required: &[],
328 optional: &["value", "format", "show", "min_date", "max_date"],
329 events: &["on_submit", "on_cancel"],
330 style_attributes: COMMON_STYLE_ATTRIBUTES,
331 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
332 },
333 WidgetKind::TimePicker => WidgetSchema {
334 required: &[],
335 optional: &["value", "format", "show", "use_24h", "show_seconds"],
336 events: &["on_submit", "on_cancel"],
337 style_attributes: COMMON_STYLE_ATTRIBUTES,
338 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
339 },
340 WidgetKind::ColorPicker => WidgetSchema {
341 required: &[],
342 optional: &["value", "show", "show_alpha", "enabled"],
343 events: &["on_submit", "on_cancel", "on_change"],
344 style_attributes: COMMON_STYLE_ATTRIBUTES,
345 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
346 },
347 WidgetKind::Menu => WidgetSchema {
348 required: &[],
349 optional: &["position", "close_on_select", "width", "spacing", "class"],
350 events: &["on_open", "on_close"],
351 style_attributes: COMMON_STYLE_ATTRIBUTES,
352 layout_attributes: &["width", "padding"],
353 },
354 WidgetKind::MenuItem => WidgetSchema {
355 required: &["label"],
356 optional: &["icon", "shortcut", "disabled", "class"],
357 events: &["on_click"],
358 style_attributes: COMMON_STYLE_ATTRIBUTES,
359 layout_attributes: &["padding"],
360 },
361 WidgetKind::MenuSeparator => WidgetSchema {
362 required: &[],
363 optional: &[],
364 events: &[],
365 style_attributes: &["color", "opacity"],
366 layout_attributes: &["height"],
367 },
368 WidgetKind::ContextMenu => WidgetSchema {
369 required: &[],
370 optional: &["context"],
371 events: &["on_open", "on_close"],
372 style_attributes: COMMON_STYLE_ATTRIBUTES,
373 layout_attributes: &[],
374 },
375 WidgetKind::Float => WidgetSchema {
376 required: &[],
377 optional: &[],
378 events: COMMON_EVENTS,
379 style_attributes: COMMON_STYLE_ATTRIBUTES,
380 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
381 },
382 WidgetKind::DataTable => WidgetSchema {
383 required: &["data"],
384 optional: &[
385 "width",
386 "height",
387 "min_width",
388 "max_width",
389 "scrollbar_width",
390 ],
391 events: &["on_row_click"],
392 style_attributes: COMMON_STYLE_ATTRIBUTES,
393 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
394 },
395 WidgetKind::DataColumn => WidgetSchema {
396 required: &["header"],
397 optional: &["field", "width", "min_width", "max_width", "align"],
398 events: &[],
399 style_attributes: &[],
400 layout_attributes: &[],
401 },
402 WidgetKind::For => WidgetSchema {
403 required: &["each", "in"],
404 optional: &["template"],
405 events: &[],
406 style_attributes: COMMON_STYLE_ATTRIBUTES,
407 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
408 },
409 WidgetKind::If => WidgetSchema {
410 required: &["condition"],
411 optional: &[],
412 events: &[],
413 style_attributes: COMMON_STYLE_ATTRIBUTES,
414 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
415 },
416 WidgetKind::TreeView => WidgetSchema {
417 required: &[],
418 optional: &[
419 "nodes",
420 "expanded",
421 "selected",
422 "indent_size",
423 "node_height",
424 "icon_size",
425 "expand_icon",
426 "collapse_icon",
427 "leaf_icon",
428 ],
429 events: &["on_toggle", "on_select", "on_double_click"],
430 style_attributes: COMMON_STYLE_ATTRIBUTES,
431 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
432 },
433 WidgetKind::TreeNode => WidgetSchema {
434 required: &["id", "label"],
435 optional: &["icon", "expanded", "selected", "disabled", "class"],
436 events: &[],
437 style_attributes: &[],
438 layout_attributes: &[],
439 },
440 WidgetKind::Custom(_) => WidgetSchema {
441 required: &[],
442 optional: &[],
443 events: &[],
444 style_attributes: &[],
445 layout_attributes: &[],
446 },
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::ir::WidgetKind;
454
455 #[test]
456 fn test_button_schema_contains_expected_attributes() {
457 let schema = WidgetKind::Button.schema();
458 let valid = schema.all_valid();
459
460 assert!(valid.contains("on_click"));
461 assert!(valid.contains("label"));
462 assert!(valid.contains("width"));
464 assert!(valid.contains("background"));
465 }
466
467 #[test]
468 fn test_container_schema_includes_layout_attributes() {
469 let schema = WidgetKind::Container.schema();
470 let valid = schema.all_valid();
471
472 assert!(valid.contains("padding"));
473 assert!(valid.contains("align_x"));
474 assert!(valid.contains("width"));
475 }
476
477 #[test]
478 fn test_textinput_schema_includes_size() {
479 let schema = WidgetKind::TextInput.schema();
480 let valid = schema.all_valid();
481
482 assert!(valid.contains("size"));
483 assert!(valid.contains("placeholder"));
484 }
485
486 #[test]
487 fn test_custom_widget_returns_permissive_schema() {
488 let schema = WidgetKind::Custom("MyWidget".to_string()).schema();
489 assert!(schema.required.is_empty());
490 }
491
492 #[test]
493 fn test_all_widget_kinds_have_schema() {
494 let kinds = [
495 WidgetKind::Column,
496 WidgetKind::Row,
497 WidgetKind::Text,
498 WidgetKind::Button,
499 WidgetKind::Image,
500 ];
501
502 for kind in kinds {
503 let _ = kind.schema();
504 }
505 }
506}