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