gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 10TOML 3CSS 1350 Definiciones

⎇ 0875874872e622131324e276dc77392517fd4bb1 tests/generated_xml.rs Rust · 317 líneas · 12.4 KB Historia Quién tocó qué crudo

Implement `direction` property (#906) 60601a657 · Kane Wang ·

1use roxmltree::Document;
2use std::{fmt::Debug, io::Write, path::PathBuf, str::FromStr};
3use taffy::{
4    prelude::TaffyZero as _, AvailableSpace, CheapCloneStr, Dimension, GridAutoTracks, GridTemplateComponent,
5    GridTemplateTracks, LengthPercentage, LengthPercentageAuto, Line, NodeId, Point, PrintTree, Rect, Size, TaffyTree,
6};
7use taffy_test_helpers::{test_measure_function, TestNodeContext};
8
9mod xml;
10
11#[derive(Debug)]
12struct OutputNode {
13    node_id: NodeId,
14    location: Point<f32>,
15    size: Size<f32>,
16    children: Vec<OutputNode>,
17}
18
19impl PartialEq for OutputNode {
20    fn eq(&self, other: &Self) -> bool {
21        self.node_id == other.node_id
22            && (self.location.x - other.location.x).abs() < 0.1
23            && (self.location.y - other.location.y).abs() < 0.1
24            && (self.size.width - other.size.width).abs() < 0.1
25            && (self.size.height - other.size.height).abs() < 0.1
26            && self.children == other.children
27    }
28}
29
30impl OutputNode {
31    fn write_metrics(&self, w: &mut impl Write) {
32        write!(
33            w,
34            "[x: {:<4} y: {:<4} w: {:<4} h: {:<4}]",
35            self.location.x, self.location.y, self.size.width, self.size.height
36        )
37        .unwrap();
38    }
39
40    /// Prints a debug representation of the computed layout for a tree of nodes
41    fn write_tree(&self, out: &mut impl Write) {
42        writeln!(out, "TREE").unwrap();
43        print_node(out, self, false, String::new());
44
45        /// Recursive function that prints each node in the tree
46        fn print_node(out: &mut impl Write, node: &OutputNode, has_sibling: bool, lines_string: String) {
47            let num_children = node.children.len();
48            let fork_string = if has_sibling { "├── " } else { "└── " };
49
50            write!(out, "{lines_string}{fork_string} {:?} ", node.node_id).unwrap();
51            node.write_metrics(out);
52            let bar = if has_sibling { "│   " } else { "    " };
53            let new_string = lines_string + bar;
54            writeln!(out).unwrap();
55
56            // Recurse into children
57            for (index, child) in node.children.iter().enumerate() {
58                let has_sibling = index < num_children - 1;
59                print_node(out, child, has_sibling, new_string.clone());
60            }
61        }
62    }
63
64    fn print_tree(&self) {
65        let mut s: Vec<u8> = Vec::new();
66        self.write_tree(&mut s);
67        let s = String::from_utf8(s).unwrap();
68        println!("{s}");
69    }
70}
71
72#[test]
73fn get_dir() {
74    let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
75    let test_dir = root_dir.join("tests");
76    let xml_test_dir = test_dir.join("xml");
77    println!("{}", xml_test_dir.display())
78}
79
80fn run_xml_test(group: &str, name: &str) {
81    let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
82    let test_dir = root_dir.join("tests");
83    let xml_test_dir = test_dir.join("xml");
84
85    let test_xml_file = {
86        let mut path = xml_test_dir.join(group).join(name);
87        path.set_extension("xml");
88        path
89    };
90
91    let raw_xml = std::fs::read_to_string(&test_xml_file).expect("test file should exist");
92    let test = Document::parse(&raw_xml).unwrap();
93
94    let root = test.root_element();
95    let use_rounding: bool = parse_or(root.attribute("use-rounding"), true);
96    let viewport = root.children().find(|node| node.has_tag_name("viewport")).unwrap();
97    let input = root.children().find(|node| node.has_tag_name("input")).unwrap();
98    let expectations = root.children().find(|node| node.has_tag_name("expectations")).unwrap();
99
100    let available_space = Size {
101        width: parse_or(viewport.attribute("width"), AvailableSpace::MaxContent),
102        height: parse_or(viewport.attribute("height"), AvailableSpace::MaxContent),
103    };
104
105    // Construct tree and expectations
106    let mut tree = TaffyTree::<TestNodeContext>::new();
107    if use_rounding {
108        tree.enable_rounding();
109    } else {
110        tree.disable_rounding();
111    }
112    let expected_output = construct_tree(
113        input.first_element_child().unwrap(),
114        expectations.first_element_child().unwrap(),
115        &mut tree,
116        None,
117    );
118    let root_node_id = expected_output.node_id;
119
120    // Compute layout
121    tree.compute_layout_with_measure(root_node_id, available_space, test_measure_function).unwrap();
122    let actual_output = get_computed_expectations(&tree, root_node_id);
123
124    println!("\nINPUT");
125    println!("{raw_xml}");
126
127    tree.print_tree(root_node_id);
128
129    println!("\nEXPECTED");
130    expected_output.print_tree();
131    println!("\nACTUAL");
132    actual_output.print_tree();
133
134    assert_eq!(expected_output, actual_output);
135}
136
137fn construct_tree(
138    input: roxmltree::Node,
139    expected_x: roxmltree::Node,
140    tree: &mut TaffyTree<TestNodeContext>,
141    parent: Option<taffy::NodeId>,
142) -> OutputNode {
143    if input.first_element_child().is_some() {
144        let tnode = tree.new_with_children(build_style(input), &[]).unwrap();
145        let mut expected = build_expectations(expected_x, tnode);
146
147        if let Some(parent) = parent {
148            tree.add_child(parent, tnode).unwrap();
149        }
150
151        for (child, child_expected_x) in input
152            .children()
153            .filter(|node| node.is_element())
154            .zip(expected_x.children().filter(|node| node.is_element()))
155        {
156            expected.children.push(construct_tree(child, child_expected_x, tree, Some(tnode)));
157        }
158        expected
159    } else {
160        let text_content = input.text().map(|text| text.trim());
161        // let aspect_ratio = input.attribute("aspect-ratio");
162        let writing_mode = parse_or_default(input.attribute("writing-mode"));
163
164        let tnode = tree.new_leaf(build_style(input)).unwrap();
165        tree.set_node_context(
166            tnode,
167            text_content.map(|text_content| TestNodeContext::ahem_text(text_content.to_string(), writing_mode)),
168        )
169        .unwrap();
170
171        if let Some(parent) = parent {
172            tree.add_child(parent, tnode).unwrap();
173        }
174
175        build_expectations(expected_x, tnode)
176    }
177}
178
179fn parse_or_default<T: FromStr<Err: Debug> + Default>(input: Option<&str>) -> T {
180    parse_or(input, Default::default())
181}
182
183fn parse_or<T: FromStr<Err: Debug>>(input: Option<&str>, fallback: T) -> T {
184    input.map(|input| input.parse().unwrap()).unwrap_or(fallback)
185}
186
187fn maybe_parse<T: FromStr>(input: Option<&str>) -> Option<T> {
188    input.and_then(|input| input.parse().ok())
189}
190
191fn get_computed_expectations(tree: &TaffyTree<TestNodeContext>, node_id: NodeId) -> OutputNode {
192    let layout = tree.get_final_layout(node_id);
193    let mut output = OutputNode { node_id, location: layout.location, size: layout.size, children: Vec::new() };
194
195    for child_id in tree.children(node_id).unwrap() {
196        output.children.push(get_computed_expectations(tree, child_id));
197    }
198
199    output
200}
201
202fn build_expectations(xnode: roxmltree::Node, node_id: NodeId) -> OutputNode {
203    OutputNode {
204        node_id,
205        location: Point {
206            x: xnode.attribute("x").unwrap().parse().unwrap(),
207            y: xnode.attribute("y").unwrap().parse().unwrap(),
208        },
209        size: Size {
210            width: xnode.attribute("width").unwrap().parse().unwrap(),
211            height: xnode.attribute("height").unwrap().parse().unwrap(),
212        },
213        children: Vec::new(),
214    }
215}
216
217fn build_style<S: CheapCloneStr>(xnode: roxmltree::Node) -> taffy::Style<S> {
218    let grid_template_rows: GridTemplateTracks<S, GridTemplateComponent<S>> =
219        parse_or_default(xnode.attribute("grid-template-rows"));
220    let grid_template_columns: GridTemplateTracks<S, GridTemplateComponent<S>> =
221        parse_or_default(xnode.attribute("grid-template-columns"));
222
223    taffy::Style {
224        dummy: std::marker::PhantomData,
225        display: parse_or_default(xnode.attribute("display")),
226        direction: parse_or_default(xnode.attribute("direction")),
227        item_is_table: false,
228        item_is_replaced: false,
229        box_sizing: parse_or_default(xnode.attribute("box-sizing")),
230        overflow: Point {
231            x: parse_or_default(xnode.attribute("overflow-x")),
232            y: parse_or_default(xnode.attribute("overflow-y")),
233        },
234        scrollbar_width: parse_or_default(xnode.attribute("scrollbar-width")),
235        float: parse_or_default(xnode.attribute("float")),
236        clear: parse_or_default(xnode.attribute("clear")),
237        position: parse_or_default(xnode.attribute("position")),
238
239        size: Size {
240            width: parse_or(xnode.attribute("width"), Dimension::auto()),
241            height: parse_or(xnode.attribute("height"), Dimension::auto()),
242        },
243        min_size: Size {
244            width: parse_or(xnode.attribute("min-width"), Dimension::auto()),
245            height: parse_or(xnode.attribute("min-height"), Dimension::auto()),
246        },
247        max_size: Size {
248            width: parse_or(xnode.attribute("max-width"), Dimension::auto()),
249            height: parse_or(xnode.attribute("max-height"), Dimension::auto()),
250        },
251        inset: Rect {
252            top: parse_or(xnode.attribute("top"), LengthPercentageAuto::auto()),
253            left: parse_or(xnode.attribute("left"), LengthPercentageAuto::auto()),
254            bottom: parse_or(xnode.attribute("bottom"), LengthPercentageAuto::auto()),
255            right: parse_or(xnode.attribute("right"), LengthPercentageAuto::auto()),
256        },
257        margin: Rect {
258            top: parse_or(xnode.attribute("margin-top"), LengthPercentageAuto::ZERO),
259            left: parse_or(xnode.attribute("margin-left"), LengthPercentageAuto::ZERO),
260            bottom: parse_or(xnode.attribute("margin-bottom"), LengthPercentageAuto::ZERO),
261            right: parse_or(xnode.attribute("margin-right"), LengthPercentageAuto::ZERO),
262        },
263        padding: Rect {
264            top: parse_or(xnode.attribute("padding-top"), LengthPercentage::ZERO),
265            left: parse_or(xnode.attribute("padding-left"), LengthPercentage::ZERO),
266            bottom: parse_or(xnode.attribute("padding-bottom"), LengthPercentage::ZERO),
267            right: parse_or(xnode.attribute("padding-right"), LengthPercentage::ZERO),
268        },
269        border: Rect {
270            top: parse_or(xnode.attribute("border-top"), LengthPercentage::ZERO),
271            left: parse_or(xnode.attribute("border-left"), LengthPercentage::ZERO),
272            bottom: parse_or(xnode.attribute("border-bottom"), LengthPercentage::ZERO),
273            right: parse_or(xnode.attribute("border-right"), LengthPercentage::ZERO),
274        },
275        gap: Size {
276            width: parse_or(xnode.attribute("column-gap"), LengthPercentage::ZERO),
277            height: parse_or(xnode.attribute("row-gap"), LengthPercentage::ZERO),
278        },
279
280        aspect_ratio: maybe_parse(xnode.attribute("aspect-ratio")),
281        align_items: maybe_parse(xnode.attribute("align-items")),
282        align_self: maybe_parse(xnode.attribute("align-self")),
283        justify_items: maybe_parse(xnode.attribute("justify-items")),
284        justify_self: maybe_parse(xnode.attribute("justify-self")),
285        align_content: maybe_parse(xnode.attribute("align-content")),
286        justify_content: maybe_parse(xnode.attribute("justify-content")),
287
288        text_align: parse_or_default(xnode.attribute("text-align")),
289        flex_direction: parse_or_default(xnode.attribute("flex-direction")),
290        flex_wrap: parse_or_default(xnode.attribute("flex-wrap")),
291        flex_grow: parse_or(xnode.attribute("flex-grow"), 0.0),
292        flex_shrink: parse_or(xnode.attribute("flex-shrink"), 1.0),
293        flex_basis: parse_or(xnode.attribute("flex-basis"), Dimension::auto()),
294
295        grid_auto_flow: parse_or_default(xnode.attribute("grid-auto-flow")),
296
297        grid_template_rows: grid_template_rows.tracks,
298        grid_template_row_names: grid_template_rows.line_names,
299
300        grid_template_columns: grid_template_columns.tracks,
301        grid_template_column_names: grid_template_columns.line_names,
302
303        // TODO
304        grid_auto_rows: parse_or_default::<GridAutoTracks>(xnode.attribute("grid-auto-rows")).0,
305        grid_auto_columns: parse_or_default::<GridAutoTracks>(xnode.attribute("grid-auto-columns")).0,
306        grid_template_areas: Default::default(),
307
308        grid_row: Line {
309            start: parse_or_default(xnode.attribute("grid-row-start")),
310            end: parse_or_default(xnode.attribute("grid-row-end")),
311        },
312        grid_column: Line {
313            start: parse_or_default(xnode.attribute("grid-column-start")),
314            end: parse_or_default(xnode.attribute("grid-column-end")),
315        },
316    }
317}