Change behavior of percent sice within the layout

So that size with percentages don't affect the maximum/minimum size

Fixes #117
This commit is contained in:
Olivier Goffart 2021-02-11 20:00:41 +01:00
parent 0174db3679
commit 096f55e365
10 changed files with 302 additions and 57 deletions

View file

@ -339,6 +339,10 @@ inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const
std::min(max_width, other.max_width), std::min(max_width, other.max_width),
std::max(min_height, other.min_height), std::max(min_height, other.min_height),
std::min(max_height, other.max_height), std::min(max_height, other.max_height),
std::max(min_width_percent, other.min_width_percent),
std::min(max_width_percent, other.max_width_percent),
std::max(min_height_percent, other.min_height_percent),
std::min(max_height_percent, other.max_height_percent),
std::min(horizontal_stretch, other.horizontal_stretch), std::min(horizontal_stretch, other.horizontal_stretch),
std::min(vertical_stretch, other.vertical_stretch) }; std::min(vertical_stretch, other.vertical_stretch) };
} }

View file

@ -1972,14 +1972,12 @@ fn get_layout_info_ref<'a, 'b>(
}; };
if item.constraints.has_explicit_restrictions() { if item.constraints.has_explicit_restrictions() {
layout_info = format!("[&]{{ auto layout_info = {};", layout_info); layout_info = format!("[&]{{ auto layout_info = {};", layout_info);
for (expr, name) in item.constraints.for_each_restrictions().iter() { for (expr, name) in item.constraints.for_each_restrictions() {
if let Some(e) = expr { layout_info += &format!(
layout_info += &format!( " layout_info.{} = {}.get();",
" layout_info.{} = {}.get();", name,
name, access_named_reference(expr, component, "self")
access_named_reference(e, component, "self") );
);
}
} }
layout_info += " return layout_info; }()"; layout_info += " return layout_info; }()";
} }
@ -2251,14 +2249,12 @@ fn compute_layout(
let root_constraints = &component.layouts.borrow().root_constraints; let root_constraints = &component.layouts.borrow().root_constraints;
if root_constraints.has_explicit_restrictions() { if root_constraints.has_explicit_restrictions() {
layout_info.push("auto layout_info_other = layout_info;".into()); layout_info.push("auto layout_info_other = layout_info;".into());
for (expr, name) in root_constraints.for_each_restrictions().iter() { for (expr, name) in root_constraints.for_each_restrictions() {
if let Some(e) = expr { layout_info.push(format!(
layout_info.push(format!( "layout_info_other.{} = {}.get();",
"layout_info_other.{} = {}.get();", name,
name, access_named_reference(expr, component, "self")
access_named_reference(e, component, "self") ));
));
}
} }
layout_info.push("return layout_info.merge(layout_info_other);".into()); layout_info.push("return layout_info.merge(layout_info_other);".into());
} else { } else {

View file

@ -1682,11 +1682,8 @@ fn apply_layout_constraint(
if constraints.has_explicit_restrictions() { if constraints.has_explicit_restrictions() {
let (name, expr): (Vec<_>, Vec<_>) = constraints let (name, expr): (Vec<_>, Vec<_>) = constraints
.for_each_restrictions() .for_each_restrictions()
.iter() .map(|(e, s)| {
.filter_map(|(e, s)| { (format_ident!("{}", s), access_named_reference(e, component, quote!(_self)))
e.as_ref().map(|e| {
(format_ident!("{}", s), access_named_reference(e, component, quote!(_self)))
})
}) })
.unzip(); .unzip();
quote!({ quote!({

View file

@ -172,14 +172,18 @@ impl LayoutConstraints {
}; };
let e = element.borrow(); let e = element.borrow();
e.bindings.get("height").map(|s| { e.bindings.get("height").map(|s| {
constraints.fixed_height = true;
apply_size_constraint("height", s, &mut constraints.minimum_height); apply_size_constraint("height", s, &mut constraints.minimum_height);
apply_size_constraint("height", s, &mut constraints.maximum_height); apply_size_constraint("height", s, &mut constraints.maximum_height);
constraints.fixed_height = true;
}); });
e.bindings.get("width").map(|s| { e.bindings.get("width").map(|s| {
if s.expression.ty() == Type::Percent {
apply_size_constraint("width", s, &mut constraints.minimum_width);
return;
}
constraints.fixed_width = true;
apply_size_constraint("width", s, &mut constraints.minimum_width); apply_size_constraint("width", s, &mut constraints.minimum_width);
apply_size_constraint("width", s, &mut constraints.maximum_width); apply_size_constraint("width", s, &mut constraints.maximum_width);
constraints.fixed_width = true;
}); });
constraints constraints
@ -194,15 +198,41 @@ impl LayoutConstraints {
|| self.vertical_stretch.is_some() || self.vertical_stretch.is_some()
} }
pub fn for_each_restrictions<'a>(&'a self) -> [(&Option<NamedReference>, &'static str); 6] { // Iterate over the constraint with a reference to a property, and the corresponding member in the sixtyfps_corelib::layout::LayoutInfo struct
[ pub fn for_each_restrictions<'a>(
(&self.minimum_width, "min_width"), &'a self,
(&self.maximum_width, "max_width"), ) -> impl Iterator<Item = (&NamedReference, &'static str)> {
(&self.minimum_height, "min_height"), std::iter::empty()
(&self.maximum_height, "max_height"), .chain(self.minimum_width.as_ref().map(|x| {
(&self.horizontal_stretch, "horizontal_stretch"), if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
(&self.vertical_stretch, "vertical_stretch"), (x, "min_width")
] } else {
(x, "min_width_percent")
}
}))
.chain(self.maximum_width.as_ref().map(|x| {
if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
(x, "max_width")
} else {
(x, "max_width_percent")
}
}))
.chain(self.minimum_height.as_ref().map(|x| {
if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
(x, "min_height")
} else {
(x, "min_height_percent")
}
}))
.chain(self.maximum_height.as_ref().map(|x| {
if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
(x, "max_height")
} else {
(x, "max_height_percent")
}
}))
.chain(self.horizontal_stretch.as_ref().map(|x| (x, "horizontal_stretch")))
.chain(self.vertical_stretch.as_ref().map(|x| (x, "vertical_stretch")))
} }
fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {

View file

@ -293,12 +293,36 @@ fn create_layout_item(
style_metrics: &Option<Rc<Component>>, style_metrics: &Option<Rc<Component>>,
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
) -> Option<LayoutItem> { ) -> Option<LayoutItem> {
let fix_explicit_percent = |prop: &str, item: &ElementRc| {
if !item.borrow().bindings.get(prop).map_or(false, |b| b.ty() == Type::Percent) {
return;
}
let mut item = item.borrow_mut();
let b = item.bindings.remove(prop).unwrap();
// FIXME: this should be the preferred size instead, progably
item.bindings.insert(format!("minimum_{}", prop), b.clone());
item.bindings.insert(format!("maximum_{}", prop), b);
item.property_declarations.insert(
format!("minimum_{}", prop),
PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
);
item.property_declarations.insert(
format!("maximum_{}", prop),
PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
);
};
fix_explicit_percent("width", item_element);
fix_explicit_percent("height", item_element);
let constraints = LayoutConstraints::new(item_element, diag); let constraints = LayoutConstraints::new(item_element, diag);
item_element.borrow_mut().child_of_layout = true; item_element.borrow_mut().child_of_layout = true;
if item_element.borrow().repeated.is_some() { if item_element.borrow().repeated.is_some() {
let rep_comp = item_element.borrow().base_type.as_component().clone(); let rep_comp = item_element.borrow().base_type.as_component().clone();
fix_explicit_percent("width", &rep_comp.root_element);
fix_explicit_percent("height", &rep_comp.root_element);
rep_comp.layouts.borrow_mut().root_constraints = rep_comp.layouts.borrow_mut().root_constraints =
LayoutConstraints::new(&rep_comp.root_element, diag); LayoutConstraints::new(&rep_comp.root_element, diag);
rep_comp.root_element.borrow_mut().child_of_layout = true; rep_comp.root_element.borrow_mut().child_of_layout = true;

View file

@ -28,6 +28,15 @@ pub struct LayoutInfo {
/// The maximum height for the item. /// The maximum height for the item.
pub max_height: f32, pub max_height: f32,
/// The minimum width in percentage of the parent (value between 0 and 100).
pub min_width_percent: f32,
/// The maximum width in percentage of the parent (value between 0 and 100).
pub max_width_percent: f32,
/// The minimum height in percentage of the parent (value between 0 and 100).
pub min_height_percent: f32,
/// The maximum height in percentage of the parent (value between 0 and 100).
pub max_height_percent: f32,
/// the horizontal stretch factor /// the horizontal stretch factor
pub horizontal_stretch: f32, pub horizontal_stretch: f32,
/// the vertical stretch factor /// the vertical stretch factor
@ -41,6 +50,10 @@ impl Default for LayoutInfo {
max_width: f32::MAX, max_width: f32::MAX,
min_height: 0., min_height: 0.,
max_height: f32::MAX, max_height: f32::MAX,
min_width_percent: 0.,
max_width_percent: 100.,
min_height_percent: 0.,
max_height_percent: 100.,
horizontal_stretch: 0., horizontal_stretch: 0.,
vertical_stretch: 0., vertical_stretch: 0.,
} }
@ -55,6 +68,10 @@ impl LayoutInfo {
max_width: self.max_width.min(other.max_width), max_width: self.max_width.min(other.max_width),
min_height: self.min_height.max(other.min_height), min_height: self.min_height.max(other.min_height),
max_height: self.max_height.min(other.max_height), max_height: self.max_height.min(other.max_height),
min_width_percent: self.min_width_percent.max(other.min_width_percent),
max_width_percent: self.max_width_percent.min(other.max_width_percent),
min_height_percent: self.min_height_percent.max(other.min_height_percent),
max_height_percent: self.max_height_percent.min(other.max_height_percent),
horizontal_stretch: self.horizontal_stretch.min(other.horizontal_stretch), horizontal_stretch: self.horizontal_stretch.min(other.horizontal_stretch),
vertical_stretch: self.vertical_stretch.min(other.vertical_stretch), vertical_stretch: self.vertical_stretch.min(other.vertical_stretch),
} }
@ -240,29 +257,35 @@ pub fn solve_grid_layout(data: &GridLayoutData) {
let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize]; let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize];
let mut col_layout_data = vec![grid_internal::LayoutData::default(); num_col as usize]; let mut col_layout_data = vec![grid_internal::LayoutData::default(); num_col as usize];
for cell in data.cells.iter() { for cell in data.cells.iter() {
let row_max = cell.constraint.max_height / (cell.rowspan as f32); let cnstr = &cell.constraint;
let row_min = cell.constraint.min_height / (cell.rowspan as f32); let row_max = cnstr.max_height.min(data.height * cnstr.max_height_percent / 100.)
let row_pref = cell.constraint.min_height / (cell.rowspan as f32); / (cell.rowspan as f32);
let row_min = cnstr.min_height.max(data.height * cnstr.min_height_percent / 100.)
/ (cell.rowspan as f32);
let row_pref = row_min;
for r in 0..(cell.rowspan as usize) { for r in 0..(cell.rowspan as usize) {
let rdata = &mut row_layout_data[cell.row as usize + r]; let rdata = &mut row_layout_data[cell.row as usize + r];
rdata.max = rdata.max.min(row_max); rdata.max = rdata.max.min(row_max);
rdata.min = rdata.min.max(row_min); rdata.min = rdata.min.max(row_min);
rdata.pref = rdata.pref.max(row_pref); rdata.pref = rdata.pref.max(row_pref);
rdata.stretch = rdata.stretch.min(cell.constraint.vertical_stretch); rdata.stretch = rdata.stretch.min(cnstr.vertical_stretch);
} }
let col_max = cell.constraint.max_width / (cell.colspan as f32); let col_max = cnstr.max_width.min(data.width * cnstr.max_width_percent / 100.)
let col_min = cell.constraint.min_width / (cell.colspan as f32); / (cell.colspan as f32);
let col_pref = cell.constraint.min_width / (cell.colspan as f32); let col_min = cnstr.min_width.max(data.width * cnstr.min_width_percent / 100.)
/ (cell.colspan as f32);
let col_pref = col_min;
for c in 0..(cell.colspan as usize) { for c in 0..(cell.colspan as usize) {
let cdata = &mut col_layout_data[cell.col as usize + c]; let cdata = &mut col_layout_data[cell.col as usize + c];
cdata.max = cdata.max.min(col_max); cdata.max = cdata.max.min(col_max);
cdata.min = cdata.min.max(col_min); cdata.min = cdata.min.max(col_min);
cdata.pref = cdata.pref.max(col_pref); cdata.pref = cdata.pref.max(col_pref);
cdata.stretch = cdata.stretch.min(cell.constraint.horizontal_stretch); cdata.stretch = cdata.stretch.min(cnstr.horizontal_stretch);
} }
} }
@ -374,6 +397,10 @@ pub fn grid_layout_info<'a>(
max_width, max_width,
min_height, min_height,
max_height, max_height,
min_width_percent: 0.,
max_width_percent: 100.,
min_height_percent: 0.,
max_height_percent: 100.,
horizontal_stretch, horizontal_stretch,
vertical_stretch, vertical_stretch,
} }
@ -502,12 +529,45 @@ pub fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
margin.bottom = Dimension::Points(data.spacing / 2.); margin.bottom = Dimension::Points(data.spacing / 2.);
} }
} }
let min = |m| if m == 0.0 { Dimension::Undefined } else { Dimension::Points(m) }; let min = |m, m_p, tot| {
let max = |m| if m == f32::MAX { Dimension::Undefined } else { Dimension::Points(m) }; if m_p <= 0.0 {
let min_size = if m <= 0.0 {
Size { width: min(cell.constraint.min_width), height: min(cell.constraint.min_height) }; Dimension::Undefined
let max_size = } else {
Size { width: max(cell.constraint.max_width), height: max(cell.constraint.max_height) }; Dimension::Points(m)
}
} else {
if m <= 0.0 {
Dimension::Percent(m_p / 100.)
} else {
Dimension::Points(m.min(m_p * tot / 100.))
}
}
};
let max = |m, m_p, tot| {
if m_p >= 100. {
if m == f32::MAX {
Dimension::Undefined
} else {
Dimension::Points(m)
}
} else {
if m == f32::MAX {
Dimension::Percent(m_p / 100.)
} else {
Dimension::Points(m.max(m_p * tot / 100.))
}
}
};
let constraint = &cell.constraint;
let min_size = Size {
width: min(constraint.min_width, constraint.min_width_percent, data.width),
height: min(constraint.min_height, constraint.min_height_percent, data.height),
};
let max_size = Size {
width: max(constraint.max_width, constraint.max_width_percent, data.width),
height: max(constraint.max_height, constraint.max_height_percent, data.height),
};
let cell_style = Style { let cell_style = Style {
min_size, min_size,
max_size, max_size,
@ -591,6 +651,10 @@ pub fn box_layout_info<'a>(
max_width, max_width,
min_height, min_height,
max_height, max_height,
min_width_percent: 0.,
max_width_percent: 100.,
min_height_percent: 0.,
max_height_percent: 100.,
horizontal_stretch, horizontal_stretch,
vertical_stretch, vertical_stretch,
} }
@ -618,6 +682,10 @@ pub fn box_layout_info<'a>(
max_width, max_width,
min_height, min_height,
max_height, max_height,
min_width_percent: 0.,
max_width_percent: 100.,
min_height_percent: 0.,
max_height_percent: 100.,
horizontal_stretch, horizontal_stretch,
vertical_stretch, vertical_stretch,
} }

