1use lyon_path::geom::euclid::approxeq::ApproxEq;
7
8use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
9use crate::expression_tree::*;
10use crate::langtype::ElementType;
11use crate::langtype::Type;
12use crate::layout::*;
13use crate::object_tree::*;
14use crate::typeloader::TypeLoader;
15use crate::typeregister::{layout_info_type, TypeRegister};
16use smol_str::{format_smolstr, SmolStr};
17use std::cell::RefCell;
18use std::collections::HashSet;
19use std::rc::Rc;
20
21pub fn lower_layouts(
22 component: &Rc<Component>,
23 type_loader: &mut TypeLoader,
24 style_metrics: &Rc<Component>,
25 diag: &mut BuildDiagnostics,
26) {
27 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
29 if check_preferred_size_100(elem, "preferred-width", diag) {
30 elem.borrow_mut().default_fill_parent.0 = true;
31 }
32 if check_preferred_size_100(elem, "preferred-height", diag) {
33 elem.borrow_mut().default_fill_parent.1 = true;
34 }
35 let base = elem.borrow().sub_component().cloned();
36 if let Some(base) = base {
37 let base = base.root_element.borrow();
38 let mut elem_mut = elem.borrow_mut();
39 elem_mut.default_fill_parent.0 |= base.default_fill_parent.0;
40 elem_mut.default_fill_parent.1 |= base.default_fill_parent.1;
41 }
42 });
43
44 *component.root_constraints.borrow_mut() =
45 LayoutConstraints::new(&component.root_element, diag, DiagnosticLevel::Error);
46
47 recurse_elem_including_sub_components(
48 component,
49 &Option::default(),
50 &mut |elem, parent_layout_type| {
51 let component = elem.borrow().enclosing_component.upgrade().unwrap();
52 let layout_type = lower_element_layout(
53 &component,
54 elem,
55 &type_loader.global_type_registry.borrow(),
56 style_metrics,
57 diag,
58 );
59 check_no_layout_properties(elem, &layout_type, &parent_layout_type, diag);
60 layout_type
61 },
62 );
63}
64
65fn check_preferred_size_100(elem: &ElementRc, prop: &str, diag: &mut BuildDiagnostics) -> bool {
66 let ret = if let Some(p) = elem.borrow().bindings.get(prop) {
67 if p.borrow().expression.ty() == Type::Percent {
68 if !matches!(p.borrow().expression.ignore_debug_hooks(), Expression::NumberLiteral(val, _) if *val == 100.)
69 {
70 diag.push_error(
71 format!("{prop} must either be a length, or the literal '100%'"),
72 &*p.borrow(),
73 );
74 }
75 true
76 } else {
77 false
78 }
79 } else {
80 false
81 };
82 if ret {
83 elem.borrow_mut().bindings.remove(prop).unwrap();
84 return true;
85 }
86 false
87}
88
89fn lower_element_layout(
92 component: &Rc<Component>,
93 elem: &ElementRc,
94 type_register: &TypeRegister,
95 style_metrics: &Rc<Component>,
96 diag: &mut BuildDiagnostics,
97) -> Option<SmolStr> {
98 let base_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type {
99 base_type.clone()
100 } else {
101 return None;
102 };
103 match base_type.name.as_str() {
104 "Row" => {
105 assert!(
108 diag.has_errors()
109 && Rc::ptr_eq(&component.root_element, elem)
110 && component
111 .parent_element
112 .upgrade()
113 .is_some_and(|e| e.borrow().repeated.is_some()),
114 "Error should have been caught at element lookup time"
115 );
116 return None;
117 }
118 "GridLayout" => lower_grid_layout(component, elem, diag, type_register),
119 "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
120 "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
121 "Dialog" => {
122 lower_dialog_layout(elem, style_metrics, diag);
123 return Some(base_type.name.clone());
125 }
126 _ => return None,
127 };
128
129 let mut elem = elem.borrow_mut();
130 let elem = &mut *elem;
131 let prev_base = std::mem::replace(&mut elem.base_type, type_register.empty_type());
132 elem.default_fill_parent = (true, true);
133 for (p, ty) in prev_base.property_list() {
136 if !elem.base_type.lookup_property(&p).is_valid()
137 && !elem.property_declarations.contains_key(&p)
138 {
139 elem.property_declarations.insert(p, ty.into());
140 }
141 }
142
143 Some(base_type.name.clone())
144}
145
146fn lower_grid_layout(
147 component: &Rc<Component>,
148 grid_layout_element: &ElementRc,
149 diag: &mut BuildDiagnostics,
150 type_register: &TypeRegister,
151) {
152 let mut grid = GridLayout {
153 elems: Default::default(),
154 geometry: LayoutGeometry::new(grid_layout_element),
155 dialog_button_roles: None,
156 };
157
158 let layout_cache_prop_h = create_new_prop(
159 grid_layout_element,
160 SmolStr::new_static("layout-cache-h"),
161 Type::LayoutCache,
162 );
163 let layout_cache_prop_v = create_new_prop(
164 grid_layout_element,
165 SmolStr::new_static("layout-cache-v"),
166 Type::LayoutCache,
167 );
168 let layout_info_prop_h = create_new_prop(
169 grid_layout_element,
170 SmolStr::new_static("layoutinfo-h"),
171 layout_info_type().into(),
172 );
173 let layout_info_prop_v = create_new_prop(
174 grid_layout_element,
175 SmolStr::new_static("layoutinfo-v"),
176 layout_info_type().into(),
177 );
178
179 let mut row = 0;
180 let mut col = 0;
181
182 let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
183 let mut collected_children = Vec::new();
184 for layout_child in layout_children {
185 let is_row = if let ElementType::Builtin(be) = &layout_child.borrow().base_type {
186 be.name == "Row"
187 } else {
188 false
189 };
190 if is_row {
191 if col > 0 {
192 row += 1;
193 col = 0;
194 }
195 let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
196 for x in row_children {
197 grid.add_element(
198 &x,
199 (&mut row, &mut col),
200 &layout_cache_prop_h,
201 &layout_cache_prop_v,
202 diag,
203 );
204 col += 1;
205 collected_children.push(x);
206 }
207 if col > 0 {
208 row += 1;
209 col = 0;
210 }
211 if layout_child.borrow().has_popup_child {
212 layout_child.borrow_mut().base_type = type_register.empty_type();
214 collected_children.push(layout_child);
215 } else {
216 component.optimized_elements.borrow_mut().push(layout_child);
217 }
218 } else {
219 grid.add_element(
220 &layout_child,
221 (&mut row, &mut col),
222 &layout_cache_prop_h,
223 &layout_cache_prop_v,
224 diag,
225 );
226 col += 1;
227 collected_children.push(layout_child);
228 }
229 }
230 grid_layout_element.borrow_mut().children = collected_children;
231 let span = grid_layout_element.borrow().to_source_location();
232 layout_cache_prop_h.element().borrow_mut().bindings.insert(
233 layout_cache_prop_h.name().clone(),
234 BindingExpression::new_with_span(
235 Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
236 span.clone(),
237 )
238 .into(),
239 );
240 layout_cache_prop_v.element().borrow_mut().bindings.insert(
241 layout_cache_prop_v.name().clone(),
242 BindingExpression::new_with_span(
243 Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
244 span.clone(),
245 )
246 .into(),
247 );
248 layout_info_prop_h.element().borrow_mut().bindings.insert(
249 layout_info_prop_h.name().clone(),
250 BindingExpression::new_with_span(
251 Expression::ComputeLayoutInfo(
252 Layout::GridLayout(grid.clone()),
253 Orientation::Horizontal,
254 ),
255 span.clone(),
256 )
257 .into(),
258 );
259 layout_info_prop_v.element().borrow_mut().bindings.insert(
260 layout_info_prop_v.name().clone(),
261 BindingExpression::new_with_span(
262 Expression::ComputeLayoutInfo(Layout::GridLayout(grid.clone()), Orientation::Vertical),
263 span,
264 )
265 .into(),
266 );
267 grid_layout_element.borrow_mut().layout_info_prop =
268 Some((layout_info_prop_h, layout_info_prop_v));
269 for d in grid_layout_element.borrow_mut().debug.iter_mut() {
270 d.layout = Some(Layout::GridLayout(grid.clone()));
271 }
272}
273
274impl GridLayout {
275 fn add_element(
276 &mut self,
277 item_element: &ElementRc,
278 (row, col): (&mut u16, &mut u16),
279 layout_cache_prop_h: &NamedReference,
280 layout_cache_prop_v: &NamedReference,
281 diag: &mut BuildDiagnostics,
282 ) {
283 let mut get_const_value = |name: &str| {
284 item_element
285 .borrow_mut()
286 .bindings
287 .get(name)
288 .and_then(|e| eval_const_expr(&e.borrow().expression, name, &*e.borrow(), diag))
289 };
290 let colspan = get_const_value("colspan").unwrap_or(1);
291 let rowspan = get_const_value("rowspan").unwrap_or(1);
292 if let Some(r) = get_const_value("row") {
293 *row = r;
294 *col = 0;
295 }
296 if let Some(c) = get_const_value("col") {
297 *col = c;
298 }
299
300 let result = self.add_element_with_coord(
301 item_element,
302 (*row, *col),
303 (rowspan, colspan),
304 layout_cache_prop_h,
305 layout_cache_prop_v,
306 diag,
307 );
308 if let Some(layout_item) = result {
309 let e = &layout_item.elem;
310 insert_fake_property(e, "row", Expression::NumberLiteral(*row as f64, Unit::None));
311 insert_fake_property(e, "col", Expression::NumberLiteral(*col as f64, Unit::None));
312 }
313 }
314
315 fn add_element_with_coord(
316 &mut self,
317 item_element: &ElementRc,
318 (row, col): (u16, u16),
319 (rowspan, colspan): (u16, u16),
320 layout_cache_prop_h: &NamedReference,
321 layout_cache_prop_v: &NamedReference,
322 diag: &mut BuildDiagnostics,
323 ) -> Option<CreateLayoutItemResult> {
324 let index = self.elems.len();
325 let result = create_layout_item(item_element, diag);
326 if let Some(ref layout_item) = result {
327 if layout_item.repeater_index.is_some() {
328 diag.push_error(
329 "'if' or 'for' expressions are not currently supported in grid layouts"
330 .to_string(),
331 &*item_element.borrow(),
332 );
333 return None;
334 }
335
336 let e = &layout_item.elem;
337 set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag);
338 if !layout_item.item.constraints.fixed_width {
339 set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag);
340 }
341 set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag);
342 if !layout_item.item.constraints.fixed_height {
343 set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag);
344 }
345
346 self.elems.push(GridLayoutElement {
347 col,
348 row,
349 colspan,
350 rowspan,
351 item: layout_item.item.clone(),
352 });
353 }
354 result
355 }
356}
357
358fn lower_box_layout(
359 layout_element: &ElementRc,
360 diag: &mut BuildDiagnostics,
361 orientation: Orientation,
362) {
363 let mut layout = BoxLayout {
364 orientation,
365 elems: Default::default(),
366 geometry: LayoutGeometry::new(layout_element),
367 };
368
369 let layout_cache_prop =
370 create_new_prop(layout_element, SmolStr::new_static("layout-cache"), Type::LayoutCache);
371 let layout_info_prop_v = create_new_prop(
372 layout_element,
373 SmolStr::new_static("layoutinfo-v"),
374 layout_info_type().into(),
375 );
376 let layout_info_prop_h = create_new_prop(
377 layout_element,
378 SmolStr::new_static("layoutinfo-h"),
379 layout_info_type().into(),
380 );
381
382 let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
383
384 let (begin_padding, end_padding) = match orientation {
385 Orientation::Horizontal => (&layout.geometry.padding.top, &layout.geometry.padding.bottom),
386 Orientation::Vertical => (&layout.geometry.padding.left, &layout.geometry.padding.right),
387 };
388 let (pos, size, pad, ortho) = match orientation {
389 Orientation::Horizontal => ("x", "width", "y", "height"),
390 Orientation::Vertical => ("y", "height", "x", "width"),
391 };
392 let pad_expr = begin_padding.clone().map(Expression::PropertyReference);
393 let mut size_expr = Expression::PropertyReference(NamedReference::new(
394 layout_element,
395 SmolStr::new_static(ortho),
396 ));
397 if let Some(p) = begin_padding {
398 size_expr = Expression::BinaryExpression {
399 lhs: Box::new(std::mem::take(&mut size_expr)),
400 rhs: Box::new(Expression::PropertyReference(p.clone())),
401 op: '-',
402 }
403 }
404 if let Some(p) = end_padding {
405 size_expr = Expression::BinaryExpression {
406 lhs: Box::new(std::mem::take(&mut size_expr)),
407 rhs: Box::new(Expression::PropertyReference(p.clone())),
408 op: '-',
409 }
410 }
411
412 for layout_child in &layout_children {
413 if let Some(item) = create_layout_item(layout_child, diag) {
414 let index = layout.elems.len() * 2;
415 let rep_idx = &item.repeater_index;
416 let (fixed_size, fixed_ortho) = match orientation {
417 Orientation::Horizontal => {
418 (item.item.constraints.fixed_width, item.item.constraints.fixed_height)
419 }
420 Orientation::Vertical => {
421 (item.item.constraints.fixed_height, item.item.constraints.fixed_width)
422 }
423 };
424 let actual_elem = &item.elem;
425 set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, diag);
426 if !fixed_size {
427 set_prop_from_cache(
428 actual_elem,
429 size,
430 &layout_cache_prop,
431 index + 1,
432 rep_idx,
433 diag,
434 );
435 }
436 if let Some(pad_expr) = pad_expr.clone() {
437 actual_elem.borrow_mut().bindings.insert(pad.into(), RefCell::new(pad_expr.into()));
438 }
439 if !fixed_ortho {
440 actual_elem
441 .borrow_mut()
442 .bindings
443 .insert(ortho.into(), RefCell::new(size_expr.clone().into()));
444 }
445 layout.elems.push(item.item);
446 }
447 }
448 layout_element.borrow_mut().children = layout_children;
449 let span = layout_element.borrow().to_source_location();
450 layout_cache_prop.element().borrow_mut().bindings.insert(
451 layout_cache_prop.name().clone(),
452 BindingExpression::new_with_span(
453 Expression::SolveLayout(Layout::BoxLayout(layout.clone()), orientation),
454 span.clone(),
455 )
456 .into(),
457 );
458 layout_info_prop_h.element().borrow_mut().bindings.insert(
459 layout_info_prop_h.name().clone(),
460 BindingExpression::new_with_span(
461 Expression::ComputeLayoutInfo(
462 Layout::BoxLayout(layout.clone()),
463 Orientation::Horizontal,
464 ),
465 span.clone(),
466 )
467 .into(),
468 );
469 layout_info_prop_v.element().borrow_mut().bindings.insert(
470 layout_info_prop_v.name().clone(),
471 BindingExpression::new_with_span(
472 Expression::ComputeLayoutInfo(Layout::BoxLayout(layout.clone()), Orientation::Vertical),
473 span,
474 )
475 .into(),
476 );
477 layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
478 for d in layout_element.borrow_mut().debug.iter_mut() {
479 d.layout = Some(Layout::BoxLayout(layout.clone()));
480 }
481}
482
483fn lower_dialog_layout(
484 dialog_element: &ElementRc,
485 style_metrics: &Rc<Component>,
486 diag: &mut BuildDiagnostics,
487) {
488 let mut grid = GridLayout {
489 elems: Default::default(),
490 geometry: LayoutGeometry::new(dialog_element),
491 dialog_button_roles: None,
492 };
493 let metrics = &style_metrics.root_element;
494 grid.geometry
495 .padding
496 .bottom
497 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
498 grid.geometry
499 .padding
500 .top
501 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
502 grid.geometry
503 .padding
504 .left
505 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
506 grid.geometry
507 .padding
508 .right
509 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
510 grid.geometry
511 .spacing
512 .horizontal
513 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
514 grid.geometry
515 .spacing
516 .vertical
517 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
518
519 let layout_cache_prop_h =
520 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-h"), Type::LayoutCache);
521 let layout_cache_prop_v =
522 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-v"), Type::LayoutCache);
523 let layout_info_prop_h = create_new_prop(
524 dialog_element,
525 SmolStr::new_static("layoutinfo-h"),
526 layout_info_type().into(),
527 );
528 let layout_info_prop_v = create_new_prop(
529 dialog_element,
530 SmolStr::new_static("layoutinfo-v"),
531 layout_info_type().into(),
532 );
533
534 let mut main_widget = None;
535 let mut button_roles = vec![];
536 let mut seen_buttons = HashSet::new();
537 let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
538 for layout_child in &layout_children {
539 let dialog_button_role_binding =
540 layout_child.borrow_mut().bindings.remove("dialog-button-role");
541 let is_button = if let Some(role_binding) = dialog_button_role_binding {
542 let role_binding = role_binding.into_inner();
543 if let Expression::EnumerationValue(val) =
544 super::ignore_debug_hooks(&role_binding.expression)
545 {
546 let en = &val.enumeration;
547 debug_assert_eq!(en.name, "DialogButtonRole");
548 button_roles.push(en.values[val.value].clone());
549 if val.value == 0 {
550 diag.push_error(
551 "The `dialog-button-role` cannot be set explicitly to none".into(),
552 &role_binding,
553 );
554 }
555 } else {
556 diag.push_error(
557 "The `dialog-button-role` property must be known at compile-time".into(),
558 &role_binding,
559 );
560 }
561 true
562 } else if matches!(&layout_child.borrow().lookup_property("kind").property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
563 {
564 match layout_child.borrow().bindings.get("kind") {
566 None => diag.push_error(
567 "The `kind` property of the StandardButton in a Dialog must be set".into(),
568 &*layout_child.borrow(),
569 ),
570 Some(binding) => {
571 let binding = &*binding.borrow();
572 if let Expression::EnumerationValue(val) =
573 super::ignore_debug_hooks(&binding.expression)
574 {
575 let en = &val.enumeration;
576 debug_assert_eq!(en.name, "StandardButtonKind");
577 let kind = &en.values[val.value];
578 let role = match kind.as_str() {
579 "ok" => "accept",
580 "cancel" => "reject",
581 "apply" => "apply",
582 "close" => "reject",
583 "reset" => "reset",
584 "help" => "help",
585 "yes" => "accept",
586 "no" => "reject",
587 "abort" => "reject",
588 "retry" => "accept",
589 "ignore" => "accept",
590 _ => unreachable!(),
591 };
592 button_roles.push(role.into());
593 if !seen_buttons.insert(val.value) {
594 diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
595 } else if Rc::ptr_eq(
596 dialog_element,
597 &dialog_element
598 .borrow()
599 .enclosing_component
600 .upgrade()
601 .unwrap()
602 .root_element,
603 ) {
604 let clicked_ty =
605 layout_child.borrow().lookup_property("clicked").property_type;
606 if matches!(&clicked_ty, Type::Callback { .. })
607 && layout_child.borrow().bindings.get("clicked").is_none_or(|c| {
608 matches!(c.borrow().expression, Expression::Invalid)
609 })
610 {
611 dialog_element
612 .borrow_mut()
613 .property_declarations
614 .entry(format_smolstr!("{}-clicked", kind))
615 .or_insert_with(|| PropertyDeclaration {
616 property_type: clicked_ty,
617 node: None,
618 expose_in_public_api: true,
619 is_alias: Some(NamedReference::new(
620 layout_child,
621 SmolStr::new_static("clicked"),
622 )),
623 visibility: PropertyVisibility::InOut,
624 pure: None,
625 });
626 }
627 }
628 } else {
629 diag.push_error(
630 "The `kind` property of the StandardButton in a Dialog must be known at compile-time"
631 .into(),
632 binding,
633 );
634 }
635 }
636 }
637 true
638 } else {
639 false
640 };
641
642 if is_button {
643 grid.add_element_with_coord(
644 layout_child,
645 (1, button_roles.len() as u16),
646 (1, 1),
647 &layout_cache_prop_h,
648 &layout_cache_prop_v,
649 diag,
650 );
651 } else if main_widget.is_some() {
652 diag.push_error(
653 "A Dialog can have only one child element that is not a StandardButton".into(),
654 &*layout_child.borrow(),
655 );
656 } else {
657 main_widget = Some(layout_child.clone())
658 }
659 }
660 dialog_element.borrow_mut().children = layout_children;
661
662 if let Some(main_widget) = main_widget {
663 grid.add_element_with_coord(
664 &main_widget,
665 (0, 0),
666 (1, button_roles.len() as u16 + 1),
667 &layout_cache_prop_h,
668 &layout_cache_prop_v,
669 diag,
670 );
671 } else {
672 diag.push_error(
673 "A Dialog must have a single child element that is not StandardButton".into(),
674 &*dialog_element.borrow(),
675 );
676 }
677 grid.dialog_button_roles = Some(button_roles);
678
679 let span = dialog_element.borrow().to_source_location();
680 layout_cache_prop_h.element().borrow_mut().bindings.insert(
681 layout_cache_prop_h.name().clone(),
682 BindingExpression::new_with_span(
683 Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
684 span.clone(),
685 )
686 .into(),
687 );
688 layout_cache_prop_v.element().borrow_mut().bindings.insert(
689 layout_cache_prop_v.name().clone(),
690 BindingExpression::new_with_span(
691 Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
692 span.clone(),
693 )
694 .into(),
695 );
696 layout_info_prop_h.element().borrow_mut().bindings.insert(
697 layout_info_prop_h.name().clone(),
698 BindingExpression::new_with_span(
699 Expression::ComputeLayoutInfo(
700 Layout::GridLayout(grid.clone()),
701 Orientation::Horizontal,
702 ),
703 span.clone(),
704 )
705 .into(),
706 );
707 layout_info_prop_v.element().borrow_mut().bindings.insert(
708 layout_info_prop_v.name().clone(),
709 BindingExpression::new_with_span(
710 Expression::ComputeLayoutInfo(Layout::GridLayout(grid.clone()), Orientation::Vertical),
711 span,
712 )
713 .into(),
714 );
715 dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
716 for d in dialog_element.borrow_mut().debug.iter_mut() {
717 d.layout = Some(Layout::GridLayout(grid.clone()));
718 }
719}
720
721struct CreateLayoutItemResult {
722 item: LayoutItem,
723 elem: ElementRc,
724 repeater_index: Option<Expression>,
725}
726
727fn create_layout_item(
729 item_element: &ElementRc,
730 diag: &mut BuildDiagnostics,
731) -> Option<CreateLayoutItemResult> {
732 let fix_explicit_percent = |prop: &str, item: &ElementRc| {
733 if !item.borrow().bindings.get(prop).is_some_and(|b| b.borrow().ty() == Type::Percent) {
734 return;
735 }
736 let min_name = format_smolstr!("min-{}", prop);
737 let max_name = format_smolstr!("max-{}", prop);
738 let mut min_ref = BindingExpression::from(Expression::PropertyReference(
739 NamedReference::new(item, min_name.clone()),
740 ));
741 let mut item = item.borrow_mut();
742 let b = item.bindings.remove(prop).unwrap().into_inner();
743 min_ref.span = b.span.clone();
744 min_ref.priority = b.priority;
745 item.bindings.insert(max_name.clone(), min_ref.into());
746 item.bindings.insert(min_name.clone(), b.into());
747 item.property_declarations.insert(
748 min_name,
749 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
750 );
751 item.property_declarations.insert(
752 max_name,
753 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
754 );
755 };
756 fix_explicit_percent("width", item_element);
757 fix_explicit_percent("height", item_element);
758
759 item_element.borrow_mut().child_of_layout = true;
760 let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
761 let rep_comp = item_element.borrow().base_type.as_component().clone();
762 fix_explicit_percent("width", &rep_comp.root_element);
763 fix_explicit_percent("height", &rep_comp.root_element);
764
765 *rep_comp.root_constraints.borrow_mut() =
766 LayoutConstraints::new(&rep_comp.root_element, diag, DiagnosticLevel::Error);
767 rep_comp.root_element.borrow_mut().child_of_layout = true;
768 (
769 Some(if r.is_conditional_element {
770 Expression::NumberLiteral(0., Unit::None)
771 } else {
772 Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
773 }),
774 rep_comp.root_element.clone(),
775 )
776 } else {
777 (None, item_element.clone())
778 };
779
780 let constraints = LayoutConstraints::new(&actual_elem, diag, DiagnosticLevel::Error);
781 Some(CreateLayoutItemResult {
782 item: LayoutItem { element: item_element.clone(), constraints },
783 elem: actual_elem,
784 repeater_index,
785 })
786}
787
788fn insert_fake_property(elem: &ElementRc, prop: &str, expr: Expression) {
789 let mut elem_mut = elem.borrow_mut();
790 let span = elem_mut.to_source_location();
791 if let std::collections::btree_map::Entry::Vacant(e) = elem_mut.bindings.entry(prop.into()) {
792 let binding = BindingExpression::new_with_span(expr, span);
793 e.insert(binding.into());
794 }
795}
796
797fn set_prop_from_cache(
798 elem: &ElementRc,
799 prop: &str,
800 layout_cache_prop: &NamedReference,
801 index: usize,
802 repeater_index: &Option<Expression>,
803 diag: &mut BuildDiagnostics,
804) {
805 let old = elem.borrow_mut().bindings.insert(
806 prop.into(),
807 BindingExpression::new_with_span(
808 Expression::LayoutCacheAccess {
809 layout_cache_prop: layout_cache_prop.clone(),
810 index,
811 repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
812 },
813 layout_cache_prop.element().borrow().to_source_location(),
814 )
815 .into(),
816 );
817 if let Some(old) = old.map(RefCell::into_inner) {
818 diag.push_error(
819 format!("The property '{prop}' cannot be set for elements placed in this layout, because the layout is already setting it"),
820 &old,
821 );
822 }
823}
824
825fn eval_const_expr(
826 expression: &Expression,
827 name: &str,
828 span: &dyn crate::diagnostics::Spanned,
829 diag: &mut BuildDiagnostics,
830) -> Option<u16> {
831 match super::ignore_debug_hooks(expression) {
832 Expression::NumberLiteral(v, Unit::None) => {
833 if *v < 0. || *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
834 diag.push_error(format!("'{name}' must be a positive integer"), span);
835 None
836 } else {
837 Some(*v as u16)
838 }
839 }
840 Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag),
841 _ => {
842 diag.push_error(format!("'{name}' must be an integer literal"), span);
843 None
844 }
845 }
846}
847
848fn check_no_layout_properties(
850 item: &ElementRc,
851 layout_type: &Option<SmolStr>,
852 parent_layout_type: &Option<SmolStr>,
853 diag: &mut BuildDiagnostics,
854) {
855 let elem = item.borrow();
856 for (prop, expr) in elem.bindings.iter() {
857 if parent_layout_type.as_deref() != Some("GridLayout")
858 && matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan")
859 {
860 diag.push_error(format!("{prop} used outside of a GridLayout's cell"), &*expr.borrow());
861 }
862 if parent_layout_type.as_deref() != Some("Dialog")
863 && matches!(prop.as_ref(), "dialog-button-role")
864 {
865 diag.push_error(
866 format!("{prop} used outside of a Dialog's direct child"),
867 &*expr.borrow(),
868 );
869 }
870 if layout_type.is_none()
871 && matches!(
872 prop.as_ref(),
873 "padding" | "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
874 )
875 {
876 diag.push_warning(
877 format!("{prop} only has effect on layout elements"),
878 &*expr.borrow(),
879 );
880 }
881 }
882}
883
884pub fn check_window_layout(component: &Rc<Component>) {
890 if component.root_constraints.borrow().fixed_height {
891 adjust_window_layout(component, "height");
892 }
893 if component.root_constraints.borrow().fixed_width {
894 adjust_window_layout(component, "width");
895 }
896}
897
898fn adjust_window_layout(component: &Rc<Component>, prop: &'static str) {
899 let new_prop = crate::layout::create_new_prop(
900 &component.root_element,
901 format_smolstr!("fixed-{prop}"),
902 Type::LogicalLength,
903 );
904 {
905 let mut root = component.root_element.borrow_mut();
906 if let Some(b) = root.bindings.remove(prop) {
907 root.bindings.insert(new_prop.name().clone(), b);
908 };
909 let mut analysis = root.property_analysis.borrow_mut();
910 if let Some(a) = analysis.remove(prop) {
911 analysis.insert(new_prop.name().clone(), a);
912 };
913 drop(analysis);
914 root.bindings.insert(
915 prop.into(),
916 RefCell::new(Expression::PropertyReference(new_prop.clone()).into()),
917 );
918 }
919
920 let old_prop = NamedReference::new(&component.root_element, SmolStr::new_static(prop));
921 crate::object_tree::visit_all_named_references(component, &mut |nr| {
922 if nr == &old_prop {
923 *nr = new_prop.clone()
924 }
925 });
926}