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: &[
264 "value",
265 "min",
266 "max",
267 "style",
268 "bar_color",
269 "background_color",
270 "border_radius",
271 "height",
272 "width",
273 ],
274 events: &[],
275 style_attributes: COMMON_STYLE_ATTRIBUTES,
276 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
277 },
278 WidgetKind::Tooltip => WidgetSchema {
279 required: &[],
280 optional: &["message", "position", "delay"],
281 events: COMMON_EVENTS,
282 style_attributes: COMMON_STYLE_ATTRIBUTES,
283 layout_attributes: &[],
286 },
287 WidgetKind::Grid => WidgetSchema {
288 required: &[],
289 optional: &["columns"],
290 events: COMMON_EVENTS,
291 style_attributes: COMMON_STYLE_ATTRIBUTES,
292 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
293 },
294 WidgetKind::Canvas => WidgetSchema {
295 required: &[],
296 optional: &["width", "height", "program", "cache"],
297 events: &["on_click", "on_drag", "on_move", "on_release"],
298 style_attributes: COMMON_STYLE_ATTRIBUTES,
299 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
300 },
301 WidgetKind::CanvasRect => WidgetSchema {
302 required: &["x", "y", "width", "height"],
303 optional: &["fill", "stroke", "stroke_width", "radius"],
304 events: &[],
305 style_attributes: &[],
306 layout_attributes: &[],
307 },
308 WidgetKind::CanvasCircle => WidgetSchema {
309 required: &["cx", "cy", "radius"],
310 optional: &["fill", "stroke", "stroke_width"],
311 events: &[],
312 style_attributes: &[],
313 layout_attributes: &[],
314 },
315 WidgetKind::CanvasLine => WidgetSchema {
316 required: &["x1", "y1", "x2", "y2"],
317 optional: &["stroke", "stroke_width"],
318 events: &[],
319 style_attributes: &[],
320 layout_attributes: &[],
321 },
322 WidgetKind::CanvasText => WidgetSchema {
323 required: &["x", "y", "content"],
324 optional: &["size", "color"],
325 events: &[],
326 style_attributes: &[],
327 layout_attributes: &[],
328 },
329 WidgetKind::CanvasGroup => WidgetSchema {
330 required: &[],
331 optional: &["transform"],
332 events: &[],
333 style_attributes: &[],
334 layout_attributes: &[],
335 },
336 WidgetKind::DatePicker => WidgetSchema {
337 required: &[],
338 optional: &["value", "format", "show", "min_date", "max_date"],
339 events: &["on_submit", "on_cancel"],
340 style_attributes: COMMON_STYLE_ATTRIBUTES,
341 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
342 },
343 WidgetKind::TimePicker => WidgetSchema {
344 required: &[],
345 optional: &["value", "format", "show", "use_24h", "show_seconds"],
346 events: &["on_submit", "on_cancel"],
347 style_attributes: COMMON_STYLE_ATTRIBUTES,
348 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
349 },
350 WidgetKind::ColorPicker => WidgetSchema {
351 required: &[],
352 optional: &["value", "show", "show_alpha", "enabled"],
353 events: &["on_submit", "on_cancel", "on_change"],
354 style_attributes: COMMON_STYLE_ATTRIBUTES,
355 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
356 },
357 WidgetKind::Menu => WidgetSchema {
358 required: &[],
359 optional: &["position", "close_on_select", "width", "spacing", "class"],
360 events: &["on_open", "on_close"],
361 style_attributes: COMMON_STYLE_ATTRIBUTES,
362 layout_attributes: &["width", "padding"],
363 },
364 WidgetKind::MenuItem => WidgetSchema {
365 required: &["label"],
366 optional: &["icon", "shortcut", "disabled", "class"],
367 events: &["on_click"],
368 style_attributes: COMMON_STYLE_ATTRIBUTES,
369 layout_attributes: &["padding"],
370 },
371 WidgetKind::MenuSeparator => WidgetSchema {
372 required: &[],
373 optional: &[],
374 events: &[],
375 style_attributes: &["color", "opacity"],
376 layout_attributes: &["height"],
377 },
378 WidgetKind::ContextMenu => WidgetSchema {
379 required: &[],
380 optional: &["context"],
381 events: &["on_open", "on_close"],
382 style_attributes: COMMON_STYLE_ATTRIBUTES,
383 layout_attributes: &[],
384 },
385 WidgetKind::Float => WidgetSchema {
386 required: &[],
387 optional: &[],
388 events: COMMON_EVENTS,
389 style_attributes: COMMON_STYLE_ATTRIBUTES,
390 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
391 },
392 WidgetKind::DataTable => WidgetSchema {
393 required: &["data"],
394 optional: &[
395 "width",
396 "height",
397 "min_width",
398 "max_width",
399 "scrollbar_width",
400 ],
401 events: &["on_row_click"],
402 style_attributes: COMMON_STYLE_ATTRIBUTES,
403 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
404 },
405 WidgetKind::DataColumn => WidgetSchema {
406 required: &["header"],
407 optional: &[
408 "field",
409 "width",
410 "min_width",
411 "max_width",
412 "align_x",
413 "align_y",
414 ],
415 events: &[],
416 style_attributes: &[],
417 layout_attributes: &[],
418 },
419 WidgetKind::For => WidgetSchema {
420 required: &["each", "in"],
421 optional: &["template"],
422 events: &[],
423 style_attributes: COMMON_STYLE_ATTRIBUTES,
424 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
425 },
426 WidgetKind::If => WidgetSchema {
427 required: &["condition"],
428 optional: &[],
429 events: &[],
430 style_attributes: COMMON_STYLE_ATTRIBUTES,
431 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
432 },
433 WidgetKind::TreeView => WidgetSchema {
434 required: &[],
435 optional: &[
436 "nodes",
437 "expanded",
438 "selected",
439 "indent_size",
440 "node_height",
441 "icon_size",
442 "expand_icon",
443 "collapse_icon",
444 "leaf_icon",
445 ],
446 events: &["on_toggle", "on_select", "on_double_click"],
447 style_attributes: COMMON_STYLE_ATTRIBUTES,
448 layout_attributes: COMMON_LAYOUT_ATTRIBUTES,
449 },
450 WidgetKind::TreeNode => WidgetSchema {
451 required: &["id", "label"],
452 optional: &["icon", "expanded", "selected", "disabled", "class"],
453 events: &[],
454 style_attributes: &[],
455 layout_attributes: &[],
456 },
457 WidgetKind::TabBar => WidgetSchema {
458 required: &["selected"],
459 optional: &[
460 "spacing",
461 "padding",
462 "icon_size",
463 "text_size",
464 "width",
465 "height",
466 "class",
467 ],
468 events: &["on_select"],
469 style_attributes: COMMON_STYLE_ATTRIBUTES,
470 layout_attributes: &["width", "height"],
471 },
472 WidgetKind::Tab => WidgetSchema {
473 required: &[],
474 optional: &["label", "icon", "enabled"],
475 events: &["on_click"],
476 style_attributes: COMMON_STYLE_ATTRIBUTES,
477 layout_attributes: &[],
478 },
479 WidgetKind::Custom(_) => WidgetSchema {
480 required: &[],
481 optional: &[],
482 events: &[],
483 style_attributes: &[],
484 layout_attributes: &[],
485 },
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use crate::ir::WidgetKind;
493
494 #[test]
495 fn test_button_schema_contains_expected_attributes() {
496 let schema = WidgetKind::Button.schema();
497 let valid = schema.all_valid();
498
499 assert!(valid.contains("on_click"));
500 assert!(valid.contains("label"));
501 assert!(valid.contains("width"));
503 assert!(valid.contains("background"));
504 }
505
506 #[test]
507 fn test_container_schema_includes_layout_attributes() {
508 let schema = WidgetKind::Container.schema();
509 let valid = schema.all_valid();
510
511 assert!(valid.contains("padding"));
512 assert!(valid.contains("align_x"));
513 assert!(valid.contains("width"));
514 }
515
516 #[test]
517 fn test_textinput_schema_includes_size() {
518 let schema = WidgetKind::TextInput.schema();
519 let valid = schema.all_valid();
520
521 assert!(valid.contains("size"));
522 assert!(valid.contains("placeholder"));
523 }
524
525 #[test]
526 fn test_custom_widget_returns_permissive_schema() {
527 let schema = WidgetKind::Custom("MyWidget".to_string()).schema();
528 assert!(schema.required.is_empty());
529 }
530
531 #[test]
532 fn test_all_widget_kinds_have_schema() {
533 let kinds = [
534 WidgetKind::Column,
535 WidgetKind::Row,
536 WidgetKind::Text,
537 WidgetKind::Button,
538 WidgetKind::Image,
539 ];
540
541 for kind in kinds {
542 let _ = kind.schema();
543 }
544 }
545}