implement `direction` property
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}