1use std::rc::Rc;
13
14use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, SourceLocation, Spanned};
15use crate::expression_tree::{
16 BindingExpression, BuiltinFunction, Expression, MinMaxOp, NamedReference, Unit,
17};
18use crate::langtype::{BuiltinElement, DefaultSizeBinding, PropertyLookupMode, Type};
19use crate::layout::{BuiltinFilter, LayoutConstraints, Orientation, implicit_layout_info_call};
20use crate::object_tree::{Component, ElementRc};
21use crate::symbol_counters::SymbolCounters;
22use smol_str::{SmolStr, format_smolstr};
23use std::collections::BTreeMap;
24
25pub fn default_geometry(
26 root_component: &Rc<Component>,
27 diag: &mut BuildDiagnostics,
28 symbol_counters: &SymbolCounters,
29) {
30 crate::object_tree::recurse_elem_including_sub_components(
31 root_component,
32 &None,
33 &mut |elem: &ElementRc, parent: &Option<ElementRc>| {
34 if elem.borrow().repeated.is_some() {
35 return None;
36 }
37 elem.borrow().geometry_props.as_ref()?;
38
39 let (mut w100, mut h100) = (false, false);
41
42 w100 |= fix_percent_size(elem, parent, "width", diag, symbol_counters);
43 h100 |= fix_percent_size(elem, parent, "height", diag, symbol_counters);
44
45 gen_layout_info_prop(elem, diag, symbol_counters);
46
47 let builtin_type = match elem.borrow().builtin_type() {
48 Some(b) => b,
49 None => return Some(elem.clone()),
50 };
51
52 let is_image = builtin_type.name == "Image";
53 if is_image {
54 adjust_image_clip_rect(elem, &builtin_type);
55 }
56
57 if let Some(parent) = parent {
58 match builtin_type.default_size_binding {
59 DefaultSizeBinding::None => {
60 if elem.borrow().default_fill_parent.0 {
61 let e_width =
62 elem.borrow().geometry_props.as_ref().unwrap().width.clone();
63 let p_width =
64 parent.borrow().geometry_props.as_ref().unwrap().width.clone();
65 w100 |= make_default_100(&e_width, &p_width);
66 } else {
67 make_default_implicit(elem, "width");
68 }
69 if elem.borrow().default_fill_parent.1 {
70 let e_height =
71 elem.borrow().geometry_props.as_ref().unwrap().height.clone();
72 let p_height =
73 parent.borrow().geometry_props.as_ref().unwrap().height.clone();
74 h100 |= make_default_100(&e_height, &p_height);
75 } else {
76 make_default_implicit(elem, "height");
77 }
78 }
79 DefaultSizeBinding::ExpandsToParentGeometry => {
80 if !elem.borrow().child_of_layout {
81 let (e_width, e_height) = elem
82 .borrow()
83 .geometry_props
84 .as_ref()
85 .map(|g| (g.width.clone(), g.height.clone()))
86 .unwrap();
87 let (p_width, p_height) = parent
88 .borrow()
89 .geometry_props
90 .as_ref()
91 .map(|g| (g.width.clone(), g.height.clone()))
92 .unwrap();
93 w100 |= make_default_100(&e_width, &p_width);
94 h100 |= make_default_100(&e_height, &p_height);
95 }
96 }
97 #[cfg(feature = "slint-sc")]
103 DefaultSizeBinding::ImplicitSize if diag.slint_sc => {
104 if is_image {
105 bind_size_to_source_image(elem);
106 }
107 }
108 DefaultSizeBinding::ImplicitSize => {
109 let has_length_property_binding = |elem: &ElementRc, property: &str| {
110 debug_assert_eq!(
111 elem.borrow()
112 .lookup_property(property, PropertyLookupMode::ComponentLocal)
113 .property_type,
114 Type::LogicalLength
115 );
116
117 elem.borrow().is_binding_set(property, true)
118 };
119
120 let width_specified = has_length_property_binding(elem, "width");
121 let height_specified = has_length_property_binding(elem, "height");
122
123 if !elem.borrow().child_of_layout {
124 if is_image && width_specified && !height_specified {
126 make_default_aspect_ratio_preserving_binding(
127 elem, "height", "width",
128 )
129 } else if is_image && height_specified && !width_specified {
130 make_default_aspect_ratio_preserving_binding(
131 elem, "width", "height",
132 )
133 } else {
134 make_default_implicit(elem, "width");
135 make_default_implicit(elem, "height");
136 }
137 } else if is_image {
138 if !width_specified || !height_specified {
141 let image_fit_lookup = elem.borrow().lookup_property(
142 "image-fit",
143 PropertyLookupMode::ComponentLocal,
144 );
145
146 elem.borrow_mut().set_binding_if_not_set(
147 image_fit_lookup.resolved_name.into(),
148 || {
149 Expression::EnumerationValue(
150 image_fit_lookup
151 .property_type
152 .as_enum()
153 .clone()
154 .try_value_from_string("contain")
155 .unwrap(),
156 )
157 },
158 );
159 }
160 }
161 }
162 }
163
164 if !elem.borrow().child_of_layout
165 && !elem.borrow().is_legacy_syntax
166 && builtin_type.name != "Window"
167 {
168 if !w100 {
169 maybe_center_in_parent(elem, parent, "x", "width");
170 }
171 if !h100 {
172 maybe_center_in_parent(elem, parent, "y", "height");
173 }
174 }
175 }
176
177 Some(elem.clone())
178 },
179 )
180}
181
182fn gen_layout_info_prop(
184 elem: &ElementRc,
185 diag: &mut BuildDiagnostics,
186 symbol_counters: &SymbolCounters,
187) {
188 if elem.borrow().layout_info_prop.is_some() || elem.borrow().is_flickable_content {
189 return;
190 }
191
192 let child_infos = elem
193 .borrow()
194 .children
195 .iter()
196 .filter(|c| {
197 !c.borrow().is_binding_set("x", false) && !c.borrow().is_binding_set("y", false)
198 })
199 .filter_map(|c| {
200 gen_layout_info_prop(c, diag, symbol_counters);
201 let cb = c.borrow();
202 cb.effective_layout_info_prop(Orientation::Horizontal)
203 .cloned()
204 .zip(cb.effective_layout_info_prop(Orientation::Vertical).cloned())
205 .map(|(h, v)| {
206 (Some(Expression::PropertyReference(h)), Some(Expression::PropertyReference(v)))
207 })
208 .or_else(|| {
209 if c.borrow().is_legacy_syntax {
210 return None;
211 }
212 if c.borrow().repeated.is_some() {
213 return None;
215 }
216 let explicit_constraints =
217 LayoutConstraints::new(c, Some((&mut *diag, DiagnosticLevel::Error)));
218
219 let compute = |orientation| {
220 if explicit_constraints.has_explicit_restrictions(orientation) {
221 Some(explicit_layout_info(c, orientation))
222 } else {
223 implicit_layout_info_call(
224 c,
225 orientation,
226 BuiltinFilter::SkipNonImplicit,
227 None,
228 )
229 }
230 };
231 Some((compute(Orientation::Horizontal), compute(Orientation::Vertical)))
232 .filter(|(a, b)| a.is_some() || b.is_some())
233 })
234 })
235 .collect::<Vec<_>>();
236
237 if child_infos.is_empty() {
238 return;
239 }
240
241 let li_v = crate::layout::create_new_prop(
242 elem,
243 SmolStr::new_static("layoutinfo-v"),
244 crate::typeregister::layout_info_type().into(),
245 );
246 let li_h = crate::layout::create_new_prop(
247 elem,
248 SmolStr::new_static("layoutinfo-h"),
249 crate::typeregister::layout_info_type().into(),
250 );
251 elem.borrow_mut().layout_info_prop = Some((li_h.clone(), li_v.clone()));
252 let mut expr_h =
253 implicit_layout_info_call(elem, Orientation::Horizontal, BuiltinFilter::All, None).unwrap();
254 let mut expr_v =
255 implicit_layout_info_call(elem, Orientation::Vertical, BuiltinFilter::All, None).unwrap();
256
257 let is_root = elem
260 .borrow()
261 .enclosing_component
262 .upgrade()
263 .is_some_and(|c| Rc::ptr_eq(elem, &c.root_element));
264 let explicit_constraints =
265 LayoutConstraints::new(elem, (!is_root).then_some((&mut *diag, DiagnosticLevel::Warning)));
266 if !explicit_constraints.fixed_width {
267 merge_explicit_constraints(
268 &mut expr_h,
269 &explicit_constraints,
270 Orientation::Horizontal,
271 symbol_counters,
272 );
273 }
274 if !explicit_constraints.fixed_height {
275 merge_explicit_constraints(
276 &mut expr_v,
277 &explicit_constraints,
278 Orientation::Vertical,
279 symbol_counters,
280 );
281 }
282
283 for child_info in child_infos {
284 if let Some(h) = child_info.0 {
285 expr_h = Expression::BinaryExpression {
286 lhs: Box::new(std::mem::take(&mut expr_h)),
287 rhs: Box::new(h),
288 op: '+',
289 source_location: None,
290 };
291 }
292 if let Some(v) = child_info.1 {
293 expr_v = Expression::BinaryExpression {
294 lhs: Box::new(std::mem::take(&mut expr_v)),
295 rhs: Box::new(v),
296 op: '+',
297 source_location: None,
298 };
299 }
300 }
301
302 let expr_v = BindingExpression::new_with_span(expr_v, elem.borrow().to_source_location());
303 li_v.element().borrow_mut().set_binding(li_v.name().clone(), expr_v);
304 let expr_h = BindingExpression::new_with_span(expr_h, elem.borrow().to_source_location());
305 li_h.element().borrow_mut().set_binding(li_h.name().clone(), expr_h);
306}
307
308fn merge_explicit_constraints(
309 expr: &mut Expression,
310 constraints: &LayoutConstraints,
311 orientation: Orientation,
312 symbol_counters: &SymbolCounters,
313) {
314 if constraints.has_explicit_restrictions(orientation) {
315 let unique_name = symbol_counters.generate_name("layout_info_");
316 let ty = expr.ty();
317 let store = Expression::StoreLocalVariable {
318 name: unique_name.clone(),
319 value: Box::new(std::mem::take(expr)),
320 };
321 let Type::Struct(s) = &ty else { unreachable!() };
322 let mut values = s
323 .fields
324 .keys()
325 .map(|p| {
326 (
327 p.clone(),
328 Expression::StructFieldAccess {
329 base: Expression::ReadLocalVariable {
330 name: unique_name.clone(),
331 ty: ty.clone(),
332 }
333 .into(),
334 name: p.clone(),
335 },
336 )
337 })
338 .collect::<BTreeMap<_, _>>();
339
340 for (nr, s) in constraints.for_each_restrictions(orientation) {
341 let e = nr
342 .element()
343 .borrow()
344 .binding(nr.name())
345 .expect("constraint must have binding")
346 .expression
347 .clone();
348 debug_assert!(!matches!(e, Expression::Invalid));
349 values.insert(s.into(), e);
350 }
351 *expr = Expression::CodeBlock([store, Expression::Struct { ty: s.clone(), values }].into());
352 }
353}
354
355fn explicit_layout_info(e: &ElementRc, orientation: Orientation) -> Expression {
356 let mut values = BTreeMap::new();
357 let (size, orient) = match orientation {
358 Orientation::Horizontal => ("width", "horizontal"),
359 Orientation::Vertical => ("height", "vertical"),
360 };
361 for (k, v) in [
362 ("min", format_smolstr!("min-{size}")),
363 ("max", format_smolstr!("max-{size}")),
364 ("preferred", format_smolstr!("preferred-{size}")),
365 ("stretch", format_smolstr!("{orient}-stretch")),
366 ] {
367 values.insert(k.into(), Expression::PropertyReference(NamedReference::new(e, v)));
368 }
369 values.insert("min_percent".into(), Expression::NumberLiteral(0., Unit::None));
370 values.insert("max_percent".into(), Expression::NumberLiteral(100., Unit::None));
371 Expression::Struct { ty: crate::typeregister::layout_info_type(), values }
372}
373
374fn fix_percent_size(
378 elem: &ElementRc,
379 parent: &Option<ElementRc>,
380 property: &'static str,
381 diag: &mut BuildDiagnostics,
382 symbol_counters: &SymbolCounters,
383) -> bool {
384 fn inner(
385 expression: &mut Expression,
386 span: &Option<SourceLocation>,
387 parent: &Option<ElementRc>,
388 property: &'static str,
389 diag: &mut BuildDiagnostics,
390 symbol_counters: &SymbolCounters,
391 ) -> bool {
392 if let Expression::DebugHook { expression, .. } = expression {
393 inner(expression, span, parent, property, diag, symbol_counters);
397
398 false
399 } else if let Expression::Condition { true_expr, false_expr, .. } = expression
400 && true_expr.ty() != false_expr.ty()
401 {
402 inner(true_expr, span, parent, property, diag, symbol_counters)
407 && inner(false_expr, span, parent, property, diag, symbol_counters)
408 } else {
409 if expression.ty() != Type::Percent {
410 let Some(parent) = parent.as_ref() else { return false };
411 return matches!(
416 expression,
417 Expression::PropertyReference(nr)
418 if *nr.name() == property && Rc::ptr_eq(&nr.element(), parent),
419 );
420 }
421 if let Some(mut parent) = parent.clone() {
422 let flickable = parent.borrow().is_flickable_content;
423 if parent.borrow().is_flickable_content {
424 parent = crate::object_tree::find_parent_element(&parent).unwrap_or(parent)
427 }
428 debug_assert_eq!(
429 parent
430 .borrow()
431 .lookup_property(property, PropertyLookupMode::ComponentLocal)
432 .property_type,
433 Type::LogicalLength
434 );
435 let fill = matches!(
438 *expression,
439 Expression::NumberLiteral(x, _) if (x - 100.).abs() < 0.001,
440 );
441
442 *expression = Expression::BinaryExpression {
443 lhs: Box::new(std::mem::take(expression).maybe_convert_to(
444 Type::Float32,
445 span,
446 diag,
447 symbol_counters,
448 )),
449 rhs: Box::new(Expression::PropertyReference(NamedReference::new(
450 &parent,
451 SmolStr::new_static(property),
452 ))),
453 op: '*',
454 source_location: None,
455 };
456
457 fill && !flickable
460 } else {
461 diag.push_error(
462 "Cannot find parent property to apply relative length".into(),
463 span,
464 );
465 false
466 }
467 }
468 }
469
470 let elem = elem.borrow();
471 let Some(mut binding) = elem.binding_mut(property) else {
472 return false;
473 };
474
475 let binding = &mut *binding;
476
477 inner(&mut binding.expression, &binding.span, parent, property, diag, symbol_counters)
478}
479
480fn make_default_100(prop: &NamedReference, parent_prop: &NamedReference) -> bool {
483 prop.element().borrow_mut().set_binding_if_not_set(prop.name().clone(), || {
484 Expression::PropertyReference(parent_prop.clone())
485 })
486}
487
488#[cfg(feature = "slint-sc")]
491fn bind_size_to_source_image(elem: &ElementRc) {
492 let source = NamedReference::new(elem, SmolStr::new_static("source"));
493 for prop in ["width", "height"] {
494 let size_field = Expression::Cast {
495 from: Box::new(Expression::StructFieldAccess {
496 base: Box::new(Expression::FunctionCall {
497 function: BuiltinFunction::ImageSize.into(),
498 arguments: vec![Expression::PropertyReference(source.clone())],
499 source_location: None,
500 }),
501 name: prop.into(),
502 }),
503 to: Type::LogicalLength,
504 };
505 elem.borrow_mut().set_binding_if_not_set(prop.into(), || size_field);
506 }
507}
508
509fn make_default_implicit(elem: &ElementRc, property: &str) {
510 let e = crate::builtin_macros::min_max_expression(
511 Expression::PropertyReference(NamedReference::new(
512 elem,
513 format_smolstr!("preferred-{}", property),
514 )),
515 Expression::PropertyReference(NamedReference::new(
516 elem,
517 format_smolstr!("min-{}", property),
518 )),
519 MinMaxOp::Max,
520 );
521 elem.borrow_mut().set_binding_if_not_set(property.into(), || e);
522}
523
524fn make_default_aspect_ratio_preserving_binding(
532 elem: &ElementRc,
533 missing_size_property: &'static str,
534 given_size_property: &'static str,
535) {
536 if elem.borrow().is_binding_set(missing_size_property, false) {
537 return;
538 }
539
540 debug_assert_eq!(
541 elem.borrow().lookup_property("source", PropertyLookupMode::ComponentLocal).property_type,
542 Type::Image
543 );
544
545 let missing_size_property = SmolStr::new_static(missing_size_property);
546 let given_size_property = SmolStr::new_static(given_size_property);
547
548 let ratio = if elem.borrow().is_binding_set("source-clip-height", false) {
549 Expression::BinaryExpression {
550 lhs: Box::new(Expression::PropertyReference(NamedReference::new(
551 elem,
552 format_smolstr!("source-clip-{missing_size_property}"),
553 ))),
554 rhs: Box::new(Expression::PropertyReference(NamedReference::new(
555 elem,
556 format_smolstr!("source-clip-{given_size_property}"),
557 ))),
558 op: '/',
559 source_location: None,
560 }
561 } else {
562 let implicit_size_var = Box::new(Expression::ReadLocalVariable {
563 name: "image_implicit_size".into(),
564 ty: BuiltinFunction::ImageSize.ty().return_type.clone(),
565 });
566
567 Expression::CodeBlock(vec![
568 Expression::StoreLocalVariable {
569 name: "image_implicit_size".into(),
570 value: Box::new(Expression::FunctionCall {
571 function: BuiltinFunction::ImageSize.into(),
572 arguments: vec![Expression::PropertyReference(NamedReference::new(
573 elem,
574 SmolStr::new_static("source"),
575 ))],
576 source_location: None,
577 }),
578 },
579 Expression::BinaryExpression {
580 lhs: Box::new(Expression::StructFieldAccess {
581 base: implicit_size_var.clone(),
582 name: missing_size_property.clone(),
583 }),
584 rhs: Box::new(Expression::StructFieldAccess {
585 base: implicit_size_var,
586 name: given_size_property.clone(),
587 }),
588 op: '/',
589 source_location: None,
590 },
591 ])
592 };
593 let binding = Expression::BinaryExpression {
594 lhs: Box::new(ratio),
595 rhs: Expression::PropertyReference(NamedReference::new(elem, given_size_property)).into(),
596 op: '*',
597 source_location: None,
598 };
599
600 let binding_expr = binding;
601 elem.borrow_mut().set_binding_if_not_set(missing_size_property, || binding_expr);
602}
603
604fn maybe_center_in_parent(
605 elem: &ElementRc,
606 parent: &ElementRc,
607 pos_prop: &'static str,
608 size_prop: &'static str,
609) {
610 if elem.borrow().is_binding_set(pos_prop, false) {
611 return;
612 }
613
614 let size_prop = SmolStr::new_static(size_prop);
615 let diff = Expression::BinaryExpression {
616 lhs: Expression::PropertyReference(NamedReference::new(parent, size_prop.clone())).into(),
617 op: '-',
618 rhs: Expression::PropertyReference(NamedReference::new(elem, size_prop)).into(),
619 source_location: None,
620 };
621
622 let pos_prop = SmolStr::new_static(pos_prop);
623 elem.borrow_mut().set_binding_if_not_set(pos_prop, || Expression::BinaryExpression {
624 lhs: diff.into(),
625 op: '/',
626 rhs: Expression::NumberLiteral(2., Unit::None).into(),
627 source_location: None,
628 });
629}
630
631fn adjust_image_clip_rect(elem: &ElementRc, builtin: &Rc<BuiltinElement>) {
632 debug_assert_eq!(builtin.native_class.class_name, "ClippedImage");
633
634 if builtin.native_class.properties.keys().any(|p| {
635 elem.borrow().binding_cell_including_synthetic(p).is_some()
640 || elem.borrow().property_analysis.borrow().get(p).is_some_and(|a| a.is_used())
641 }) {
642 let source = NamedReference::new(elem, SmolStr::new_static("source"));
643 let x = NamedReference::new(elem, SmolStr::new_static("source-clip-x"));
644 let y = NamedReference::new(elem, SmolStr::new_static("source-clip-y"));
645 let make_expr = |dim: &str, prop: NamedReference| Expression::BinaryExpression {
646 source_location: None,
647 lhs: Box::new(Expression::StructFieldAccess {
648 base: Box::new(Expression::FunctionCall {
649 function: BuiltinFunction::ImageSize.into(),
650 arguments: vec![Expression::PropertyReference(source.clone())],
651 source_location: None,
652 }),
653 name: dim.into(),
654 }),
655 rhs: Expression::PropertyReference(prop).into(),
656 op: '-',
657 };
658
659 elem.borrow_mut()
660 .set_binding_if_not_set("source-clip-width".into(), || make_expr("width", x));
661 elem.borrow_mut()
662 .set_binding_if_not_set("source-clip-height".into(), || make_expr("height", y));
663 }
664}
665
666#[test]
667fn test_no_property_for_100pc() {
668 let mut compiler_config =
670 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
671 compiler_config.style = Some("fluent".into());
672 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
673 let doc_node = crate::parser::parse(
674 r#"
675 export component Foo inherits Window {
676 r1 := Rectangle {
677 r2 := Rectangle {
678 width: 100%;
679 background: blue;
680 }
681 r3 := Rectangle {
682 height: parent.height;
683 width: 50%;
684 background: red;
685 }
686 }
687
688 out property <length> r2x: r2.x;
689 out property <length> r2y: r2.y;
690 out property <length> r3x: r3.x;
691 out property <length> r3y: r3.y;
692 }
693"#
694 .into(),
695 Some(std::path::Path::new("HELLO")),
696 &mut test_diags,
697 );
698 let (doc, diag, _) =
699 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
700 assert!(!diag.has_errors(), "{:?}", diag.to_string_vec());
701
702 let root_elem = doc.inner_components.last().unwrap().root_element.borrow();
703
704 assert!(matches!(
706 root_elem.binding("r2x").unwrap().value_expression(),
707 Expression::NumberLiteral(v, _) if *v == 0.
708 ));
709 assert!(matches!(
710 root_elem.binding("r2y").unwrap().value_expression(),
711 Expression::NumberLiteral(v, _) if *v == 0.
712 ));
713 assert!(matches!(
714 root_elem.binding("r3y").unwrap().value_expression(),
715 Expression::NumberLiteral(v, _) if *v == 0.
716 ));
717 assert!(!matches!(
719 root_elem.binding("r3x").unwrap().value_expression(),
720 Expression::BinaryExpression { .. }
721 ));
722}