Skip to content

Commit c5e5a98

Browse files
committed
feat(layout): support the gap property
1 parent bdcfe14 commit c5e5a98

File tree

2 files changed

+190
-106
lines changed

2 files changed

+190
-106
lines changed

float-pigment-layout/src/algo/flex_box.rs

Lines changed: 184 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@ use crate::*;
22

33
use float_pigment_css::num_traits::Zero;
44

5+
/// Computes the total space taken up by gaps in an axis given:
6+
/// - The size of each gap
7+
/// - The number of items (children or flex-lines) between which there are gaps
8+
#[inline(always)]
9+
fn sum_axis_gaps<L: LengthNum>(gap: L, num_items: usize) -> L {
10+
if num_items <= 1 {
11+
L::zero()
12+
} else {
13+
gap.mul_i32(num_items as i32 - 1)
14+
}
15+
}
16+
17+
#[inline(always)]
18+
fn main_gap<T: LayoutTreeNode>(
19+
style: &T::Style,
20+
dir: AxisDirection,
21+
) -> DefLength<T::Length, T::LengthCustom> {
22+
match dir {
23+
AxisDirection::Horizontal => style.row_gap(),
24+
AxisDirection::Vertical => style.column_gap(),
25+
}
26+
}
27+
28+
#[inline(always)]
29+
fn cross_gap<T: LayoutTreeNode>(
30+
style: &T::Style,
31+
dir: AxisDirection,
32+
) -> DefLength<T::Length, T::LengthCustom> {
33+
match dir {
34+
AxisDirection::Horizontal => style.column_gap(),
35+
AxisDirection::Vertical => style.row_gap(),
36+
}
37+
}
38+
539
pub(crate) fn align_self<T: LayoutTreeNode>(child: &T::Style, parent: &T::Style) -> AlignSelf {
640
let s = child.align_self();
741
if s == AlignSelf::Auto {
@@ -335,15 +369,24 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
335369
});
336370
} else {
337371
let mut flex_items = &mut flex_items[..];
372+
let main_axis_gap = main_gap::<T>(style, dir)
373+
.resolve(requested_inner_size.main_size(dir), node)
374+
.or_zero();
338375
while !flex_items.is_empty() {
339376
let mut line_length = T::Length::zero();
340377
let index = flex_items
341378
.iter()
342379
.enumerate()
343-
.find(|(idx, child)| {
344-
line_length += child.hypothetical_outer_size.main_size(dir);
380+
.find(|&(idx, child)| {
381+
let gap_contribution = if idx == 0 {
382+
T::Length::zero()
383+
} else {
384+
main_axis_gap
385+
};
386+
line_length +=
387+
child.hypothetical_outer_size.main_size(dir) + gap_contribution;
345388
match available_space.main_size(dir).val() {
346-
Some(x) => line_length > x && *idx != 0,
389+
Some(x) => line_length > x && idx != 0,
347390
None => false,
348391
}
349392
})
@@ -369,16 +412,23 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
369412

370413
let multi_flex_line = flex_lines.len() > 1;
371414
for line in &mut flex_lines {
415+
let total_main_axis_gap = sum_axis_gaps(
416+
main_gap::<T>(style, dir)
417+
.resolve(requested_inner_size.main_size(dir), node)
418+
.or_zero(),
419+
line.items.len(),
420+
);
372421
// 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
373422
// items on the line. If the sum is less than the flex container’s inner main size,
374423
// use the flex grow factor for the rest of this algorithm; otherwise, use the
375424
// flex shrink factor.
376425

377-
let used_flex_factor: T::Length = length_sum(
378-
line.items
379-
.iter()
380-
.map(|child| child.hypothetical_outer_size.main_size(dir)),
381-
);
426+
let used_flex_factor: T::Length = total_main_axis_gap
427+
+ length_sum(
428+
line.items
429+
.iter()
430+
.map(|child| child.hypothetical_outer_size.main_size(dir)),
431+
);
382432
let growing = used_flex_factor < requested_inner_size.main_size(dir).or_zero();
383433
let shrinking = !growing;
384434

@@ -469,14 +519,15 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
469519
// and subtract this from the flex container’s inner main size. For frozen items,
470520
// use their outer target main size; for other items, use their outer flex base size.
471521

472-
let used_space: T::Length = length_sum(line.items.iter().map(|child| {
473-
child.margin.main_axis_sum(dir)
474-
+ if child.frozen {
475-
child.target_size.main_size(dir)
476-
} else {
477-
child.flex_basis
478-
}
479-
}));
522+
let used_space: T::Length = total_main_axis_gap
523+
+ length_sum(line.items.iter().map(|child| {
524+
child.margin.main_axis_sum(dir)
525+
+ if child.frozen {
526+
child.target_size.main_size(dir)
527+
} else {
528+
child.flex_basis
529+
}
530+
}));
480531
let initial_free_space = (target_len - used_space).or_zero();
481532
let mut prev_free_space: Option<T::Length> = None;
482533

@@ -496,14 +547,15 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
496547
// value is less than the magnitude of the remaining free space, use this
497548
// as the remaining free space.
498549

499-
let used_space: T::Length = length_sum(line.items.iter().map(|child| {
500-
child.margin.main_axis_sum(dir)
501-
+ if child.frozen {
502-
child.target_size.main_size(dir)
503-
} else {
504-
child.flex_basis
505-
}
506-
}));
550+
let used_space: T::Length = total_main_axis_gap
551+
+ length_sum(line.items.iter().map(|child| {
552+
child.margin.main_axis_sum(dir)
553+
+ if child.frozen {
554+
child.target_size.main_size(dir)
555+
} else {
556+
child.flex_basis
557+
}
558+
}));
507559

508560
let mut unfrozen: Vec<&mut FlexItem<T>> = line
509561
.items
@@ -518,12 +570,10 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
518570
(flex_grow + child.flex_grow, flex_shrink + child.flex_shrink)
519571
});
520572
let free_space = if growing && sum_flex_grow < 1.0 {
521-
initial_free_space
522-
.mul_f32(sum_flex_grow)
573+
(initial_free_space.mul_f32(sum_flex_grow) - total_main_axis_gap)
523574
.maybe_min(target_len - used_space)
524575
} else if shrinking && sum_flex_shrink < 1.0 {
525-
initial_free_space
526-
.mul_f32(sum_flex_shrink)
576+
(initial_free_space.mul_f32(sum_flex_shrink) - total_main_axis_gap)
527577
.maybe_max(target_len - used_space)
528578
} else {
529579
(target_len - used_space).or_zero()
@@ -852,8 +902,14 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
852902
let requested_cross_size = requested_size.cross_size(dir).or_zero();
853903
let min_inner_cross =
854904
self_min_max_limit.cross_size(requested_cross_size, dir) - padding_border_cross;
905+
let total_cross_axis_gap = sum_axis_gaps(
906+
cross_gap::<T>(style, dir)
907+
.resolve(requested_inner_size.main_size(dir), node)
908+
.or_zero(),
909+
flex_lines.len(),
910+
);
855911
let line_total_cross: T::Length =
856-
length_sum(flex_lines.iter().map(|line| line.cross_size));
912+
length_sum(flex_lines.iter().map(|line| line.cross_size)) + total_cross_axis_gap;
857913

858914
if line_total_cross < min_inner_cross {
859915
let remaining = min_inner_cross - line_total_cross;
@@ -980,11 +1036,18 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
9801036
// 2. Align the items along the main-axis per justify-content.
9811037

9821038
for line in &mut flex_lines {
983-
let used_space: T::Length = length_sum(
984-
line.items
985-
.iter()
986-
.map(|child| child.outer_target_size.main_size(dir)),
1039+
let total_main_axis_gap = sum_axis_gaps(
1040+
main_gap::<T>(style, dir)
1041+
.resolve(requested_inner_size.main_size(dir), node)
1042+
.or_zero(),
1043+
line.items.len(),
9871044
);
1045+
let used_space: T::Length = total_main_axis_gap
1046+
+ length_sum(
1047+
line.items
1048+
.iter()
1049+
.map(|child| child.outer_target_size.main_size(dir)),
1050+
);
9881051
let free_space = inner_container_size.main_size(dir) - used_space;
9891052
let mut num_auto_margins = 0;
9901053

@@ -1021,63 +1084,67 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
10211084
}
10221085
} else {
10231086
let num_items = line.items.len() as i32;
1087+
let is_reversed = main_dir_rev == AxisReverse::Reversed;
10241088
for (index, flex_child) in line.items.iter_mut().enumerate() {
10251089
let is_first = index == 0;
1026-
flex_child.extra_offset_main = match style.justify_content() {
1027-
JustifyContent::FlexStart
1028-
| JustifyContent::Start
1029-
| JustifyContent::Baseline
1030-
| JustifyContent::Stretch => T::Length::zero(),
1031-
JustifyContent::Center => {
1032-
if is_first {
1033-
free_space.div_i32(2)
1034-
} else {
1035-
T::Length::zero()
1036-
}
1037-
}
1038-
JustifyContent::FlexEnd | JustifyContent::End => {
1039-
if is_first {
1040-
free_space
1041-
} else {
1042-
T::Length::zero()
1043-
}
1044-
}
1045-
JustifyContent::Left => match main_dir_rev {
1046-
AxisReverse::NotReversed => T::Length::zero(),
1047-
AxisReverse::Reversed => {
1048-
if is_first {
1090+
let gap = main_gap::<T>(style, dir)
1091+
.resolve(requested_inner_size.main_size(dir), node)
1092+
.or_zero();
1093+
flex_child.extra_offset_main = if is_first {
1094+
match style.justify_content() {
1095+
JustifyContent::Start
1096+
| JustifyContent::Baseline
1097+
| JustifyContent::Stretch => T::Length::zero(),
1098+
JustifyContent::FlexStart => T::Length::zero(),
1099+
JustifyContent::Center => free_space.div_i32(2),
1100+
JustifyContent::FlexEnd => free_space,
1101+
JustifyContent::End => free_space,
1102+
JustifyContent::Left => {
1103+
if is_reversed {
10491104
free_space
10501105
} else {
10511106
T::Length::zero()
10521107
}
10531108
}
1054-
},
1055-
JustifyContent::Right => match main_dir_rev {
1056-
AxisReverse::NotReversed => {
1057-
if is_first {
1109+
JustifyContent::Right => {
1110+
if is_reversed {
1111+
T::Length::zero()
1112+
} else {
10581113
free_space
1114+
}
1115+
}
1116+
JustifyContent::SpaceBetween => T::Length::zero(),
1117+
JustifyContent::SpaceAround => {
1118+
if free_space >= T::Length::zero() {
1119+
free_space.div_i32(num_items).div_i32(2)
10591120
} else {
1060-
T::Length::zero()
1121+
free_space.div_i32(2)
10611122
}
10621123
}
1063-
AxisReverse::Reversed => T::Length::zero(),
1064-
},
1065-
JustifyContent::SpaceBetween => {
1066-
if is_first {
1067-
T::Length::zero()
1068-
} else {
1069-
free_space.div_i32(num_items - 1)
1124+
JustifyContent::SpaceEvenly => {
1125+
if free_space >= T::Length::zero() {
1126+
free_space.div_i32(num_items + 1)
1127+
} else {
1128+
free_space.div_i32(2)
1129+
}
10701130
}
10711131
}
1072-
JustifyContent::SpaceAround => {
1073-
if is_first {
1074-
free_space.div_i32(num_items).div_i32(2)
1075-
} else {
1076-
free_space.div_i32(num_items)
1077-
}
1132+
} else {
1133+
let free_space = free_space.max(T::Length::zero());
1134+
gap + match style.justify_content() {
1135+
JustifyContent::FlexStart
1136+
| JustifyContent::Start
1137+
| JustifyContent::Baseline
1138+
| JustifyContent::Stretch => T::Length::zero(),
1139+
JustifyContent::Center => T::Length::zero(),
1140+
JustifyContent::FlexEnd | JustifyContent::End => T::Length::zero(),
1141+
JustifyContent::Left => T::Length::zero(),
1142+
JustifyContent::Right => T::Length::zero(),
1143+
JustifyContent::SpaceBetween => free_space.div_i32(num_items - 1),
1144+
JustifyContent::SpaceAround => free_space.div_i32(num_items),
1145+
JustifyContent::SpaceEvenly => free_space.div_i32(num_items + 1),
10781146
}
1079-
JustifyContent::SpaceEvenly => free_space.div_i32(num_items + 1),
1080-
};
1147+
}
10811148
}
10821149
}
10831150
}
@@ -1162,45 +1229,56 @@ impl<T: LayoutTreeNode> FlexBox<T> for LayoutUnit<T> {
11621229
}
11631230

11641231
// 16. Align all flex lines per align-content.
1165-
let free_space =
1166-
(inner_container_size.cross_size(dir) - total_cross_size).max(T::Length::zero());
1232+
11671233
let num_lines = flex_lines.len() as i32;
1234+
let gap = cross_gap::<T>(style, dir)
1235+
.resolve(requested_inner_size.main_size(dir), node)
1236+
.or_zero();
1237+
let total_cross_axis_gap = sum_axis_gaps(gap, flex_lines.len());
1238+
let free_space = (inner_container_size.cross_size(dir) - total_cross_size)
1239+
.max(T::Length::zero())
1240+
+ total_cross_axis_gap;
11681241
for (index, line) in flex_lines.iter_mut().enumerate() {
11691242
let is_first = index == 0;
1170-
line.extra_offset_cross = match style.align_content() {
1171-
AlignContent::FlexStart
1172-
| AlignContent::Start
1173-
| AlignContent::Normal
1174-
| AlignContent::Baseline => T::Length::zero(),
1175-
AlignContent::FlexEnd | AlignContent::End => {
1176-
if is_first {
1177-
free_space
1178-
} else {
1243+
line.extra_offset_cross = if is_first {
1244+
match style.align_content() {
1245+
AlignContent::Start | AlignContent::Normal | AlignContent::Baseline => {
11791246
T::Length::zero()
11801247
}
1181-
}
1182-
AlignContent::Center => {
1183-
if is_first {
1184-
free_space.div_i32(2)
1185-
} else {
1186-
T::Length::zero()
1248+
AlignContent::FlexStart => T::Length::zero(),
1249+
AlignContent::End => free_space,
1250+
AlignContent::FlexEnd => free_space,
1251+
AlignContent::Center => free_space.div_i32(2),
1252+
AlignContent::Stretch => T::Length::zero(),
1253+
AlignContent::SpaceBetween => T::Length::zero(),
1254+
AlignContent::SpaceEvenly => {
1255+
if free_space >= T::Length::zero() {
1256+
free_space.div_i32(num_lines + 1)
1257+
} else {
1258+
free_space.div_i32(2)
1259+
}
11871260
}
1188-
}
1189-
AlignContent::Stretch => T::Length::zero(),
1190-
AlignContent::SpaceBetween => {
1191-
if is_first {
1192-
T::Length::zero()
1193-
} else {
1194-
free_space.div_i32(num_lines - 1)
1261+
AlignContent::SpaceAround => {
1262+
if free_space >= T::Length::zero() {
1263+
free_space.div_i32(num_lines).div_i32(2)
1264+
} else {
1265+
free_space.div_i32(2)
1266+
}
11951267
}
11961268
}
1197-
AlignContent::SpaceEvenly => free_space.div_i32(num_lines + 1),
1198-
AlignContent::SpaceAround => {
1199-
if is_first {
1200-
free_space.div_i32(num_lines).div_i32(2)
1201-
} else {
1202-
free_space.div_i32(num_lines)
1269+
} else {
1270+
gap + match style.align_content() {
1271+
AlignContent::Start | AlignContent::Normal | AlignContent::Baseline => {
1272+
T::Length::zero()
12031273
}
1274+
AlignContent::FlexStart => T::Length::zero(),
1275+
AlignContent::End => T::Length::zero(),
1276+
AlignContent::FlexEnd => T::Length::zero(),
1277+
AlignContent::Center => T::Length::zero(),
1278+
AlignContent::Stretch => T::Length::zero(),
1279+
AlignContent::SpaceBetween => free_space.div_i32(num_lines - 1),
1280+
AlignContent::SpaceEvenly => free_space.div_i32(num_lines + 1),
1281+
AlignContent::SpaceAround => free_space.div_i32(num_lines),
12041282
}
12051283
};
12061284
}

0 commit comments

Comments
 (0)