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