Introduce layout stretching

This commit is contained in:
Olivier Goffart 2020-10-26 16:33:38 +01:00
parent 7ff0b4b73f
commit 47be71e16d
10 changed files with 134 additions and 33 deletions

View file

@ -271,11 +271,15 @@ inline FocusEventResult process_focus_event(ComponentRef component, int64_t &foc
using cbindgen_private::grid_layout_info;
using cbindgen_private::GridLayoutCellData;
using cbindgen_private::GridLayoutData;
using cbindgen_private::BoxLayoutData;
using cbindgen_private::BoxLayoutCellData;
using cbindgen_private::LayoutInfo;
using cbindgen_private::Padding;
using cbindgen_private::PathLayoutData;
using cbindgen_private::PathLayoutItemData;
using cbindgen_private::solve_grid_layout;
using cbindgen_private::solve_box_layout;
using cbindgen_private::box_layout_info;
using cbindgen_private::solve_path_layout;
// models

View file

@ -64,7 +64,7 @@ MainWindow := Window {
}
}
HorizontalLayout {
Rectangle {} // Fixme: remove and use alignement instead
Rectangle { horizontal-stretch: 1; } // Fixme: remove and use alignement instead
Button {
text: "Remove Done Items";
clicked => { root.remove_done(); }

View file

@ -1529,11 +1529,11 @@ impl crate::layout::gen::Language for CppLanguageLayoutGen {
let mut creation_code = cells;
creation_code.insert(
0,
format!(" sixtyfps::GridLayoutCellData {}_data[] = {{", cell_ref_variable,),
format!(" sixtyfps::BoxLayoutCellData {}_data[] = {{", cell_ref_variable,),
);
creation_code.push(" };".to_owned());
creation_code.push(format!(
" const sixtyfps::Slice<sixtyfps::GridLayoutCellData> {cv}{{{cv}_data, std::size({cv}_data)}};",
" const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> {cv}{{{cv}_data, std::size({cv}_data)}};",
cv = cell_ref_variable
));

View file

@ -119,6 +119,8 @@ pub struct LayoutConstraints {
pub maximum_width: Option<NamedReference>,
pub minimum_height: Option<NamedReference>,
pub maximum_height: Option<NamedReference>,
pub horizontal_stretch: Option<NamedReference>,
pub vertical_stretch: Option<NamedReference>,
}
impl LayoutConstraints {
@ -128,6 +130,8 @@ impl LayoutConstraints {
maximum_width: binding_reference(&element, "maximum_width"),
minimum_height: binding_reference(&element, "minimum_height"),
maximum_height: binding_reference(&element, "maximum_height"),
horizontal_stretch: binding_reference(&element, "horizontal_stretch"),
vertical_stretch: binding_reference(&element, "vertical_stretch"),
}
}
@ -136,24 +140,28 @@ impl LayoutConstraints {
|| self.maximum_width.is_some()
|| self.minimum_height.is_some()
|| self.maximum_height.is_some()
|| self.horizontal_stretch.is_some()
|| self.vertical_stretch.is_some()
}
pub fn for_each_restrictions<'a>(&'a self) -> [(&Option<NamedReference>, &'static str); 4] {
pub fn for_each_restrictions<'a>(&'a self) -> [(&Option<NamedReference>, &'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"),
]
}
}
impl LayoutConstraints {
fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
self.maximum_width.as_mut().map(|e| visitor(&mut *e));
self.minimum_width.as_mut().map(|e| visitor(&mut *e));
self.maximum_height.as_mut().map(|e| visitor(&mut *e));
self.minimum_height.as_mut().map(|e| visitor(&mut *e));
self.horizontal_stretch.as_mut().map(|e| visitor(&mut *e));
self.vertical_stretch.as_mut().map(|e| visitor(&mut *e));
}
}

View file

@ -30,6 +30,8 @@ pub fn reserved_property(name: &str) -> Type {
("padding_right", Type::Length),
("padding_top", Type::Length),
("padding_bottom", Type::Length),
("horizontal_stretch", Type::Float32),
("vertical_stretch", Type::Float32),
("clip", Type::Bool),
("opacity", Type::Float32),
("visible", Type::Bool),

View file

@ -466,12 +466,7 @@ impl Item for Text {
let font = fc.find_font(&font_family, font_size);
let width = font.text_width(&text);
let height = font.height();
LayoutInfo {
min_width: width,
max_width: f32::MAX,
min_height: height,
max_height: height,
}
LayoutInfo { min_width: width, min_height: height, ..LayoutInfo::default() }
})
}
@ -1019,12 +1014,16 @@ impl Item for TextInput {
}
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
let text = Self::FIELD_OFFSETS.text.apply_pin(self).get();
let (width, height) = TextInput::with_font(self, window, |font| {
(font.text_width("********************"), font.height())
});
let (width, height) =
TextInput::with_font(self, window, |font| (font.text_width(&text), font.height()));
LayoutInfo { min_width: width, max_width: f32::MAX, min_height: height, max_height: height }
LayoutInfo {
min_width: width,
min_height: height,
horizontal_stretch: 1.,
..LayoutInfo::default()
}
}
fn input_event(

View file

@ -27,11 +27,23 @@ pub struct LayoutInfo {
pub min_height: f32,
/// The maximum height for the item.
pub max_height: f32,
/// the horizontal stretch factor
pub horizontal_stretch: f32,
/// the vertical stretch factor
pub vertical_stretch: f32,
}
impl Default for LayoutInfo {
fn default() -> Self {
LayoutInfo { min_width: 0., max_width: f32::MAX, min_height: 0., max_height: f32::MAX }
LayoutInfo {
min_width: 0.,
max_width: f32::MAX,
min_height: 0.,
max_height: f32::MAX,
horizontal_stretch: 0.,
vertical_stretch: 0.,
}
}
}
@ -43,6 +55,8 @@ 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),
horizontal_stretch: self.horizontal_stretch.min(other.horizontal_stretch),
vertical_stretch: self.vertical_stretch.min(other.vertical_stretch),
}
}
}
@ -65,7 +79,7 @@ mod grid_internal {
impl Default for LayoutData {
fn default() -> Self {
LayoutData { min: 0., max: Coord::MAX, pref: 0., stretch: 1., pos: 0., size: 0. }
LayoutData { min: 0., max: Coord::MAX, pref: 0., stretch: f32::MAX, pos: 0., size: 0. }
}
}
@ -111,7 +125,7 @@ mod grid_internal {
min_size: Size { width: min, height: Dimension::Auto },
max_size: Size { width: max, height: Dimension::Auto },
size: Size { width: pref, height: Dimension::Auto },
flex_grow: 1.,
flex_grow: cell.stretch,
margin,
..Default::default()
};
@ -238,6 +252,7 @@ pub extern "C" fn solve_grid_layout(data: &GridLayoutData) {
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);
}
let col_max = cell.constraint.max_width / (cell.colspan as f32);
@ -249,9 +264,17 @@ pub extern "C" fn solve_grid_layout(data: &GridLayoutData) {
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);
}
}
let normalize_stretch = |v: &mut Vec<grid_internal::LayoutData>| {
let s = v.iter().map(|x| x.stretch).sum::<f32>();
v.iter_mut().for_each(|x| x.stretch = if s == 0. { 1. } else { x.stretch / s })
};
normalize_stretch(&mut row_layout_data);
normalize_stretch(&mut col_layout_data);
grid_internal::layout_items(
&mut row_layout_data,
data.y + data.padding.top,
@ -299,7 +322,7 @@ pub extern "C" fn grid_layout_info<'a>(
}
if num_col < 1 || num_row < 1 {
return LayoutInfo { min_width: 0., max_width: 0., min_height: 0., max_height: 0. };
return LayoutInfo { max_width: 0., max_height: 0., ..LayoutInfo::default() };
};
let mut row_layout_data = vec![grid_internal::LayoutData::default(); num_row as usize];
@ -313,6 +336,8 @@ pub extern "C" fn grid_layout_info<'a>(
cdata.min = cdata.min.max(cell.constraint.min_width);
rdata.pref = rdata.pref.max(cell.constraint.min_height);
cdata.pref = cdata.pref.max(cell.constraint.min_width);
rdata.stretch = rdata.stretch.min(cell.constraint.vertical_stretch);
cdata.stretch = cdata.stretch.min(cell.constraint.horizontal_stretch);
}
let spacing_h = spacing * (num_row - 1) as Coord;
@ -335,7 +360,17 @@ pub extern "C" fn grid_layout_info<'a>(
+ padding.left
+ padding.right;
LayoutInfo { min_width, max_width, min_height, max_height }
let horizontal_stretch = col_layout_data.iter().map(|data| data.stretch).sum::<Coord>();
let vertical_stretch = row_layout_data.iter().map(|data| data.stretch).sum::<Coord>();
LayoutInfo {
min_width,
max_width,
min_height,
max_height,
horizontal_stretch,
vertical_stretch,
}
}
#[repr(C)]
@ -381,6 +416,15 @@ pub extern "C" fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
..Default::default()
};
let stretch_factor = |cell: &BoxLayoutCellData| {
if is_horizontal {
cell.constraint.horizontal_stretch
} else {
cell.constraint.vertical_stretch
}
};
let stretch_factor_sum = data.cells.iter().map(stretch_factor).sum::<Coord>();
let flex_box = stretch.new_node(box_style, vec![]).unwrap();
for (index, cell) in data.cells.iter().enumerate() {
@ -409,7 +453,12 @@ pub extern "C" fn solve_box_layout(data: &BoxLayoutData, is_horizontal: bool) {
let cell_style = Style {
min_size,
max_size,
flex_grow: 1.,
flex_grow: if stretch_factor_sum == 0. {
1.
} else {
stretch_factor(cell) / stretch_factor_sum
},
flex_basis: if is_horizontal { min_size.width } else { min_size.height },
margin,
align_self: AlignSelf::Stretch,
..Default::default()
@ -452,7 +501,7 @@ pub extern "C" fn box_layout_info<'a>(
) -> LayoutInfo {
let count = cells.len();
if count < 1 {
return LayoutInfo { min_width: 0., max_width: 0., min_height: 0., max_height: 0. };
return LayoutInfo { max_width: 0., max_height: 0., ..LayoutInfo::default() };
};
let order_float = |a: &Coord, b: &Coord| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal);
@ -467,7 +516,17 @@ pub extern "C" fn box_layout_info<'a>(
+ padding.bottom;
let min_width = cells.iter().map(|c| c.constraint.min_width).sum::<Coord>() + extra_w;
let max_width = cells.iter().map(|c| c.constraint.max_width).sum::<Coord>() + extra_w;
LayoutInfo { min_width, max_width, min_height, max_height }
let horizontal_stretch = cells.iter().map(|c| c.constraint.horizontal_stretch).sum::<f32>();
let vertical_stretch =
cells.iter().map(|c| c.constraint.vertical_stretch).min_by(order_float).unwrap();
LayoutInfo {
min_width,
max_width,
min_height,
max_height,
horizontal_stretch,
vertical_stretch,
}
} else {
let extra_h = padding.top + padding.bottom + spacing * (count - 1) as Coord;
@ -479,7 +538,17 @@ pub extern "C" fn box_layout_info<'a>(
+ padding.right;
let min_height = cells.iter().map(|c| c.constraint.min_height).sum::<Coord>() + extra_h;
let max_height = cells.iter().map(|c| c.constraint.max_height).sum::<Coord>() + extra_h;
LayoutInfo { min_width, max_width, min_height, max_height }
let horizontal_stretch =
cells.iter().map(|c| c.constraint.horizontal_stretch).min_by(order_float).unwrap();
let vertical_stretch = cells.iter().map(|c| c.constraint.vertical_stretch).sum::<f32>();
LayoutInfo {
min_width,
max_width,
min_height,
max_height,
horizontal_stretch,
vertical_stretch,
}
}
}

