mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 04:45:13 +00:00

Since they no longer have a builtin width/height property, the test that checked the type was wrong. But since we can't override the width or height property with another type, this should not be relevant
428 lines
17 KiB
Rust
428 lines
17 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 {
|
|
w100 |= make_default_100(elem, parent, "width");
|
|
} else {
|
|
make_default_implicit(elem, "width");
|
|
}
|
|
if elem.borrow().default_fill_parent.1 {
|
|
h100 |= make_default_100(elem, parent, "height");
|
|
} else {
|
|
make_default_implicit(elem, "height");
|
|
}
|
|
}
|
|
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");
|
|
|
|
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)| (Expression::PropertyReference(h), 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);
|
|
if !explicit_constraints.has_explicit_restrictions() {
|
|
c.borrow()
|
|
.builtin_type()
|
|
.map_or(false, |b| {
|
|
b.default_size_binding == DefaultSizeBinding::ImplicitSize
|
|
})
|
|
.then(|| {
|
|
(
|
|
implicit_layout_info_call(c, Orientation::Horizontal),
|
|
implicit_layout_info_call(c, Orientation::Vertical),
|
|
)
|
|
})
|
|
} else {
|
|
Some((
|
|
explicit_layout_info(c, Orientation::Horizontal),
|
|
explicit_layout_info(c, Orientation::Vertical),
|
|
))
|
|
}
|
|
})
|
|
})
|
|
.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 {
|
|
expr_h = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut expr_h)),
|
|
rhs: Box::new(child_info.0),
|
|
op: '+',
|
|
};
|
|
expr_v = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut expr_v)),
|
|
rhs: Box::new(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());
|
|
}
|
|
|
|
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(elem: &ElementRc, parent_element: &ElementRc, property: &str) -> bool {
|
|
elem.borrow_mut().set_binding_if_not_set(property.to_string(), || {
|
|
Expression::PropertyReference(NamedReference::new(parent_element, property))
|
|
})
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|