tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1077HTML 1008Markdown 12TOML 83209 Definitions

⎇ morphorm examples/measure.rs Rust · 146 lines · 5.6 KB History Who touched what raw

Replace per-node measure functions with a single measure function + per-node context (#490) be627e7fa · Nico Burns ·

1use taffy::prelude::*;
2
3const LOREM_IPSUM : &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
4
5struct FontMetrics {
6    char_width: f32,
7    char_height: f32,
8}
9enum WritingMode {
10    Horizontal,
11    Vertical,
12}
13struct TextContext {
14    text_content: String,
15    writing_mode: WritingMode,
16}
17struct ImageContext {
18    width: f32,
19    height: f32,
20}
21enum NodeContext {
22    Text(TextContext),
23    Image(ImageContext),
24}
25
26fn main() -> Result<(), taffy::TaffyError> {
27    let mut taffy: Taffy<NodeContext> = Taffy::new();
28
29    let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
30
31    let text_node = taffy.new_leaf_with_context(
32        Style::default(),
33        NodeContext::Text(TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal }),
34    )?;
35
36    let image_node = taffy
37        .new_leaf_with_context(Style::default(), NodeContext::Image(ImageContext { width: 400.0, height: 300.0 }))?;
38
39    let root = taffy.new_with_children(
40        Style {
41            display: Display::Flex,
42            flex_direction: FlexDirection::Column,
43            size: Size { width: length(200.0), height: auto() },
44            ..Default::default()
45        },
46        &[text_node, image_node],
47    )?;
48
49    // Compute layout and print result
50    taffy.compute_layout_with_measure(
51        root,
52        Size::MAX_CONTENT,
53        // Note: this closure is a FnMut closure and can be used to borrow external context for the duration of layout
54        // For example, you may wish to borrow a global font registry and pass it into your text measuring function
55        |known_dimensions, available_space, _node_id, node_context| {
56            measure_function(known_dimensions, available_space, node_context, &font_metrics)
57        },
58    )?;
59    taffy.print_tree(root);
60
61    Ok(())
62}
63
64fn measure_function(
65    known_dimensions: taffy::geometry::Size<Option<f32>>,
66    available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
67    node_context: Option<&mut NodeContext>,
68    font_metrics: &FontMetrics,
69) -> Size<f32> {
70    if let Size { width: Some(width), height: Some(height) } = known_dimensions {
71        return Size { width, height };
72    }
73
74    match node_context {
75        None => Size::ZERO,
76        Some(NodeContext::Text(text_context)) => {
77            text_measure_function(known_dimensions, available_space, &*text_context, font_metrics)
78        }
79        Some(NodeContext::Image(image_context)) => image_measure_function(known_dimensions, image_context),
80    }
81}
82
83fn image_measure_function(
84    known_dimensions: taffy::geometry::Size<Option<f32>>,
85    image_context: &ImageContext,
86) -> taffy::geometry::Size<f32> {
87    match (known_dimensions.width, known_dimensions.height) {
88        (Some(width), Some(height)) => Size { width, height },
89        (Some(width), None) => Size { width, height: (width / image_context.width) * image_context.height },
90        (None, Some(height)) => Size { width: (height / image_context.height) * image_context.width, height },
91        (None, None) => Size { width: image_context.width, height: image_context.height },
92    }
93}
94
95fn text_measure_function(
96    known_dimensions: taffy::geometry::Size<Option<f32>>,
97    available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
98    text_context: &TextContext,
99    font_metrics: &FontMetrics,
100) -> taffy::geometry::Size<f32> {
101    use taffy::geometry::AbsoluteAxis;
102    use taffy::prelude::*;
103
104    let inline_axis = match text_context.writing_mode {
105        WritingMode::Horizontal => AbsoluteAxis::Horizontal,
106        WritingMode::Vertical => AbsoluteAxis::Vertical,
107    };
108    let block_axis = inline_axis.other_axis();
109    let words: Vec<&str> = text_context.text_content.split_whitespace().collect();
110
111    if words.is_empty() {
112        return Size::ZERO;
113    }
114
115    let min_line_length: usize = words.iter().map(|line| line.len()).max().unwrap_or(0);
116    let max_line_length: usize = words.iter().map(|line| line.len()).sum();
117    let inline_size =
118        known_dimensions.get_abs(inline_axis).unwrap_or_else(|| match available_space.get_abs(inline_axis) {
119            AvailableSpace::MinContent => min_line_length as f32 * font_metrics.char_width,
120            AvailableSpace::MaxContent => max_line_length as f32 * font_metrics.char_width,
121            AvailableSpace::Definite(inline_size) => inline_size
122                .min(max_line_length as f32 * font_metrics.char_width)
123                .max(min_line_length as f32 * font_metrics.char_width),
124        });
125    let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
126        let inline_line_length = (inline_size / font_metrics.char_width).floor() as usize;
127        let mut line_count = 1;
128        let mut current_line_length = 0;
129        for word in &words {
130            if current_line_length + word.len() > inline_line_length {
131                if current_line_length > 0 {
132                    line_count += 1
133                };
134                current_line_length = word.len();
135            } else {
136                current_line_length += word.len();
137            };
138        }
139        (line_count as f32) * font_metrics.char_height
140    });
141
142    match text_context.writing_mode {
143        WritingMode::Horizontal => Size { width: inline_size, height: block_size },
144        WritingMode::Vertical => Size { width: block_size, height: inline_size },
145    }
146}