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