⎇ a16ec6535c4ae72c805e5a459ade3bab03e3c394
src/tree/layout.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Increase coverage of serde derives (#686)
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};
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))]
10pub 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))]
23pub 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))]
33pub struct CollapsibleMarginSet {
34 /// The largest positive margin
35 positive: f32,
36 /// The smallest negative margin (with largest absolute value)
37 negative: f32,
38}
39
40impl 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
45 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
54 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
64 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
72 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)]
79#[cfg_attr(feature = "serde", derive(Serialize))]
80pub enum RequestedAxis {
81 /// The horizontal axis
82 Horizontal,
83 /// The vertical axis
84 Vertical,
85 /// Both axes
86 Both,
87}
88
89impl From<AbsoluteAxis> for RequestedAxis {
90 fn from(value: AbsoluteAxis) -> Self {
91 match value {
92 AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
93 AbsoluteAxis::Vertical => RequestedAxis::Vertical,
94 }
95 }
96}
97impl TryFrom<RequestedAxis> for AbsoluteAxis {
98 type 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)]
110#[cfg_attr(feature = "serde", derive(Serialize))]
111pub 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
138impl 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)]
160#[cfg_attr(feature = "serde", derive(Serialize))]
161pub 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
180impl 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 /// Constructor to create a `LayoutOutput` from just the size and baselines
193 pub fn from_sizes_and_baselines(
194 size: Size<f32>,
195 #[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>,
196 first_baselines: Point<Option<f32>>,
197 ) -> Self {
198 Self {
199 size,
200 #[cfg(feature = "content_size")]
201 content_size,
202 first_baselines,
203 top_margin: CollapsibleMarginSet::ZERO,
204 bottom_margin: CollapsibleMarginSet::ZERO,
205 margins_can_collapse_through: false,
206 }
207 }
208
209 /// Construct a SizeBaselinesAndMargins from just the container and content sizes
210 pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self {
211 Self::from_sizes_and_baselines(size, content_size, Point::NONE)
212 }
213
214 /// Construct a SizeBaselinesAndMargins from just the container's size.
215 pub fn from_outer_size(size: Size<f32>) -> Self {
216 Self::from_sizes(size, Size::zero())
217 }
218}
219
220/// The final result of a layout algorithm for a single node.
221#[derive(Debug, Copy, Clone)]
222#[cfg_attr(feature = "serde", derive(Serialize))]
223pub struct Layout {
224 /// The relative ordering of the node
225 ///
226 /// Nodes with a higher order should be rendered on top of those with a lower order.
227 /// This is effectively a topological sort of each tree.
228 pub order: u32,
229 /// The top-left corner of the node
230 pub location: Point<f32>,
231 /// The width and height of the node
232 pub size: Size<f32>,
233 #[cfg(feature = "content_size")]
234 /// The width and height of the content inside the node. This may be larger than the size of the node in the case of
235 /// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes
236 pub content_size: Size<f32>,
237 /// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
238 pub scrollbar_size: Size<f32>,
239 /// The size of the borders of the node
240 pub border: Rect<f32>,
241 /// The size of the padding of the node
242 pub padding: Rect<f32>,
243 /// The size of the margin of the node
244 pub margin: Rect<f32>,
245}
246
247impl Default for Layout {
248 fn default() -> Self {
249 Self::new()
250 }
251}
252
253impl Layout {
254 /// Creates a new zero-[`Layout`].
255 ///
256 /// The Zero-layout has size and location set to ZERO.
257 /// The `order` value of this layout is set to the minimum value of 0.
258 /// This means it should be rendered below all other [`Layout`]s.
259 #[must_use]
260 pub const fn new() -> Self {
261 Self {
262 order: 0,
263 location: Point::ZERO,
264 size: Size::zero(),
265 #[cfg(feature = "content_size")]
266 content_size: Size::zero(),
267 scrollbar_size: Size::zero(),
268 border: Rect::zero(),
269 padding: Rect::zero(),
270 margin: Rect::zero(),
271 }
272 }
273
274 /// Creates a new zero-[`Layout`] with the supplied `order` value.
275 ///
276 /// Nodes with a higher order should be rendered on top of those with a lower order.
277 /// The Zero-layout has size and location set to ZERO.
278 #[must_use]
279 pub const fn with_order(order: u32) -> Self {
280 Self {
281 order,
282 size: Size::zero(),
283 location: Point::ZERO,
284 #[cfg(feature = "content_size")]
285 content_size: Size::zero(),
286 scrollbar_size: Size::zero(),
287 border: Rect::zero(),
288 padding: Rect::zero(),
289 margin: Rect::zero(),
290 }
291 }
292}
293
294#[cfg(feature = "content_size")]
295impl Layout {
296 /// Return the scroll width of the node.
297 /// The scroll width is the difference between the width and the content width, floored at zero
298 pub fn scroll_width(&self) -> f32 {
299 f32_max(
300 0.0,
301 self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
302 + self.border.right,
303 )
304 }
305
306 /// Return the scroll width of the node.
307 /// The scroll width is the difference between the width and the content width, floored at zero
308 pub fn scroll_height(&self) -> f32 {
309 f32_max(
310 0.0,
311 self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height
312 + self.border.bottom,
313 )
314 }
315}