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