View file

@ -988,6 +988,8 @@ fn fill_layout_info_constraints(
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));
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));
}
fn collect_layouts_recursively<'a, 'b>(

View file

@ -176,8 +176,6 @@ impl Item for NativeButton {
LayoutInfo {
min_width: size.width as f32,
min_height: size.height as f32,
max_width: size.width as f32,
max_height: size.height as f32,
..LayoutInfo::default()
}
}
@ -299,6 +297,7 @@ impl Item for NativeCheckBox {
min_width: size.width as f32,
min_height: size.height as f32,
max_height: size.height as f32,
horizontal_stretch: 1.,
..LayoutInfo::default()
}
}
@ -452,6 +451,7 @@ impl Item for NativeSpinBox {
min_width: size.width as f32,
min_height: size.height as f32,
max_height: size.height as f32,
horizontal_stretch: 1.,
..LayoutInfo::default()
}
}
@ -649,6 +649,7 @@ impl Item for NativeSlider {
min_width: size.width as f32,
min_height: size.height as f32,
max_height: size.height as f32,
horizontal_stretch: 1.,
..LayoutInfo::default()
}
}
@ -895,7 +896,13 @@ impl Item for NativeGroupBox {
let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get();
let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get();
let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get();
LayoutInfo { min_width: left + right, min_height: top + bottom, ..LayoutInfo::default() }
LayoutInfo {
min_width: left + right,
min_height: top + bottom,
horizontal_stretch: 1.,
vertical_stretch: 1.,
..LayoutInfo::default()
}
}
fn input_event(
@ -1036,7 +1043,12 @@ impl Item for NativeLineEdit {
let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get();
let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get();
let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get();
LayoutInfo { min_width: left + right, min_height: top + bottom, ..LayoutInfo::default() }
LayoutInfo {
min_width: left + right,
min_height: top + bottom,
horizontal_stretch: 1.,
..LayoutInfo::default()
}
}
fn input_event(
@ -1268,7 +1280,13 @@ impl Item for NativeScrollView {
let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get();
let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get();
let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get();
LayoutInfo { min_width: left + right, min_height: top + bottom, ..LayoutInfo::default() }
LayoutInfo {
min_width: left + right,
min_height: top + bottom,
horizontal_stretch: 1.,
vertical_stretch: 1.,
..LayoutInfo::default()
}
}
fn input_event(

View file

@ -33,8 +33,8 @@ TestCase := Rectangle {
}
}
rect_blue := Rectangle {
horizontal-stretch: 1;
color: blue;
minimum_width: 100phx; // FIXME: replace with a smaller stretch factor
}
}
rect_yellow := Rectangle {
@ -131,7 +131,6 @@ let instance = TestCase::new();
let instance = instance.as_ref();
use sixtyfps::re_exports::Component;
instance.compute_layout();
println!("{:?}", instance.get_debug_());
assert!(instance.get_rect_blue_ok());
assert!(instance.get_rect_orange_ok());
assert!(instance.get_rect_red_ok());