taffy/tree/
layout.rs

1//! Final data structures that represent the high-level UI layout
2use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
3use crate::style::AvailableSpace;
4use crate::style_helpers::TaffyMaxContent;
5use crate::util::sys::{f32_max, f32_min};
6
7/// Whether we are performing a full layout, or we merely need to size the node
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub enum RunMode {
10    /// A full layout for this node and all children should be computed
11    PerformLayout,
12    /// The layout algorithm should be executed such that an accurate container size for the node can be determined.
13    /// Layout steps that aren't necessary for determining the container size of the current node can be skipped.
14    ComputeSize,
15    /// This node should have a null layout set as it has been hidden (i.e. using `Display::None`)
16    PerformHiddenLayout,
17}
18
19/// Whether styles should be taken into account when computing size
20#[derive(Copy, Clone, Debug, PartialEq, Eq)]
21pub enum SizingMode {
22    /// Only content contributions should be taken into account
23    ContentSize,
24    /// Inherent size styles should be taken into account in addition to content contributions
25    InherentSize,
26}
27
28/// A set of margins that are available for collapsing with for block layout's margin collapsing
29#[derive(Copy, Clone, Debug, PartialEq)]
30pub struct CollapsibleMarginSet {
31    /// The largest positive margin
32    positive: f32,
33    /// The smallest negative margin (with largest absolute value)
34    negative: f32,
35}
36
37impl CollapsibleMarginSet {
38    /// A default margin set with no collapsible margins
39    pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
40
41    /// Create a set from a single margin
42    pub fn from_margin(margin: f32) -> Self {
43        if margin >= 0.0 {
44            Self { positive: margin, negative: 0.0 }
45        } else {
46            Self { positive: 0.0, negative: margin }
47        }
48    }
49
50    /// Collapse a single margin with this set
51    pub fn collapse_with_margin(mut self, margin: f32) -> Self {
52        if margin >= 0.0 {
53            self.positive = f32_max(self.positive, margin);
54        } else {
55            self.negative = f32_min(self.negative, margin);
56        }
57        self
58    }
59
60    /// Collapse another margin set with this set
61    pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
62        self.positive = f32_max(self.positive, other.positive);
63        self.negative = f32_min(self.negative, other.negative);
64        self
65    }
66
67    /// Resolve the resultant margin from this set once all collapsible margins
68    /// have been collapsed into it
69    pub fn resolve(&self) -> f32 {
70        self.positive + self.negative
71    }
72}
73
74/// An axis that layout algorithms can be requested to compute a size for
75#[derive(Debug, Copy, Clone)]
76pub enum RequestedAxis {
77    /// The horizontal axis
78    Horizontal,
79    /// The vertical axis
80    Vertical,
81    /// Both axes
82    Both,
83}
84
85impl From<AbsoluteAxis> for RequestedAxis {
86    fn from(value: AbsoluteAxis) -> Self {
87        match value {
88            AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
89            AbsoluteAxis::Vertical => RequestedAxis::Vertical,
90        }
91    }
92}
93impl TryFrom<RequestedAxis> for AbsoluteAxis {
94    type Error = ();
95    fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
96        match value {
97            RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
98            RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
99            RequestedAxis::Both => Err(()),
100        }
101    }
102}
103
104/// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent
105#[derive(Debug, Copy, Clone)]
106pub struct LayoutInput {
107    /// Whether we only need to know the Node's size, or whe
108    pub run_mode: RunMode,
109    /// Whether a Node's style sizes should be taken into account or ignored
110    pub sizing_mode: SizingMode,
111    /// Which axis we need the size of
112    pub axis: RequestedAxis,
113
114    /// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
115    /// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
116    ///
117    ///    "What would the height of this node be, assuming the width is WIDTH"
118    ///
119    /// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is:
120    ///
121    ///   "The exact size of this node is WIDTHxHEIGHT. Please lay out your children"
122    ///
123    pub known_dimensions: Size<Option<f32>>,
124    /// Parent size dimensions are intended to be used for percentage resolution.
125    pub parent_size: Size<Option<f32>>,
126    /// Available space represents an amount of space to layout into, and is used as a soft constraint
127    /// for the purpose of wrapping.
128    pub available_space: Size<AvailableSpace>,
129    /// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
130    pub vertical_margins_are_collapsible: Line<bool>,
131}
132
133impl LayoutInput {
134    /// A LayoutInput that can be used to request hidden layout
135    pub const HIDDEN: LayoutInput = LayoutInput {
136        // The important property for hidden layout
137        run_mode: RunMode::PerformHiddenLayout,
138        // The rest will be ignored
139        known_dimensions: Size::NONE,
140        parent_size: Size::NONE,
141        available_space: Size::MAX_CONTENT,
142        sizing_mode: SizingMode::InherentSize,
143        axis: RequestedAxis::Both,
144        vertical_margins_are_collapsible: Line::FALSE,
145    };
146}
147
148/// A struct containing the result of laying a single node, which is returned up to the parent node
149///
150/// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
151/// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
152/// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Point::NONE`
153/// for the first_baselines field
154#[derive(Debug, Copy, Clone)]
155pub struct LayoutOutput {
156    /// The size of the node
157    pub size: Size<f32>,
158    #[cfg(feature = "content_size")]
159    /// The size of the content within the node
160    pub content_size: Size<f32>,
161    /// The first baseline of the node in each dimension, if any
162    pub first_baselines: Point<Option<f32>>,
163    /// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
164    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
165    pub top_margin: CollapsibleMarginSet,
166    /// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
167    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
168    pub bottom_margin: CollapsibleMarginSet,
169    /// Whether margins can be collapsed through this node. This is used for CSS block layout and can
170    /// be set to `false` for other layout modes that don't support margin collapsing
171    pub margins_can_collapse_through: bool,
172}
173
174impl LayoutOutput {
175    /// An all-zero `LayoutOutput` for hidden nodes
176    pub const HIDDEN: Self = Self {
177        size: Size::ZERO,
178        #[cfg(feature = "content_size")]
179        content_size: Size::ZERO,
180        first_baselines: Point::NONE,
181        top_margin: CollapsibleMarginSet::ZERO,
182        bottom_margin: CollapsibleMarginSet::ZERO,
183        margins_can_collapse_through: false,
184    };
185
186    /// Constructor to create a `LayoutOutput` from just the size and baselines
187    pub fn from_sizes_and_baselines(
188        size: Size<f32>,
189        #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>,
190        first_baselines: Point<Option<f32>>,
191    ) -> Self {
192        Self {
193            size,
194            #[cfg(feature = "content_size")]
195            content_size,
196            first_baselines,
197            top_margin: CollapsibleMarginSet::ZERO,
198            bottom_margin: CollapsibleMarginSet::ZERO,
199            margins_can_collapse_through: false,
200        }
201    }
202
203    /// Construct a SizeBaselinesAndMargins from just the container and content sizes
204    pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self {
205        Self::from_sizes_and_baselines(size, content_size, Point::NONE)
206    }
207
208    /// Construct a SizeBaselinesAndMargins from just the container's size.
209    pub fn from_outer_size(size: Size<f32>) -> Self {
210        Self::from_sizes(size, Size::zero())
211    }
212}
213
214/// The final result of a layout algorithm for a single node.
215#[derive(Debug, Copy, Clone)]
216pub struct Layout {
217    /// The relative ordering of the node
218    ///
219    /// Nodes with a higher order should be rendered on top of those with a lower order.
220    /// This is effectively a topological sort of each tree.
221    pub order: u32,
222    /// The top-left corner of the node
223    pub location: Point<f32>,
224    /// The width and height of the node
225    pub size: Size<f32>,
226    #[cfg(feature = "content_size")]
227    /// The width and height of the content inside the node. This may be larger than the size of the node in the case of
228    /// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes
229    pub content_size: Size<f32>,
230    /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
231    pub scrollbar_size: Size<f32>,
232    /// The size of the borders of the node
233    pub border: Rect<f32>,
234    /// The size of the padding of the node
235    pub padding: Rect<f32>,
236}
237
238impl Layout {
239    /// Creates a new zero-[`Layout`].
240    ///
241    /// The Zero-layout has size and location set to ZERO.
242    /// The `order` value of this layout is set to the minimum value of 0.
243    /// This means it should be rendered below all other [`Layout`]s.
244    #[must_use]
245    pub const fn new() -> Self {
246        Self {
247            order: 0,
248            location: Point::ZERO,
249            size: Size::zero(),
250            #[cfg(feature = "content_size")]
251            content_size: Size::zero(),
252            scrollbar_size: Size::zero(),
253            border: Rect::zero(),
254            padding: Rect::zero(),
255        }
256    }
257
258    /// Creates a new zero-[`Layout`] with the supplied `order` value.
259    ///
260    /// Nodes with a higher order should be rendered on top of those with a lower order.
261    /// The Zero-layout has size and location set to ZERO.
262    #[must_use]
263    pub const fn with_order(order: u32) -> Self {
264        Self {
265            order,
266            size: Size::zero(),
267            location: Point::ZERO,
268            #[cfg(feature = "content_size")]
269            content_size: Size::zero(),
270            scrollbar_size: Size::zero(),
271            border: Rect::zero(),
272            padding: Rect::zero(),
273        }
274    }
275}
276
277#[cfg(feature = "content_size")]
278impl Layout {
279    /// Return the scroll width of the node.
280    /// The scroll width is the difference between the width and the content width, floored at zero
281    pub fn scroll_width(&self) -> f32 {
282        f32_max(
283            0.0,
284            self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
285                + self.border.right,
286        )
287    }
288
289    /// Return the scroll width of the node.
290    /// The scroll width is the difference between the width and the content width, floored at zero
291    pub fn scroll_height(&self) -> f32 {
292        f32_max(
293            0.0,
294            self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height
295                + self.border.bottom,
296        )
297    }
298}