Skip to main content

taffy/compute/common/
sizing_keyword.rs

1//! Shared resolution logic for sizing keywords (`min-content`, `max-content`, `fit-content`,
2//! `fit-content(...)`, and `stretch`) on the `width`/`height` style properties
3use crate::geometry::{AbsoluteAxis, Line, Rect, Size};
4use crate::style::AvailableSpace;
5use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
6use crate::util::sys::f32_max;
7use crate::{CompactLength, Dimension};
8
9/// How a sizing keyword resolves to a used size
10pub(crate) enum SizingKeywordResolution {
11    /// The size is the result of measuring the item under the given available space constraint
12    Measure(AvailableSpace),
13    /// The size resolves to an exact value without measuring the item
14    Exact(f32),
15}
16
17/// Resolve an item's size style in one axis if it is a sizing keyword (`min-content`,
18/// `max-content`, `fit-content`, `fit-content(...)`, or `stretch`).
19///
20/// - `stretch_size` is the size the item would take if stretched to fill the available space
21///   (available space minus margins). Used by `fit-content` and `stretch`.
22/// - `percent_resolution_basis` is the size that percentages resolve against in this axis.
23///   Used by `fit-content(<percentage>)`.
24///
25/// Returns `None` if the size style is not a sizing keyword, or if it cannot
26/// be resolved in the current context (in which case it behaves as `auto`).
27#[inline]
28pub(crate) fn resolve_sizing_keyword(
29    style: Dimension,
30    stretch_size: Option<f32>,
31    percent_resolution_basis: Option<f32>,
32) -> Option<SizingKeywordResolution> {
33    match style.tag() {
34        CompactLength::MIN_CONTENT_TAG => Some(SizingKeywordResolution::Measure(AvailableSpace::MinContent)),
35        CompactLength::MAX_CONTENT_TAG => Some(SizingKeywordResolution::Measure(AvailableSpace::MaxContent)),
36        CompactLength::FIT_CONTENT_PX_TAG => {
37            Some(SizingKeywordResolution::Measure(AvailableSpace::Definite(style.value())))
38        }
39        CompactLength::FIT_CONTENT_PERCENT_TAG => percent_resolution_basis
40            .map(|basis| SizingKeywordResolution::Measure(AvailableSpace::Definite(basis * style.value()))),
41        CompactLength::FIT_CONTENT_KEYWORD_TAG => {
42            stretch_size.map(|size| SizingKeywordResolution::Measure(AvailableSpace::Definite(size)))
43        }
44        CompactLength::STRETCH_TAG => stretch_size.map(SizingKeywordResolution::Exact),
45        _ => None,
46    }
47}
48
49/// Resolve the sizing keywords (`min-content`, `max-content`, `fit-content`, `fit-content(...)`,
50/// and `stretch`) on the size styles of an absolutely positioned item, filling in the
51/// corresponding `known_dimensions` axes.
52///
53/// - `area_size` is the size of the item's containing block (which insets and percentages
54///   resolve against).
55/// - The stretch size in each axis is the containing block minus the item's insets and margins
56///   in that axis.
57#[allow(clippy::too_many_arguments)]
58pub(crate) fn resolve_absolute_sizing_keywords(
59    tree: &mut impl LayoutPartialTree,
60    node: NodeId,
61    known_dimensions: &mut Size<Option<f32>>,
62    size_style: Size<Dimension>,
63    area_size: Size<f32>,
64    inset: Rect<Option<f32>>,
65    margin: Rect<Option<f32>>,
66    sizing_mode: SizingMode,
67) {
68    let stretch_size = Size {
69        width: f32_max(
70            area_size.width
71                - inset.left.unwrap_or(0.0)
72                - inset.right.unwrap_or(0.0)
73                - margin.left.unwrap_or(0.0)
74                - margin.right.unwrap_or(0.0),
75            0.0,
76        ),
77        height: f32_max(
78            area_size.height
79                - inset.top.unwrap_or(0.0)
80                - inset.bottom.unwrap_or(0.0)
81                - margin.top.unwrap_or(0.0)
82                - margin.bottom.unwrap_or(0.0),
83            0.0,
84        ),
85    };
86
87    let keyword_width = if known_dimensions.width.is_none() {
88        resolve_sizing_keyword(size_style.width, Some(stretch_size.width), Some(area_size.width))
89    } else {
90        None
91    };
92    let keyword_height = if known_dimensions.height.is_none() {
93        resolve_sizing_keyword(size_style.height, Some(stretch_size.height), Some(area_size.height))
94    } else {
95        None
96    };
97
98    match (keyword_width, keyword_height) {
99        // If both axes need to be measured then resolve them with a single measure call
100        (
101            Some(SizingKeywordResolution::Measure(available_width)),
102            Some(SizingKeywordResolution::Measure(available_height)),
103        ) => {
104            let measured_size = tree.measure_child_size_both(
105                node,
106                Size::NONE,
107                area_size.map(Some),
108                Size { width: available_width, height: available_height },
109                sizing_mode,
110                Line::FALSE,
111            );
112            *known_dimensions = measured_size.map(Some);
113        }
114        (keyword_width, keyword_height) => {
115            if let Some(resolution) = keyword_width {
116                known_dimensions.width = Some(match resolution {
117                    SizingKeywordResolution::Exact(width) => width,
118                    SizingKeywordResolution::Measure(available_width) => tree.measure_child_size(
119                        node,
120                        *known_dimensions,
121                        area_size.map(Some),
122                        Size { width: available_width, height: AvailableSpace::Definite(stretch_size.height) },
123                        sizing_mode,
124                        AbsoluteAxis::Horizontal,
125                        Line::FALSE,
126                    ),
127                });
128            }
129            if let Some(resolution) = keyword_height {
130                known_dimensions.height = Some(match resolution {
131                    SizingKeywordResolution::Exact(height) => height,
132                    SizingKeywordResolution::Measure(available_height) => tree.measure_child_size(
133                        node,
134                        *known_dimensions,
135                        area_size.map(Some),
136                        Size {
137                            width: known_dimensions
138                                .width
139                                .map(AvailableSpace::Definite)
140                                .unwrap_or(AvailableSpace::Definite(stretch_size.width)),
141                            height: available_height,
142                        },
143                        sizing_mode,
144                        AbsoluteAxis::Vertical,
145                        Line::FALSE,
146                    ),
147                });
148            }
149        }
150    }
151}