Skip to main content

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)]
9#[cfg_attr(feature = "serde", derive(Serialize))]
10pub enum RunMode {
11    /// A full layout for this node and all children should be computed
12    PerformLayout,
13    /// The layout algorithm should be executed such that an accurate container size for the node can be determined.
14    /// Layout steps that aren't necessary for determining the container size of the current node can be skipped.
15    ComputeSize,
16    /// This node should have a null layout set as it has been hidden (i.e. using `Display::None`)
17    PerformHiddenLayout,
18}
19
20/// Whether styles should be taken into account when computing size
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(Serialize))]
23pub enum SizingMode {
24    /// Only content contributions should be taken into account
25    ContentSize,
26    /// Inherent size styles should be taken into account in addition to content contributions
27    InherentSize,
28}
29
30/// A set of margins that are available for collapsing with for block layout's margin collapsing
31#[derive(Copy, Clone, Debug, PartialEq)]
32#[cfg_attr(feature = "serde", derive(Serialize))]
33pub struct CollapsibleMarginSet {
34    /// The largest positive margin
35    positive: f32,
36    /// The smallest negative margin (with largest absolute value)
37    negative: f32,
38}
39
40impl CollapsibleMarginSet {
41    /// A default margin set with no collapsible margins
42    pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
43
44    /// Create a set from a single margin
45    pub fn from_margin(margin: f32) -> Self {
46        if margin >= 0.0 {
47            Self { positive: margin, negative: 0.0 }
48        } else {
49            Self { positive: 0.0, negative: margin }
50        }
51    }
52
53    /// Collapse a single margin with this set
54    pub fn collapse_with_margin(mut self, margin: f32) -> Self {
55        if margin >= 0.0 {
56            self.positive = f32_max(self.positive, margin);
57        } else {
58            self.negative = f32_min(self.negative, margin);
59        }
60        self
61    }
62
63    /// Collapse another margin set with this set
64    pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
65        self.positive = f32_max(self.positive, other.positive);
66        self.negative = f32_min(self.negative, other.negative);
67        self
68    }
69
70    /// Resolve the resultant margin from this set once all collapsible margins
71    /// have been collapsed into it
72    pub fn resolve(&self) -> f32 {
73        self.positive + self.negative
74    }
75}
76
77/// An axis that layout algorithms can be requested to compute a size for
78#[derive(Debug, Copy, Clone, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(Serialize))]
80pub enum RequestedAxis {
81    /// The horizontal axis
82    Horizontal,
83    /// The vertical axis
84    Vertical,
85    /// Both axes
86    Both,
87}
88
89impl From<AbsoluteAxis> for RequestedAxis {
90    fn from(value: AbsoluteAxis) -> Self {
91        match value {
92            AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
93            AbsoluteAxis::Vertical => RequestedAxis::Vertical,
94        }
95    }
96}
97impl TryFrom<RequestedAxis> for AbsoluteAxis {
98    type Error = ();
99    fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
100        match value {
101            RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
102            RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
103            RequestedAxis::Both => Err(()),
104        }
105    }
106}
107
108/// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent
109#[derive(Debug, Copy, Clone, PartialEq)]
110#[cfg_attr(feature = "serde", derive(Serialize))]
111pub struct LayoutInput {
112    /// Whether we only need to know the Node's size, or whether we need to perform a full layout
113    pub run_mode: RunMode,
114    /// Whether a Node's style sizes should be taken into account or ignored
115    pub sizing_mode: SizingMode,
116    /// Which axis we need the size of
117    pub axis: RequestedAxis,
118
119    /// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
120    /// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
121    ///
122    ///    "What would the height of this node be, assuming the width is WIDTH"
123    ///
124    /// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is:
125    ///
126    ///   "The exact size of this node is WIDTHxHEIGHT. Please lay out your children"
127    ///
128    pub known_dimensions: Size<Option<f32>>,
129    /// Whether each known dimension should be treated as a *definite* size when laying out the node's
130    /// own content (resolving percentage sizes of children, and collecting flex items into flex lines).
131    ///
132    /// This should be set to `false` for a dimension when a parent imposes a known dimension on a node
133    /// that is derived from the node's own content, and is therefore indefinite per CSS. For example,
134    /// the post-flexing main size of a flex item is indefinite if the flex container's main size is
135    /// indefinite and the item's used flex basis is not definite
136    /// (see <https://www.w3.org/TR/css-flexbox-1/#definite-sizes>).
137    ///
138    /// This flag is ignored (treated as `true`) for axes where the corresponding known dimension is `None`.
139    pub known_dimensions_are_definite: Size<bool>,
140    /// Parent size dimensions are intended to be used for percentage resolution.
141    pub parent_size: Size<Option<f32>>,
142    /// Available space represents an amount of space to layout into, and is used as a soft constraint
143    /// for the purpose of wrapping.
144    pub available_space: Size<AvailableSpace>,
145    /// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
146    pub vertical_margins_are_collapsible: Line<bool>,
147}
148
149impl LayoutInput {
150    /// A LayoutInput that can be used to request hidden layout
151    pub const HIDDEN: LayoutInput = LayoutInput {
152        // The important property for hidden layout
153        run_mode: RunMode::PerformHiddenLayout,
154        // The rest will be ignored
155        known_dimensions: Size::NONE,
156        known_dimensions_are_definite: Size { width: true, height: true },
157        parent_size: Size::NONE,
158        available_space: Size::MAX_CONTENT,
159        sizing_mode: SizingMode::InherentSize,
160        axis: RequestedAxis::Both,
161        vertical_margins_are_collapsible: Line::FALSE,
162    };
163}
164
165/// The first and last baselines of a node in the horizontal axis (i.e. baselines for horizontal text,
166/// measured as an offset from the top edge of the node's border box).
167///
168/// A baseline is the line on which text sits. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines>
169/// for details.
170#[derive(Debug, Copy, Clone, PartialEq)]
171#[cfg_attr(feature = "serde", derive(Serialize))]
172pub struct Baselines {
173    /// The first baseline of the node, if any
174    pub first: Option<f32>,
175    /// The last baseline of the node, if any
176    pub last: Option<f32>,
177}
178
179impl Baselines {
180    /// A `Baselines` with neither a first nor a last baseline
181    pub const NONE: Self = Self { first: None, last: None };
182
183    /// Create a `Baselines` from just a first baseline
184    pub const fn from_first(first: Option<f32>) -> Self {
185        Self { first, last: None }
186    }
187}
188
189/// A struct containing the result of laying a single node, which is returned up to the parent node
190///
191/// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
192/// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
193/// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Baselines::NONE`
194/// for the baselines field
195#[derive(Debug, Copy, Clone, PartialEq)]
196#[cfg_attr(feature = "serde", derive(Serialize))]
197pub struct LayoutOutput {
198    /// The size of the node
199    pub size: Size<f32>,
200    #[cfg(feature = "content_size")]
201    /// The scrollable overflow rectangle of the node's content
202    /// (see [`Layout::scrollable_overflow_rect`] for the coordinate conventions)
203    pub scrollable_overflow_rect: Rect<f32>,
204    /// The first and last baselines of the node in the horizontal axis, if any
205    pub baselines: Baselines,
206    /// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
207    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
208    pub top_margin: CollapsibleMarginSet,
209    /// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
210    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
211    pub bottom_margin: CollapsibleMarginSet,
212    /// Whether margins can be collapsed through this node. This is used for CSS block layout and can
213    /// be set to `false` for other layout modes that don't support margin collapsing
214    pub margins_can_collapse_through: bool,
215}
216
217impl LayoutOutput {
218    /// An all-zero `LayoutOutput` for hidden nodes
219    pub const HIDDEN: Self = Self {
220        size: Size::ZERO,
221        #[cfg(feature = "content_size")]
222        scrollable_overflow_rect: Rect::ZERO,
223        baselines: Baselines::NONE,
224        top_margin: CollapsibleMarginSet::ZERO,
225        bottom_margin: CollapsibleMarginSet::ZERO,
226        margins_can_collapse_through: false,
227    };
228
229    /// A blank layout output
230    pub const DEFAULT: Self = Self::HIDDEN;
231
232    /// Constructor to create a `LayoutOutput` from just the size, scrollable overflow rectangle and baselines
233    pub fn from_sizes_and_baselines(
234        size: Size<f32>,
235        #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] scrollable_overflow_rect: Rect<f32>,
236        baselines: Baselines,
237    ) -> Self {
238        Self {
239            size,
240            #[cfg(feature = "content_size")]
241            scrollable_overflow_rect,
242            baselines,
243            top_margin: CollapsibleMarginSet::ZERO,
244            bottom_margin: CollapsibleMarginSet::ZERO,
245            margins_can_collapse_through: false,
246        }
247    }
248
249    /// Construct a `LayoutOutput` from just the container size and scrollable overflow rectangle
250    pub fn from_sizes(size: Size<f32>, scrollable_overflow_rect: Rect<f32>) -> Self {
251        Self::from_sizes_and_baselines(size, scrollable_overflow_rect, Baselines::NONE)
252    }
253
254    /// Construct a `LayoutOutput` from just the container's size.
255    pub fn from_outer_size(size: Size<f32>) -> Self {
256        Self::from_sizes(size, Rect::ZERO)
257    }
258}
259
260/// The final result of a layout algorithm for a single node.
261#[derive(Debug, Copy, Clone, PartialEq)]
262#[cfg_attr(feature = "serde", derive(Serialize))]
263pub struct Layout {
264    /// The relative ordering of the node
265    ///
266    /// Nodes with a higher order should be rendered on top of those with a lower order.
267    /// This is effectively a topological sort of each tree.
268    pub order: u32,
269    /// The top-left corner of the node
270    pub location: Point<f32>,
271    /// The width and height of the node
272    pub size: Size<f32>,
273    #[cfg(feature = "content_size")]
274    /// The scrollable overflow rectangle of the node: the axis-aligned rectangle containing the
275    /// content of the node (the border boxes of its descendants plus their non-clipped overflow),
276    /// corresponding to the CSS "scrollable overflow rectangle"
277    /// (<https://www.w3.org/TR/css-overflow-3/#scrollable>), except that transforms are not
278    /// accounted for.
279    ///
280    /// Coordinates are measured from the node's *scroll origin*: the corner of the padding box at
281    /// the block-start/inline-start edge (the top-left corner in LTR, the top-*right* corner in
282    /// RTL), with `left`/`right` measuring along the inline axis in the direction of reachable
283    /// scrolling. The rectangle always contains the origin, so `left`/`top` are `<= 0.0` (negative
284    /// values represent overflow before the scroll origin, which is unreachable by scrolling) and
285    /// `right`/`bottom` are `>= 0.0` (representing the reachable extent of the content, which is
286    /// useful for computing a "scroll width/height" for scrollable nodes).
287    pub scrollable_overflow_rect: Rect<f32>,
288    /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
289    pub scrollbar_size: Size<f32>,
290    /// The size of the borders of the node
291    pub border: Rect<f32>,
292    /// The size of the padding of the node
293    pub padding: Rect<f32>,
294    /// The size of the margin of the node
295    pub margin: Rect<f32>,
296}
297
298impl Default for Layout {
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304impl Layout {
305    /// Creates a new zero-[`Layout`].
306    ///
307    /// The Zero-layout has size and location set to ZERO.
308    /// The `order` value of this layout is set to the minimum value of 0.
309    /// This means it should be rendered below all other [`Layout`]s.
310    #[must_use]
311    pub const fn new() -> Self {
312        Self {
313            order: 0,
314            location: Point::ZERO,
315            size: Size::zero(),
316            #[cfg(feature = "content_size")]
317            scrollable_overflow_rect: Rect::ZERO,
318            scrollbar_size: Size::zero(),
319            border: Rect::zero(),
320            padding: Rect::zero(),
321            margin: Rect::zero(),
322        }
323    }
324
325    /// Creates a new zero-[`Layout`] with the supplied `order` value.
326    ///
327    /// Nodes with a higher order should be rendered on top of those with a lower order.
328    /// The Zero-layout has size and location set to ZERO.
329    #[must_use]
330    pub const fn with_order(order: u32) -> Self {
331        Self {
332            order,
333            size: Size::zero(),
334            location: Point::ZERO,
335            #[cfg(feature = "content_size")]
336            scrollable_overflow_rect: Rect::ZERO,
337            scrollbar_size: Size::zero(),
338            border: Rect::zero(),
339            padding: Rect::zero(),
340            margin: Rect::zero(),
341        }
342    }
343
344    /// Get the width of the node's content box
345    #[inline]
346    pub fn content_box_width(&self) -> f32 {
347        self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
348    }
349
350    /// Get the height of the node's content box
351    #[inline]
352    pub fn content_box_height(&self) -> f32 {
353        self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
354    }
355
356    /// Get the size of the node's content box
357    #[inline]
358    pub fn content_box_size(&self) -> Size<f32> {
359        Size { width: self.content_box_width(), height: self.content_box_height() }
360    }
361
362    /// Get x offset of the node's content box relative to it's parent's border box
363    pub fn content_box_x(&self) -> f32 {
364        self.location.x + self.border.left + self.padding.left
365    }
366
367    /// Get x offset of the node's content box relative to it's parent's border box
368    pub fn content_box_y(&self) -> f32 {
369        self.location.y + self.border.top + self.padding.top
370    }
371}
372
373#[cfg(feature = "content_size")]
374impl Layout {
375    /// Return the maximum horizontal scroll offset of the node.
376    /// This is the reachable extent of the content less the width of the padding box, floored at zero.
377    pub fn scroll_width(&self) -> f32 {
378        f32_max(
379            0.0,
380            self.scrollable_overflow_rect.right + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
381                + self.border.left
382                + self.border.right,
383        )
384    }
385
386    /// Return the maximum vertical scroll offset of the node.
387    /// This is the reachable extent of the content less the height of the padding box, floored at zero.
388    pub fn scroll_height(&self) -> f32 {
389        f32_max(
390            0.0,
391            self.scrollable_overflow_rect.bottom + f32_min(self.scrollbar_size.height, self.size.height)
392                - self.size.height
393                + self.border.top
394                + self.border.bottom,
395        )
396    }
397}
398
399/// The additional information from layout algorithm
400#[cfg(feature = "detailed_layout_info")]
401#[derive(Debug, Clone, PartialEq)]
402pub enum DetailedLayoutInfo {
403    /// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo)
404    #[cfg(feature = "grid")]
405    Grid(Box<crate::compute::grid::DetailedGridInfo>),
406    /// For node that hasn't had any detailed information yet
407    None,
408}