gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Definitions

⎇ 230ab9359bba75fdda0ff35319d082e79189046f src/tree/layout.rs Rust · 361 lines · 13.6 KB History Who touched what raw

implement `direction` property 230ab9359 · Kane Wang ·

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