• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Final data structures that represent the high-level UI layout
2 use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
3 use crate::style::AvailableSpace;
4 use crate::style_helpers::TaffyMaxContent;
5 use 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))]
10 pub 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))]
23 pub 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))]
33 pub struct CollapsibleMarginSet {
34     /// The largest positive margin
35     positive: f32,
36     /// The smallest negative margin (with largest absolute value)
37     negative: f32,
38 }
39 
40 impl 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
from_margin(margin: f32) -> Self45     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
collapse_with_margin(mut self, margin: f32) -> Self54     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
collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self64     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
resolve(&self) -> f3272     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))]
80 pub enum RequestedAxis {
81     /// The horizontal axis
82     Horizontal,
83     /// The vertical axis
84     Vertical,
85     /// Both axes
86     Both,
87 }
88 
89 impl From<AbsoluteAxis> for RequestedAxis {
from(value: AbsoluteAxis) -> Self90     fn from(value: AbsoluteAxis) -> Self {
91         match value {
92             AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
93             AbsoluteAxis::Vertical => RequestedAxis::Vertical,
94         }
95     }
96 }
97 impl TryFrom<RequestedAxis> for AbsoluteAxis {
98     type Error = ();
try_from(value: RequestedAxis) -> Result<Self, Self::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))]
111 pub struct LayoutInput {
112     /// Whether we only need to know the Node's size, or whe
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     /// Parent size dimensions are intended to be used for percentage resolution.
130     pub parent_size: Size<Option<f32>>,
131     /// Available space represents an amount of space to layout into, and is used as a soft constraint
132     /// for the purpose of wrapping.
133     pub available_space: Size<AvailableSpace>,
134     /// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
135     pub vertical_margins_are_collapsible: Line<bool>,
136 }
137 
138 impl LayoutInput {
139     /// A LayoutInput that can be used to request hidden layout
140     pub const HIDDEN: LayoutInput = LayoutInput {
141         // The important property for hidden layout
142         run_mode: RunMode::PerformHiddenLayout,
143         // The rest will be ignored
144         known_dimensions: Size::NONE,
145         parent_size: Size::NONE,
146         available_space: Size::MAX_CONTENT,
147         sizing_mode: SizingMode::InherentSize,
148         axis: RequestedAxis::Both,
149         vertical_margins_are_collapsible: Line::FALSE,
150     };
151 }
152 
153 /// A struct containing the result of laying a single node, which is returned up to the parent node
154 ///
155 /// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
156 /// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
157 /// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Point::NONE`
158 /// for the first_baselines field
159 #[derive(Debug, Copy, Clone, PartialEq)]
160 #[cfg_attr(feature = "serde", derive(Serialize))]
161 pub struct LayoutOutput {
162     /// The size of the node
163     pub size: Size<f32>,
164     #[cfg(feature = "content_size")]
165     /// The size of the content within the node
166     pub content_size: Size<f32>,
167     /// The first baseline of the node in each dimension, if any
168     pub first_baselines: Point<Option<f32>>,
169     /// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
170     /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
171     pub top_margin: CollapsibleMarginSet,
172     /// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
173     /// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
174     pub bottom_margin: CollapsibleMarginSet,
175     /// Whether margins can be collapsed through this node. This is used for CSS block layout and can
176     /// be set to `false` for other layout modes that don't support margin collapsing
177     pub margins_can_collapse_through: bool,
178 }
179 
180 impl LayoutOutput {
181     /// An all-zero `LayoutOutput` for hidden nodes
182     pub const HIDDEN: Self = Self {
183         size: Size::ZERO,
184         #[cfg(feature = "content_size")]
185         content_size: Size::ZERO,
186         first_baselines: Point::NONE,
187         top_margin: CollapsibleMarginSet::ZERO,
188         bottom_margin: CollapsibleMarginSet::ZERO,
189         margins_can_collapse_through: false,
190     };
191 
192     /// A blank layout output
193     pub const DEFAULT: Self = Self::HIDDEN;
194 
195     /// Constructor to create a `LayoutOutput` from just the size and baselines
from_sizes_and_baselines( size: Size<f32>, #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>, first_baselines: Point<Option<f32>>, ) -> Self196     pub fn from_sizes_and_baselines(
197         size: Size<f32>,
198         #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>,
199         first_baselines: Point<Option<f32>>,
200     ) -> Self {
201         Self {
202             size,
203             #[cfg(feature = "content_size")]
204             content_size,
205             first_baselines,
206             top_margin: CollapsibleMarginSet::ZERO,
207             bottom_margin: CollapsibleMarginSet::ZERO,
208             margins_can_collapse_through: false,
209         }
210     }
211 
212     /// Construct a SizeBaselinesAndMargins from just the container and content sizes
from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self213     pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self {
214         Self::from_sizes_and_baselines(size, content_size, Point::NONE)
215     }
216 
217     /// Construct a SizeBaselinesAndMargins from just the container's size.
from_outer_size(size: Size<f32>) -> Self218     pub fn from_outer_size(size: Size<f32>) -> Self {
219         Self::from_sizes(size, Size::zero())
220     }
221 }
222 
223 /// The final result of a layout algorithm for a single node.
224 #[derive(Debug, Copy, Clone, PartialEq)]
225 #[cfg_attr(feature = "serde", derive(Serialize))]
226 pub struct Layout {
227     /// The relative ordering of the node
228     ///
229     /// Nodes with a higher order should be rendered on top of those with a lower order.
230     /// This is effectively a topological sort of each tree.
231     pub order: u32,
232     /// The top-left corner of the node
233     pub location: Point<f32>,
234     /// The width and height of the node
235     pub size: Size<f32>,
236     #[cfg(feature = "content_size")]
237     /// The width and height of the content inside the node. This may be larger than the size of the node in the case of
238     /// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes
239     pub content_size: Size<f32>,
240     /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
241     pub scrollbar_size: Size<f32>,
242     /// The size of the borders of the node
243     pub border: Rect<f32>,
244     /// The size of the padding of the node
245     pub padding: Rect<f32>,
246     /// The size of the margin of the node
247     pub margin: Rect<f32>,
248 }
249 
250 impl Default for Layout {
default() -> Self251     fn default() -> Self {
252         Self::new()
253     }
254 }
255 
256 impl Layout {
257     /// Creates a new zero-[`Layout`].
258     ///
259     /// The Zero-layout has size and location set to ZERO.
260     /// The `order` value of this layout is set to the minimum value of 0.
261     /// This means it should be rendered below all other [`Layout`]s.
262     #[must_use]
new() -> Self263     pub const fn new() -> Self {
264         Self {
265             order: 0,
266             location: Point::ZERO,
267             size: Size::zero(),
268             #[cfg(feature = "content_size")]
269             content_size: Size::zero(),
270             scrollbar_size: Size::zero(),
271             border: Rect::zero(),
272             padding: Rect::zero(),
273             margin: Rect::zero(),
274         }
275     }
276 
277     /// Creates a new zero-[`Layout`] with the supplied `order` value.
278     ///
279     /// Nodes with a higher order should be rendered on top of those with a lower order.
280     /// The Zero-layout has size and location set to ZERO.
281     #[must_use]
with_order(order: u32) -> Self282     pub const fn with_order(order: u32) -> Self {
283         Self {
284             order,
285             size: Size::zero(),
286             location: Point::ZERO,
287             #[cfg(feature = "content_size")]
288             content_size: Size::zero(),
289             scrollbar_size: Size::zero(),
290             border: Rect::zero(),
291             padding: Rect::zero(),
292             margin: Rect::zero(),
293         }
294     }
295 
296     /// Get the width of the node's content box
297     #[inline]
content_box_width(&self) -> f32298     pub fn content_box_width(&self) -> f32 {
299         self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
300     }
301 
302     /// Get the height of the node's content box
303     #[inline]
content_box_height(&self) -> f32304     pub fn content_box_height(&self) -> f32 {
305         self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
306     }
307 
308     /// Get the size of the node's content box
309     #[inline]
content_box_size(&self) -> Size<f32>310     pub fn content_box_size(&self) -> Size<f32> {
311         Size { width: self.content_box_width(), height: self.content_box_height() }
312     }
313 
314     /// Get x offset of the node's content box relative to it's parent's border box
content_box_x(&self) -> f32315     pub fn content_box_x(&self) -> f32 {
316         self.location.x + self.border.left + self.padding.left
317     }
318 
319     /// Get x offset of the node's content box relative to it's parent's border box
content_box_y(&self) -> f32320     pub fn content_box_y(&self) -> f32 {
321         self.location.y + self.border.top + self.padding.top
322     }
323 }
324 
325 #[cfg(feature = "content_size")]
326 impl Layout {
327     /// Return the scroll width of the node.
328     /// The scroll width is the difference between the width and the content width, floored at zero
scroll_width(&self) -> f32329     pub fn scroll_width(&self) -> f32 {
330         f32_max(
331             0.0,
332             self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
333                 + self.border.right,
334         )
335     }
336 
337     /// Return the scroll height of the node.
338     /// The scroll height is the difference between the height and the content height, floored at zero
scroll_height(&self) -> f32339     pub fn scroll_height(&self) -> f32 {
340         f32_max(
341             0.0,
342             self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height
343                 + self.border.bottom,
344         )
345     }
346 }
347 
348 /// The additional information from layout algorithm
349 #[cfg(feature = "detailed_layout_info")]
350 #[derive(Debug, Clone, PartialEq)]
351 pub enum DetailedLayoutInfo {
352     /// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo)
353     #[cfg(feature = "grid")]
354     Grid(Box<crate::compute::grid::DetailedGridInfo>),
355     /// For node that hasn't had any detailed information yet
356     None,
357 }
358