Delay the percentage size conversion to after the layouting phase

So we can see inthe layouting phase if the size was in percent
This commit is contained in:
Olivier Goffart 2021-02-11 14:59:49 +01:00
parent 92218a8b6e
commit 0174db3679
6 changed files with 67 additions and 53 deletions

View file

@ -628,6 +628,7 @@ fn test_select_minimal_class_based_on_property_usage() {
assert_eq!(reduce_to_second.class_name, second.class_name); assert_eq!(reduce_to_second.class_name, second.class_name);
} }
#[derive(PartialEq, Debug)]
pub struct PropertyLookupResult<'a> { pub struct PropertyLookupResult<'a> {
pub resolved_name: std::borrow::Cow<'a, str>, pub resolved_name: std::borrow::Cow<'a, str>,
pub property_type: Type, pub property_type: Type,

View file

@ -182,7 +182,7 @@ pub async fn run_passes(
passes::lower_popups::lower_popups(&doc.root_component, &doc.local_registry, diag); passes::lower_popups::lower_popups(&doc.root_component, &doc.local_registry, diag);
passes::lower_layout::lower_layouts(&doc.root_component, &mut type_loader, diag).await; passes::lower_layout::lower_layouts(&doc.root_component, &mut type_loader, diag).await;
passes::lower_shadows::lower_shadow_properties(&doc.root_component, &doc.local_registry, diag); passes::lower_shadows::lower_shadow_properties(&doc.root_component, &doc.local_registry, diag);
passes::default_geometry::default_geometry(&doc.root_component); passes::default_geometry::default_geometry(&doc.root_component, diag);
passes::apply_default_properties_from_style::apply_default_properties_from_style( passes::apply_default_properties_from_style::apply_default_properties_from_style(
&doc.root_component, &doc.root_component,
&mut type_loader, &mut type_loader,

View file

@ -12,6 +12,7 @@ LICENSE END */
use std::rc::Rc; use std::rc::Rc;
use crate::diagnostics::BuildDiagnostics;
use crate::langtype::DefaultSizeBinding; use crate::langtype::DefaultSizeBinding;
use crate::langtype::Type; use crate::langtype::Type;
use crate::object_tree::{Component, ElementRc}; use crate::object_tree::{Component, ElementRc};
@ -20,11 +21,14 @@ use crate::{
langtype::PropertyLookupResult, langtype::PropertyLookupResult,
}; };
pub fn default_geometry(root_component: &Rc<Component>) { pub fn default_geometry(root_component: &Rc<Component>, diag: &mut BuildDiagnostics) {
crate::object_tree::recurse_elem_including_sub_components( crate::object_tree::recurse_elem_including_sub_components(
&root_component, &root_component,
&None, &None,
&mut |elem: &ElementRc, parent: &Option<ElementRc>| { &mut |elem: &ElementRc, parent: &Option<ElementRc>| {
fix_percent_size(elem, parent, "width", diag);
fix_percent_size(elem, parent, "height", diag);
let base_type = elem.borrow().base_type.clone(); let base_type = elem.borrow().base_type.clone();
if let (Some(parent), Type::Builtin(builtin_type)) = (parent, base_type) { if let (Some(parent), Type::Builtin(builtin_type)) = (parent, base_type) {
match builtin_type.default_size_binding { match builtin_type.default_size_binding {
@ -46,6 +50,37 @@ pub fn default_geometry(root_component: &Rc<Component>) {
) )
} }
/// 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,
) {
if !elem.borrow().bindings.get(property).map_or(false, |b| b.ty() == Type::Percent) {
return;
}
let mut elem = elem.borrow_mut();
let b = elem.bindings.get_mut(property).unwrap();
if let Some(parent) = parent {
debug_assert_eq!(
parent.borrow().lookup_property(property),
PropertyLookupResult { resolved_name: property.into(), property_type: Type::Length }
);
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 lenght".into(), &b.span);
}
}
fn make_default_100(elem: &ElementRc, parent_element: &ElementRc, property: &str) { fn make_default_100(elem: &ElementRc, parent_element: &ElementRc, property: &str) {
let PropertyLookupResult { resolved_name, property_type } = let PropertyLookupResult { resolved_name, property_type } =
parent_element.borrow().lookup_property(property); parent_element.borrow().lookup_property(property);

View file

@ -212,55 +212,6 @@ fn find_parent_element(e: &ElementRc) -> Option<ElementRc> {
recurse(&root, e) recurse(&root, e)
} }
/// If the type of the expression is a percentage, and the current property evaluated is
/// `width` or `height`, attempt to multiply by the parent `width` or `height`
fn attempt_percent_conversion(
ctx: &mut LookupCtx,
e: Expression,
node: &dyn SpannedWithSourceFile,
) -> Expression {
if ctx.property_type != Type::Length || e.ty() != Type::Percent {
return e;
}
const RELATIVE_TO_PARENT_PROPERTIES: [&str; 2] = ["width", "height"];
let property_name = ctx.property_name.unwrap_or_default();
if !RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
ctx.diag.push_error(
format!(
"Automatic conversion from percentage to lenght is only possible for the properties {}",
RELATIVE_TO_PARENT_PROPERTIES.join(" and ")
),
node
);
return Expression::Invalid;
}
let mut parent = ctx.component_scope.last().and_then(find_parent_element);
while let Some(p) = parent {
let PropertyLookupResult { resolved_name, property_type } =
p.borrow().lookup_property(property_name);
if property_type == Type::Length {
return Expression::BinaryExpression {
lhs: Box::new(Expression::BinaryExpression {
lhs: Box::new(e),
rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
op: '*',
}),
rhs: Box::new(Expression::PropertyReference(NamedReference {
element: Rc::downgrade(&p),
name: resolved_name.to_string(),
})),
op: '*',
};
}
parent = find_parent_element(&p);
}
ctx.diag.push_error("Cannot find parent property to apply relative lenght".into(), node);
Expression::Invalid
}
impl Expression { impl Expression {
pub fn from_binding_expression_node( pub fn from_binding_expression_node(
node: SyntaxNodeWithSourceFile, node: SyntaxNodeWithSourceFile,
@ -275,7 +226,23 @@ impl Expression {
.map(|c| Self::from_codeblock_node(c.into(), ctx)) .map(|c| Self::from_codeblock_node(c.into(), ctx))
}) })
.unwrap_or(Self::Invalid); .unwrap_or(Self::Invalid);
let e = attempt_percent_conversion(ctx, e, &node); if ctx.property_type == Type::Length && e.ty() == Type::Percent {
// See if a conversion from percentage to lenght is allowed
const RELATIVE_TO_PARENT_PROPERTIES: [&str; 2] = ["width", "height"];
let property_name = ctx.property_name.unwrap_or_default();
if RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
return e;
} else {
ctx.diag.push_error(
format!(
"Automatic conversion from percentage to lenght is only possible for the properties {}",
RELATIVE_TO_PARENT_PROPERTIES.join(" and ")
),
&node
);
return Expression::Invalid;
}
};
e.maybe_convert_to(ctx.property_type.clone(), &node, &mut ctx.diag) e.maybe_convert_to(ctx.property_type.clone(), &node, &mut ctx.diag)
} }

View file

@ -10,7 +10,6 @@ LICENSE END */
Foo := Rectangle { Foo := Rectangle {
width: 30%; width: 30%;
// ^error{Cannot find parent property to apply relative lenght}
Rectangle { Rectangle {
height: 111%; height: 111%;

View file

@ -7,12 +7,21 @@
This file is also available under commercial licensing terms. This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information. Please contact info@sixtyfps.io for more information.
LICENSE END */ LICENSE END */
Sub := Rectangle {
width: 25%;
height: 35%;
}
TestCase := Rectangle { TestCase := Rectangle {
width: 600phx; width: 600phx;
inner_rect := Rectangle { inner_rect := Rectangle {
width: 50%; width: 50%;
inner_inner := Sub { }
} }
property<length> test_length: inner_rect.width; property<length> test_length: inner_rect.width;
property<length> test_inner_inner_length: inner_inner.width;
height: 200phx; height: 200phx;
property<percent> controller: 10%; property<percent> controller: 10%;
@ -29,6 +38,7 @@ TestCase := Rectangle {
auto handle = TestCase::create(); auto handle = TestCase::create();
const TestCase &instance = *handle; const TestCase &instance = *handle;
assert_eq(instance.get_test_length(), 300.); assert_eq(instance.get_test_length(), 300.);
assert_eq(instance.get_test_inner_inner_length(), 300. / 4.);
assert_eq(instance.get_controlled_test_length(), 20.); assert_eq(instance.get_controlled_test_length(), 20.);
instance.set_controller(50); instance.set_controller(50);
@ -40,6 +50,7 @@ assert_eq(instance.get_controlled_test_length(), 100.);
```rust ```rust
let instance = TestCase::new(); let instance = TestCase::new();
assert_eq!(instance.get_test_length(), 300.); assert_eq!(instance.get_test_length(), 300.);
assert_eq!(instance.get_test_inner_inner_length(), 300. / 4.);
assert_eq!(instance.get_controlled_test_length(), 20.); assert_eq!(instance.get_controlled_test_length(), 20.);
instance.set_controller(50.); instance.set_controller(50.);
@ -49,6 +60,7 @@ assert_eq!(instance.get_controlled_test_length(), 100.);
```js ```js
var instance = new sixtyfps.TestCase({}); var instance = new sixtyfps.TestCase({});
assert.equal(instance.test_length, 300.); assert.equal(instance.test_length, 300.);
assert.equal(instance.test_inner_inner_length, 300. / 4.);
assert.equal(instance.controlled_test_length, 20.); assert.equal(instance.controlled_test_length, 20.);
instance.controller = 50.; instance.controller = 50.;