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