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::{AlignItemsKeyword, AlignSelf, AlignmentSafety, AvailableSpace, CheapCloneStr, Position};
4use crate::style_helpers::TaffyMaxContent;
5use crate::sys::DefaultCheapStr;
6use crate::tree::NodeId;
7use crate::util::sys::{f32_max, f32_min};
8
9/// Whether we are performing a full layout, or we merely need to size the node
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(Serialize))]
12pub enum RunMode {
13    /// A full layout for this node and all children should be computed
14    PerformLayout,
15    /// The layout algorithm should be executed such that an accurate container size for the node can be determined.
16    /// Layout steps that aren't necessary for determining the container size of the current node can be skipped.
17    ComputeSize,
18    /// This node should have a null layout set as it has been hidden (i.e. using `Display::None`)
19    PerformHiddenLayout,
20}
21
22/// Whether styles should be taken into account when computing size
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(Serialize))]
25pub enum SizingMode {
26    /// Only content contributions should be taken into account
27    ContentSize,
28    /// Inherent size styles should be taken into account in addition to content contributions
29    InherentSize,
30}
31
32/// A set of margins that are available for collapsing with for block layout's margin collapsing
33#[derive(Copy, Clone, Debug, PartialEq)]
34#[cfg_attr(feature = "serde", derive(Serialize))]
35pub struct CollapsibleMarginSet {
36    /// The largest positive margin
37    positive: f32,
38    /// The smallest negative margin (with largest absolute value)
39    negative: f32,
40}
41
42impl CollapsibleMarginSet {
43    /// A default margin set with no collapsible margins
44    pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
45
46    /// Create a set from a single margin
47    pub fn from_margin(margin: f32) -> Self {
48        if margin >= 0.0 {
49            Self { positive: margin, negative: 0.0 }
50        } else {
51            Self { positive: 0.0, negative: margin }
52        }
53    }
54
55    /// Collapse a single margin with this set
56    pub fn collapse_with_margin(mut self, margin: f32) -> Self {
57        if margin >= 0.0 {
58            self.positive = f32_max(self.positive, margin);
59        } else {
60            self.negative = f32_min(self.negative, margin);
61        }
62        self
63    }
64
65    /// Collapse another margin set with this set
66    pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
67        self.positive = f32_max(self.positive, other.positive);
68        self.negative = f32_min(self.negative, other.negative);
69        self
70    }
71
72    /// Resolve the resultant margin from this set once all collapsible margins
73    /// have been collapsed into it
74    pub fn resolve(&self) -> f32 {
75        self.positive + self.negative
76    }
77}
78
79/// An axis that layout algorithms can be requested to compute a size for
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(Serialize))]
82pub enum RequestedAxis {
83    /// The horizontal axis
84    Horizontal,
85    /// The vertical axis
86    Vertical,
87    /// Both axes
88    Both,
89}
90
91impl RequestedAxis {
92    /// Swap the horizontal and vertical axes (`Both` is unchanged)
93    #[inline(always)]
94    pub fn transpose(self) -> RequestedAxis {
95        match self {
96            RequestedAxis::Horizontal => RequestedAxis::Vertical,
97            RequestedAxis::Vertical => RequestedAxis::Horizontal,
98            RequestedAxis::Both => RequestedAxis::Both,
99        }
100    }
101}
102
103impl From<AbsoluteAxis> for RequestedAxis {
104    fn from(value: AbsoluteAxis) -> Self {
105        match value {
106            AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
107            AbsoluteAxis::Vertical => RequestedAxis::Vertical,
108        }
109    }
110}
111impl TryFrom<RequestedAxis> for AbsoluteAxis {
112    type Error = ();
113    fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
114        match value {
115            RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
116            RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
117            RequestedAxis::Both => Err(()),
118        }
119    }
120}
121
122/// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent
123#[derive(Debug, Copy, Clone, PartialEq)]
124#[cfg_attr(feature = "serde", derive(Serialize))]
125pub struct LayoutInput {
126    /// Whether we only need to know the Node's size, or whether we need to perform a full layout
127    pub run_mode: RunMode,
128    /// Whether a Node's style sizes should be taken into account or ignored
129    pub sizing_mode: SizingMode,
130    /// Which axis we need the size of
131    pub axis: RequestedAxis,
132
133    /// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
134    /// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
135    ///
136    ///    "What would the height of this node be, assuming the width is WIDTH"
137    ///
138    /// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is:
139    ///
140    ///   "The exact size of this node is WIDTHxHEIGHT. Please lay out your children"
141    ///
142    pub known_dimensions: Size<Option<f32>>,
143    /// Whether each known dimension should be treated as a *definite* size when laying out the node's
144    /// own content (resolving percentage sizes of children, and collecting flex items into flex lines).
145    ///
146    /// This should be set to `false` for a dimension when a parent imposes a known dimension on a node
147    /// that is derived from the node's own content, and is therefore indefinite per CSS. For example,
148    /// the post-flexing main size of a flex item is indefinite if the flex container's main size is
149    /// indefinite and the item's used flex basis is not definite
150    /// (see <https://www.w3.org/TR/css-flexbox-1/#definite-sizes>).
151    ///
152    /// This flag is ignored (treated as `true`) for axes where the corresponding known dimension is `None`.
153    pub known_dimensions_are_definite: Size<bool>,
154    /// Parent size dimensions are intended to be used for percentage resolution.
155    pub parent_size: Size<Option<f32>>,
156    /// Available space represents an amount of space to layout into, and is used as a soft constraint
157    /// for the purpose of wrapping.
158    ///
159    /// A definite available space is the space available to the node's border box: the parent
160    /// subtracts the node's margins before passing it, and the node must not subtract them again.
161    pub available_space: Size<AvailableSpace>,
162    /// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
163    pub vertical_margins_are_collapsible: Line<bool>,
164}
165
166impl LayoutInput {
167    /// A LayoutInput that can be used to request hidden layout
168    pub const HIDDEN: LayoutInput = LayoutInput {
169        // The important property for hidden layout
170        run_mode: RunMode::PerformHiddenLayout,
171        // The rest will be ignored
172        known_dimensions: Size::NONE,
173        known_dimensions_are_definite: Size { width: true, height: true },
174        parent_size: Size::NONE,
175        available_space: Size::MAX_CONTENT,
176        sizing_mode: SizingMode::InherentSize,
177        axis: RequestedAxis::Both,
178        vertical_margins_are_collapsible: Line::FALSE,
179    };
180
181    /// Swap the horizontal and vertical axes of every per-axis input.
182    ///
183    /// Lets a tree lay out a node in a vertical writing mode: transpose the inputs, run the
184    /// (horizontal-tb) algorithm, then transpose the [`LayoutOutput`] back.
185    /// `vertical_margins_are_collapsible` is reset to `Line::FALSE` as margins cannot collapse
186    /// across a change of block axis.
187    pub fn transpose(self) -> LayoutInput {
188        LayoutInput {
189            run_mode: self.run_mode,
190            sizing_mode: self.sizing_mode,
191            axis: self.axis.transpose(),
192            known_dimensions: self.known_dimensions.transpose(),
193            known_dimensions_are_definite: self.known_dimensions_are_definite.transpose(),
194            parent_size: self.parent_size.transpose(),
195            available_space: self.available_space.transpose(),
196            vertical_margins_are_collapsible: Line::FALSE,
197        }
198    }
199}
200
201/// The first and last baselines of a node in the horizontal axis (i.e. baselines for horizontal text,
202/// measured as an offset from the top edge of the node's border box).
203///
204/// A baseline is the line on which text sits. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines>
205/// for details.
206#[derive(Debug, Copy, Clone, PartialEq)]
207#[cfg_attr(feature = "serde", derive(Serialize))]
208pub struct Baselines {
209    /// The first baseline of the node, if any
210    pub first: Option<f32>,
211    /// The last baseline of the node, if any
212    pub last: Option<f32>,
213}
214
215impl Baselines {
216    /// A `Baselines` with neither a first nor a last baseline
217    pub const NONE: Self = Self { first: None, last: None };
218
219    /// Create a `Baselines` from just a first baseline
220    pub const fn from_first(first: Option<f32>) -> Self {
221        Self { first, last: None }
222    }
223}
224
225/// The physical alignment of an out-of-flow box's margin box within its static-position
226/// area (per-axis).
227///
228/// The static position of an out-of-flow box may depend on alignment properties
229/// (`justify-content`/`align-self` in flexbox, `justify-self`/`align-self` in grid) which cannot
230/// be fully resolved until the box's final size is known. To defer that resolution, the static
231/// position is recorded as an *area* plus the physical alignment keyword to apply to the box's
232/// margin box within that area.
233#[derive(Debug, Copy, Clone, PartialEq, Eq)]
234pub enum AxisStaticEdge {
235    /// Align the start (left/top) edge of the box's margin box to the start of the area
236    Start,
237    /// Center the box within the area
238    Center,
239    /// Align the end (right/bottom) edge of the box's margin box to the end of the area
240    End,
241}
242
243/// The alignment to apply to an out-of-flow box's margin box within its static-position area
244/// in a single axis.
245///
246/// Keywords are physical: container-specific semantics (`justify-content`/`align-self`
247/// resolution, flex-direction/wrap-reverse reversal, RTL flipping, `self-start`/`self-end`)
248/// are resolved by the emitting layout algorithm.
249#[derive(Debug, Copy, Clone, PartialEq, Eq)]
250pub struct AxisStaticAlign {
251    /// The physical alignment keyword
252    pub keyword: AxisStaticEdge,
253    /// The `safe` overflow-position keyword from CSS Box Alignment: when `Safe`, the
254    /// `fallback` keyword is used instead if the box's margin box overflows the area
255    pub safety: AlignmentSafety,
256    /// The physical alignment keyword to fall back to when `safety` is `Safe` and the box's
257    /// margin box overflows the area
258    pub fallback: AxisStaticEdge,
259}
260
261impl AxisStaticAlign {
262    /// Create a `AxisStaticAlign` from a keyword with no safe fallback
263    pub const fn from_keyword(keyword: AxisStaticEdge) -> Self {
264        Self { keyword, safety: AlignmentSafety::Default, fallback: keyword }
265    }
266}
267
268/// The static position of an out-of-flow box in a single axis: the alignment container
269/// (start/end coordinates) plus the alignment to apply to the box's margin box within it.
270///
271/// Area coordinates are initially relative to the border box of the node which produced the
272/// candidate and are translated by each node's location as the candidate bubbles up the tree, so
273/// they are always relative to the border box of the node currently holding the candidate.
274///
275/// Emitters which have no alignment area in an axis (e.g. block layout, where the static
276/// position is a point) emit a degenerate area (`start == end`).
277#[derive(Debug, Copy, Clone, PartialEq)]
278pub struct AxisStaticPosition {
279    /// The alignment container in this axis
280    pub area: Line<f32>,
281    /// The alignment to apply to the box's margin box within `area`
282    pub align: AxisStaticAlign,
283}
284
285impl AxisStaticPosition {
286    /// Create a `AxisStaticPosition` with a degenerate (zero-extent) area at `anchor` and no
287    /// safe fallback
288    pub const fn from_edge(anchor: f32, edge: AxisStaticEdge) -> Self {
289        Self { area: Line { start: anchor, end: anchor }, align: AxisStaticAlign::from_keyword(edge) }
290    }
291
292    /// Create a `AxisStaticPosition` which aligns the box within `area` according to its
293    /// self-alignment property in this axis (`justify-self` in the inline axis, `align-self` in
294    /// the block axis). `normal` and `stretch` behave as `start`. This is the static position
295    /// used by block and inline layout.
296    ///
297    /// `alignment` must already be resolved: `auto` (`None`) replaced by the container's
298    /// `justify-items`/`align-items`, and `self-start`/`self-end` resolved against the
299    /// container's direction (see [`AlignSelf::resolve_self_relative`]).
300    /// `axis_is_rtl` is whether this axis is the inline axis of a right-to-left static-position
301    /// containing block, in which case `start` and `end` are flipped.
302    pub fn from_alignment(alignment: AlignSelf, area: Line<f32>, axis_is_rtl: bool) -> Self {
303        debug_assert!(
304            !matches!(alignment.keyword, AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd),
305            "self-start/self-end must be resolved before computing the static position"
306        );
307        /// The edge of the static-position rectangle that a (resolved) alignment keyword
308        /// aligns the box to. Normal and Stretch (and anything else that is not an end or
309        /// center alignment) fall back to start-alignment for static-position purposes.
310        fn edge_for(keyword: AlignItemsKeyword, axis_is_rtl: bool) -> AxisStaticEdge {
311            let start_position = !matches!(keyword, AlignItemsKeyword::End | AlignItemsKeyword::FlexEnd) ^ axis_is_rtl;
312            match keyword {
313                AlignItemsKeyword::Center => AxisStaticEdge::Center,
314                _ if start_position => AxisStaticEdge::Start,
315                _ => AxisStaticEdge::End,
316            }
317        }
318        let fallback = if matches!(alignment.safety, AlignmentSafety::Safe) {
319            AlignItemsKeyword::Start
320        } else {
321            alignment.keyword
322        };
323        Self {
324            area,
325            align: AxisStaticAlign {
326                keyword: edge_for(alignment.keyword, axis_is_rtl),
327                safety: alignment.safety,
328                fallback: edge_for(fallback, axis_is_rtl),
329            },
330        }
331    }
332}
333
334/// A record of an out-of-flow (`position: absolute` or `position: fixed`) box which has not yet
335/// been laid out because its containing block is an ancestor of its parent.
336///
337/// Candidates are produced by the layout algorithm of the box's parent (which computes the static
338/// position) and bubble up through `LayoutOutput` until they reach a node which acts as the box's
339/// containing block, which lays the box out.
340#[derive(Debug, Copy, Clone, PartialEq)]
341pub struct OofCandidate {
342    /// The id of the out-of-flow node
343    pub node: NodeId,
344    /// The `order` value that the node's layout should be assigned (its child index within its parent)
345    pub order: u32,
346    /// The position style of the node (`Position::Absolute` or `Position::Fixed`)
347    pub position: Position,
348    /// The static position of the box in each axis
349    pub static_position: Point<AxisStaticPosition>,
350}
351
352/// A list of [`OofCandidate`]s.
353///
354/// This is a lazily-allocated heap vector (an empty list performs no allocation).
355#[derive(Debug, Clone, PartialEq, Default)]
356pub struct OofCandidates {
357    /// Backing storage for the candidates
358    inner: Option<crate::util::sys::Box<crate::util::sys::Vec<OofCandidate>>>,
359}
360
361impl OofCandidates {
362    /// An empty list of candidates
363    pub const NONE: Self = Self { inner: None };
364
365    /// Create a new empty list of candidates
366    pub const fn new() -> Self {
367        Self::NONE
368    }
369
370    /// Whether the list contains no candidates
371    #[inline]
372    pub fn is_empty(&self) -> bool {
373        self.as_slice().is_empty()
374    }
375
376    /// The candidates as a slice
377    #[inline]
378    pub fn as_slice(&self) -> &[OofCandidate] {
379        self.inner.as_ref().map(|boxed| boxed.as_slice()).unwrap_or(&[])
380    }
381
382    /// The candidates as a mutable slice
383    #[inline]
384    pub fn as_mut_slice(&mut self) -> &mut [OofCandidate] {
385        self.inner.as_mut().map(|boxed| boxed.as_mut_slice()).unwrap_or(&mut [])
386    }
387
388    /// Append a candidate to the list
389    #[inline]
390    pub fn push(&mut self, candidate: OofCandidate) {
391        self.inner.get_or_insert_with(Default::default).push(candidate);
392    }
393
394    /// Move all candidates from `other` into `self`, leaving `other` empty
395    #[inline]
396    pub fn append(&mut self, other: &mut OofCandidates) {
397        if other.is_empty() {
398            return;
399        }
400        match &mut self.inner {
401            None => self.inner = other.inner.take(),
402            Some(vec) => vec.append(other.inner.as_mut().unwrap()),
403        }
404    }
405
406    /// Take the candidates out of this list, leaving it empty
407    #[inline]
408    pub fn take(&mut self) -> Self {
409        core::mem::take(self)
410    }
411
412    /// Remove all candidates from the list
413    #[inline]
414    pub fn clear(&mut self) {
415        self.inner = None;
416    }
417
418    /// Iterate over the candidates
419    #[inline]
420    pub fn iter(&self) -> core::slice::Iter<'_, OofCandidate> {
421        self.as_slice().iter()
422    }
423
424    /// Translate the static-position areas of every candidate in the list by `offset`.
425    ///
426    /// This is used to convert areas from being relative to a child node's border box to being
427    /// relative to the parent node's border box as candidates bubble up the tree.
428    #[inline]
429    pub fn translate(&mut self, offset: Point<f32>) {
430        for candidate in self.as_mut_slice() {
431            candidate.static_position.x.area.start += offset.x;
432            candidate.static_position.x.area.end += offset.x;
433            candidate.static_position.y.area.start += offset.y;
434            candidate.static_position.y.area.end += offset.y;
435        }
436    }
437}
438
439impl<'a> IntoIterator for &'a OofCandidates {
440    type Item = &'a OofCandidate;
441    type IntoIter = core::slice::Iter<'a, OofCandidate>;
442    fn into_iter(self) -> Self::IntoIter {
443        self.iter()
444    }
445}
446
447/// The area of a container against which the insets of its out-of-flow (absolute/fixed) boxes
448/// are resolved: the container's border box minus its borders and scrollbar gutters.
449///
450/// Container layout algorithms record this on [`LayoutOutput`] (for `RunMode::PerformLayout` runs)
451/// so that the out-of-flow positioning pass ([`compute_oof_layout`](crate::compute_oof_layout))
452/// can run after the algorithm has finished. When the container is a grid, per-box grid areas are
453/// resolved within this area by the positioning pass itself (from the container's detailed grid
454/// info).
455#[derive(Debug, Copy, Clone, PartialEq)]
456#[cfg_attr(feature = "serde", derive(Serialize))]
457pub struct OofPositioningArea {
458    /// The size of the area
459    pub size: Size<f32>,
460    /// The offset of the area from the container's border box origin
461    pub offset: Point<f32>,
462}
463
464/// A struct containing the result of laying a single node, which is returned up to the parent node
465///
466/// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
467/// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
468/// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Baselines::NONE`
469/// for the baselines field
470#[derive(Debug, Clone, PartialEq)]
471#[cfg_attr(feature = "serde", derive(Serialize))]
472pub struct LayoutOutput {
473    /// The size of the node
474    pub size: Size<f32>,
475    #[cfg(feature = "content_size")]
476    /// The scrollable overflow rectangle of the node's content
477    /// (see [`Layout::scrollable_overflow_rect`] for the coordinate conventions)
478    pub scrollable_overflow_rect: Rect<f32>,
479    /// The first and last baselines of the node in the horizontal axis, if any
480    pub baselines: Baselines,
481    /// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
482    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
483    pub top_margin: CollapsibleMarginSet,
484    /// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
485    /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
486    pub bottom_margin: CollapsibleMarginSet,
487    /// Whether margins can be collapsed through this node. This is used for CSS block layout and can
488    /// be set to `false` for other layout modes that don't support margin collapsing
489    pub margins_can_collapse_through: bool,
490    /// Out-of-flow (absolute/fixed) descendants of this node for which this node is *not* the
491    /// containing block. These bubble up the tree until they reach their containing block, which
492    /// lays them out. Empty (allocation-free) when there are no such descendants (the common case).
493    ///
494    /// Candidates are only produced by `RunMode::PerformLayout` passes.
495    ///
496    /// Container layout algorithms place the *full* document-ordered candidate list here (direct
497    /// out-of-flow children plus candidates bubbled from in-flow children). The out-of-flow
498    /// positioning pass ([`compute_oof_layout`](crate::compute_oof_layout)) then lays out those
499    /// for which the node is the containing block and replaces this list with the unclaimed
500    /// remainder.
501    #[cfg_attr(feature = "serde", serde(skip_serializing))]
502    pub oof_candidates: OofCandidates,
503    /// The node's inset-resolution area for out-of-flow boxes. Set by container layout
504    /// algorithms for `RunMode::PerformLayout` runs; `None` for leaves and size-only runs
505    /// (in which case the out-of-flow positioning pass is a no-op).
506    pub oof_positioning_area: Option<OofPositioningArea>,
507}
508
509impl LayoutOutput {
510    /// An all-zero `LayoutOutput` for hidden nodes
511    pub const HIDDEN: Self = Self {
512        size: Size::ZERO,
513        #[cfg(feature = "content_size")]
514        scrollable_overflow_rect: Rect::ZERO,
515        baselines: Baselines::NONE,
516        top_margin: CollapsibleMarginSet::ZERO,
517        bottom_margin: CollapsibleMarginSet::ZERO,
518        margins_can_collapse_through: false,
519        oof_candidates: OofCandidates::NONE,
520        oof_positioning_area: None,
521    };
522
523    /// A blank layout output
524    pub const DEFAULT: Self = Self::HIDDEN;
525
526    /// Constructor to create a `LayoutOutput` from just the size, scrollable overflow rectangle and baselines
527    pub fn from_sizes_and_baselines(
528        size: Size<f32>,
529        #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] scrollable_overflow_rect: Rect<f32>,
530        baselines: Baselines,
531    ) -> Self {
532        Self {
533            size,
534            #[cfg(feature = "content_size")]
535            scrollable_overflow_rect,
536            baselines,
537            top_margin: CollapsibleMarginSet::ZERO,
538            bottom_margin: CollapsibleMarginSet::ZERO,
539            margins_can_collapse_through: false,
540            oof_candidates: OofCandidates::NONE,
541            oof_positioning_area: None,
542        }
543    }
544
545    /// Construct a `LayoutOutput` from just the container size and scrollable overflow rectangle
546    pub fn from_sizes(size: Size<f32>, scrollable_overflow_rect: Rect<f32>) -> Self {
547        Self::from_sizes_and_baselines(size, scrollable_overflow_rect, Baselines::NONE)
548    }
549
550    /// Swap the horizontal and vertical axes of the output (inverse of [`LayoutInput::transpose`]).
551    ///
552    /// Baselines and collapsible margins are block-axis quantities that have no meaning on the
553    /// parent's inline axis, so they are dropped. Out-of-flow candidates are unaffected.
554    pub fn transpose(self) -> Self {
555        Self {
556            size: self.size.transpose(),
557            #[cfg(feature = "content_size")]
558            scrollable_overflow_rect: self.scrollable_overflow_rect.transpose(),
559            baselines: Baselines::NONE,
560            top_margin: CollapsibleMarginSet::ZERO,
561            bottom_margin: CollapsibleMarginSet::ZERO,
562            margins_can_collapse_through: false,
563            oof_candidates: self.oof_candidates,
564            oof_positioning_area: self
565                .oof_positioning_area
566                .map(|area| OofPositioningArea { size: area.size.transpose(), offset: area.offset.transpose() }),
567        }
568    }
569
570    /// Construct a `LayoutOutput` from just the container's size.
571    pub fn from_outer_size(size: Size<f32>) -> Self {
572        Self::from_sizes(size, Rect::ZERO)
573    }
574}
575
576/// The final result of a layout algorithm for a single node.
577#[derive(Debug, Copy, Clone, PartialEq)]
578#[cfg_attr(feature = "serde", derive(Serialize))]
579pub struct Layout {
580    /// The relative ordering of the node
581    ///
582    /// Nodes with a higher order should be rendered on top of those with a lower order.
583    /// This is effectively a topological sort of each tree.
584    pub order: u32,
585    /// The top-left corner of the node
586    pub location: Point<f32>,
587    /// The width and height of the node
588    pub size: Size<f32>,
589    #[cfg(feature = "content_size")]
590    /// The scrollable overflow rectangle of the node: the axis-aligned rectangle containing the
591    /// content of the node (the border boxes of its descendants plus their non-clipped overflow),
592    /// corresponding to the CSS "scrollable overflow rectangle"
593    /// (<https://www.w3.org/TR/css-overflow-3/#scrollable>), except that transforms are not
594    /// accounted for.
595    ///
596    /// Coordinates are measured from the node's *scroll origin*: the corner of the padding box at
597    /// the block-start/inline-start edge (the top-left corner in LTR, the top-*right* corner in
598    /// RTL), with `left`/`right` measuring along the inline axis in the direction of reachable
599    /// scrolling. The rectangle always contains the origin, so `left`/`top` are `<= 0.0` (negative
600    /// values represent overflow before the scroll origin, which is unreachable by scrolling) and
601    /// `right`/`bottom` are `>= 0.0` (representing the reachable extent of the content, which is
602    /// useful for computing a "scroll width/height" for scrollable nodes).
603    pub scrollable_overflow_rect: Rect<f32>,
604    /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
605    pub scrollbar_size: Size<f32>,
606    /// The size of the borders of the node
607    pub border: Rect<f32>,
608    /// The size of the padding of the node
609    pub padding: Rect<f32>,
610    /// The size of the margin of the node
611    pub margin: Rect<f32>,
612}
613
614impl Default for Layout {
615    fn default() -> Self {
616        Self::new()
617    }
618}
619
620impl Layout {
621    /// Creates a new zero-[`Layout`].
622    ///
623    /// The Zero-layout has size and location set to ZERO.
624    /// The `order` value of this layout is set to the minimum value of 0.
625    /// This means it should be rendered below all other [`Layout`]s.
626    #[must_use]
627    pub const fn new() -> Self {
628        Self {
629            order: 0,
630            location: Point::ZERO,
631            size: Size::zero(),
632            #[cfg(feature = "content_size")]
633            scrollable_overflow_rect: Rect::ZERO,
634            scrollbar_size: Size::zero(),
635            border: Rect::zero(),
636            padding: Rect::zero(),
637            margin: Rect::zero(),
638        }
639    }
640
641    /// Creates a new zero-[`Layout`] with the supplied `order` value.
642    ///
643    /// Nodes with a higher order should be rendered on top of those with a lower order.
644    /// The Zero-layout has size and location set to ZERO.
645    #[must_use]
646    pub const fn with_order(order: u32) -> Self {
647        Self {
648            order,
649            size: Size::zero(),
650            location: Point::ZERO,
651            #[cfg(feature = "content_size")]
652            scrollable_overflow_rect: Rect::ZERO,
653            scrollbar_size: Size::zero(),
654            border: Rect::zero(),
655            padding: Rect::zero(),
656            margin: Rect::zero(),
657        }
658    }
659
660    /// Get the width of the node's content box
661    #[inline]
662    pub fn content_box_width(&self) -> f32 {
663        self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
664    }
665
666    /// Get the height of the node's content box
667    #[inline]
668    pub fn content_box_height(&self) -> f32 {
669        self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
670    }
671
672    /// Get the size of the node's content box
673    #[inline]
674    pub fn content_box_size(&self) -> Size<f32> {
675        Size { width: self.content_box_width(), height: self.content_box_height() }
676    }
677
678    /// Get x offset of the node's content box relative to it's parent's border box
679    pub fn content_box_x(&self) -> f32 {
680        self.location.x + self.border.left + self.padding.left
681    }
682
683    /// Get x offset of the node's content box relative to it's parent's border box
684    pub fn content_box_y(&self) -> f32 {
685        self.location.y + self.border.top + self.padding.top
686    }
687}
688
689#[cfg(feature = "content_size")]
690impl Layout {
691    /// Return the maximum horizontal scroll offset of the node.
692    /// This is the reachable extent of the content less the width of the padding box, floored at zero.
693    pub fn scroll_width(&self) -> f32 {
694        f32_max(
695            0.0,
696            self.scrollable_overflow_rect.right + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
697                + self.border.left
698                + self.border.right,
699        )
700    }
701
702    /// Return the maximum vertical scroll offset of the node.
703    /// This is the reachable extent of the content less the height of the padding box, floored at zero.
704    pub fn scroll_height(&self) -> f32 {
705        f32_max(
706            0.0,
707            self.scrollable_overflow_rect.bottom + f32_min(self.scrollbar_size.height, self.size.height)
708                - self.size.height
709                + self.border.top
710                + self.border.bottom,
711        )
712    }
713}
714
715/// The additional information from layout algorithm
716#[derive(Debug, Clone, PartialEq)]
717pub enum DetailedLayoutInfo<S: CheapCloneStr = DefaultCheapStr> {
718    /// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo)
719    #[cfg(feature = "grid")]
720    Grid(crate::util::sys::Box<crate::compute::grid::DetailedGridInfo<S>>),
721    /// For node that hasn't had any detailed information yet
722    None,
723    /// Unused variant which exists only to consume the `S` type parameter when no variant
724    /// carrying detailed layout info is enabled
725    #[cfg(not(feature = "grid"))]
726    #[doc(hidden)]
727    Phantom(core::convert::Infallible, core::marker::PhantomData<S>),
728}