View file

@ -1196,10 +1196,36 @@ fn fill_layout_info_constraints(
constraints: &LayoutConstraints, constraints: &LayoutConstraints,
expr_eval: &impl Fn(&NamedReference) -> f32, expr_eval: &impl Fn(&NamedReference) -> f32,
) { ) {
constraints.minimum_width.as_ref().map(|e| layout_info.min_width = expr_eval(e)); let is_percent =
constraints.maximum_width.as_ref().map(|e| layout_info.max_width = expr_eval(e)); |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
constraints.minimum_height.as_ref().map(|e| layout_info.min_height = expr_eval(e)); constraints.minimum_width.as_ref().map(|e| {
constraints.maximum_height.as_ref().map(|e| layout_info.max_height = expr_eval(e)); if !is_percent(e) {
layout_info.min_width = expr_eval(e)
} else {
layout_info.min_width_percent = expr_eval(e)
}
});
constraints.maximum_width.as_ref().map(|e| {
if !is_percent(e) {
layout_info.max_width = expr_eval(e)
} else {
layout_info.max_width_percent = expr_eval(e)
}
});
constraints.minimum_height.as_ref().map(|e| {
if !is_percent(e) {
layout_info.min_height = expr_eval(e)
} else {
layout_info.min_height_percent = expr_eval(e)
}
});
constraints.maximum_height.as_ref().map(|e| {
if !is_percent(e) {
layout_info.max_height = expr_eval(e)
} else {
layout_info.max_height_percent = expr_eval(e)
}
});
constraints.horizontal_stretch.as_ref().map(|e| layout_info.horizontal_stretch = expr_eval(e)); constraints.horizontal_stretch.as_ref().map(|e| layout_info.horizontal_stretch = expr_eval(e));
constraints.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e)); constraints.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e));
} }

