orinium_browser/engine/layouter/
table_layout.rs1use ui_layout::{BoxModel, LayoutBox, LayoutNode};
9
10use super::types::{ContainerRole, InfoNode, NodeKind};
11
12pub fn align_table_columns(layout: &mut LayoutNode, info: &InfoNode) {
13 for (child_layout, child_info) in layout.children.iter_mut().zip(&info.children) {
14 if let Some(child_layout) = child_layout.node_mut() {
15 align_table_columns(child_layout, child_info);
16 }
17 }
18
19 if container_role(info) == Some(&ContainerRole::Table) {
20 align_one_table(layout, info);
21 }
22}
23
24fn align_one_table(layout: &mut LayoutNode, info: &InfoNode) {
25 let mut widths: Vec<f32> = Vec::new();
26 visit_rows(layout, info, &mut |row, row_info| {
27 let mut column = 0;
28 for (cell, cell_info) in row.children.iter().zip(&row_info.children) {
29 if container_role(cell_info) != Some(&ContainerRole::TableCell) {
30 continue;
31 }
32 let Some(cell) = cell.node() else {
33 continue;
34 };
35 if widths.len() <= column {
36 widths.resize(column + 1, 0.0);
37 }
38 widths[column] = widths[column].max(cell.layout_box.width_box());
39 column += 1;
40 }
41 });
42
43 if widths.is_empty() {
44 return;
45 }
46
47 let table_width = widths.iter().sum();
48 visit_rows_mut(layout, info, table_width, &widths);
49 grow_box_width(&mut layout.layout_box, table_width);
50}
51
52fn visit_rows(
53 layout: &LayoutNode,
54 info: &InfoNode,
55 visit: &mut impl FnMut(&LayoutNode, &InfoNode),
56) {
57 for (child, child_info) in layout.children.iter().zip(&info.children) {
58 let Some(child) = child.node() else {
59 continue;
60 };
61 match container_role(child_info) {
62 Some(ContainerRole::TableRow) => visit(child, child_info),
63 Some(ContainerRole::TableRowGroup) => visit_rows(child, child_info, visit),
64 _ => {}
65 }
66 }
67}
68
69fn visit_rows_mut(layout: &mut LayoutNode, info: &InfoNode, table_width: f32, widths: &[f32]) {
70 for (child, child_info) in layout.children.iter_mut().zip(&info.children) {
71 let Some(child) = child.node_mut() else {
72 continue;
73 };
74 match container_role(child_info) {
75 Some(ContainerRole::TableRow) => align_row(child, child_info, widths),
76 Some(ContainerRole::TableRowGroup) => {
77 visit_rows_mut(child, child_info, table_width, widths);
78 grow_box_width(&mut child.layout_box, table_width);
79 }
80 _ => {}
81 }
82 }
83}
84
85fn align_row(row: &mut LayoutNode, info: &InfoNode, widths: &[f32]) {
86 let start_x = row
87 .children
88 .iter()
89 .zip(&info.children)
90 .find_map(|(cell, cell_info)| {
91 if container_role(cell_info) != Some(&ContainerRole::TableCell) {
92 return None;
93 }
94 Some(cell.node()?.layout_box.iter().next()?.border_box.x)
95 })
96 .unwrap_or(0.0);
97
98 let mut column = 0;
99 let mut x = start_x;
100 for (cell, cell_info) in row.children.iter_mut().zip(&info.children) {
101 if container_role(cell_info) != Some(&ContainerRole::TableCell) {
102 continue;
103 }
104 let Some(cell) = cell.node_mut() else {
105 continue;
106 };
107 let Some(width) = widths.get(column).copied() else {
108 break;
109 };
110 move_box_x(&mut cell.layout_box, x);
111 grow_box_width(&mut cell.layout_box, width);
112 x += width;
113 column += 1;
114 }
115 grow_box_width(&mut row.layout_box, x - start_x);
116}
117
118fn container_role(info: &InfoNode) -> Option<&ContainerRole> {
119 match &info.kind {
120 NodeKind::Container { role, .. } => Some(role),
121 _ => None,
122 }
123}
124
125fn move_box_x(layout_box: &mut LayoutBox, target_x: f32) {
126 match layout_box {
127 LayoutBox::None => {}
128 LayoutBox::BlockBox(model) => translate_box_x(model, target_x - model.border_box.x),
129 LayoutBox::InlineBox(inline) => {
130 let dx = target_x - inline.box_model.border_box.x;
131 translate_box_x(&mut inline.box_model, dx);
132 inline
133 .line_spans
134 .iter_mut()
135 .for_each(|l| l.line_pos.0 += dx);
136 }
137 }
138}
139
140fn translate_box_x(model: &mut BoxModel, dx: f32) {
141 model.border_box.x += dx;
142 model.padding_box.x += dx;
143 model.content_box.x += dx;
144 model.children_box.x += dx;
145}
146
147fn grow_box_width(layout_box: &mut LayoutBox, target_border_width: f32) {
148 let model = match layout_box {
149 LayoutBox::None => return,
150 LayoutBox::BlockBox(model) => model,
151 LayoutBox::InlineBox(inline) => &mut inline.box_model,
152 };
153 let delta = (target_border_width - model.border_box.width).max(0.0);
154 model.border_box.width += delta;
155 model.padding_box.width += delta;
156 model.content_box.width += delta;
157 model.children_box.width += delta;
158}
159
160#[cfg(test)]
161mod tests {
162 use ui_layout::{LayoutNode, Rect, Style};
163
164 use super::*;
165 use crate::engine::layouter::types::ContainerStyle;
166
167 fn info(role: ContainerRole, children: Vec<InfoNode>) -> InfoNode {
168 InfoNode {
169 kind: NodeKind::Container {
170 scroll_x: false,
171 scroll_y: false,
172 scroll_offset_x: 0.0,
173 scroll_offset_y: 0.0,
174 style: ContainerStyle::default(),
175 role,
176 },
177 children,
178 dom_id: None,
179 }
180 }
181
182 fn box_node(x: f32, width: f32, children: Vec<LayoutNode>) -> LayoutNode {
183 let mut node = LayoutNode::with_children(Style::default(), children);
184 node.layout_box = LayoutBox::BlockBox(
185 Rect {
186 x,
187 width,
188 height: 20.0,
189 ..Default::default()
190 }
191 .into(),
192 );
193 node
194 }
195
196 #[test]
197 fn shares_column_widths_across_row_groups() {
198 let header = box_node(
199 0.0,
200 140.0,
201 vec![box_node(0.0, 70.0, vec![]), box_node(70.0, 70.0, vec![])],
202 );
203 let body = box_node(
204 0.0,
205 100.0,
206 vec![box_node(0.0, 60.0, vec![]), box_node(60.0, 40.0, vec![])],
207 );
208 let mut table = box_node(
209 0.0,
210 140.0,
211 vec![
212 box_node(0.0, 140.0, vec![header]),
213 box_node(0.0, 100.0, vec![body]),
214 ],
215 );
216 let row_info = || {
217 info(
218 ContainerRole::TableRow,
219 vec![
220 info(ContainerRole::TableCell, vec![]),
221 info(ContainerRole::TableCell, vec![]),
222 ],
223 )
224 };
225 let table_info = info(
226 ContainerRole::Table,
227 vec![
228 info(ContainerRole::TableRowGroup, vec![row_info()]),
229 info(ContainerRole::TableRowGroup, vec![row_info()]),
230 ],
231 );
232
233 align_table_columns(&mut table, &table_info);
234
235 let body_group = table.children[1].node().unwrap();
236 let body_row = body_group.children[0].node().unwrap();
237 let first = body_row.children[0].node().unwrap();
238 let second = body_row.children[1].node().unwrap();
239 assert_eq!(first.layout_box.width_box(), 70.0);
240 assert_eq!(second.layout_box.width_box(), 70.0);
241 assert_eq!(second.layout_box.iter().next().unwrap().border_box.x, 70.0);
242 assert_eq!(body_row.layout_box.width_box(), 140.0);
243 assert_eq!(body_group.layout_box.width_box(), 140.0);
244 }
245}