1use lyon_path::geom::euclid::approxeq::ApproxEq;
7use std::sync::Arc;
8
9use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
10use crate::expression_tree::*;
11use crate::langtype::Type;
12use crate::langtype::{ElementType, PropertyLookupMode};
13use crate::layout::*;
14use crate::object_tree::*;
15use crate::typeloader::TypeLoader;
16use crate::typeregister::{TypeRegister, layout_info_type};
17use smol_str::{SmolStr, format_smolstr};
18use std::cell::RefCell;
19use std::collections::HashSet;
20use std::rc::Rc;
21
22pub(crate) fn synthesize_layoutinfo_v_with_constraint_on(
26 elem: &ElementRc,
27 span: crate::diagnostics::SourceLocation,
28 body: Expression,
29) {
30 let function_ty = Type::Function(Arc::new(crate::langtype::Function {
31 return_type: crate::typeregister::layout_info_type().into(),
32 args: vec![Type::LogicalLength],
33 arg_names: vec![SmolStr::new_static("width")],
34 }));
35 let prop_name = SmolStr::new_static("layoutinfo-v-with-constraint");
36 let nr = crate::namedreference::NamedReference::new(elem, prop_name.clone());
37
38 let mut elem_mut = elem.borrow_mut();
39 elem_mut.property_declarations.insert(
40 prop_name.clone(),
41 PropertyDeclaration {
42 property_type: function_ty,
43 visibility: crate::object_tree::PropertyVisibility::Private,
44 pure: Some(true),
45 ..Default::default()
46 },
47 );
48 elem_mut.set_binding(prop_name, BindingExpression::new_with_span(body, span));
49 elem_mut.layout_info_v_with_constraint = Some(nr);
50}
51
52fn rewrite_layoutinfo_v_for_constraint(expr: &mut Expression, width_param: &Expression) {
56 expr.visit_recursive_mut(&mut |sub| match sub {
57 Expression::ComputeBoxLayoutInfo {
58 orientation: Orientation::Vertical,
59 cross_axis_size,
60 ..
61 }
62 | Expression::ComputeGridLayoutInfo {
63 orientation: Orientation::Vertical,
64 cross_axis_size,
65 ..
66 }
67 | Expression::ComputeFlexboxLayoutInfo {
68 orientation: Orientation::Vertical,
69 cross_axis_size,
70 ..
71 } => {
72 *cross_axis_size = Some(Box::new(width_param.clone()));
73 }
74 Expression::FunctionCall {
75 function: Callable::Builtin(BuiltinFunction::ImplicitLayoutInfo(Orientation::Vertical)),
76 arguments,
77 ..
78 } => {
79 let target = match arguments.first() {
81 Some(Expression::ElementReference(weak)) => weak.upgrade(),
82 _ => None,
83 };
84 if let Some(target) = target {
85 if let Some(constrained_nr) =
87 target.borrow().inherited_layout_info_v_with_constraint()
88 {
89 *sub = Expression::FunctionCall {
90 function: Callable::Function(crate::namedreference::NamedReference::new(
91 &target,
92 constrained_nr.name().clone(),
93 )),
94 arguments: vec![width_param.clone()],
95 source_location: None,
96 };
97 return;
98 }
99 if target.borrow().is_builtin_height_for_width() {
103 debug_assert!(arguments.len() >= 2);
104 if let Some(second) = arguments.get_mut(1) {
105 *second = width_param.clone();
106 }
107 }
108 }
109 }
110 Expression::PropertyReference(nr) => {
111 let target = nr.element();
114 let constraint_field = match nr.name().as_str() {
119 "min-height" => Some("min"),
120 "preferred-height" => Some("preferred"),
121 "max-height" => Some("max"),
122 "vertical-stretch" => Some("stretch"),
123 _ => None,
124 };
125 if let Some(field) = constraint_field {
126 let own_binding = target.borrow().binding(nr.name()).and_then(|b| {
129 (!matches!(b.expression, Expression::PropertyReference(..)))
130 .then(|| b.expression.clone())
131 });
132 if let Some(mut own_binding) = own_binding {
133 rewrite_layoutinfo_v_for_constraint(&mut own_binding, width_param);
134 *sub = own_binding;
135 return;
136 }
137 if let Some(constrained_nr) =
138 target.borrow().inherited_layout_info_v_with_constraint()
139 {
140 *sub = Expression::StructFieldAccess {
141 base: Expression::FunctionCall {
142 function: Callable::Function(
143 crate::namedreference::NamedReference::new(
144 &target,
145 constrained_nr.name().clone(),
146 ),
147 ),
148 arguments: vec![width_param.clone()],
149 source_location: None,
150 }
151 .into(),
152 name: field.into(),
153 };
154 }
155 return;
156 }
157 let is_vertical_layout_info = target
158 .borrow()
159 .effective_layout_info_prop(Orientation::Vertical)
160 .map(|prop_nr| {
161 prop_nr.name() == nr.name() && Rc::ptr_eq(&prop_nr.element(), &target)
162 })
163 .unwrap_or(false);
164 if !is_vertical_layout_info {
165 return;
166 }
167 if let Some(constrained_nr) = target.borrow().inherited_layout_info_v_with_constraint()
168 {
169 *sub = Expression::FunctionCall {
170 function: Callable::Function(crate::namedreference::NamedReference::new(
171 &target,
172 constrained_nr.name().clone(),
173 )),
174 arguments: vec![width_param.clone()],
175 source_location: None,
176 };
177 }
178 }
179 _ => {}
180 });
181}
182
183pub fn synthesize_layoutinfo_v_with_constraint(component: &Rc<Component>) {
188 fn walk(elem: &ElementRc) -> bool {
192 let children = elem.borrow().children.clone();
193 let mut has_v_cross = false;
194 for c in &children {
195 has_v_cross |= walk(c);
196 }
197 let repeated_body = {
199 let elem_b = elem.borrow();
200 if elem_b.repeated.is_some() {
201 if let ElementType::Component(base_comp) = &elem_b.base_type {
202 Some(base_comp.root_element.clone())
203 } else {
204 None
205 }
206 } else {
207 None
208 }
209 };
210 if let Some(body_root) = repeated_body {
211 has_v_cross |= walk(&body_root);
212 }
213
214 let (already_synthesized, base_has_constraint, self_is_v_cross_flex, v_nr_clone) = {
215 let elem_b = elem.borrow();
216 has_v_cross |= elem_b.is_builtin_height_for_width();
217 let layout_type = elem_b.debug.first().and_then(|d| d.layout.as_ref()).cloned();
218 let self_is = matches!(
219 layout_type,
220 Some(crate::layout::Layout::FlexboxLayout(ref l))
221 if !matches!(
222 l.axis_relation(Orientation::Vertical),
223 crate::layout::FlexboxAxisRelation::MainAxis,
224 )
225 );
226 let base_has = matches!(
227 &elem_b.base_type,
228 ElementType::Component(base_comp)
229 if base_comp.root_element.borrow().layout_info_v_with_constraint.is_some()
230 );
231 (
232 elem_b.layout_info_v_with_constraint.is_some(),
233 base_has,
234 self_is,
235 elem_b.effective_layout_info_prop(Orientation::Vertical).cloned(),
236 )
237 };
238 has_v_cross |= self_is_v_cross_flex | base_has_constraint;
239
240 if !has_v_cross || already_synthesized {
241 return has_v_cross;
242 }
243 let Some(v_nr) = v_nr_clone else {
244 return has_v_cross;
245 };
246 let Some(v_binding) = elem.borrow().binding(v_nr.name()).map(|b| b.clone()) else {
247 return has_v_cross;
248 };
249
250 let span = v_binding.span.clone().unwrap_or_else(|| elem.borrow().to_source_location());
251 let mut body = v_binding.expression.clone();
252 let width_param =
253 Expression::FunctionParameterReference { index: 0, ty: Type::LogicalLength };
254 rewrite_layoutinfo_v_for_constraint(&mut body, &width_param);
255
256 synthesize_layoutinfo_v_with_constraint_on(elem, span, body);
257 has_v_cross
258 }
259 walk(&component.root_element);
260}
261
262pub fn lower_layouts(
264 component: &Rc<Component>,
265 type_loader: &mut TypeLoader,
266 style_metrics: &Rc<Component>,
267 diag: &mut BuildDiagnostics,
268) {
269 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
271 if check_preferred_size_100(elem, "preferred-width", diag) {
272 elem.borrow_mut().default_fill_parent.0 = true;
273 }
274 if check_preferred_size_100(elem, "preferred-height", diag) {
275 elem.borrow_mut().default_fill_parent.1 = true;
276 }
277 let base = elem.borrow().sub_component().cloned();
278 if let Some(base) = base {
279 let base = base.root_element.borrow();
280 let mut elem_mut = elem.borrow_mut();
281 elem_mut.default_fill_parent.0 |= base.default_fill_parent.0;
282 elem_mut.default_fill_parent.1 |= base.default_fill_parent.1;
283 }
284 });
285 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
287 let height_is_literal = crate::object_tree::Element::compute_height_is_literal(elem);
288 elem.borrow_mut().height_is_literal = height_is_literal;
289 });
290
291 *component.root_constraints.borrow_mut() =
292 LayoutConstraints::new(&component.root_element, Some((diag, DiagnosticLevel::Error)));
293
294 recurse_elem_including_sub_components(
295 component,
296 &Option::default(),
297 &mut |elem, parent_layout_type| {
298 let component = elem.borrow().enclosing_component.upgrade().unwrap();
299
300 if Rc::ptr_eq(elem, &component.root_element) {
305 for popup in component.popup_windows.borrow().iter() {
306 *popup.component.root_constraints.borrow_mut() = LayoutConstraints::new(
307 &popup.component.root_element,
308 Some((&mut *diag, DiagnosticLevel::Warning)),
309 );
310 }
311 }
312
313 lower_element_layout(
314 &component,
315 elem,
316 &type_loader.global_type_registry.borrow(),
317 style_metrics,
318 parent_layout_type,
319 diag,
320 )
321 },
322 );
323}
324
325fn check_preferred_size_100(elem: &ElementRc, prop: &str, diag: &mut BuildDiagnostics) -> bool {
326 let ret = if let Some(p) = elem.borrow().binding(prop) {
327 if p.expression.ty() == Type::Percent {
328 if !matches!(p.value_expression(), Expression::NumberLiteral(val, _) if *val == 100.) {
329 diag.push_error(
330 format!("{prop} must either be a length, or the literal '100%'"),
331 &*p,
332 );
333 }
334 true
335 } else {
336 false
337 }
338 } else {
339 false
340 };
341 if ret {
342 elem.borrow_mut().take_binding(prop).unwrap();
343 return true;
344 }
345 false
346}
347
348fn lower_element_layout(
351 component: &Rc<Component>,
352 elem: &ElementRc,
353 type_register: &TypeRegister,
354 style_metrics: &Rc<Component>,
355 parent_layout_type: &Option<SmolStr>,
356 diag: &mut BuildDiagnostics,
357) -> Option<SmolStr> {
358 let layout_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type {
359 Some(base_type.name.clone())
360 } else {
361 None
362 };
363
364 check_no_layout_properties(elem, &layout_type, parent_layout_type, diag);
365
366 match layout_type.as_ref()?.as_str() {
367 "Row" => return layout_type,
368 "GridLayout" => lower_grid_layout(component, elem, diag, type_register),
369 "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
370 "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
371 "FlexboxLayout" => lower_flexbox_layout(elem, diag),
372 "Dialog" => {
373 lower_dialog_layout(elem, style_metrics, diag);
374 return layout_type;
376 }
377 _ => return None,
378 };
379
380 let mut elem = elem.borrow_mut();
381 let elem = &mut *elem;
382 let prev_base = std::mem::replace(&mut elem.base_type, type_register.empty_type());
383 elem.default_fill_parent = (true, true);
384 for (p, ty) in prev_base.property_list() {
387 if !elem.base_type.lookup_property(&p, PropertyLookupMode::ComponentLocal).is_valid()
388 && !elem.property_declarations.contains_key(&p)
389 {
390 elem.property_declarations.insert(p, ty.into());
391 }
392 }
393
394 layout_type
395}
396
397#[derive(Debug, PartialEq, Eq, Clone, Copy)]
399enum RowColExpressionType {
400 Auto, Literal,
402 RuntimeExpression,
403}
404impl RowColExpressionType {
405 fn from_option_expr(
406 expr: &Option<Expression>,
407 is_number_literal: bool,
408 ) -> RowColExpressionType {
409 match expr {
410 None => RowColExpressionType::Auto,
411 Some(_) if is_number_literal => RowColExpressionType::Literal,
412 Some(_) => RowColExpressionType::RuntimeExpression,
413 }
414 }
415}
416
417#[derive(Default)]
422struct NumberingTypes {
423 strict: Option<RowColExpressionType>,
424 lenient: Option<RowColExpressionType>,
425}
426
427fn lower_grid_layout(
428 component: &Rc<Component>,
429 grid_layout_element: &ElementRc,
430 diag: &mut BuildDiagnostics,
431 type_register: &TypeRegister,
432) {
433 let mut grid = GridLayout {
434 elems: Default::default(),
435 geometry: LayoutGeometry::new(grid_layout_element),
436 dialog_button_roles: None,
437 uses_auto: false,
438 };
439
440 let layout_organized_data_prop = create_new_prop(
441 grid_layout_element,
442 SmolStr::new_static("layout-organized-data"),
443 Type::ArrayOfU16,
444 );
445 let layout_cache_prop_h = create_new_prop(
446 grid_layout_element,
447 SmolStr::new_static("layout-cache-h"),
448 Type::LayoutCache,
449 );
450 let layout_cache_prop_v = create_new_prop(
451 grid_layout_element,
452 SmolStr::new_static("layout-cache-v"),
453 Type::LayoutCache,
454 );
455 let layout_info_prop_h = create_new_prop(
456 grid_layout_element,
457 SmolStr::new_static("layoutinfo-h"),
458 layout_info_type().into(),
459 );
460 let layout_info_prop_v = create_new_prop(
461 grid_layout_element,
462 SmolStr::new_static("layoutinfo-v"),
463 layout_info_type().into(),
464 );
465
466 let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
467 let mut collected_children = Vec::new();
468 let mut new_row = false; let mut numbering_type = NumberingTypes::default();
487 let mut num_cached_items: usize = 0;
488 for layout_child in layout_children {
489 let is_repeated_row = {
490 if layout_child.borrow().repeated.is_some()
491 && let ElementType::Component(comp) = &layout_child.borrow().base_type
492 {
493 match &comp.root_element.borrow().base_type {
494 ElementType::Builtin(b) => b.name == "Row",
495 _ => false,
496 }
497 } else {
498 false
499 }
500 };
501 if is_repeated_row {
502 grid.add_repeated_row(
503 &layout_child,
504 &layout_cache_prop_h,
505 &layout_cache_prop_v,
506 &layout_organized_data_prop,
507 diag,
508 &mut num_cached_items,
509 );
510 collected_children.push(layout_child);
511 new_row = true;
512 } else if matches!(&layout_child.borrow().base_type, ElementType::Builtin(b) if b.name == "Row")
513 {
514 new_row = true;
515 let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
516 for row_child in row_children {
517 if let Some(binding) = row_child.borrow_mut().binding("row") {
518 diag.push_warning(
519 "The 'row' property cannot be used for elements inside a Row. This was accepted by previous versions of Slint, but may become an error in the future".to_string(),
520 &*binding,
521 );
522 }
523 grid.add_element(
524 &row_child,
525 new_row,
526 &layout_cache_prop_h,
527 &layout_cache_prop_v,
528 &layout_organized_data_prop,
529 &mut numbering_type,
530 diag,
531 &mut num_cached_items,
532 );
533 collected_children.push(row_child);
534 new_row = false;
535 }
536 new_row = true; if layout_child.borrow().has_popup_child {
538 layout_child.borrow_mut().base_type = type_register.empty_type();
540 collected_children.push(layout_child);
541 } else {
542 component.optimized_elements.borrow_mut().push(layout_child);
543 }
544 } else {
545 grid.add_element(
546 &layout_child,
547 new_row,
548 &layout_cache_prop_h,
549 &layout_cache_prop_v,
550 &layout_organized_data_prop,
551 &mut numbering_type,
552 diag,
553 &mut num_cached_items,
554 );
555 collected_children.push(layout_child);
556 new_row = false;
557 }
558 }
559 grid_layout_element.borrow_mut().children = collected_children;
560 grid.uses_auto = numbering_type.strict == Some(RowColExpressionType::Auto);
561 let span = grid_layout_element.borrow().to_source_location();
562
563 layout_organized_data_prop.element().borrow_mut().set_binding(
564 layout_organized_data_prop.name().clone(),
565 BindingExpression::new_with_span(
566 Expression::OrganizeGridLayout(grid.clone()),
567 span.clone(),
568 ),
569 );
570 layout_cache_prop_h.element().borrow_mut().set_binding(
571 layout_cache_prop_h.name().clone(),
572 BindingExpression::new_with_span(
573 Expression::SolveGridLayout {
574 layout_organized_data_prop: layout_organized_data_prop.clone(),
575 layout: grid.clone(),
576 orientation: Orientation::Horizontal,
577 },
578 span.clone(),
579 ),
580 );
581 layout_cache_prop_v.element().borrow_mut().set_binding(
582 layout_cache_prop_v.name().clone(),
583 BindingExpression::new_with_span(
584 Expression::SolveGridLayout {
585 layout_organized_data_prop: layout_organized_data_prop.clone(),
586 layout: grid.clone(),
587 orientation: Orientation::Vertical,
588 },
589 span.clone(),
590 ),
591 );
592 layout_info_prop_h.element().borrow_mut().set_binding(
593 layout_info_prop_h.name().clone(),
594 BindingExpression::new_with_span(
595 Expression::ComputeGridLayoutInfo {
596 layout_organized_data_prop: layout_organized_data_prop.clone(),
597 layout: grid.clone(),
598 orientation: Orientation::Horizontal,
599 cross_axis_size: None,
600 },
601 span.clone(),
602 ),
603 );
604 layout_info_prop_v.element().borrow_mut().set_binding(
605 layout_info_prop_v.name().clone(),
606 BindingExpression::new_with_span(
607 Expression::ComputeGridLayoutInfo {
608 layout_organized_data_prop: layout_organized_data_prop.clone(),
609 layout: grid.clone(),
610 orientation: Orientation::Vertical,
611 cross_axis_size: None,
612 },
613 span,
614 ),
615 );
616 grid_layout_element.borrow_mut().layout_info_prop =
617 Some((layout_info_prop_h, layout_info_prop_v));
618 for d in grid_layout_element.borrow_mut().debug.iter_mut() {
619 d.layout = Some(Layout::GridLayout(grid.clone()));
620 }
621}
622
623impl GridLayout {
624 fn add_element(
625 &mut self,
626 item_element: &ElementRc,
627 new_row: bool,
628 layout_cache_prop_h: &NamedReference,
629 layout_cache_prop_v: &NamedReference,
630 organized_data_prop: &NamedReference,
631 numbering_type: &mut NumberingTypes,
632 diag: &mut BuildDiagnostics,
633 num_cached_items: &mut usize,
634 ) {
635 {
637 let mut check_expr = |name: &str| {
657 let mut is_number_literal = false;
658 let mut read = |elem: &ElementRc, lit: &mut bool| -> Option<Expression> {
659 let b = elem.borrow().binding(name).map(|b| b.clone())?;
660 if !b.has_binding() {
661 return None;
662 }
663 *lit = check_number_literal_is_positive_integer(&b.expression, name, &b, diag);
664 Some(b.expression.clone())
665 };
666 let lenient = read(item_element, &mut is_number_literal);
667 let strict = if lenient.is_some() {
668 lenient.clone()
669 } else if item_element.borrow().repeated.is_some()
670 && let ElementType::Component(base) = item_element.borrow().base_type.clone()
671 {
672 read(&base.root_element, &mut is_number_literal)
673 } else {
674 None
675 };
676 (strict, lenient, is_number_literal)
677 };
678
679 let (row_strict, row_lenient, row_lit) = check_expr("row");
680 let (col_strict, col_lenient, col_lit) = check_expr("col");
681 check_expr("rowspan");
682 check_expr("colspan");
683
684 let would_conflict = |num: &Option<RowColExpressionType>,
689 ty: &RowColExpressionType|
690 -> bool {
691 !matches!(ty, RowColExpressionType::Literal) && matches!(num, Some(t) if t != ty)
692 };
693
694 let mut classify_and_update =
699 |strict: RowColExpressionType, lenient: RowColExpressionType| -> Option<bool> {
700 let diag = if would_conflict(&numbering_type.strict, &strict) {
701 Some(would_conflict(&numbering_type.lenient, &lenient))
705 } else {
706 None
707 };
708 if numbering_type.strict.is_none()
712 && !matches!(strict, RowColExpressionType::Literal)
713 {
714 numbering_type.strict = Some(strict);
715 }
716 if numbering_type.lenient.is_none()
717 && !matches!(lenient, RowColExpressionType::Literal)
718 {
719 numbering_type.lenient = Some(lenient);
720 }
721 diag
722 };
723
724 let row_strict_ty = RowColExpressionType::from_option_expr(&row_strict, row_lit);
725 let row_lenient_ty = RowColExpressionType::from_option_expr(&row_lenient, row_lit);
726 let col_strict_ty = RowColExpressionType::from_option_expr(&col_strict, col_lit);
727 let col_lenient_ty = RowColExpressionType::from_option_expr(&col_lenient, col_lit);
728
729 let row_diag = classify_and_update(row_strict_ty, row_lenient_ty);
730 let col_diag = classify_and_update(col_strict_ty, col_lenient_ty);
731
732 let report = match (row_diag, col_diag) {
736 (Some(true), _) => Some(("row", true)),
737 (_, Some(true)) => Some(("col", true)),
738 (Some(false), _) => Some(("row", false)),
739 (_, Some(false)) => Some(("col", false)),
740 _ => None,
741 };
742
743 if let Some((prop_name, is_error)) = report {
744 let element_ref = item_element.borrow();
749 let inner_borrow = match &element_ref.base_type {
750 ElementType::Component(base) if element_ref.repeated.is_some() => {
751 Some(base.root_element.clone())
752 }
753 _ => None,
754 };
755 let direct_binding = element_ref.binding(prop_name).map(|b| b.clone());
756 let inner_binding = inner_borrow
757 .as_ref()
758 .and_then(|e| e.borrow().binding(prop_name).map(|b| b.clone()));
759 let binding = direct_binding.or(inner_binding);
760 let span: &dyn Spanned = match &binding {
761 Some(b) => b,
762 None => &*element_ref,
763 };
764 if is_error {
765 diag.push_error(
766 format!("Cannot mix auto-numbering and runtime expressions for the '{prop_name}' property"),
767 span,
768 );
769 } else {
770 diag.push_warning(
771 format!("Cannot mix auto-numbering and runtime expressions for the '{prop_name}' property. This was accepted by previous versions of Slint, but may become an error in the future"),
772 span,
773 );
774 }
775 }
776 }
777
778 let propref = |name: &'static str| -> Option<RowColExpr> {
779 let nr = crate::layout::binding_reference(item_element, name).map(|nr| {
780 let e = nr.element();
782 let mut nr = nr.clone();
783 if e.borrow().repeated.is_some()
784 && let crate::langtype::ElementType::Component(c) = e.borrow().base_type.clone()
785 {
786 nr = NamedReference::new(&c.root_element, nr.name().clone())
787 };
788 nr
789 });
790 nr.map(RowColExpr::Named)
791 };
792
793 let row_expr = propref("row");
794 let col_expr = propref("col");
795 let rowspan_expr = propref("rowspan");
796 let colspan_expr = propref("colspan");
797
798 self.add_element_with_coord_as_expr(
799 item_element,
800 new_row,
801 (&row_expr, &col_expr),
802 (&rowspan_expr, &colspan_expr),
803 layout_cache_prop_h,
804 layout_cache_prop_v,
805 organized_data_prop,
806 diag,
807 num_cached_items,
808 );
809 }
810
811 fn add_element_with_coord(
812 &mut self,
813 item_element: &ElementRc,
814 (row, col): (u16, u16),
815 (rowspan, colspan): (u16, u16),
816 layout_cache_prop_h: &NamedReference,
817 layout_cache_prop_v: &NamedReference,
818 organized_data_prop: &NamedReference,
819 diag: &mut BuildDiagnostics,
820 num_cached_items: &mut usize,
821 ) {
822 self.add_element_with_coord_as_expr(
823 item_element,
824 false, (&Some(RowColExpr::Literal(row)), &Some(RowColExpr::Literal(col))),
826 (&Some(RowColExpr::Literal(rowspan)), &Some(RowColExpr::Literal(colspan))),
827 layout_cache_prop_h,
828 layout_cache_prop_v,
829 organized_data_prop,
830 diag,
831 num_cached_items,
832 )
833 }
834
835 fn add_repeated_row(
836 &mut self,
837 item_element: &ElementRc,
838 layout_cache_prop_h: &NamedReference,
839 layout_cache_prop_v: &NamedReference,
840 organized_data_prop: &NamedReference,
841 diag: &mut BuildDiagnostics,
842 num_cached_items: &mut usize,
843 ) {
844 let layout_item = create_layout_item(item_element, diag);
845 if let ElementType::Component(comp) = &item_element.borrow().base_type {
846 let mut children_layout_items = Vec::new();
847 let jump_pos = *num_cached_items;
848
849 let children_ref = comp.root_element.borrow().children.clone();
851 let has_inner_repeaters = children_ref.iter().any(|c| c.borrow().repeated.is_some());
852
853 let step = children_ref.len() as f64;
858 let (stride_h_expr, stride_v_expr, stride_org_expr): (
859 Expression,
860 Expression,
861 Expression,
862 ) = if has_inner_repeaters {
863 (
866 Expression::LayoutCacheAccess {
867 layout_cache_prop: layout_cache_prop_h.clone(),
868 index: jump_pos * 2 + 1,
869 repeater_index: None,
870 entries_per_item: 1,
871 },
872 Expression::LayoutCacheAccess {
873 layout_cache_prop: layout_cache_prop_v.clone(),
874 index: jump_pos * 2 + 1,
875 repeater_index: None,
876 entries_per_item: 1,
877 },
878 Expression::LayoutCacheAccess {
879 layout_cache_prop: organized_data_prop.clone(),
880 index: jump_pos * 4 + 1,
881 repeater_index: None,
882 entries_per_item: 1,
883 },
884 )
885 } else {
886 (
888 Expression::NumberLiteral(step * 2.0, Unit::None), Expression::NumberLiteral(step * 2.0, Unit::None), Expression::NumberLiteral(step * 4.0, Unit::None), )
892 };
893
894 let mut cumulative_pos: Option<Expression> = None;
905
906 for child in children_ref.iter() {
907 let is_nested_repeater = child.borrow().repeated.is_some();
908 let sub_item = create_layout_item(child, diag);
909
910 let propref = |name: &'static str, elem: &ElementRc| -> Option<RowColExpr> {
912 let nr = crate::layout::binding_reference(elem, name).map(|nr| {
913 let e = nr.element();
914 let mut nr = nr.clone();
915 if e.borrow().repeated.is_some()
916 && let crate::langtype::ElementType::Component(c) =
917 e.borrow().base_type.clone()
918 {
919 nr = NamedReference::new(&c.root_element, nr.name().clone())
920 };
921 nr
922 });
923 nr.map(RowColExpr::Named)
924 };
925 let colspan_expr = propref("colspan", child);
926 let rowspan_expr = propref("rowspan", child);
927 let child_grid_cell = Rc::new(RefCell::new(GridLayoutCell {
928 new_row: false,
929 col_expr: RowColExpr::Auto,
930 row_expr: RowColExpr::Auto,
931 colspan_expr: colspan_expr.unwrap_or(RowColExpr::Literal(1)),
932 rowspan_expr: rowspan_expr.unwrap_or(RowColExpr::Literal(1)),
933 child_items: None,
934 }));
935 sub_item.elem.borrow_mut().grid_layout_cell = Some(child_grid_cell);
938
939 let effective_inner_rep_idx = if is_nested_repeater {
945 let model_idx = sub_item.repeater_index.clone().unwrap();
947 Some(if let Some(ref base) = cumulative_pos {
948 Expression::BinaryExpression {
949 lhs: Box::new(base.clone()),
950 rhs: Box::new(model_idx),
951 op: '+',
952 source_location: None,
953 }
954 } else {
955 model_idx
956 })
957 } else {
958 cumulative_pos.clone()
960 };
961
962 let repeater_params = RepeaterCacheParams {
963 index: jump_pos,
964 rep_idx: &layout_item.repeater_index,
965 child_offset: 0,
966 inner_rep_idx: &effective_inner_rep_idx,
967 };
968 set_coord_prop_from_cache(
970 &sub_item.elem,
971 &sub_item.item.constraints,
972 layout_cache_prop_h,
973 layout_cache_prop_v,
974 &repeater_params,
975 Some(&stride_h_expr),
976 Some(&stride_v_expr),
977 diag,
978 );
979 set_grid_rowcol_from_cache(
981 &sub_item.elem,
982 organized_data_prop,
983 &repeater_params,
984 Some(&stride_org_expr),
985 (&None::<RowColExpr>, &None::<RowColExpr>),
986 diag,
987 );
988
989 if is_nested_repeater {
991 let (model_expr, is_conditional) = {
995 let b = child.borrow();
996 let r = b.repeated.as_ref().unwrap();
997 (r.model.clone(), r.is_conditional_element)
998 };
999 let len_expr = if is_conditional {
1000 Expression::Condition {
1001 condition: Box::new(model_expr),
1002 true_expr: Box::new(Expression::NumberLiteral(1., Unit::None)),
1003 false_expr: Box::new(Expression::NumberLiteral(0., Unit::None)),
1004 source_location: None,
1005 }
1006 } else {
1007 Expression::FunctionCall {
1008 function: Callable::Builtin(BuiltinFunction::ArrayLength),
1009 arguments: vec![model_expr],
1010 source_location: None,
1011 }
1012 };
1013 cumulative_pos = Some(if let Some(prev) = cumulative_pos.take() {
1014 Expression::BinaryExpression {
1015 lhs: Box::new(prev),
1016 rhs: Box::new(len_expr),
1017 op: '+',
1018 source_location: None,
1019 }
1020 } else {
1021 len_expr
1022 });
1023 } else {
1024 cumulative_pos = Some(if let Some(prev) = cumulative_pos.take() {
1026 Expression::BinaryExpression {
1027 lhs: Box::new(prev),
1028 rhs: Box::new(Expression::NumberLiteral(1., Unit::None)),
1029 op: '+',
1030 source_location: None,
1031 }
1032 } else {
1033 Expression::NumberLiteral(1., Unit::None)
1034 });
1035 }
1036
1037 if is_nested_repeater {
1038 children_layout_items.push(RowChildTemplate::Repeated {
1039 item: sub_item.item,
1040 repeated_element: child.clone(),
1041 });
1042 } else {
1043 children_layout_items.push(RowChildTemplate::Static(sub_item.item));
1044 }
1045 }
1046
1047 *num_cached_items += 1;
1049 let grid_layout_cell = Rc::new(RefCell::new(GridLayoutCell {
1051 new_row: true,
1052 col_expr: RowColExpr::Auto,
1053 row_expr: RowColExpr::Auto,
1054 colspan_expr: RowColExpr::Literal(1),
1055 rowspan_expr: RowColExpr::Literal(1),
1056 child_items: Some(children_layout_items),
1057 }));
1058 let grid_layout_element = GridLayoutElement {
1059 cell: grid_layout_cell.clone(),
1060 item: layout_item.item.clone(),
1061 };
1062 comp.root_element.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
1063 self.elems.push(grid_layout_element);
1064 }
1065 }
1066
1067 fn add_element_with_coord_as_expr(
1068 &mut self,
1069 item_element: &ElementRc,
1070 new_row: bool,
1071 (row_expr, col_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
1072 (rowspan_expr, colspan_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
1073 layout_cache_prop_h: &NamedReference,
1074 layout_cache_prop_v: &NamedReference,
1075 organized_data_prop: &NamedReference,
1076 diag: &mut BuildDiagnostics,
1077 num_cached_items: &mut usize,
1078 ) {
1079 let layout_item = create_layout_item(item_element, diag);
1080
1081 let has_repeater_indirection = layout_item.repeater_index.is_some();
1082 let stride_coord =
1084 has_repeater_indirection.then(|| Expression::NumberLiteral(2.0, Unit::None));
1085 let stride_org =
1086 has_repeater_indirection.then(|| Expression::NumberLiteral(4.0, Unit::None));
1087 let repeater_params = RepeaterCacheParams {
1088 index: *num_cached_items,
1089 rep_idx: &layout_item.repeater_index,
1090 child_offset: 0,
1091 inner_rep_idx: &None,
1092 };
1093 set_coord_prop_from_cache(
1094 &layout_item.elem,
1095 &layout_item.item.constraints,
1096 layout_cache_prop_h,
1097 layout_cache_prop_v,
1098 &repeater_params,
1099 stride_coord.as_ref(),
1100 stride_coord.as_ref(),
1101 diag,
1102 );
1103 set_grid_rowcol_from_cache(
1104 &layout_item.elem,
1105 organized_data_prop,
1106 &repeater_params,
1107 stride_org.as_ref(),
1108 (row_expr, col_expr),
1109 diag,
1110 );
1111
1112 let expr_or_default = |expr: &Option<RowColExpr>, default: RowColExpr| -> RowColExpr {
1113 match expr {
1114 Some(RowColExpr::Literal(v)) => RowColExpr::Literal(*v),
1115 Some(RowColExpr::Named(nr)) => RowColExpr::Named(nr.clone()),
1116 Some(RowColExpr::Auto) => RowColExpr::Auto,
1117 None => default,
1118 }
1119 };
1120
1121 let grid_layout_cell = Rc::new(RefCell::new(GridLayoutCell {
1122 new_row,
1123 col_expr: expr_or_default(col_expr, RowColExpr::Auto),
1124 row_expr: expr_or_default(row_expr, RowColExpr::Auto),
1125 colspan_expr: expr_or_default(colspan_expr, RowColExpr::Literal(1)),
1126 rowspan_expr: expr_or_default(rowspan_expr, RowColExpr::Literal(1)),
1127 child_items: None,
1128 }));
1129 let grid_layout_element =
1130 GridLayoutElement { cell: grid_layout_cell.clone(), item: layout_item.item.clone() };
1131 layout_item.elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
1132 self.elems.push(grid_layout_element);
1133 *num_cached_items += 1;
1134 }
1135}
1136
1137pub fn optimize_single_cell_layouts(component: &Rc<Component>) {
1144 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
1145 let solves = elem
1147 .borrow()
1148 .real_bindings()
1149 .filter_map(|(name, b)| match b.borrow().value_expression() {
1150 Expression::SolveBoxLayout(l, o) if *o == l.orientation && l.elems.len() == 1 => {
1151 Some((name.clone(), l.clone()))
1152 }
1153 _ => None,
1154 })
1155 .collect::<Vec<_>>();
1156 for (cache_name, layout) in solves {
1157 optimize_single_cell_layout(elem, &cache_name, &layout);
1158 }
1159 });
1160}
1161
1162fn optimize_single_cell_layout(
1163 layout_element: &ElementRc,
1164 cache_name: &SmolStr,
1165 layout: &BoxLayout,
1166) {
1167 let Some(single_cell) = single_cell_box_layout(layout) else { return };
1168 let orientation = layout.orientation;
1169 let cell = &layout.elems[0].element;
1170 let (pos, size) = match orientation {
1171 Orientation::Horizontal => ("x", "width"),
1172 Orientation::Vertical => ("y", "height"),
1173 };
1174 let replace = |prop: &str, expr: Expression| {
1175 let elem = cell.borrow();
1176 let mut binding = elem.binding_mut(prop).expect("the layout has set the cell's geometry");
1177 let expression = &mut binding.expression;
1178 debug_assert!(matches!(
1179 expression.ignore_debug_hooks(),
1180 Expression::LayoutCacheAccess { .. }
1181 ));
1182
1183 *expression.ignore_debug_hooks_mut() = expr;
1184 };
1185 let pads = layout.geometry.padding.begin_end(orientation);
1186 let available = || size_minus_padding(layout_element, size, pads);
1187 let mut pos_expr = pads.0.map_or(Expression::NumberLiteral(0., Unit::Px), |nr| {
1188 Expression::PropertyReference(nr.clone())
1189 });
1190 if single_cell.pos_factor != 0. {
1191 let cell_size =
1194 Expression::PropertyReference(NamedReference::new(cell, SmolStr::new_static(size)));
1195 let leftover = min_max(
1196 MinMaxOp::Max,
1197 Expression::NumberLiteral(0., Unit::Px),
1198 bin('-', available(), cell_size),
1199 );
1200 let factor = Expression::NumberLiteral(single_cell.pos_factor, Unit::None);
1201 pos_expr = bin('+', pos_expr, bin('*', leftover, factor));
1202 }
1203 replace(pos, pos_expr);
1204 if let Some((min_expr, max_expr, pref_expr)) = &single_cell.size {
1205 let mut size_expr = available();
1206 if let Some(pref) = pref_expr {
1207 size_expr = min_max(MinMaxOp::Min, size_expr, pref.clone());
1208 }
1209 size_expr = min_max(
1211 MinMaxOp::Max,
1212 min_max(MinMaxOp::Min, size_expr, max_expr.clone()),
1213 min_expr.clone(),
1214 );
1215 replace(size, size_expr);
1216 }
1217 layout_element.borrow_mut().take_binding(cache_name);
1218 layout_element.borrow_mut().property_declarations.remove(cache_name);
1219 for o in [Orientation::Horizontal, Orientation::Vertical] {
1220 let Some(nr) = layout_element.borrow().layout_info_prop.as_ref().map(|p| match o {
1224 Orientation::Horizontal => p.0.clone(),
1225 Orientation::Vertical => p.1.clone(),
1226 }) else {
1227 continue;
1228 };
1229 let Some(info) =
1230 single_cell_layout_info_binding(layout, &layout.elems[0], o, single_cell.stretch)
1231 else {
1232 continue;
1233 };
1234 if let Some(mut binding) = nr.element().borrow().binding_mut(nr.name()) {
1235 *binding.expression.ignore_debug_hooks_mut() = info;
1236 }
1237 }
1238}
1239
1240struct SingleCellBoxLayout {
1244 stretch: bool,
1246 pos_factor: f64,
1249 size: Option<(Expression, Expression, Option<Expression>)>,
1253}
1254
1255fn single_cell_box_layout(layout: &BoxLayout) -> Option<SingleCellBoxLayout> {
1256 let orientation = layout.orientation;
1257 let [item] = layout.elems.as_slice() else { return None };
1258 if item.element.borrow().repeated.is_some() {
1259 return None;
1260 }
1261 let alignment = match &layout.geometry.alignment {
1264 None => None,
1265 Some(nr) => {
1266 let elem = nr.element();
1267 let elem = elem.borrow();
1268 let analysis = elem.property_analysis.borrow();
1269 if analysis.get(nr.name()).is_some_and(|a| a.is_set || a.is_set_externally) {
1270 return None;
1271 }
1272 let binding = elem.binding(nr.name())?;
1273 if !binding.two_way_bindings.is_empty() {
1274 return None;
1275 }
1276 let Expression::EnumerationValue(ev) = binding.value_expression() else {
1277 return None;
1278 };
1279 Some(ev.enumeration.values[ev.value].clone())
1280 }
1281 };
1282 let (stretch, pos_factor) = match alignment.as_deref() {
1283 None | Some("stretch") => (true, 0.),
1284 Some("start" | "space-between") => (false, 0.),
1285 Some("center" | "space-around" | "space-evenly") => (false, 0.5),
1286 Some("end") => (false, 1.),
1287 _ => return None,
1288 };
1289 let c = item.constraints.for_orientation(orientation);
1290 if [c.min, c.max, c.preferred].into_iter().flatten().any(|nr| nr.ty() == Type::Percent) {
1292 return None;
1293 }
1294 if c.fixed {
1295 return Some(SingleCellBoxLayout { stretch, pos_factor, size: None });
1296 }
1297 let implicit = cell_implicit_info(&item.element, orientation);
1298 let side = |explicit: &Option<NamedReference>, name: &str| {
1299 explicit
1300 .clone()
1301 .map(Expression::PropertyReference)
1302 .or_else(|| implicit.as_ref().and_then(|info| implicit_info_field(info, name)))
1303 };
1304 let pref = if stretch { None } else { Some(side(c.preferred, "preferred")?) };
1305 Some(SingleCellBoxLayout {
1306 stretch,
1307 pos_factor,
1308 size: Some((side(c.min, "min")?, side(c.max, "max")?, pref)),
1309 })
1310}
1311
1312fn bin(op: char, lhs: Expression, rhs: Expression) -> Expression {
1313 Expression::BinaryExpression {
1314 lhs: Box::new(lhs),
1315 rhs: Box::new(rhs),
1316 op,
1317 source_location: None,
1318 }
1319}
1320
1321fn min_max(op: MinMaxOp, lhs: Expression, rhs: Expression) -> Expression {
1322 crate::builtin_macros::min_max_expression(lhs, rhs, op)
1323}
1324
1325fn size_minus_padding(
1326 layout_element: &ElementRc,
1327 size_prop: &'static str,
1328 (begin_padding, end_padding): (Option<&NamedReference>, Option<&NamedReference>),
1329) -> Expression {
1330 let mut e = Expression::PropertyReference(NamedReference::new(
1331 layout_element,
1332 SmolStr::new_static(size_prop),
1333 ));
1334 for p in [begin_padding, end_padding].into_iter().flatten() {
1335 e = bin('-', e, Expression::PropertyReference(p.clone()));
1336 }
1337 e
1338}
1339
1340fn single_cell_layout_info_binding(
1344 layout: &BoxLayout,
1345 item: &LayoutItem,
1346 o: Orientation,
1347 stretch: bool,
1348) -> Option<Expression> {
1349 let c = item.constraints.for_orientation(o);
1350 let size_prop = match o {
1351 Orientation::Horizontal => "width",
1352 Orientation::Vertical => "height",
1353 };
1354 let info_ty = crate::typeregister::layout_info_type();
1355 enum Implicit {
1358 Literal(std::collections::BTreeMap<SmolStr, Expression>),
1359 Prop(Expression),
1360 Expensive,
1361 }
1362 let implicit = match cell_implicit_info(&item.element, o) {
1363 Some(Expression::Struct { values, .. }) => Implicit::Literal(values),
1364 Some(base @ Expression::PropertyReference(_)) => Implicit::Prop(base),
1365 _ => Implicit::Expensive,
1366 };
1367 let mut implicit_used = false;
1368 let mut implicit_field = |name: &str| match &implicit {
1369 Implicit::Literal(values) => values.get(name).cloned(),
1370 Implicit::Prop(_) => {
1371 implicit_used = true;
1372 Some(Expression::StructFieldAccess {
1373 base: Box::new(Expression::ReadLocalVariable {
1374 name: "cell_layout_info".into(),
1375 ty: info_ty.clone().into(),
1376 }),
1377 name: name.into(),
1378 })
1379 }
1380 Implicit::Expensive => None,
1381 };
1382 let explicit = |nr: &Option<NamedReference>| {
1384 nr.clone().filter(|nr| nr.ty() != Type::Percent).map(Expression::PropertyReference)
1385 };
1386 let (cell_min, cell_max, cell_pref) = if c.fixed {
1387 let sz =
1388 Expression::ReadLocalVariable { name: "cell_size".into(), ty: Type::LogicalLength };
1389 (sz.clone(), sz.clone(), sz)
1390 } else {
1391 (
1392 explicit(c.min).or_else(|| implicit_field("min"))?,
1393 explicit(c.max).or_else(|| implicit_field("max"))?,
1394 explicit(c.preferred).or_else(|| implicit_field("preferred"))?,
1395 )
1396 };
1397 let cell_stretch = c
1398 .stretch
1399 .clone()
1400 .map(Expression::PropertyReference)
1401 .or_else(|| implicit_field("stretch"))
1402 .or_else(|| static_native_stretch(&item.element))?;
1403
1404 let mut prelude = Vec::new();
1405 if implicit_used && let Implicit::Prop(base) = implicit {
1406 prelude.push(Expression::StoreLocalVariable {
1407 name: "cell_layout_info".into(),
1408 value: Box::new(base),
1409 });
1410 }
1411 if c.fixed {
1412 prelude.push(Expression::StoreLocalVariable {
1413 name: "cell_size".into(),
1414 value: Box::new(Expression::PropertyReference(NamedReference::new(
1415 &item.element,
1416 SmolStr::new_static(size_prop),
1417 ))),
1418 });
1419 }
1420 let (pad_begin, pad_end) = layout.geometry.padding.begin_end(o);
1421 let pad_sum = [pad_begin, pad_end]
1422 .into_iter()
1423 .flatten()
1424 .map(|nr| Expression::PropertyReference(nr.clone()))
1425 .reduce(|lhs, rhs| bin('+', lhs, rhs));
1426 if let Some(pad_sum) = pad_sum.clone() {
1427 prelude.push(Expression::StoreLocalVariable {
1428 name: "layout_padding".into(),
1429 value: Box::new(pad_sum),
1430 });
1431 }
1432 let plus_pads = |e: Expression| {
1433 if pad_sum.is_none() {
1434 e
1435 } else {
1436 let pads = Expression::ReadLocalVariable {
1437 name: "layout_padding".into(),
1438 ty: Type::LogicalLength,
1439 };
1440 bin('+', e, pads)
1441 }
1442 };
1443 let (min, max, preferred) = if o == layout.orientation {
1444 let min = plus_pads(cell_min.clone());
1445 let max = if stretch {
1446 min_max(MinMaxOp::Max, plus_pads(cell_max.clone()), min.clone())
1447 } else {
1448 Expression::NumberLiteral(f32::MAX as f64, Unit::Px)
1449 };
1450 let pref = plus_pads(min_max(
1451 MinMaxOp::Max,
1452 min_max(MinMaxOp::Min, cell_pref, cell_max),
1453 cell_min,
1454 ));
1455 (min, max, pref)
1456 } else {
1457 let bounded_max = min_max(MinMaxOp::Max, cell_max, cell_min.clone());
1458 let pref = plus_pads(min_max(
1459 MinMaxOp::Max,
1460 min_max(MinMaxOp::Min, cell_pref, bounded_max.clone()),
1461 cell_min.clone(),
1462 ));
1463 (plus_pads(cell_min), plus_pads(bounded_max), pref)
1464 };
1465 let values = [
1466 ("min", min),
1467 ("max", max),
1468 ("min_percent", Expression::NumberLiteral(0., Unit::None)),
1469 ("max_percent", Expression::NumberLiteral(100., Unit::None)),
1470 ("preferred", preferred),
1471 ("stretch", cell_stretch),
1472 ]
1473 .into_iter()
1474 .map(|(name, e)| (SmolStr::new_static(name), e))
1475 .collect();
1476 let info = Expression::Struct { ty: info_ty, values };
1477 Some(if prelude.is_empty() {
1478 info
1479 } else {
1480 prelude.push(info);
1481 Expression::CodeBlock(prelude)
1482 })
1483}
1484
1485fn cell_implicit_info(elem: &ElementRc, orientation: Orientation) -> Option<Expression> {
1489 let own_info = elem.borrow().effective_layout_info_prop(orientation).cloned();
1490 match own_info {
1491 Some(nr) => Some(Expression::PropertyReference(nr)),
1492 None => implicit_layout_info_call(elem, orientation, BuiltinFilter::All, None),
1493 }
1494}
1495
1496fn implicit_info_field(info: &Expression, name: &str) -> Option<Expression> {
1499 match info {
1500 Expression::Struct { values, .. } => values.get(name).cloned(),
1501 base @ Expression::PropertyReference(_) => {
1502 Some(Expression::StructFieldAccess { base: Box::new(base.clone()), name: name.into() })
1503 }
1504 _ => None,
1505 }
1506}
1507
1508fn clamp_cross_stretch_size(
1516 available: &Expression,
1517 c: &LayoutConstraints,
1518 ortho: Orientation,
1519) -> Expression {
1520 let c = c.for_orientation(ortho);
1521 let side = |explicit: &Option<NamedReference>| match explicit {
1522 Some(nr) if nr.ty() == Type::Percent => Some(bin(
1523 '/',
1524 bin('*', available.clone(), Expression::PropertyReference(nr.clone())),
1525 Expression::NumberLiteral(100., Unit::None),
1526 )),
1527 Some(nr) => Some(Expression::PropertyReference(nr.clone())),
1528 None => None,
1529 };
1530 let mut size_expr = available.clone();
1531 if let Some(max_expr) = side(c.max) {
1532 size_expr = min_max(MinMaxOp::Min, size_expr, max_expr);
1533 }
1534 if let Some(min_expr) = side(c.min) {
1535 size_expr = min_max(MinMaxOp::Max, size_expr, min_expr);
1536 }
1537 size_expr
1538}
1539
1540fn check_cross_axis_alignment_not_auto(elem: &ElementRc, diag: &mut BuildDiagnostics) {
1543 if let Some(b) = elem.borrow().binding("cross-axis-alignment")
1544 && let Expression::EnumerationValue(ev) = b.value_expression()
1545 && ev.to_string() == "auto"
1546 {
1547 diag.push_error(
1548 "cross-axis-alignment cannot be set to 'auto', which is only valid for cross-axis-self-alignment; the default is 'stretch'".into(),
1549 &*b,
1550 );
1551 }
1552}
1553
1554fn lower_box_layout(
1555 layout_element: &ElementRc,
1556 diag: &mut BuildDiagnostics,
1557 orientation: Orientation,
1558) {
1559 check_cross_axis_alignment_not_auto(layout_element, diag);
1560 let mut layout = BoxLayout {
1561 orientation,
1562 elems: Default::default(),
1563 geometry: LayoutGeometry::new(layout_element),
1564 cross_alignment: binding_reference(layout_element, "cross-axis-alignment"),
1565 };
1566
1567 let layout_info_prop_v = create_new_prop(
1568 layout_element,
1569 SmolStr::new_static("layoutinfo-v"),
1570 layout_info_type().into(),
1571 );
1572 let layout_info_prop_h = create_new_prop(
1573 layout_element,
1574 SmolStr::new_static("layoutinfo-h"),
1575 layout_info_type().into(),
1576 );
1577
1578 let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
1579
1580 let items: Vec<_> =
1583 layout_children.iter().map(|child| create_layout_item(child, diag)).collect();
1584 for item in &items {
1589 if item.repeater_index.is_some() {
1590 item.elem.borrow_mut().parent_box_layout_orientation = Some(orientation);
1591 }
1592 }
1593
1594 let any_cell_align_self =
1597 items.iter().any(|item| item.item.cross_axis_self_alignment.is_some());
1598
1599 let layout_cache_ortho_prop =
1600 (layout.cross_alignment.is_some() || any_cell_align_self).then(|| {
1601 create_new_prop(
1602 layout_element,
1603 SmolStr::new_static("layout-cache-ortho"),
1604 Type::LayoutCache,
1605 )
1606 });
1607
1608 let (pos, size, pad, ortho) = match orientation {
1609 Orientation::Horizontal => ("x", "width", "y", "height"),
1610 Orientation::Vertical => ("y", "height", "x", "width"),
1611 };
1612
1613 let layout_cache_prop =
1614 create_new_prop(layout_element, SmolStr::new_static("layout-cache"), Type::LayoutCache);
1615 let stretch_bindings = layout_cache_ortho_prop.is_none().then(|| {
1617 let pads = layout.geometry.padding.begin_end(orientation.orthogonal());
1618 let pad_expr = pads.0.map(|nr| Expression::PropertyReference(nr.clone()));
1619 (pad_expr, size_minus_padding(layout_element, ortho, pads))
1620 });
1621
1622 for item in items {
1623 let index = layout.elems.len() * BOX_LAYOUT_CACHE_ENTRIES_PER_CELL;
1624 let rep_idx = &item.repeater_index;
1625 let (fixed_size, fixed_ortho) = match orientation {
1626 Orientation::Horizontal => {
1627 (item.item.constraints.fixed_width, item.item.constraints.fixed_height)
1628 }
1629 Orientation::Vertical => {
1630 (item.item.constraints.fixed_height, item.item.constraints.fixed_width)
1631 }
1632 };
1633 let actual_elem = &item.elem;
1634 let entries = BOX_LAYOUT_CACHE_ENTRIES_PER_CELL;
1635 set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, entries, diag);
1636 if !fixed_size {
1637 set_prop_from_cache(
1638 actual_elem,
1639 size,
1640 &layout_cache_prop,
1641 index + 1,
1642 rep_idx,
1643 entries,
1644 diag,
1645 );
1646 }
1647 if let Some(cache_ortho) = &layout_cache_ortho_prop {
1648 set_prop_from_cache(actual_elem, pad, cache_ortho, index, rep_idx, entries, diag);
1649 if !fixed_ortho {
1650 set_prop_from_cache(
1651 actual_elem,
1652 ortho,
1653 cache_ortho,
1654 index + 1,
1655 rep_idx,
1656 entries,
1657 diag,
1658 );
1659 }
1660 } else {
1661 let (pad_expr, size_expr) = stretch_bindings.as_ref().unwrap();
1662 if let Some(pad_expr) = pad_expr {
1663 actual_elem.borrow_mut().set_binding(pad.into(), pad_expr.clone().into());
1664 }
1665 if !fixed_ortho {
1666 let clamped = clamp_cross_stretch_size(
1667 size_expr,
1668 &item.item.constraints,
1669 orientation.orthogonal(),
1670 );
1671 actual_elem.borrow_mut().set_binding(ortho.into(), clamped.into());
1672 }
1673 }
1674 layout.elems.push(item.item);
1675 }
1676 layout_element.borrow_mut().children = layout_children;
1677 let span = layout_element.borrow().to_source_location();
1678 layout_cache_prop.element().borrow_mut().set_binding(
1679 layout_cache_prop.name().clone(),
1680 BindingExpression::new_with_span(
1681 Expression::SolveBoxLayout(layout.clone(), orientation),
1682 span.clone(),
1683 ),
1684 );
1685 if let Some(cache_ortho) = &layout_cache_ortho_prop {
1686 cache_ortho.element().borrow_mut().set_binding(
1687 cache_ortho.name().clone(),
1688 BindingExpression::new_with_span(
1689 Expression::SolveBoxLayout(layout.clone(), orientation.orthogonal()),
1690 span.clone(),
1691 ),
1692 );
1693 }
1694 layout_info_prop_h.element().borrow_mut().set_binding(
1695 layout_info_prop_h.name().clone(),
1696 BindingExpression::new_with_span(
1697 Expression::ComputeBoxLayoutInfo {
1698 layout: layout.clone(),
1699 orientation: Orientation::Horizontal,
1700 cross_axis_size: None,
1701 },
1702 span.clone(),
1703 ),
1704 );
1705 layout_info_prop_v.element().borrow_mut().set_binding(
1706 layout_info_prop_v.name().clone(),
1707 BindingExpression::new_with_span(
1708 Expression::ComputeBoxLayoutInfo {
1709 layout: layout.clone(),
1710 orientation: Orientation::Vertical,
1711 cross_axis_size: None,
1712 },
1713 span,
1714 ),
1715 );
1716 layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
1717 for d in layout_element.borrow_mut().debug.iter_mut() {
1718 d.layout = Some(Layout::BoxLayout(layout.clone()));
1719 }
1720}
1721
1722fn lower_flexbox_layout(layout_element: &ElementRc, diag: &mut BuildDiagnostics) {
1723 check_cross_axis_alignment_not_auto(layout_element, diag);
1724 let direction = crate::layout::binding_reference(layout_element, "flex-direction");
1725 let cross_axis_line_alignment =
1726 crate::layout::binding_reference(layout_element, "cross-axis-line-alignment");
1727 let cross_axis_alignment =
1728 crate::layout::binding_reference(layout_element, "cross-axis-alignment");
1729 let flex_wrap = crate::layout::binding_reference(layout_element, "flex-wrap");
1730
1731 let mut layout = crate::layout::FlexboxLayout {
1732 elems: Default::default(),
1733 geometry: LayoutGeometry::new(layout_element),
1734 direction,
1735 cross_axis_line_alignment,
1736 cross_axis_alignment,
1737 flex_wrap,
1738 };
1739
1740 let layout_cache_prop =
1742 create_new_prop(layout_element, SmolStr::new_static("layout-cache"), Type::LayoutCache);
1743 let layout_info_prop_v = create_new_prop(
1744 layout_element,
1745 SmolStr::new_static("layoutinfo-v"),
1746 layout_info_type().into(),
1747 );
1748 let layout_info_prop_h = create_new_prop(
1749 layout_element,
1750 SmolStr::new_static("layoutinfo-h"),
1751 layout_info_type().into(),
1752 );
1753
1754 let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
1755
1756 for layout_child in &layout_children {
1757 let item = create_layout_item(layout_child, diag);
1758 let index = layout.elems.len() * 4; let rep_idx = &item.repeater_index;
1760 let actual_elem = &item.elem;
1761 actual_elem.borrow_mut().child_of_flexbox = true;
1762
1763 set_prop_from_cache(actual_elem, "x", &layout_cache_prop, index, rep_idx, 4, diag);
1765 set_prop_from_cache(actual_elem, "y", &layout_cache_prop, index + 1, rep_idx, 4, diag);
1767 if !item.item.constraints.fixed_width {
1769 set_prop_from_cache(
1770 actual_elem,
1771 "width",
1772 &layout_cache_prop,
1773 index + 2,
1774 rep_idx,
1775 4,
1776 diag,
1777 );
1778 }
1779 if !item.item.constraints.fixed_height {
1781 set_prop_from_cache(
1782 actual_elem,
1783 "height",
1784 &layout_cache_prop,
1785 index + 3,
1786 rep_idx,
1787 4,
1788 diag,
1789 );
1790 }
1791 layout.elems.push(item.item);
1792 }
1793 layout_element.borrow_mut().children = layout_children;
1794 let span = layout_element.borrow().to_source_location();
1795
1796 layout_cache_prop.element().borrow_mut().set_binding(
1797 layout_cache_prop.name().clone(),
1798 BindingExpression::new_with_span(
1799 Expression::SolveFlexboxLayout(layout.clone()),
1800 span.clone(),
1801 ),
1802 );
1803 layout_info_prop_h.element().borrow_mut().set_binding(
1804 layout_info_prop_h.name().clone(),
1805 BindingExpression::new_with_span(
1806 Expression::ComputeFlexboxLayoutInfo {
1807 layout: layout.clone(),
1808 orientation: Orientation::Horizontal,
1809 cross_axis_size: None,
1810 },
1811 span.clone(),
1812 ),
1813 );
1814 layout_info_prop_v.element().borrow_mut().set_binding(
1815 layout_info_prop_v.name().clone(),
1816 BindingExpression::new_with_span(
1817 Expression::ComputeFlexboxLayoutInfo {
1818 layout: layout.clone(),
1819 orientation: Orientation::Vertical,
1820 cross_axis_size: None,
1821 },
1822 span.clone(),
1823 ),
1824 );
1825 let is_root = layout_element
1829 .borrow()
1830 .enclosing_component
1831 .upgrade()
1832 .is_some_and(|c| Rc::ptr_eq(&c.root_element, layout_element));
1833 if layout.axis_relation(Orientation::Horizontal) != crate::layout::FlexboxAxisRelation::MainAxis
1834 && (is_root || layout_element.borrow().height_is_literal)
1835 && let Some(height) = layout.geometry.rect.height_reference.clone()
1836 {
1837 let at_own_height = create_new_prop(
1838 layout_element,
1839 SmolStr::new_static("layoutinfo-h-at-own-height"),
1840 layout_info_type().into(),
1841 );
1842 at_own_height.element().borrow_mut().set_binding(
1843 at_own_height.name().clone(),
1844 BindingExpression::new_with_span(
1845 Expression::ComputeFlexboxLayoutInfo {
1846 layout: layout.clone(),
1847 orientation: Orientation::Horizontal,
1848 cross_axis_size: Some(Box::new(Expression::PropertyReference(height))),
1849 },
1850 span,
1851 ),
1852 );
1853 layout_element.borrow_mut().layout_info_h_at_own_height = Some(at_own_height);
1854 }
1855 layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
1856 for d in layout_element.borrow_mut().debug.iter_mut() {
1857 d.layout = Some(Layout::FlexboxLayout(layout.clone()));
1858 }
1859}
1860
1861fn lower_dialog_layout(
1862 dialog_element: &ElementRc,
1863 style_metrics: &Rc<Component>,
1864 diag: &mut BuildDiagnostics,
1865) {
1866 let mut grid = GridLayout {
1867 elems: Default::default(),
1868 geometry: LayoutGeometry::new(dialog_element),
1869 dialog_button_roles: None,
1870 uses_auto: true,
1871 };
1872 let metrics = &style_metrics.root_element;
1873 grid.geometry
1874 .padding
1875 .bottom
1876 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
1877 grid.geometry
1878 .padding
1879 .top
1880 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
1881 grid.geometry
1882 .padding
1883 .left
1884 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
1885 grid.geometry
1886 .padding
1887 .right
1888 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
1889 grid.geometry
1890 .spacing
1891 .horizontal
1892 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
1893 grid.geometry
1894 .spacing
1895 .vertical
1896 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
1897
1898 let layout_organized_data_prop = create_new_prop(
1899 dialog_element,
1900 SmolStr::new_static("layout-organized-data"),
1901 Type::ArrayOfU16,
1902 );
1903 let layout_cache_prop_h =
1904 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-h"), Type::LayoutCache);
1905 let layout_cache_prop_v =
1906 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-v"), Type::LayoutCache);
1907 let layout_info_prop_h = create_new_prop(
1908 dialog_element,
1909 SmolStr::new_static("layoutinfo-h"),
1910 layout_info_type().into(),
1911 );
1912 let layout_info_prop_v = create_new_prop(
1913 dialog_element,
1914 SmolStr::new_static("layoutinfo-v"),
1915 layout_info_type().into(),
1916 );
1917
1918 let mut main_widget = None;
1919 let mut button_roles = Vec::new();
1920 let mut seen_buttons = HashSet::new();
1921 let mut num_cached_items: usize = 0;
1922 let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
1923 for layout_child in &layout_children {
1924 let dialog_button_role_binding =
1925 layout_child.borrow_mut().take_binding("dialog-button-role");
1926 let is_button = if let Some(role_binding) = dialog_button_role_binding {
1927 if let Expression::EnumerationValue(val) = role_binding.expression.ignore_debug_hooks()
1928 {
1929 let en = &val.enumeration;
1930 debug_assert_eq!(en.name, "DialogButtonRole");
1931 button_roles.push(en.values[val.value].clone());
1932 if val.value == 0 {
1933 diag.push_error(
1934 "The `dialog-button-role` cannot be set explicitly to none".into(),
1935 &role_binding,
1936 );
1937 }
1938 } else {
1939 diag.push_error(
1940 "The `dialog-button-role` property must be known at compile-time".into(),
1941 &role_binding,
1942 );
1943 }
1944 true
1945 } else if matches!(&layout_child.borrow().lookup_property("kind", PropertyLookupMode::ComponentLocal).property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
1946 {
1947 match layout_child.borrow().binding("kind") {
1949 None => diag.push_error(
1950 "The `kind` property of the StandardButton in a Dialog must be set".into(),
1951 &*layout_child.borrow(),
1952 ),
1953 Some(binding) => {
1954 if let Expression::EnumerationValue(val) =
1955 binding.expression.ignore_debug_hooks()
1956 {
1957 let en = &val.enumeration;
1958 debug_assert_eq!(en.name, "StandardButtonKind");
1959 let kind = &en.values[val.value];
1960 let role = match kind.as_str() {
1961 "ok" => "accept",
1962 "cancel" => "reject",
1963 "apply" => "apply",
1964 "close" => "reject",
1965 "reset" => "reset",
1966 "help" => "help",
1967 "yes" => "accept",
1968 "no" => "reject",
1969 "abort" => "reject",
1970 "retry" => "accept",
1971 "ignore" => "accept",
1972 _ => unreachable!(),
1973 };
1974 button_roles.push(role.into());
1975 if !seen_buttons.insert(val.value) {
1976 diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), &*binding);
1977 } else if Rc::ptr_eq(
1978 dialog_element,
1979 &dialog_element
1980 .borrow()
1981 .enclosing_component
1982 .upgrade()
1983 .unwrap()
1984 .root_element,
1985 ) {
1986 let clicked_ty = layout_child
1987 .borrow()
1988 .lookup_property("clicked", PropertyLookupMode::ComponentLocal)
1989 .property_type;
1990 if matches!(&clicked_ty, Type::Callback { .. })
1991 && layout_child.borrow().binding("clicked").is_none_or(|c| {
1992 matches!(c.value_expression(), Expression::Invalid)
1993 })
1994 {
1995 dialog_element
1996 .borrow_mut()
1997 .property_declarations
1998 .entry(format_smolstr!("{}-clicked", kind))
1999 .or_insert_with(|| PropertyDeclaration {
2000 property_type: clicked_ty,
2001 node: None,
2002 expose_in_public_api: true,
2003 is_alias: Some(NamedReference::new(
2004 layout_child,
2005 SmolStr::new_static("clicked"),
2006 )),
2007 visibility: PropertyVisibility::InOut,
2008 pure: None,
2009 shadowed_name: None,
2010 shadowable: false,
2011 moved_from: None,
2012 deprecated: None,
2013 });
2014 }
2015 }
2016 } else {
2017 diag.push_error(
2018 "The `kind` property of the StandardButton in a Dialog must be known at compile-time"
2019 .into(),
2020 &*binding,
2021 );
2022 }
2023 }
2024 }
2025 true
2026 } else {
2027 false
2028 };
2029
2030 if is_button {
2031 grid.add_element_with_coord(
2032 layout_child,
2033 (1, button_roles.len() as u16),
2034 (1, 1),
2035 &layout_cache_prop_h,
2036 &layout_cache_prop_v,
2037 &layout_organized_data_prop,
2038 diag,
2039 &mut num_cached_items,
2040 );
2041 } else if main_widget.is_some() {
2042 diag.push_error(
2043 "A Dialog can have only one child element that is not a StandardButton".into(),
2044 &*layout_child.borrow(),
2045 );
2046 } else {
2047 main_widget = Some(layout_child.clone())
2048 }
2049 }
2050 dialog_element.borrow_mut().children = layout_children;
2051
2052 if let Some(main_widget) = main_widget {
2053 grid.add_element_with_coord(
2054 &main_widget,
2055 (0, 0),
2056 (1, button_roles.len() as u16 + 1),
2057 &layout_cache_prop_h,
2058 &layout_cache_prop_v,
2059 &layout_organized_data_prop,
2060 diag,
2061 &mut num_cached_items,
2062 );
2063 } else {
2064 diag.push_error(
2065 "A Dialog must have a single child element that is not StandardButton".into(),
2066 &*dialog_element.borrow(),
2067 );
2068 }
2069 grid.dialog_button_roles = Some(button_roles);
2070
2071 let span = dialog_element.borrow().to_source_location();
2072 layout_organized_data_prop.element().borrow_mut().set_binding(
2073 layout_organized_data_prop.name().clone(),
2074 BindingExpression::new_with_span(
2075 Expression::OrganizeGridLayout(grid.clone()),
2076 span.clone(),
2077 ),
2078 );
2079 layout_cache_prop_h.element().borrow_mut().set_binding(
2080 layout_cache_prop_h.name().clone(),
2081 BindingExpression::new_with_span(
2082 Expression::SolveGridLayout {
2083 layout_organized_data_prop: layout_organized_data_prop.clone(),
2084 layout: grid.clone(),
2085 orientation: Orientation::Horizontal,
2086 },
2087 span.clone(),
2088 ),
2089 );
2090 layout_cache_prop_v.element().borrow_mut().set_binding(
2091 layout_cache_prop_v.name().clone(),
2092 BindingExpression::new_with_span(
2093 Expression::SolveGridLayout {
2094 layout_organized_data_prop: layout_organized_data_prop.clone(),
2095 layout: grid.clone(),
2096 orientation: Orientation::Vertical,
2097 },
2098 span.clone(),
2099 ),
2100 );
2101 layout_info_prop_h.element().borrow_mut().set_binding(
2102 layout_info_prop_h.name().clone(),
2103 BindingExpression::new_with_span(
2104 Expression::ComputeGridLayoutInfo {
2105 layout_organized_data_prop: layout_organized_data_prop.clone(),
2106 layout: grid.clone(),
2107 orientation: Orientation::Horizontal,
2108 cross_axis_size: None,
2109 },
2110 span.clone(),
2111 ),
2112 );
2113 layout_info_prop_v.element().borrow_mut().set_binding(
2114 layout_info_prop_v.name().clone(),
2115 BindingExpression::new_with_span(
2116 Expression::ComputeGridLayoutInfo {
2117 layout_organized_data_prop: layout_organized_data_prop.clone(),
2118 layout: grid.clone(),
2119 orientation: Orientation::Vertical,
2120 cross_axis_size: None,
2121 },
2122 span,
2123 ),
2124 );
2125 dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
2126 for d in dialog_element.borrow_mut().debug.iter_mut() {
2127 d.layout = Some(Layout::GridLayout(grid.clone()));
2128 }
2129}
2130
2131struct CreateLayoutItemResult {
2132 item: LayoutItem,
2133 elem: ElementRc,
2134 repeater_index: Option<Expression>,
2135}
2136
2137fn create_layout_item(
2139 item_element: &ElementRc,
2140 diag: &mut BuildDiagnostics,
2141) -> CreateLayoutItemResult {
2142 let fix_explicit_percent = |prop: &str, item: &ElementRc| {
2143 if !item.borrow().binding(prop).is_some_and(|b| b.ty() == Type::Percent) {
2144 return;
2145 }
2146 let min_name = format_smolstr!("min-{}", prop);
2147 let max_name = format_smolstr!("max-{}", prop);
2148 let mut min_ref = BindingExpression::from(Expression::PropertyReference(
2149 NamedReference::new(item, min_name.clone()),
2150 ));
2151 let mut item = item.borrow_mut();
2152 let b = item.take_binding(prop).unwrap();
2153 min_ref.span = b.span.clone();
2154 min_ref.priority = b.priority;
2155 item.set_binding(max_name.clone(), min_ref);
2156 item.set_binding(min_name.clone(), b);
2157 item.property_declarations.insert(
2158 min_name,
2159 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
2160 );
2161 item.property_declarations.insert(
2162 max_name,
2163 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
2164 );
2165 };
2166 fix_explicit_percent("width", item_element);
2167 fix_explicit_percent("height", item_element);
2168
2169 item_element.borrow_mut().child_of_layout = true;
2170 let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
2171 let rep_comp = item_element.borrow().base_type.as_component().clone();
2172 fix_explicit_percent("width", &rep_comp.root_element);
2173 fix_explicit_percent("height", &rep_comp.root_element);
2174
2175 *rep_comp.root_constraints.borrow_mut() = LayoutConstraints::new(
2176 &rep_comp.root_element,
2177 Some((&mut *diag, DiagnosticLevel::Error)),
2178 );
2179 rep_comp.root_element.borrow_mut().child_of_layout = true;
2180 (
2181 Some(if r.is_conditional_element {
2182 Expression::NumberLiteral(0., Unit::None)
2183 } else {
2184 Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
2185 }),
2186 rep_comp.root_element.clone(),
2187 )
2188 } else {
2189 (None, item_element.clone())
2190 };
2191
2192 let constraints = LayoutConstraints::new(&actual_elem, Some((diag, DiagnosticLevel::Error)));
2193 let cross_axis_self_alignment =
2194 crate::layout::binding_reference(&actual_elem, "cross-axis-self-alignment");
2195 let layout_order = crate::layout::binding_reference(&actual_elem, "layout-order");
2196 CreateLayoutItemResult {
2197 item: LayoutItem {
2198 element: item_element.clone(),
2199 constraints,
2200 cross_axis_self_alignment,
2201 layout_order,
2202 },
2203 elem: actual_elem,
2204 repeater_index,
2205 }
2206}
2207
2208fn set_grid_prop_from_cache(
2209 elem: &ElementRc,
2210 prop: &str,
2211 layout_cache_prop: &NamedReference,
2212 index: usize,
2213 repeater_index: &Option<Expression>,
2214 child_offset: usize,
2215 stride_expr: Option<&Expression>,
2217 inner_repeater_index: Option<Expression>,
2218 entries_per_item: usize,
2219 diag: &mut BuildDiagnostics,
2220) {
2221 if let Some(stride) = stride_expr {
2222 let repeater_index_boxed = repeater_index.as_ref().map(|x| Box::new(x.clone()));
2224 let expr = Expression::GridRepeaterCacheAccess {
2225 layout_cache_prop: layout_cache_prop.clone(),
2226 index,
2227 repeater_index: repeater_index_boxed.unwrap(),
2228 stride: Box::new(stride.clone()),
2229 child_offset,
2230 inner_repeater_index: inner_repeater_index.map(Box::new),
2231 entries_per_item,
2232 };
2233 insert_cache_prop_binding(expr, elem, prop, layout_cache_prop, diag);
2234 } else {
2235 set_prop_from_cache(
2237 elem,
2238 prop,
2239 layout_cache_prop,
2240 index,
2241 repeater_index,
2242 entries_per_item,
2243 diag,
2244 );
2245 }
2246}
2247
2248fn set_prop_from_cache(
2249 elem: &ElementRc,
2250 prop: &str,
2251 layout_cache_prop: &NamedReference,
2252 index: usize,
2253 repeater_index: &Option<Expression>,
2254 entries_per_item: usize,
2255 diag: &mut BuildDiagnostics,
2256) {
2257 let expr = Expression::LayoutCacheAccess {
2258 layout_cache_prop: layout_cache_prop.clone(),
2259 index,
2260 repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
2261 entries_per_item,
2262 };
2263 insert_cache_prop_binding(expr, elem, prop, layout_cache_prop, diag);
2264}
2265
2266fn insert_cache_prop_binding(
2267 expr: Expression,
2268 elem: &ElementRc,
2269 prop: &str,
2270 layout_cache_prop: &NamedReference,
2271 diag: &mut BuildDiagnostics,
2272) {
2273 let new_binding = BindingExpression::new_with_span(
2274 expr,
2275 layout_cache_prop.element().borrow().to_source_location(),
2276 );
2277 if let Some(old) = elem.borrow_mut().set_binding(prop.into(), new_binding) {
2278 diag.push_error(
2279 format!("The property '{prop}' cannot be set for elements placed in this layout, because the layout is already setting it"),
2280 &old,
2281 );
2282 }
2283}
2284
2285#[derive(Copy, Clone)]
2287struct RepeaterCacheParams<'a> {
2288 index: usize,
2290 rep_idx: &'a Option<Expression>,
2292 child_offset: usize,
2294 inner_rep_idx: &'a Option<Expression>,
2296}
2297
2298fn set_coord_prop_from_cache(
2300 elem: &ElementRc,
2301 constraints: &LayoutConstraints,
2302 layout_cache_prop_h: &NamedReference,
2303 layout_cache_prop_v: &NamedReference,
2304 repeater_params: &RepeaterCacheParams<'_>,
2305 stride_h: Option<&Expression>,
2306 stride_v: Option<&Expression>,
2307 diag: &mut BuildDiagnostics,
2308) {
2309 let has_repeater_indirection = stride_h.is_some();
2310 let cache_idx = repeater_params.index * 2;
2311 let pos_offset = repeater_params.child_offset;
2312 let size_offset = repeater_params.child_offset + 1;
2313 let inner_idx_clone = repeater_params.inner_rep_idx.clone();
2314
2315 let size_cache_idx = if has_repeater_indirection { cache_idx } else { cache_idx + 1 };
2317
2318 set_grid_prop_from_cache(
2319 elem,
2320 "x",
2321 layout_cache_prop_h,
2322 cache_idx,
2323 repeater_params.rep_idx,
2324 pos_offset,
2325 stride_h,
2326 inner_idx_clone.clone(),
2327 2,
2328 diag,
2329 );
2330 if !constraints.fixed_width {
2331 set_grid_prop_from_cache(
2332 elem,
2333 "width",
2334 layout_cache_prop_h,
2335 size_cache_idx,
2336 repeater_params.rep_idx,
2337 size_offset,
2338 stride_h,
2339 inner_idx_clone.clone(),
2340 2,
2341 diag,
2342 );
2343 }
2344 set_grid_prop_from_cache(
2345 elem,
2346 "y",
2347 layout_cache_prop_v,
2348 cache_idx,
2349 repeater_params.rep_idx,
2350 pos_offset,
2351 stride_v,
2352 inner_idx_clone.clone(),
2353 2,
2354 diag,
2355 );
2356 if !constraints.fixed_height {
2357 set_grid_prop_from_cache(
2358 elem,
2359 "height",
2360 layout_cache_prop_v,
2361 size_cache_idx,
2362 repeater_params.rep_idx,
2363 size_offset,
2364 stride_v,
2365 inner_idx_clone,
2366 2,
2367 diag,
2368 );
2369 }
2370}
2371
2372fn set_grid_rowcol_from_cache(
2375 elem: &ElementRc,
2376 organized_data_prop: &NamedReference,
2377 repeater_params: &RepeaterCacheParams<'_>,
2378 stride: Option<&Expression>,
2379 (row_expr, col_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
2380 diag: &mut BuildDiagnostics,
2381) {
2382 let has_repeater_indirection = stride.is_some();
2383 let org_cache_idx = repeater_params.index * 4;
2384
2385 let col_cache_idx = org_cache_idx;
2388 let col_offset = if has_repeater_indirection { repeater_params.child_offset * 4 } else { 0 };
2389
2390 let (row_cache_idx, row_offset) = if has_repeater_indirection {
2391 (org_cache_idx, repeater_params.child_offset * 4 + 2)
2392 } else {
2393 (org_cache_idx + 2, 0)
2394 };
2395
2396 if col_expr.is_none() {
2397 set_grid_prop_from_cache(
2398 elem,
2399 "col",
2400 organized_data_prop,
2401 col_cache_idx,
2402 repeater_params.rep_idx,
2403 col_offset,
2404 stride,
2405 repeater_params.inner_rep_idx.clone(),
2406 4,
2407 diag,
2408 );
2409 }
2410 if row_expr.is_none() {
2411 set_grid_prop_from_cache(
2412 elem,
2413 "row",
2414 organized_data_prop,
2415 row_cache_idx,
2416 repeater_params.rep_idx,
2417 row_offset,
2418 stride,
2419 repeater_params.inner_rep_idx.clone(),
2420 4,
2421 diag,
2422 );
2423 }
2424}
2425
2426fn check_number_literal_is_positive_integer(
2430 expression: &Expression,
2431 name: &str,
2432 span: &dyn crate::diagnostics::Spanned,
2433 diag: &mut BuildDiagnostics,
2434) -> bool {
2435 match expression.ignore_debug_hooks() {
2436 Expression::NumberLiteral(v, Unit::None) => {
2437 if *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
2438 diag.push_error(format!("'{name}' must be a positive integer"), span);
2439 }
2440 true
2441 }
2442 Expression::UnaryOp { op: '-', sub } => {
2443 if let Expression::NumberLiteral(_, Unit::None) = sub.ignore_debug_hooks() {
2444 diag.push_error(format!("'{name}' must be a positive integer"), span);
2445 }
2446 true
2447 }
2448 Expression::Cast { from, .. } => {
2449 check_number_literal_is_positive_integer(from, name, span, diag)
2450 }
2451 _ => false,
2452 }
2453}
2454
2455fn recognized_layout_types() -> &'static [&'static str] {
2456 &["Row", "GridLayout", "HorizontalLayout", "VerticalLayout", "FlexboxLayout", "Dialog"]
2457}
2458
2459fn check_no_layout_properties(
2461 item: &ElementRc,
2462 layout_type: &Option<SmolStr>,
2463 parent_layout_type: &Option<SmolStr>,
2464 diag: &mut BuildDiagnostics,
2465) {
2466 let elem = item.borrow();
2467 for (prop, expr) in elem.real_bindings() {
2468 if !matches!(parent_layout_type.as_deref(), Some("GridLayout") | Some("Row"))
2469 && matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan")
2470 {
2471 diag.push_error(format!("{prop} used outside of a GridLayout's cell"), &*expr.borrow());
2472 }
2473 if matches!(prop.as_ref(), "layout-order" | "cross-axis-self-alignment")
2474 && !matches!(
2475 parent_layout_type.as_deref(),
2476 Some("FlexboxLayout" | "HorizontalLayout" | "VerticalLayout")
2477 )
2478 {
2479 diag.push_error(
2480 format!(
2481 "{prop} used outside of a FlexboxLayout, HorizontalLayout, or VerticalLayout"
2482 ),
2483 &*expr.borrow(),
2484 );
2485 }
2486 if parent_layout_type.as_deref() != Some("Dialog")
2487 && matches!(prop.as_ref(), "dialog-button-role")
2488 {
2489 diag.push_error(
2490 format!("{prop} used outside of a Dialog's direct child"),
2491 &*expr.borrow(),
2492 );
2493 }
2494 if (layout_type.is_none()
2495 || !recognized_layout_types().contains(&layout_type.as_ref().unwrap().as_str()))
2496 && matches!(
2497 prop.as_ref(),
2498 "padding" | "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
2499 )
2500 && !check_inherits_layout(item)
2501 {
2502 diag.push_warning(
2503 format!("{prop} only has effect on layout elements"),
2504 &*expr.borrow(),
2505 );
2506 }
2507 }
2508
2509 fn check_inherits_layout(item: &ElementRc) -> bool {
2511 if let ElementType::Component(c) = &item.borrow().base_type {
2512 c.root_element.borrow().debug.iter().any(|d| d.layout.is_some())
2513 || check_inherits_layout(&c.root_element)
2514 } else {
2515 false
2516 }
2517 }
2518}
2519
2520pub fn check_window_layout(component: &Rc<Component>) {
2526 if component.root_constraints.borrow().fixed_height {
2527 adjust_window_layout(component, "height");
2528 }
2529 if component.root_constraints.borrow().fixed_width {
2530 adjust_window_layout(component, "width");
2531 }
2532}
2533
2534pub fn check_popup_layout(component: &Rc<Component>) {
2535 component.popup_windows.borrow().iter().for_each(|p| {
2536 if p.component.root_constraints.borrow().fixed_height {
2537 adjust_window_layout(&p.component, "height");
2538 }
2539
2540 if p.component.root_constraints.borrow().fixed_width {
2541 adjust_window_layout(&p.component, "width");
2542 }
2543 });
2544}
2545
2546fn adjust_window_layout(component: &Rc<Component>, prop: &'static str) {
2547 let new_prop = crate::layout::create_new_prop(
2548 &component.root_element,
2549 format_smolstr!("fixed-{prop}"),
2550 Type::LogicalLength,
2551 );
2552 {
2553 let mut root = component.root_element.borrow_mut();
2554 if let Some(b) = root.take_binding(prop) {
2555 root.set_binding(new_prop.name().clone(), b);
2556 };
2557 let mut analysis = root.property_analysis.borrow_mut();
2558 if let Some(a) = analysis.remove(prop) {
2559 analysis.insert(new_prop.name().clone(), a);
2560 };
2561 drop(analysis);
2562 root.set_binding(prop.into(), Expression::PropertyReference(new_prop.clone()).into());
2563 }
2564
2565 let old_prop = NamedReference::new(&component.root_element, SmolStr::new_static(prop));
2566 crate::object_tree::visit_all_named_references(component, &mut |nr| {
2567 if nr == &old_prop {
2568 *nr = new_prop.clone()
2569 }
2570 });
2571}