gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Sutichaykuna

⎇ 568f643327ec6b4879df0087def7f4e66b6acafb src/compute/leaf.rs Rust · 191 siq'ikuna · 8.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

Reserve space for padding/borders/scrollbars in lead nodes with measure functions (#497) 568f64332 · Nico Burns ·

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