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