slint/sixtyfps_compiler/passes/default_geometry.rs
Olivier Goffart dd3fa1c221 Make the BindingMap hold RefCell of the BindingExpression
This will allow later to be able to operate on the binding despite the
element is borrowed.

Since the Binding itself is in a RefCell, the analysis don't need to
be anymore.
To do this change, a small change in the binding_analysis logic was required
which means that we will now detect binding loop if a binding was causing
two binding loop. (before, only one binding loop was detected)
2021-11-11 11:14:59 +01:00

283 lines
11 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
/*! 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::{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<Component>, diag: &mut BuildDiagnostics) {
crate::object_tree::recurse_elem_including_sub_components(
root_component,
&None,
&mut |elem: &ElementRc, parent: &Option<ElementRc>| {
fix_percent_size(elem, parent, "width", diag);
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 {
make_default_100(elem, parent, "width");
make_default_100(elem, parent, "height");
}
}
DefaultSizeBinding::ImplicitSize => {
let has_length_property_binding = |elem: &ElementRc, property: &str| {
debug_assert!({
let PropertyLookupResult { resolved_name: _, property_type } =
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 PropertyLookupResult {
resolved_name: image_fit_prop_name,
property_type: image_fit_prop_type,
} = elem.borrow().lookup_property("image-fit");
elem.borrow_mut().set_binding_if_not_set(
image_fit_prop_name.into(),
|| {
Expression::EnumerationValue(
image_fit_prop_type
.as_enum()
.clone()
.try_value_from_string("contain")
.unwrap(),
)
},
);
}
}
}
}
}
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::<Vec<_>>();
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;`
fn fix_percent_size(
elem: &ElementRc,
parent: &Option<ElementRc>,
property: &str,
diag: &mut BuildDiagnostics,
) {
let elem = elem.borrow();
let binding = match elem.bindings.get(property) {
Some(b) => b,
None => return,
};
if binding.borrow().ty() != Type::Percent {
return;
}
let mut b = binding.borrow_mut();
if let Some(parent) = parent {
debug_assert_eq!(
parent.borrow().lookup_property(property),
PropertyLookupResult {
resolved_name: property.into(),
property_type: Type::LogicalLength
}
);
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: '*',
}
} else {
diag.push_error("Cannot find parent property to apply relative length".into(), &b.span);
}
}
fn make_default_100(elem: &ElementRc, parent_element: &ElementRc, property: &str) {
let PropertyLookupResult { resolved_name, property_type } =
parent_element.borrow().lookup_property(property);
if property_type != Type::LogicalLength {
return;
}
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 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()));
}