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}