tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 695HTML 648Markdown 12TOML 62320 Definitions

⎇ 66bcb10bff823bfde7828eeef133cb2fa7de7dba src/compute/leaf.rs Rust · 213 lines · 9.0 KB History Who touched what raw

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

1//! Computes size using styles and measure functions
2
3use crate::geometry::{Point, Size};
4use crate::style::{AvailableSpace, Display, Overflow, Position, Style};
5use crate::tree::CollapsibleMarginSet;
6use crate::tree::NodeId;
7use crate::tree::{SizeBaselinesAndMargins, SizingMode};
8use crate::util::sys::f32_max;
9use crate::util::MaybeMath;
10use crate::util::{MaybeResolve, ResolveOrZero};
11
12#[cfg(feature = "debug")]
13use crate::util::debug::NODE_LOGGER;
14
15/// Perform full layout on a leaf node
16#[allow(clippy::too_many_arguments)]
17pub(crate) fn perform_layout<NodeContext, MeasureFunction>(
18    style: &Style,
19    known_dimensions: Size<Option<f32>>,
20    parent_size: Size<Option<f32>>,
21    available_space: Size<AvailableSpace>,
22    sizing_mode: SizingMode,
23    measure_function: MeasureFunction,
24    node_id: NodeId,
25    context: Option<&mut NodeContext>,
26) -> SizeBaselinesAndMargins
27where
28    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
29{
30    compute(style, known_dimensions, parent_size, available_space, sizing_mode, measure_function, node_id, context)
31}
32
33/// Measure a leaf node's size
34#[allow(clippy::too_many_arguments)]
35pub(crate) fn measure_size<NodeContext, MeasureFunction>(
36    style: &Style,
37    known_dimensions: Size<Option<f32>>,
38    parent_size: Size<Option<f32>>,
39    available_space: Size<AvailableSpace>,
40    sizing_mode: SizingMode,
41    measure_function: MeasureFunction,
42    node_id: NodeId,
43    context: Option<&mut NodeContext>,
44) -> Size<f32>
45where
46    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
47{
48    compute(style, known_dimensions, parent_size, available_space, sizing_mode, measure_function, node_id, context).size
49}
50
51/// Compute the size of a leaf node (node with no children)
52#[allow(clippy::too_many_arguments)]
53pub fn compute<NodeContext, MeasureFunction>(
54    style: &Style,
55    known_dimensions: Size<Option<f32>>,
56    parent_size: Size<Option<f32>>,
57    available_space: Size<AvailableSpace>,
58    sizing_mode: SizingMode,
59    mut measure_function: MeasureFunction,
60    node_id: NodeId,
61    context: Option<&mut NodeContext>,
62) -> SizeBaselinesAndMargins
63where
64    MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
65{
66    // Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
67    // For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
68    let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
69        SizingMode::ContentSize => {
70            let node_size = known_dimensions;
71            let node_min_size = Size::NONE;
72            let node_max_size = Size::NONE;
73            (node_size, node_min_size, node_max_size, None)
74        }
75        SizingMode::InherentSize => {
76            let aspect_ratio = style.aspect_ratio;
77            let style_size = style.size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
78            let style_min_size = style.min_size.maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio);
79            let style_max_size = style.max_size.maybe_resolve(parent_size);
80
81            let node_size = known_dimensions.or(style_size);
82            (node_size, style_min_size, style_max_size, aspect_ratio)
83        }
84    };
85
86    // Note: both horizontal and vertical percentage padding/borders are resolved against the container's inline size (i.e. width).
87    // This is not a bug, but is how CSS is specified (see: https://developer.mozilla.org/en-US/docs/Web/CSS/padding#values)
88    let margin = style.margin.resolve_or_zero(parent_size.width);
89    let padding = style.padding.resolve_or_zero(parent_size.width);
90    let border = style.border.resolve_or_zero(parent_size.width);
91    let padding_border = padding + border;
92
93    // Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
94    // However, the axis are switched (transposed) because a node that scrolls vertically needs
95    // *horizontal* space to be reserved for a scrollbar
96    let scrollbar_gutter = style.overflow.transpose().map(|overflow| match overflow {
97        Overflow::Scroll => style.scrollbar_width,
98        _ => 0.0,
99    });
100    // TODO: make side configurable based on the `direction` property
101    let mut content_box_inset = padding_border;
102    content_box_inset.right += scrollbar_gutter.x;
103    content_box_inset.bottom += scrollbar_gutter.y;
104
105    #[cfg(feature = "block_layout")]
106    let is_block = style.display == Display::Block;
107    #[cfg(not(feature = "block_layout"))]
108    let is_block = false;
109
110    let has_styles_preventing_being_collapsed_through = !is_block
111        || style.overflow.x.is_scroll_container()
112        || style.overflow.y.is_scroll_container()
113        || style.position == Position::Absolute
114        || padding.top > 0.0
115        || padding.bottom > 0.0
116        || border.top > 0.0
117        || border.bottom > 0.0;
118
119    #[cfg(feature = "debug")]
120    NODE_LOGGER.log("LEAF");
121    #[cfg(feature = "debug")]
122    NODE_LOGGER.labelled_debug_log("node_size", node_size);
123    #[cfg(feature = "debug")]
124    NODE_LOGGER.labelled_debug_log("min_size ", node_min_size);
125    #[cfg(feature = "debug")]
126    NODE_LOGGER.labelled_debug_log("max_size ", node_max_size);
127
128    // Return early if both width and height are known
129    if let Size { width: Some(width), height: Some(height) } = node_size {
130        let size = Size { width, height }
131            .maybe_clamp(node_min_size, node_max_size)
132            .maybe_max(padding_border.sum_axes().map(Some));
133        return SizeBaselinesAndMargins {
134            size,
135            first_baselines: Point::NONE,
136            top_margin: CollapsibleMarginSet::ZERO,
137            bottom_margin: CollapsibleMarginSet::ZERO,
138            margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
139                && size.height == 0.0
140                && context.is_none(),
141        };
142    };
143
144    if let Some(context) = context {
145        // Compute available space
146        let available_space = Size {
147            width: available_space
148                .width
149                .maybe_sub(margin.horizontal_axis_sum())
150                .maybe_set(node_size.width)
151                .maybe_set(node_max_size.width)
152                .map_definite_value(|size| {
153                    size.maybe_clamp(node_min_size.width, node_max_size.width) - content_box_inset.horizontal_axis_sum()
154                }),
155            height: available_space
156                .height
157                .maybe_sub(margin.vertical_axis_sum())
158                .maybe_set(node_size.height)
159                .maybe_set(node_max_size.height)
160                .map_definite_value(|size| {
161                    size.maybe_clamp(node_min_size.height, node_max_size.height) - content_box_inset.vertical_axis_sum()
162                }),
163        };
164
165        // Measure node
166        let measured_size = measure_function(known_dimensions, available_space, node_id, Some(context));
167        let clamped_size =
168            node_size.unwrap_or(measured_size + content_box_inset.sum_axes()).maybe_clamp(node_min_size, node_max_size);
169        let size = Size {
170            width: clamped_size.width,
171            height: f32_max(clamped_size.height, aspect_ratio.map(|ratio| clamped_size.width / ratio).unwrap_or(0.0)),
172        };
173        let size = size.maybe_max(padding_border.sum_axes().map(Some));
174
175        return SizeBaselinesAndMargins {
176            size,
177            first_baselines: Point::NONE,
178            top_margin: CollapsibleMarginSet::ZERO,
179            bottom_margin: CollapsibleMarginSet::ZERO,
180            margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
181                && size.height == 0.0
182                && measured_size.height == 0.0,
183        };
184    }
185
186    let size = Size {
187        width: node_size
188            .width
189            // .unwrap_or(0.0) + padding.horizontal_axis_sum() + border.horizontal_axis_sum(), // content-box
190            .unwrap_or(content_box_inset.horizontal_axis_sum()) // border-box
191            .maybe_clamp(node_min_size.width, node_max_size.width)
192            .maybe_max(padding_border.horizontal_axis_sum().into()),
193        height: node_size
194            .height
195            // .unwrap_or(0.0) + padding.vertical_axis_sum() + border.vertical_axis_sum(), // content-box
196            .unwrap_or(content_box_inset.vertical_axis_sum()) // border-box
197            .maybe_clamp(node_min_size.height, node_max_size.height)
198            .maybe_max(padding_border.vertical_axis_sum().into()),
199    };
200
201    let size = Size {
202        width: f32_max(size.width, aspect_ratio.map(|ratio| size.height * ratio).unwrap_or(0.0)),
203        height: f32_max(size.height, aspect_ratio.map(|ratio| size.width / ratio).unwrap_or(0.0)),
204    };
205
206    SizeBaselinesAndMargins {
207        size,
208        first_baselines: Point::NONE,
209        top_margin: CollapsibleMarginSet::ZERO,
210        bottom_margin: CollapsibleMarginSet::ZERO,
211        margins_can_collapse_through: !has_styles_preventing_being_collapsed_through && size.height == 0.0,
212    }
213}