View file

@ -0,0 +1,96 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 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 */
TestCase := Rectangle {
width: 300phx;
height: 300phx;
VerticalLayout {
spacing: 0phx;
padding: 0phx;
HorizontalLayout {
spacing: 0phx;
padding: 0phx;
rect1 := Rectangle {
background: red;
width: 10%;
height: 20%;
}
rect2 := Rectangle {
background: blue;
height: 90px;
}
}
rect3 := Rectangle {
background: green;
height: 15%;
width: 100%;
}
HorizontalLayout {
spacing: 0phx;
padding: 0phx;
rect4 := Rectangle {
background: cyan;
}
rect5 := Rectangle {
background: yellow;
width: 90%;
}
}
}
property <length> expected_y1: 90phx;
property <length> expected_y2: 90phx + 300phx * 0.15;
property <length> expected_x1: 30phx;
property <bool> rect1_pos_ok: rect1.x == 0phx && rect1.y == 0phx && rect1.width == expected_x1 && rect1.height == expected_y1 * 0.2;
property <bool> rect2_pos_ok: rect2.x == expected_x1 && rect2.y == 0phx && rect2.width == 270phx && rect2.height == expected_y1;
property <bool> rect3_pos_ok: rect3.x == 0phx && rect3.y == expected_y1 && rect3.width == 300phx && rect3.height == 300phx * 0.15;
property <bool> rect4_pos_ok: rect4.x == 0phx && rect4.y == expected_y2 && rect4.width == expected_x1 && rect4.height == 300phx - expected_y2;
property <bool> rect5_pos_ok: rect5.x == expected_x1 && rect5.y == expected_y2 && rect5.width == 270phx && rect5.height == 300phx - expected_y2;
}
/*
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
sixtyfps::testing::send_mouse_click(&instance, 5., 95.);
assert(instance.get_rect1_pos_ok());
assert(instance.get_rect2_pos_ok());
assert(instance.get_rect3_pos_ok());
assert(instance.get_rect4_pos_ok());
assert(instance.get_rect5_pos_ok());
```
```rust
let instance = TestCase::new();
sixtyfps::testing::send_mouse_click(&instance, 5., 95.);
assert!(instance.get_rect1_pos_ok());
assert!(instance.get_rect2_pos_ok());
assert!(instance.get_rect3_pos_ok());
assert!(instance.get_rect4_pos_ok());
assert!(instance.get_rect5_pos_ok());
```
```js
var instance = new sixtyfps.TestCase();
instance.send_mouse_click(5., 5.);
assert(instance.rect1_pos_ok);
assert(instance.rect2_pos_ok);
assert(instance.rect3_pos_ok);
assert(instance.rect4_pos_ok);
assert(instance.rect5_pos_ok);
```
*/

