diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index f39ad0b89..a9fe869bb 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -339,6 +339,10 @@ inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const std::min(max_width, other.max_width), std::max(min_height, other.min_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(vertical_stretch, other.vertical_stretch) }; } diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index 9c7224342..0da54920a 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -1972,14 +1972,12 @@ fn get_layout_info_ref<'a, 'b>( }; if item.constraints.has_explicit_restrictions() { layout_info = format!("[&]{{ auto layout_info = {};", layout_info); - for (expr, name) in item.constraints.for_each_restrictions().iter() { - if let Some(e) = expr { - layout_info += &format!( - " layout_info.{} = {}.get();", - name, - access_named_reference(e, component, "self") - ); - } + for (expr, name) in item.constraints.for_each_restrictions() { + layout_info += &format!( + " layout_info.{} = {}.get();", + name, + access_named_reference(expr, component, "self") + ); } layout_info += " return layout_info; }()"; } @@ -2251,14 +2249,12 @@ fn compute_layout( let root_constraints = &component.layouts.borrow().root_constraints; if root_constraints.has_explicit_restrictions() { layout_info.push("auto layout_info_other = layout_info;".into()); - for (expr, name) in root_constraints.for_each_restrictions().iter() { - if let Some(e) = expr { - layout_info.push(format!( - "layout_info_other.{} = {}.get();", - name, - access_named_reference(e, component, "self") - )); - } + for (expr, name) in root_constraints.for_each_restrictions() { + layout_info.push(format!( + "layout_info_other.{} = {}.get();", + name, + access_named_reference(expr, component, "self") + )); } layout_info.push("return layout_info.merge(layout_info_other);".into()); } else { diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index 24b2b91ac..6790b49de 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -1682,11 +1682,8 @@ fn apply_layout_constraint( if constraints.has_explicit_restrictions() { let (name, expr): (Vec<_>, Vec<_>) = constraints .for_each_restrictions() - .iter() - .filter_map(|(e, s)| { - e.as_ref().map(|e| { - (format_ident!("{}", s), access_named_reference(e, component, quote!(_self))) - }) + .map(|(e, s)| { + (format_ident!("{}", s), access_named_reference(e, component, quote!(_self))) }) .unzip(); quote!({ diff --git a/sixtyfps_compiler/layout.rs b/sixtyfps_compiler/layout.rs index b5bb3affc..00392ff17 100644 --- a/sixtyfps_compiler/layout.rs +++ b/sixtyfps_compiler/layout.rs @@ -172,14 +172,18 @@ impl LayoutConstraints { }; let e = element.borrow(); 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.maximum_height); - constraints.fixed_height = true; }); 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.maximum_width); - constraints.fixed_width = true; }); constraints @@ -194,15 +198,41 @@ impl LayoutConstraints { || self.vertical_stretch.is_some() } - pub fn for_each_restrictions<'a>(&'a self) -> [(&Option, &'static str); 6] { - [ - (&self.minimum_width, "min_width"), - (&self.maximum_width, "max_width"), - (&self.minimum_height, "min_height"), - (&self.maximum_height, "max_height"), - (&self.horizontal_stretch, "horizontal_stretch"), - (&self.vertical_stretch, "vertical_stretch"), - ] + // 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>( + &'a self, + ) -> impl Iterator { + std::iter::empty() + .chain(self.minimum_width.as_ref().map(|x| { + if Expression::PropertyReference(x.clone()).ty() != Type::Percent { + (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)) { diff --git a/sixtyfps_compiler/passes/lower_layout.rs b/sixtyfps_compiler/passes/lower_layout.rs index df36b9794..39951ac27 100644 --- a/sixtyfps_compiler/passes/lower_layout.rs +++ b/sixtyfps_compiler/passes/lower_layout.rs @@ -293,12 +293,36 @@ fn create_layout_item( style_metrics: &Option>, diag: &mut BuildDiagnostics, ) -> Option { + 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); item_element.borrow_mut().child_of_layout = true; if item_element.borrow().repeated.is_some() { 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 = LayoutConstraints::new(&rep_comp.root_element, diag); rep_comp.root_element.borrow_mut().child_of_layout = true; diff --git a/sixtyfps_runtime/corelib/layout.rs b/sixtyfps_runtime/corelib/layout.rs index 25350c5cc..8787d6251 100644 --- a/sixtyfps_runtime/corelib/layout.rs +++ b/sixtyfps_runtime/corelib/layout.rs @@ -28,6 +28,15 @@ pub struct LayoutInfo { /// The maximum height for the item. 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 pub horizontal_stretch: f32, /// the vertical stretch factor @@ -41,6 +50,10 @@ impl Default for LayoutInfo { max_width: f32::MAX, min_height: 0., max_height: f32::MAX, + min_width_percent: 0., + max_width_percent: 100., + min_height_percent: 0., + max_height_percent: 100., horizontal_stretch: 0., vertical_stretch: 0., } @@ -55,6 +68,10 @@ impl LayoutInfo { max_width: self.max_width.min(other.max_width), min_height: self.min_height.max(other.min_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), 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 col_layout_data = vec![grid_internal::LayoutData::default(); num_col as usize]; + for cell in data.cells.iter() { - let row_max = cell.constraint.max_height / (cell.rowspan as f32); - let row_min = cell.constraint.min_height / (cell.rowspan as f32); - let row_pref = cell.constraint.min_height / (cell.rowspan as f32); + let cnstr = &cell.constraint; + let row_max = cnstr.max_height.min(data.height * cnstr.max_height_percent / 100.) + / (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) { let rdata = &mut row_layout_data[cell.row as usize + r]; rdata.max = rdata.max.min(row_max); rdata.min = rdata.min.max(row_min); 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_min = cell.constraint.min_width / (cell.colspan as f32); - let col_pref = cell.constraint.min_width / (cell.colspan as f32); + let col_max = cnstr.max_width.min(data.width * cnstr.max_width_percent / 100.) + / (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) { let cdata = &mut col_layout_data[cell.col as usize + c]; cdata.max = cdata.max.min(col_max); cdata.min = cdata.min.max(col_min); 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, min_height, max_height, + min_width_percent: 0., + max_width_percent: 100., + min_height_percent: 0., + max_height_percent: 100., horizontal_stretch, vertical_stretch, } @@ -502,12 +529,45 @@ pub fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) { margin.bottom = Dimension::Points(data.spacing / 2.); } } - let min = |m| if m == 0.0 { Dimension::Undefined } else { Dimension::Points(m) }; - let max = |m| if m == f32::MAX { Dimension::Undefined } else { Dimension::Points(m) }; - let min_size = - Size { width: min(cell.constraint.min_width), height: min(cell.constraint.min_height) }; - let max_size = - Size { width: max(cell.constraint.max_width), height: max(cell.constraint.max_height) }; + let min = |m, m_p, tot| { + if m_p <= 0.0 { + if m <= 0.0 { + Dimension::Undefined + } else { + 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 { min_size, max_size, @@ -591,6 +651,10 @@ pub fn box_layout_info<'a>( max_width, min_height, max_height, + min_width_percent: 0., + max_width_percent: 100., + min_height_percent: 0., + max_height_percent: 100., horizontal_stretch, vertical_stretch, } @@ -618,6 +682,10 @@ pub fn box_layout_info<'a>( max_width, min_height, max_height, + min_width_percent: 0., + max_width_percent: 100., + min_height_percent: 0., + max_height_percent: 100., horizontal_stretch, vertical_stretch, } diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index bb4407664..e4e07791e 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -1196,10 +1196,36 @@ fn fill_layout_info_constraints( constraints: &LayoutConstraints, expr_eval: &impl Fn(&NamedReference) -> f32, ) { - constraints.minimum_width.as_ref().map(|e| layout_info.min_width = expr_eval(e)); - constraints.maximum_width.as_ref().map(|e| layout_info.max_width = expr_eval(e)); - constraints.minimum_height.as_ref().map(|e| layout_info.min_height = expr_eval(e)); - constraints.maximum_height.as_ref().map(|e| layout_info.max_height = expr_eval(e)); + let is_percent = + |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent; + constraints.minimum_width.as_ref().map(|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.vertical_stretch.as_ref().map(|e| layout_info.vertical_stretch = expr_eval(e)); } diff --git a/tests/cases/layout/box_precentages.60 b/tests/cases/layout/box_precentages.60 new file mode 100644 index 000000000..bc8f2f707 --- /dev/null +++ b/tests/cases/layout/box_precentages.60 @@ -0,0 +1,96 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + 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 expected_y1: 90phx; + property expected_y2: 90phx + 300phx * 0.15; + property expected_x1: 30phx; + + property rect1_pos_ok: rect1.x == 0phx && rect1.y == 0phx && rect1.width == expected_x1 && rect1.height == expected_y1 * 0.2; + property rect2_pos_ok: rect2.x == expected_x1 && rect2.y == 0phx && rect2.width == 270phx && rect2.height == expected_y1; + property rect3_pos_ok: rect3.x == 0phx && rect3.y == expected_y1 && rect3.width == 300phx && rect3.height == 300phx * 0.15; + property rect4_pos_ok: rect4.x == 0phx && rect4.y == expected_y2 && rect4.width == expected_x1 && rect4.height == 300phx - expected_y2; + property 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); +``` +*/ \ No newline at end of file diff --git a/tests/cases/layout/grid_fixed_size.60 b/tests/cases/layout/grid_fixed_size.60 index 18446ee5b..bb124d88c 100644 --- a/tests/cases/layout/grid_fixed_size.60 +++ b/tests/cases/layout/grid_fixed_size.60 @@ -60,27 +60,32 @@ TestCase := Rectangle { ```cpp auto handle = TestCase::create(); const TestCase &instance = *handle; -TestCase::apply_layout({&TestCase::static_vtable, const_cast(&instance) }, sixtyfps::Rect{0, 0, 300, 300}); +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::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_rect2_pos_ok()); assert!(instance.get_rect3_pos_ok()); assert!(instance.get_rect4_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); +``` */ \ No newline at end of file diff --git a/tests/driver/nodejs/Cargo.toml b/tests/driver/nodejs/Cargo.toml index 13e7ad963..10d1d2574 100644 --- a/tests/driver/nodejs/Cargo.toml +++ b/tests/driver/nodejs/Cargo.toml @@ -11,7 +11,6 @@ path = "main.rs" name = "test-driver-nodejs" [dev-dependencies] -sixtyfps-compilerlib = { path = "../../../sixtyfps_compiler", features = ["cpp", "display-diagnostics"] } test_driver_lib = { path = "../driverlib" } lazy_static = "1.4.0" which = "4.0.2"