1use std::cell::RefCell;
13use std::rc::Rc;
14
15use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
16use crate::expression_tree::{
17 BindingExpression, BuiltinFunction, Expression, MinMaxOp, NamedReference, Unit,
18};
19use crate::langtype::{BuiltinElement, DefaultSizeBinding, Type};
20use crate::layout::{BuiltinFilter, LayoutConstraints, Orientation, implicit_layout_info_call};
21use crate::object_tree::{Component, ElementRc};
22use smol_str::{SmolStr, format_smolstr};
23use std::collections::HashMap;
24
25pub fn default_geometry(root_component: &Rc<Component>, diag: &mut BuildDiagnostics) {
26 crate::object_tree::recurse_elem_including_sub_components(
27 root_component,
28 &None,
29 &mut |elem: &ElementRc, parent: &Option<ElementRc>| {
30 if elem.borrow().repeated.is_some() {
31 return None;
32 }
33 elem.borrow().geometry_props.as_ref()?;
34
35 let (mut w100, mut h100) = (false, false);
37
38 w100 |= fix_percent_size(elem, parent, "width", diag);
39 h100 |= fix_percent_size(elem, parent, "height", diag);
40
41 gen_layout_info_prop(elem, diag);
42
43 let builtin_type = match elem.borrow().builtin_type() {
44 Some(b) => b,
45 None => return Some(elem.clone()),
46 };
47
48 let is_image = builtin_type.name == "Image";
49 if is_image {
50 adjust_image_clip_rect(elem, &builtin_type);
51 }
52
53 if let Some(parent) = parent {
54 match builtin_type.default_size_binding {
55 DefaultSizeBinding::None => {
56 if elem.borrow().default_fill_parent.0 {
57 let e_width =
58 elem.borrow().geometry_props.as_ref().unwrap().width.clone();
59 let p_width =
60 parent.borrow().geometry_props.as_ref().unwrap().width.clone();
61 w100 |= make_default_100(&e_width, &p_width);
62 } else {
63 make_default_implicit(elem, "width");
64 }
65 if elem.borrow().default_fill_parent.1 {
66 let e_height =
67 elem.borrow().geometry_props.as_ref().unwrap().height.clone();
68 let p_height =
69 parent.borrow().geometry_props.as_ref().unwrap().height.clone();
70 h100 |= make_default_100(&e_height, &p_height);
71 } else {
72 make_default_implicit(elem, "height");
73 }
74 }
75 DefaultSizeBinding::ExpandsToParentGeometry => {
76 if !elem.borrow().child_of_layout {
77 let (e_width, e_height) = elem
78 .borrow()
79 .geometry_props
80 .as_ref()
81 .map(|g| (g.width.clone(), g.height.clone()))
82 .unwrap();
83 let (p_width, p_height) = parent
84 .borrow()
85 .geometry_props
86 .as_ref()
87 .map(|g| (g.width.clone(), g.height.clone()))
88 .unwrap();
89 w100 |= make_default_100(&e_width, &p_width);
90 h100 |= make_default_100(&e_height, &p_height);
91 }
92 }
93 DefaultSizeBinding::ImplicitSize => {
94 let has_length_property_binding = |elem: &ElementRc, property: &str| {
95 debug_assert_eq!(
96 elem.borrow().lookup_property(property).property_type,
97 Type::LogicalLength
98 );
99
100 elem.borrow().is_binding_set(property, true)
101 };
102
103 let width_specified = has_length_property_binding(elem, "width");
104 let height_specified = has_length_property_binding(elem, "height");
105
106 if !elem.borrow().child_of_layout {
107 if is_image && width_specified && !height_specified {
109 make_default_aspect_ratio_preserving_binding(
110 elem, "height", "width",
111 )
112 } else if is_image && height_specified && !width_specified {
113 make_default_aspect_ratio_preserving_binding(
114 elem, "width", "height",
115 )
116 } else {
117 make_default_implicit(elem, "width");
118 make_default_implicit(elem, "height");
119 }
120 } else if is_image {
121 if !width_specified || !height_specified {
124 let image_fit_lookup = elem.borrow().lookup_property("image-fit");
125
126 elem.borrow_mut().set_binding_if_not_set(
127 image_fit_lookup.resolved_name.into(),
128 || {
129 Expression::EnumerationValue(
130 image_fit_lookup
131 .property_type
132 .as_enum()
133 .clone()
134 .try_value_from_string("contain")
135 .unwrap(),
136 )
137 },
138 );
139 }
140 }
141 }
142 }
143
144 if !elem.borrow().child_of_layout
145 && !elem.borrow().is_legacy_syntax
146 && builtin_type.name != "Window"
147 {
148 if !w100 {
149 maybe_center_in_parent(elem, parent, "x", "width");
150 }
151 if !h100 {
152 maybe_center_in_parent(elem, parent, "y", "height");
153 }
154 }
155 }
156
157 Some(elem.clone())
158 },
159 )
160}
161
162fn gen_layout_info_prop(elem: &ElementRc, diag: &mut BuildDiagnostics) {
164 if elem.borrow().layout_info_prop.is_some() || elem.borrow().is_flickable_viewport {
165 return;
166 }
167
168 let child_infos = elem
169 .borrow()
170 .children
171 .iter()
172 .filter(|c| {
173 !c.borrow().bindings.contains_key("x") && !c.borrow().bindings.contains_key("y")
174 })
175 .filter_map(|c| {
176 gen_layout_info_prop(c, diag);
177 c.borrow()
178 .layout_info_prop
179 .clone()
180 .map(|(h, v)| {
181 (Some(Expression::PropertyReference(h)), Some(Expression::PropertyReference(v)))
182 })
183 .or_else(|| {
184 if c.borrow().is_legacy_syntax {
185 return None;
186 }
187 if c.borrow().repeated.is_some() {
188 return None;
190 }
191 let explicit_constraints =
192 LayoutConstraints::new(c, Some((&mut *diag, DiagnosticLevel::Error)));
193
194 let compute = |orientation| {
195 if explicit_constraints.has_explicit_restrictions(orientation) {
196 Some(explicit_layout_info(c, orientation))
197 } else {
198 implicit_layout_info_call(
199 c,
200 orientation,
201 BuiltinFilter::SkipNonImplicit,
202 None,
203 )
204 }
205 };
206 Some((compute(Orientation::Horizontal), compute(Orientation::Vertical)))
207 .filter(|(a, b)| a.is_some() || b.is_some())
208 })
209 })
210 .collect::<Vec<_>>();
211
212 if child_infos.is_empty() {
213 return;
214 }
215
216 let li_v = crate::layout::create_new_prop(
217 elem,
218 SmolStr::new_static("layoutinfo-v"),
219 crate::typeregister::layout_info_type().into(),
220 );
221 let li_h = crate::layout::create_new_prop(
222 elem,
223 SmolStr::new_static("layoutinfo-h"),
224 crate::typeregister::layout_info_type().into(),
225 );
226 elem.borrow_mut().layout_info_prop = Some((li_h.clone(), li_v.clone()));
227 let mut expr_h =
228 implicit_layout_info_call(elem, Orientation::Horizontal, BuiltinFilter::All, None).unwrap();
229 let mut expr_v =
230 implicit_layout_info_call(elem, Orientation::Vertical, BuiltinFilter::All, None).unwrap();
231
232 let is_root = elem
235 .borrow()
236 .enclosing_component
237 .upgrade()
238 .is_some_and(|c| Rc::ptr_eq(elem, &c.root_element));
239 let explicit_constraints =
240 LayoutConstraints::new(elem, (!is_root).then_some((&mut *diag, DiagnosticLevel::Warning)));
241 if !explicit_constraints.fixed_width {
242 merge_explicit_constraints(&mut expr_h, &explicit_constraints, Orientation::Horizontal);
243 }
244 if !explicit_constraints.fixed_height {
245 merge_explicit_constraints(&mut expr_v, &explicit_constraints, Orientation::Vertical);
246 }
247
248 for child_info in child_infos {
249 if let Some(h) = child_info.0 {
250 expr_h = Expression::BinaryExpression {
251 lhs: Box::new(std::mem::take(&mut expr_h)),
252 rhs: Box::new(h),
253 op: '+',
254 };
255 }
256 if let Some(v) = child_info.1 {
257 expr_v = Expression::BinaryExpression {
258 lhs: Box::new(std::mem::take(&mut expr_v)),
259 rhs: Box::new(v),
260 op: '+',
261 };
262 }
263 }
264
265 let expr_v = BindingExpression::new_with_span(expr_v, elem.borrow().to_source_location());
266 li_v.element().borrow_mut().bindings.insert(li_v.name().clone(), expr_v.into());
267 let expr_h = BindingExpression::new_with_span(expr_h, elem.borrow().to_source_location());
268 li_h.element().borrow_mut().bindings.insert(li_h.name().clone(), expr_h.into());
269}
270
271fn merge_explicit_constraints(
272 expr: &mut Expression,
273 constraints: &LayoutConstraints,
274 orientation: Orientation,
275) {
276 if constraints.has_explicit_restrictions(orientation) {
277 static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
278 let unique_name = format_smolstr!(
279 "layout_info_{}",
280 COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
281 );
282 let ty = expr.ty();
283 let store = Expression::StoreLocalVariable {
284 name: unique_name.clone(),
285 value: Box::new(std::mem::take(expr)),
286 };
287 let Type::Struct(s) = &ty else { unreachable!() };
288 let mut values = s
289 .fields
290 .keys()
291 .map(|p| {
292 (
293 p.clone(),
294 Expression::StructFieldAccess {
295 base: Expression::ReadLocalVariable {
296 name: unique_name.clone(),
297 ty: ty.clone(),
298 }
299 .into(),
300 name: p.clone(),
301 },
302 )
303 })
304 .collect::<HashMap<_, _>>();
305
306 for (nr, s) in constraints.for_each_restrictions(orientation) {
307 let e = nr
308 .element()
309 .borrow()
310 .bindings
311 .get(nr.name())
312 .expect("constraint must have binding")
313 .borrow()
314 .expression
315 .clone();
316 debug_assert!(!matches!(e, Expression::Invalid));
317 values.insert(s.into(), e);
318 }
319 *expr = Expression::CodeBlock([store, Expression::Struct { ty: s.clone(), values }].into());
320 }
321}
322
323fn explicit_layout_info(e: &ElementRc, orientation: Orientation) -> Expression {
324 let mut values = HashMap::new();
325 let (size, orient) = match orientation {
326 Orientation::Horizontal => ("width", "horizontal"),
327 Orientation::Vertical => ("height", "vertical"),
328 };
329 for (k, v) in [
330 ("min", format_smolstr!("min-{size}")),
331 ("max", format_smolstr!("max-{size}")),
332 ("preferred", format_smolstr!("preferred-{size}")),
333 ("stretch", format_smolstr!("{orient}-stretch")),
334 ] {
335 values.insert(k.into(), Expression::PropertyReference(NamedReference::new(e, v)));
336 }
337 values.insert("min_percent".into(), Expression::NumberLiteral(0., Unit::None));
338 values.insert("max_percent".into(), Expression::NumberLiteral(100., Unit::None));
339 Expression::Struct { ty: crate::typeregister::layout_info_type(), values }
340}
341
342fn fix_percent_size(
346 elem: &ElementRc,
347 parent: &Option<ElementRc>,
348 property: &'static str,
349 diag: &mut BuildDiagnostics,
350) -> bool {
351 let elem = elem.borrow();
352 let binding = match elem.bindings.get(property) {
353 Some(b) => b,
354 None => return false,
355 };
356
357 if binding.borrow().ty() != Type::Percent {
358 let Some(parent) = parent.as_ref() else { return false };
359 return matches!(&binding.borrow().expression, Expression::PropertyReference(nr) if *nr.name() == property && Rc::ptr_eq(&nr.element(), parent));
361 }
362 let mut b = binding.borrow_mut();
363 if let Some(mut parent) = parent.clone() {
364 if parent.borrow().is_flickable_viewport {
365 parent = crate::object_tree::find_parent_element(&parent).unwrap_or(parent)
367 }
368 debug_assert_eq!(
369 parent.borrow().lookup_property(property).property_type,
370 Type::LogicalLength
371 );
372 let fill =
373 matches!(b.expression, Expression::NumberLiteral(x, _) if (x - 100.).abs() < 0.001);
374 b.expression = Expression::BinaryExpression {
375 lhs: Box::new(std::mem::take(&mut b.expression).maybe_convert_to(
376 Type::Float32,
377 &b.span,
378 diag,
379 )),
380 rhs: Box::new(Expression::PropertyReference(NamedReference::new(
381 &parent,
382 SmolStr::new_static(property),
383 ))),
384 op: '*',
385 };
386 fill
387 } else {
388 diag.push_error("Cannot find parent property to apply relative length".into(), &b.span);
389 false
390 }
391}
392
393fn make_default_100(prop: &NamedReference, parent_prop: &NamedReference) -> bool {
396 prop.element().borrow_mut().set_binding_if_not_set(prop.name().clone(), || {
397 Expression::PropertyReference(parent_prop.clone())
398 })
399}
400
401fn make_default_implicit(elem: &ElementRc, property: &str) {
402 let e = crate::builtin_macros::min_max_expression(
403 Expression::PropertyReference(NamedReference::new(
404 elem,
405 format_smolstr!("preferred-{}", property),
406 )),
407 Expression::PropertyReference(NamedReference::new(
408 elem,
409 format_smolstr!("min-{}", property),
410 )),
411 MinMaxOp::Max,
412 );
413 elem.borrow_mut().set_binding_if_not_set(property.into(), || e);
414}
415
416fn make_default_aspect_ratio_preserving_binding(
424 elem: &ElementRc,
425 missing_size_property: &'static str,
426 given_size_property: &'static str,
427) {
428 if elem.borrow().is_binding_set(missing_size_property, false) {
429 return;
430 }
431
432 debug_assert_eq!(elem.borrow().lookup_property("source").property_type, Type::Image);
433
434 let missing_size_property = SmolStr::new_static(missing_size_property);
435 let given_size_property = SmolStr::new_static(given_size_property);
436
437 let ratio = if elem.borrow().is_binding_set("source-clip-height", false) {
438 Expression::BinaryExpression {
439 lhs: Box::new(Expression::PropertyReference(NamedReference::new(
440 elem,
441 format_smolstr!("source-clip-{missing_size_property}"),
442 ))),
443 rhs: Box::new(Expression::PropertyReference(NamedReference::new(
444 elem,
445 format_smolstr!("source-clip-{given_size_property}"),
446 ))),
447 op: '/',
448 }
449 } else {
450 let implicit_size_var = Box::new(Expression::ReadLocalVariable {
451 name: "image_implicit_size".into(),
452 ty: BuiltinFunction::ImageSize.ty().return_type.clone(),
453 });
454
455 Expression::CodeBlock(vec![
456 Expression::StoreLocalVariable {
457 name: "image_implicit_size".into(),
458 value: Box::new(Expression::FunctionCall {
459 function: BuiltinFunction::ImageSize.into(),
460 arguments: vec![Expression::PropertyReference(NamedReference::new(
461 elem,
462 SmolStr::new_static("source"),
463 ))],
464 source_location: None,
465 }),
466 },
467 Expression::BinaryExpression {
468 lhs: Box::new(Expression::StructFieldAccess {
469 base: implicit_size_var.clone(),
470 name: missing_size_property.clone(),
471 }),
472 rhs: Box::new(Expression::StructFieldAccess {
473 base: implicit_size_var,
474 name: given_size_property.clone(),
475 }),
476 op: '/',
477 },
478 ])
479 };
480 let binding = Expression::BinaryExpression {
481 lhs: Box::new(ratio),
482 rhs: Expression::PropertyReference(NamedReference::new(elem, given_size_property)).into(),
483 op: '*',
484 };
485
486 elem.borrow_mut().bindings.insert(missing_size_property, RefCell::new(binding.into()));
487}
488
489fn maybe_center_in_parent(
490 elem: &ElementRc,
491 parent: &ElementRc,
492 pos_prop: &'static str,
493 size_prop: &'static str,
494) {
495 if elem.borrow().is_binding_set(pos_prop, false) {
496 return;
497 }
498
499 let size_prop = SmolStr::new_static(size_prop);
500 let diff = Expression::BinaryExpression {
501 lhs: Expression::PropertyReference(NamedReference::new(parent, size_prop.clone())).into(),
502 op: '-',
503 rhs: Expression::PropertyReference(NamedReference::new(elem, size_prop)).into(),
504 };
505
506 let pos_prop = SmolStr::new_static(pos_prop);
507 elem.borrow_mut().set_binding_if_not_set(pos_prop, || Expression::BinaryExpression {
508 lhs: diff.into(),
509 op: '/',
510 rhs: Expression::NumberLiteral(2., Unit::None).into(),
511 });
512}
513
514fn adjust_image_clip_rect(elem: &ElementRc, builtin: &Rc<BuiltinElement>) {
515 debug_assert_eq!(builtin.native_class.class_name, "ClippedImage");
516
517 if builtin.native_class.properties.keys().any(|p| {
518 elem.borrow().bindings.contains_key(p)
519 || elem.borrow().property_analysis.borrow().get(p).is_some_and(|a| a.is_used())
520 }) {
521 let source = NamedReference::new(elem, SmolStr::new_static("source"));
522 let x = NamedReference::new(elem, SmolStr::new_static("source-clip-x"));
523 let y = NamedReference::new(elem, SmolStr::new_static("source-clip-y"));
524 let make_expr = |dim: &str, prop: NamedReference| Expression::BinaryExpression {
525 lhs: Box::new(Expression::StructFieldAccess {
526 base: Box::new(Expression::FunctionCall {
527 function: BuiltinFunction::ImageSize.into(),
528 arguments: vec![Expression::PropertyReference(source.clone())],
529 source_location: None,
530 }),
531 name: dim.into(),
532 }),
533 rhs: Expression::PropertyReference(prop).into(),
534 op: '-',
535 };
536
537 elem.borrow_mut()
538 .set_binding_if_not_set("source-clip-width".into(), || make_expr("width", x));
539 elem.borrow_mut()
540 .set_binding_if_not_set("source-clip-height".into(), || make_expr("height", y));
541 }
542}
543
544#[test]
545fn test_no_property_for_100pc() {
546 let mut compiler_config =
548 crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
549 compiler_config.style = Some("fluent".into());
550 let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
551 let doc_node = crate::parser::parse(
552 r#"
553 export component Foo inherits Window {
554 r1 := Rectangle {
555 r2 := Rectangle {
556 width: 100%;
557 background: blue;
558 }
559 r3 := Rectangle {
560 height: parent.height;
561 width: 50%;
562 background: red;
563 }
564 }
565
566 out property <length> r2x: r2.x;
567 out property <length> r2y: r2.y;
568 out property <length> r3x: r3.x;
569 out property <length> r3y: r3.y;
570 }
571"#
572 .into(),
573 Some(std::path::Path::new("HELLO")),
574 &mut test_diags,
575 );
576 let (doc, diag, _) =
577 spin_on::spin_on(crate::compile_syntax_node(doc_node, test_diags, compiler_config));
578 assert!(!diag.has_errors(), "{:?}", diag.to_string_vec());
579
580 let root_elem = doc.inner_components.last().unwrap().root_element.borrow();
581
582 assert!(matches!(
584 &root_elem.bindings.get("r2x").unwrap().borrow().expression,
585 Expression::NumberLiteral(v, _) if *v == 0.
586 ));
587 assert!(matches!(
588 &root_elem.bindings.get("r2y").unwrap().borrow().expression,
589 Expression::NumberLiteral(v, _) if *v == 0.
590 ));
591 assert!(matches!(
592 &root_elem.bindings.get("r3y").unwrap().borrow().expression,
593 Expression::NumberLiteral(v, _) if *v == 0.
594 ));
595 assert!(!matches!(
597 &root_elem.bindings.get("r3x").unwrap().borrow().expression,
598 Expression::BinaryExpression { .. }
599 ));
600}