| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | /*! Set the width and height of Rectangle, TouchArea, ... to 100%, |
| 5 | the implicit width or aspect ratio preserving for Images. |
| 6 | Also set the Image.image-fit default depending on the presence of a |
| 7 | layout parent. |
| 8 | |
| 9 | This pass must be run after lower_layout |
| 10 | */ |
| 11 | |
| 12 | use std::cell::RefCell; |
| 13 | use std::rc::Rc; |
| 14 | |
| 15 | use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned}; |
| 16 | use crate::expression_tree::{ |
| 17 | BindingExpression, BuiltinFunction, Expression, MinMaxOp, NamedReference, Unit, |
| 18 | }; |
| 19 | use crate::langtype::{BuiltinElement, DefaultSizeBinding, Type}; |
| 20 | use crate::layout::{implicit_layout_info_call, LayoutConstraints, Orientation}; |
| 21 | use crate::object_tree::{Component, ElementRc}; |
| 22 | use smol_str::{format_smolstr, SmolStr}; |
| 23 | use std::collections::HashMap; |
| 24 | |
| 25 | pub 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 | // Not an element with geometry, skip it |
| 35 | return None; |
| 36 | } |
| 37 | |
| 38 | // whether the width, or height, is filling the parent |
| 39 | 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 | // Add aspect-ratio preserving width or height bindings |
| 111 | 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 an image is in a layout and has no explicit width or height specified, change the default for image-fit |
| 125 | // to `contain` |
| 126 | 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 | |
| 165 | /// Generate a layout_info_prop based on the children layouts |
| 166 | fn 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 | // FIXME: we should ideally add runtime code to merge layout info of all elements that are repeated (same as #407) |
| 192 | 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 | |
| 262 | fn 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 | |
| 314 | fn explicit_layout_info(e: &ElementRc, orientation: Orientation) -> Expression { |
| 315 | let mut values: HashMap = HashMap::new(); |
| 316 | let (size: &'static str, orient: &'static str) = match orientation { |
| 317 | Orientation::Horizontal => ("width" , "horizontal" ), |
| 318 | Orientation::Vertical => ("height" , "vertical" ), |
| 319 | }; |
| 320 | for (k: &'static str, v: SmolStr) 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(), v:Expression::PropertyReference(NamedReference::new(element:e, name:v))); |
| 327 | } |
| 328 | values.insert(k:"min_percent" .into(), v:Expression::NumberLiteral(0., Unit::None)); |
| 329 | values.insert(k:"max_percent" .into(), v:Expression::NumberLiteral(100., Unit::None)); |
| 330 | Expression::Struct { ty: crate::typeregister::layout_info_type(), values } |
| 331 | } |
| 332 | |
| 333 | /// Replace expression such as `"width: 30%;` with `width: 0.3 * parent.width;` |
| 334 | /// |
| 335 | /// Returns true if the expression was 100% |
| 336 | fn 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 | // Pattern match to check it was already parent.<property> |
| 351 | 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 | // the `%` in a flickable need to refer to the size of the flickable, not the size of the viewport |
| 357 | 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 | |
| 384 | /// Generate a size property that covers the parent. |
| 385 | /// Return true if it was changed |
| 386 | fn make_default_100(prop: &NamedReference, parent_prop: &NamedReference) -> bool { |
| 387 | prop.element().borrow_mut().set_binding_if_not_set(property_name:prop.name().clone(), || { |
| 388 | Expression::PropertyReference(parent_prop.clone()) |
| 389 | }) |
| 390 | } |
| 391 | |
| 392 | fn make_default_implicit(elem: &ElementRc, property: &str) { |
| 393 | let e: Expression = crate::builtin_macros::min_max_expression( |
| 394 | lhs:Expression::PropertyReference(NamedReference::new( |
| 395 | elem, |
| 396 | format_smolstr!("preferred- {}" , property), |
| 397 | )), |
| 398 | rhs:Expression::PropertyReference(NamedReference::new( |
| 399 | elem, |
| 400 | format_smolstr!("min- {}" , property), |
| 401 | )), |
| 402 | op:MinMaxOp::Max, |
| 403 | ); |
| 404 | elem.borrow_mut().set_binding_if_not_set(property.into(), || e); |
| 405 | } |
| 406 | |
| 407 | // For an element with `width`, `height`, `preferred-width` and `preferred-height`, make an aspect |
| 408 | // ratio preserving binding. This is currently only called for Image elements. For example when for an |
| 409 | // image the `width` is specified and there is no `height` binding, it is called with `missing_size_property = height` |
| 410 | // and `given_size_property = width` and install a binding like this: |
| 411 | // |
| 412 | // height: self.width * self.preferred_height / self.preferred_width; |
| 413 | // |
| 414 | fn 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 | |
| 480 | fn 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(property_name:pos_prop, need_explicit:false) { |
| 487 | return; |
| 488 | } |
| 489 | |
| 490 | let size_prop: SmolStr = SmolStr::new_static(text:size_prop); |
| 491 | let diff: Expression = Expression::BinaryExpression { |
| 492 | lhs: Expression::PropertyReference(NamedReference::new(element:parent, name:size_prop.clone())).into(), |
| 493 | op: '-' , |
| 494 | rhs: Expression::PropertyReference(NamedReference::new(element:elem, name:size_prop)).into(), |
| 495 | }; |
| 496 | |
| 497 | let pos_prop: SmolStr = SmolStr::new_static(text:pos_prop); |
| 498 | elem.borrow_mut().set_binding_if_not_set(property_name:pos_prop, || Expression::BinaryExpression { |
| 499 | lhs: diff.into(), |
| 500 | op: '/' , |
| 501 | rhs: Expression::NumberLiteral(2., Unit::None).into(), |
| 502 | }); |
| 503 | } |
| 504 | |
| 505 | fn 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 ] |
| 536 | fn test_no_property_for_100pc() { |
| 537 | //! Test that we don't generate x or y property to center elements if the size is filling the parent |
| 538 | 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 | // const propagation must have seen that the x and y property are literal 0 |
| 574 | 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 | // this one is 50% so it should be set to be in the center |
| 587 | assert!(!matches!( |
| 588 | &root_elem.bindings.get("r3x" ).unwrap().borrow().expression, |
| 589 | Expression::BinaryExpression { .. } |
| 590 | )); |
| 591 | } |
| 592 | |