mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-10 05:39:16 +00:00

Parents surch as Opacity, Clip, and co, used to steal the x and y property of their children, making the property not what they ought to be. Now that we refactored recently the code so that geometry need not to be always linked to a property of the same name, we can dissociate the x and y property of these generated elements and their content so that the actual "x" property of the former elementstay some value, despite its relative item property is now 0. Had to change a bit of code that was still assuming a literal "height" or "width" or "y" or "x" property that no longer worked when the geometry is dissociated from its property Fix #1072
449 lines
18 KiB
Rust
449 lines
18 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 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, Spanned};
|
|
use crate::expression_tree::{
|
|
BindingExpression, BuiltinFunction, Expression, MinMaxOp, NamedReference, Unit,
|
|
};
|
|
use crate::langtype::{BuiltinElement, DefaultSizeBinding, Type};
|
|
use crate::layout::{implicit_layout_info_call, LayoutConstraints, Orientation};
|
|
use crate::object_tree::{Component, ElementRc};
|
|
use std::collections::HashMap;
|
|
|
|
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>| {
|
|
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, diag);
|
|
|
|
let builtin_type = match elem.borrow().builtin_type() {
|
|
Some(b) => b,
|
|
None => return Some(elem.clone()),
|
|
};
|
|
|
|
let is_image = builtin_type.name == "Image";
|
|
if is_image {
|
|
adjust_image_clip_rect(elem, &builtin_type);
|
|
}
|
|
|
|
if let Some(parent) = parent {
|
|
match builtin_type.default_size_binding {
|
|
DefaultSizeBinding::None => {
|
|
if elem.borrow().default_fill_parent.0 {
|
|
let e_width =
|
|
elem.borrow().geometry_props.as_ref().unwrap().width.clone();
|
|
let p_width =
|
|
parent.borrow().geometry_props.as_ref().unwrap().width.clone();
|
|
w100 |= make_default_100(&e_width, &p_width);
|
|
} else {
|
|
make_default_implicit(elem, "width");
|
|
}
|
|
if elem.borrow().default_fill_parent.1 {
|
|
let e_height =
|
|
elem.borrow().geometry_props.as_ref().unwrap().height.clone();
|
|
let p_height =
|
|
parent.borrow().geometry_props.as_ref().unwrap().height.clone();
|
|
h100 |= make_default_100(&e_height, &p_height);
|
|
} else {
|
|
make_default_implicit(elem, "height");
|
|
}
|
|
}
|
|
DefaultSizeBinding::ExpandsToParentGeometry => {
|
|
if !elem.borrow().child_of_layout {
|
|
let (e_width, e_height) = elem
|
|
.borrow()
|
|
.geometry_props
|
|
.as_ref()
|
|
.map(|g| (g.width.clone(), g.height.clone()))
|
|
.unwrap();
|
|
let (p_width, p_height) = parent
|
|
.borrow()
|
|
.geometry_props
|
|
.as_ref()
|
|
.map(|g| (g.width.clone(), g.height.clone()))
|
|
.unwrap();
|
|
w100 |= make_default_100(&e_width, &p_width);
|
|
h100 |= make_default_100(&e_height, &p_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");
|
|
|
|
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");
|
|
make_default_implicit(elem, "height");
|
|
}
|
|
} 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
|
|
&& builtin_type.name != "Window"
|
|
{
|
|
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, diag: &mut BuildDiagnostics) {
|
|
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, diag);
|
|
c.borrow()
|
|
.layout_info_prop
|
|
.clone()
|
|
.map(|(h, v)| {
|
|
(Some(Expression::PropertyReference(h)), Some(Expression::PropertyReference(v)))
|
|
})
|
|
.or_else(|| {
|
|
if c.borrow().is_legacy_syntax {
|
|
return None;
|
|
}
|
|
if c.borrow().repeated.is_some() {
|
|
// FIXME: we should ideally add runtime code to merge layout info of all elements that are repeated (same as #407)
|
|
return None;
|
|
}
|
|
let explicit_constraints = LayoutConstraints::new(c, diag);
|
|
let use_implicit_size = c.borrow().builtin_type().map_or(false, |b| {
|
|
b.default_size_binding == DefaultSizeBinding::ImplicitSize
|
|
});
|
|
|
|
let compute = |orientation| {
|
|
if !explicit_constraints.has_explicit_restrictions(orientation) {
|
|
use_implicit_size.then(|| implicit_layout_info_call(c, orientation))
|
|
} else {
|
|
Some(explicit_layout_info(c, orientation))
|
|
}
|
|
};
|
|
Some((compute(Orientation::Horizontal), compute(Orientation::Vertical)))
|
|
.filter(|(a, b)| a.is_some() || b.is_some())
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if child_infos.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let li_v =
|
|
crate::layout::create_new_prop(elem, "layoutinfo-v", crate::layout::layout_info_type());
|
|
let li_h =
|
|
crate::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 = implicit_layout_info_call(elem, Orientation::Horizontal);
|
|
let mut expr_v = implicit_layout_info_call(elem, Orientation::Vertical);
|
|
|
|
for child_info in child_infos {
|
|
if let Some(h) = child_info.0 {
|
|
expr_h = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut expr_h)),
|
|
rhs: Box::new(h),
|
|
op: '+',
|
|
};
|
|
}
|
|
if let Some(v) = child_info.1 {
|
|
expr_v = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut expr_v)),
|
|
rhs: Box::new(v),
|
|
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());
|
|
}
|
|
|
|
fn explicit_layout_info(e: &ElementRc, orientation: Orientation) -> Expression {
|
|
let mut values = HashMap::new();
|
|
let (size, orient) = match orientation {
|
|
Orientation::Horizontal => ("width", "horizontal"),
|
|
Orientation::Vertical => ("height", "vertical"),
|
|
};
|
|
for (k, v) in [
|
|
("min", format!("min-{size}")),
|
|
("max", format!("max-{size}")),
|
|
("preferred", format!("preferred-{size}")),
|
|
("stretch", format!("{orient}-stretch")),
|
|
] {
|
|
values.insert(k.into(), Expression::PropertyReference(NamedReference::new(e, &v)));
|
|
}
|
|
values.insert("min_percent".into(), Expression::NumberLiteral(0., Unit::None));
|
|
values.insert("max_percent".into(), Expression::NumberLiteral(100., Unit::None));
|
|
Expression::Struct { ty: crate::layout::layout_info_type(), values }
|
|
}
|
|
|
|
/// 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<ElementRc>,
|
|
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(prop: &NamedReference, parent_prop: &NamedReference) -> bool {
|
|
prop.element().borrow_mut().set_binding_if_not_set(prop.name().to_string(), || {
|
|
Expression::PropertyReference(parent_prop.clone())
|
|
})
|
|
}
|
|
|
|
fn make_default_implicit(elem: &ElementRc, property: &str) {
|
|
let e = crate::builtin_macros::min_max_expression(
|
|
Expression::PropertyReference(NamedReference::new(
|
|
elem,
|
|
&format!("preferred-{}", property),
|
|
)),
|
|
Expression::PropertyReference(NamedReference::new(elem, &format!("min-{}", property))),
|
|
MinMaxOp::Max,
|
|
);
|
|
elem.borrow_mut().set_binding_if_not_set(property.into(), || e);
|
|
}
|
|
|
|
// 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 ratio = if elem.borrow().is_binding_set("source-clip-height", false) {
|
|
Expression::BinaryExpression {
|
|
lhs: Box::new(Expression::PropertyReference(NamedReference::new(
|
|
elem,
|
|
&format!("source-clip-{missing_size_property}"),
|
|
))),
|
|
rhs: Box::new(Expression::PropertyReference(NamedReference::new(
|
|
elem,
|
|
&format!("source-clip-{given_size_property}"),
|
|
))),
|
|
op: '/',
|
|
}
|
|
} else {
|
|
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"),
|
|
},
|
|
});
|
|
|
|
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::StructFieldAccess {
|
|
base: implicit_size_var.clone(),
|
|
name: missing_size_property.into(),
|
|
}),
|
|
rhs: Box::new(Expression::StructFieldAccess {
|
|
base: implicit_size_var,
|
|
name: given_size_property.into(),
|
|
}),
|
|
op: '/',
|
|
},
|
|
])
|
|
};
|
|
let binding = Expression::BinaryExpression {
|
|
lhs: Box::new(ratio),
|
|
rhs: Expression::PropertyReference(NamedReference::new(elem, 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;
|
|
}
|
|
|
|
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(),
|
|
});
|
|
}
|
|
|
|
fn adjust_image_clip_rect(elem: &ElementRc, builtin: &Rc<BuiltinElement>) {
|
|
debug_assert_eq!(builtin.native_class.class_name, "ClippedImage");
|
|
|
|
if builtin.native_class.properties.keys().any(|p| {
|
|
elem.borrow().bindings.contains_key(p)
|
|
|| elem.borrow().property_analysis.borrow().get(p).map_or(false, |a| a.is_used())
|
|
}) {
|
|
let source = NamedReference::new(elem, "source");
|
|
let x = NamedReference::new(elem, "source-clip-x");
|
|
let y = NamedReference::new(elem, "source-clip-y");
|
|
let make_expr = |dim: &str, prop: NamedReference| Expression::BinaryExpression {
|
|
lhs: Box::new(Expression::StructFieldAccess {
|
|
base: Box::new(Expression::FunctionCall {
|
|
function: Box::new(Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::ImageSize,
|
|
None,
|
|
)),
|
|
arguments: vec![Expression::PropertyReference(source.clone())],
|
|
source_location: None,
|
|
}),
|
|
name: dim.into(),
|
|
}),
|
|
rhs: Expression::PropertyReference(prop).into(),
|
|
op: '-',
|
|
};
|
|
|
|
elem.borrow_mut()
|
|
.set_binding_if_not_set("source-clip-width".into(), || make_expr("width", x));
|
|
elem.borrow_mut()
|
|
.set_binding_if_not_set("source-clip-height".into(), || make_expr("height", y));
|
|
}
|
|
}
|