View file

@ -60,27 +60,32 @@ TestCase := Rectangle {
```cpp ```cpp
auto handle = TestCase::create(); auto handle = TestCase::create();
const TestCase &instance = *handle; const TestCase &instance = *handle;
TestCase::apply_layout({&TestCase::static_vtable, const_cast<TestCase*>(&instance) }, sixtyfps::Rect{0, 0, 300, 300}); sixtyfps::testing::send_mouse_click(&instance, 5., 95.);
assert(instance.get_rect1_pos_ok()); assert(instance.get_rect1_pos_ok());
assert(instance.get_rect2_pos_ok()); assert(instance.get_rect2_pos_ok());
assert(instance.get_rect3_pos_ok()); assert(instance.get_rect3_pos_ok());
assert(instance.get_rect4_pos_ok()); assert(instance.get_rect4_pos_ok());
assert(instance.get_rect5_pos_ok()); assert(instance.get_rect5_pos_ok());
``` ```
```rust ```rust
let instance = TestCase::new(); let instance = TestCase::new();
sixtyfps::testing::apply_layout(&instance, sixtyfps::re_exports::Rect::new(Default::default(), sixtyfps::re_exports::Size::new(300., 300.))); sixtyfps::testing::send_mouse_click(&instance, 5., 95.);
assert!(instance.get_rect1_pos_ok()); assert!(instance.get_rect1_pos_ok());
assert!(instance.get_rect2_pos_ok()); assert!(instance.get_rect2_pos_ok());
assert!(instance.get_rect3_pos_ok()); assert!(instance.get_rect3_pos_ok());
assert!(instance.get_rect4_pos_ok()); assert!(instance.get_rect4_pos_ok());
assert!(instance.get_rect5_pos_ok()); assert!(instance.get_rect5_pos_ok());
``` ```
// FIXME! interpreter test
```js
var instance = new sixtyfps.TestCase();
instance.send_mouse_click(5., 5.);
assert(instance.rect1_pos_ok);
assert(instance.rect2_pos_ok);
assert(instance.rect3_pos_ok);
assert(instance.rect4_pos_ok);
assert(instance.rect5_pos_ok);
```
*/ */

View file

@ -11,7 +11,6 @@ path = "main.rs"
name = "test-driver-nodejs" name = "test-driver-nodejs"
[dev-dependencies] [dev-dependencies]
sixtyfps-compilerlib = { path = "../../../sixtyfps_compiler", features = ["cpp", "display-diagnostics"] }
test_driver_lib = { path = "../driverlib" } test_driver_lib = { path = "../driverlib" }
lazy_static = "1.4.0" lazy_static = "1.4.0"
which = "4.0.2" which = "4.0.2"