mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Introduce layout stretching
This commit is contained in:
parent
7ff0b4b73f
commit
47be71e16d
10 changed files with 134 additions and 33 deletions
|
@ -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
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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
|
||||
));
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue