// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial /*! Set the width and height of Rectangle, TouchArea, ... to 100%, the implicit width or aspect ratio preserving for Images. Also set the Image.image-fit default depending on the presence of a layout parent. This pass must be run after lower_layout */ use std::cell::RefCell; use std::rc::Rc; use crate::diagnostics::BuildDiagnostics; use crate::diagnostics::Spanned; use crate::expression_tree::Unit; use crate::expression_tree::{BindingExpression, BuiltinFunction, Expression, NamedReference}; use crate::langtype::{DefaultSizeBinding, PropertyLookupResult, Type}; use crate::layout::Orientation; use crate::object_tree::{Component, ElementRc}; pub fn default_geometry(root_component: &Rc, diag: &mut BuildDiagnostics) { crate::object_tree::recurse_elem_including_sub_components( root_component, &None, &mut |elem: &ElementRc, parent: &Option| { if elem.borrow().repeated.is_some() { return None; } // whether the width, or height, is filling the parent let (mut w100, mut h100) = (false, false); w100 |= fix_percent_size(elem, parent, "width", diag); h100 |= fix_percent_size(elem, parent, "height", diag); gen_layout_info_prop(elem); let base_type = elem.borrow().builtin_type(); if let (Some(parent), Some(builtin_type)) = (parent, base_type) { match builtin_type.default_size_binding { DefaultSizeBinding::None => {} DefaultSizeBinding::ExpandsToParentGeometry => { if !elem.borrow().child_of_layout { w100 |= make_default_100(elem, parent, "width"); h100 |= make_default_100(elem, parent, "height"); } } DefaultSizeBinding::ImplicitSize => { let has_length_property_binding = |elem: &ElementRc, property: &str| { debug_assert_eq!( elem.borrow().lookup_property(property).property_type, Type::LogicalLength ); elem.borrow().is_binding_set(property, true) }; let width_specified = has_length_property_binding(elem, "width"); let height_specified = has_length_property_binding(elem, "height"); let is_image = builtin_type.name == "Image"; if !elem.borrow().child_of_layout { // Add aspect-ratio preserving width or height bindings if is_image && width_specified && !height_specified { make_default_aspect_ratio_preserving_binding( elem, "height", "width", ) } else if is_image && height_specified && !width_specified { make_default_aspect_ratio_preserving_binding( elem, "width", "height", ) } else { make_default_implicit(elem, "width", Orientation::Horizontal); make_default_implicit(elem, "height", Orientation::Vertical); } } else if is_image { // If an image is in a layout and has no explicit width or height specified, change the default for image-fit // to `contain` if !width_specified || !height_specified { let image_fit_lookup = elem.borrow().lookup_property("image-fit"); elem.borrow_mut().set_binding_if_not_set( image_fit_lookup.resolved_name.into(), || { Expression::EnumerationValue( image_fit_lookup .property_type .as_enum() .clone() .try_value_from_string("contain") .unwrap(), ) }, ); } } } } if !elem.borrow().child_of_layout && !elem.borrow().is_legacy_syntax { if !w100 { maybe_center_in_parent(elem, parent, "x", "width"); } if !h100 { maybe_center_in_parent(elem, parent, "y", "height"); } } } Some(elem.clone()) }, ) } /// Generate a layout_info_prop based on the children layouts fn gen_layout_info_prop(elem: &ElementRc) { if elem.borrow().layout_info_prop.is_some() || elem.borrow().is_flickable_viewport { return; } let child_infos = elem .borrow() .children .iter() .filter(|c| { !c.borrow().bindings.contains_key("x") && !c.borrow().bindings.contains_key("y") }) .filter_map(|c| { gen_layout_info_prop(c); c.borrow().layout_info_prop.clone() }) .collect::>(); if child_infos.is_empty() { return; } let li_v = super::lower_layout::create_new_prop( elem, "layoutinfo-v", crate::layout::layout_info_type(), ); let li_h = super::lower_layout::create_new_prop( elem, "layoutinfo-h", crate::layout::layout_info_type(), ); elem.borrow_mut().layout_info_prop = Some((li_h.clone(), li_v.clone())); let mut expr_h = crate::layout::implicit_layout_info_call(elem, Orientation::Horizontal); let mut expr_v = crate::layout::implicit_layout_info_call(elem, Orientation::Vertical); for child_info in child_infos { expr_h = Expression::BinaryExpression { lhs: Box::new(std::mem::take(&mut expr_h)), rhs: Box::new(Expression::PropertyReference(child_info.0)), op: '+', }; expr_v = Expression::BinaryExpression { lhs: Box::new(std::mem::take(&mut expr_v)), rhs: Box::new(Expression::PropertyReference(child_info.1)), op: '+', }; } let expr_v = BindingExpression::new_with_span(expr_v, elem.borrow().to_source_location()); li_v.element().borrow_mut().bindings.insert(li_v.name().into(), expr_v.into()); let expr_h = BindingExpression::new_with_span(expr_h, elem.borrow().to_source_location()); li_h.element().borrow_mut().bindings.insert(li_h.name().into(), expr_h.into()); } /// Replace expression such as `"width: 30%;` with `width: 0.3 * parent.width;` /// /// Returns true if the expression was 100% fn fix_percent_size( elem: &ElementRc, parent: &Option, property: &str, diag: &mut BuildDiagnostics, ) -> bool { let elem = elem.borrow(); let binding = match elem.bindings.get(property) { Some(b) => b, None => return false, }; if binding.borrow().ty() != Type::Percent { return false; } let mut b = binding.borrow_mut(); if let Some(parent) = parent { debug_assert_eq!( parent.borrow().lookup_property(property).property_type, Type::LogicalLength ); let fill = matches!(b.expression, Expression::NumberLiteral(x, _) if (x - 100.).abs() < 0.001); b.expression = Expression::BinaryExpression { lhs: Box::new(std::mem::take(&mut b.expression).maybe_convert_to( Type::Float32, &b.span, diag, )), rhs: Box::new(Expression::PropertyReference(NamedReference::new(parent, property))), op: '*', }; fill } else { diag.push_error("Cannot find parent property to apply relative length".into(), &b.span); false } } /// Generate a size property that covers the parent. /// Return true if it was changed fn make_default_100(elem: &ElementRc, parent_element: &ElementRc, property: &str) -> bool { let PropertyLookupResult { resolved_name, property_type, .. } = parent_element.borrow().lookup_property(property); if property_type != Type::LogicalLength { return false; } elem.borrow_mut().set_binding_if_not_set(resolved_name.to_string(), || { Expression::PropertyReference(NamedReference::new(parent_element, resolved_name.as_ref())) }) } fn make_default_implicit(elem: &ElementRc, property: &str, orientation: Orientation) { let base = crate::layout::implicit_layout_info_call(elem, orientation).into(); elem.borrow_mut().set_binding_if_not_set(property.into(), || Expression::StructFieldAccess { base, name: "preferred".into(), }); } // For an element with `width`, `height`, `preferred-width` and `preferred-height`, make an aspect // ratio preserving binding. This is currently only called for Image elements. For example when for an // image the `width` is specified and there is no `height` binding, it is called with `missing_size_property = height` // and `given_size_property = width` and install a binding like this: // // height: self.width * self.preferred_height / self.preferred_width; // fn make_default_aspect_ratio_preserving_binding( elem: &ElementRc, missing_size_property: &str, given_size_property: &str, ) { if elem.borrow().is_binding_set(missing_size_property, false) { return; } debug_assert_eq!(elem.borrow().lookup_property("source").property_type, Type::Image); let implicit_size_var = Box::new(Expression::ReadLocalVariable { name: "image_implicit_size".into(), ty: match BuiltinFunction::ImageSize.ty() { Type::Function { return_type, .. } => *return_type, _ => panic!("invalid type for ImplicitItemSize built-in function"), }, }); let binding = Expression::CodeBlock(vec![ Expression::StoreLocalVariable { name: "image_implicit_size".into(), value: Box::new(Expression::FunctionCall { function: Box::new(Expression::BuiltinFunctionReference( BuiltinFunction::ImageSize, None, )), arguments: vec![Expression::PropertyReference(NamedReference::new(elem, "source"))], source_location: None, }), }, Expression::BinaryExpression { lhs: Box::new(Expression::BinaryExpression { lhs: Expression::PropertyReference(NamedReference::new(elem, given_size_property)) .into(), rhs: Box::new(Expression::StructFieldAccess { base: implicit_size_var.clone(), name: missing_size_property.into(), }), op: '*', }), rhs: Box::new(Expression::StructFieldAccess { base: implicit_size_var, name: given_size_property.into(), }), op: '/', }, ]); elem.borrow_mut() .bindings .insert(missing_size_property.to_string(), RefCell::new(binding.into())); } fn maybe_center_in_parent(elem: &ElementRc, parent: &ElementRc, pos_prop: &str, size_prop: &str) { if elem.borrow().is_binding_set(pos_prop, false) { return; } if elem.borrow().lookup_property(pos_prop).property_type != Type::LogicalLength || elem.borrow().lookup_property(size_prop).property_type != Type::LogicalLength { return; } let diff = Expression::BinaryExpression { lhs: Expression::PropertyReference(NamedReference::new(parent, size_prop)).into(), op: '-', rhs: Expression::PropertyReference(NamedReference::new(elem, size_prop)).into(), }; elem.borrow_mut().set_binding_if_not_set(pos_prop.into(), || Expression::BinaryExpression { lhs: diff.into(), op: '/', rhs: Expression::NumberLiteral(2., Unit::None).into(), }); }