Refactor/module hierarchy (#443)
1//! A typed representation of [CSS style properties](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) in Rust. Used as input to layout computation.
2mod alignment;
3mod dimension;
4
5#[cfg(feature = "flexbox")]
6mod flex;
7
8pub use self::alignment::{AlignContent, AlignItems, AlignSelf, JustifyContent, JustifyItems, JustifySelf};
9pub use self::dimension::{AvailableSpace, Dimension, LengthPercentage, LengthPercentageAuto};
10
11#[cfg(feature = "flexbox")]
12pub use self::flex::{FlexDirection, FlexWrap};
13
14#[cfg(feature = "grid")]
15mod grid;
16#[cfg(feature = "grid")]
17pub(crate) use self::grid::{GenericGridPlacement, OriginZeroGridPlacement};
18#[cfg(feature = "grid")]
19pub use self::grid::{
20 GridAutoFlow, GridPlacement, GridTrackRepetition, MaxTrackSizingFunction, MinTrackSizingFunction,
21 NonRepeatedTrackSizingFunction, TrackSizingFunction,
22};
23use crate::geometry::{Point, Rect, Size};
24
25#[cfg(feature = "grid")]
26use crate::geometry::Line;
27#[cfg(feature = "serde")]
28use crate::style_helpers;
29#[cfg(feature = "grid")]
30use crate::util::sys::GridTrackVec;
31
32/// Sets the layout used for the children of this node
33///
34/// The default values depends on on which feature flags are enabled. The order of precedence is: Flex, Grid, None.
35#[derive(Copy, Clone, PartialEq, Eq, Debug)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub enum Display {
38 /// The children will follow the flexbox layout algorithm
39 #[cfg(feature = "flexbox")]
40 Flex,
41 /// The children will follow the CSS Grid layout algorithm
42 #[cfg(feature = "grid")]
43 Grid,
44 /// The children will not be laid out, and will follow absolute positioning
45 None,
46}
47
48impl Display {
49 /// The default of Display.
50 #[cfg(feature = "flexbox")]
51 pub const DEFAULT: Display = Display::Flex;
52
53 /// The default of Display.
54 #[cfg(all(feature = "grid", not(feature = "flexbox")))]
55 pub const DEFAULT: Display = Display::Grid;
56
57 /// The default of Display.
58 #[cfg(all(not(feature = "flexbox"), not(feature = "grid")))]
59 pub const DEFAULT: Display = Display::None;
60}
61
62impl Default for Display {
63 fn default() -> Self {
64 Self::DEFAULT
65 }
66}
67
68/// The positioning strategy for this item.
69///
70/// This controls both how the origin is determined for the [`Style::position`] field,
71/// and whether or not the item will be controlled by flexbox's layout algorithm.
72///
73/// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position),
74/// which can be unintuitive.
75///
76/// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS.
77#[derive(Copy, Clone, PartialEq, Eq, Debug)]
78#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
79pub enum Position {
80 /// The offset is computed relative to the final position given by the layout algorithm.
81 /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end.
82 Relative,
83 /// The offset is computed relative to this item's closest positioned ancestor, if any.
84 /// Otherwise, it is placed relative to the origin.
85 /// No space is created for the item in the page layout, and its size will not be altered.
86 ///
87 /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object.
88 Absolute,
89}
90
91impl Default for Position {
92 fn default() -> Self {
93 Self::Relative
94 }
95}
96
97/// How children overflowing their container should affect layout
98///
99/// In CSS the primary effect of this property is to control whether contents of a parent container that overflow that container should
100/// be displayed anyway, be clipped, or trigger the container to become a scroll container. However it also has secondary effects on layout,
101/// the main ones being:
102///
103/// - The automatic minimum size Flexbox/CSS Grid items with non-`Visible` overflow is `0` rather than being content based
104///
105/// In Taffy, we only implement the layout related secondary effects as we are not concerned with drawing/painting.
106///
107/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>
108#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110pub enum Overflow {
111 /// The automatic minimum size of this node as a flexbox/grid item should be based on the size of it's content.
112 #[default]
113 Visible,
114 /// The automatic minimum size of this node as a flexbox/grid item should be `0`.
115 Hidden,
116}
117
118impl Overflow {
119 /// Returns true for overflow modes that contain their contents (`Overflow::Hidden`, `Overflow::Scroll`, `Overflow::Auto`)
120 /// or else false for overflow modes that allow their contains to spill (`Overflow::Visible`).
121 #[inline(always)]
122 pub(crate) fn is_scroll_container(self) -> bool {
123 self != Overflow::Visible
124 }
125
126 /// Returns `Some(0.0)` if the overflow mode would cause the automatic minimum size of a Flexbox or CSS Grid item
127 /// to be `0`. Else returns None.
128 #[inline(always)]
129 pub(crate) fn maybe_into_automatic_min_size(self) -> Option<f32> {
130 match self.is_scroll_container() {
131 true => Some(0.0),
132 false => None,
133 }
134 }
135}
136
137/// The flexbox layout information for a single node.
138///
139/// The most important idea in flexbox is the notion of a "main" and "cross" axis, which are always perpendicular to each other.
140/// The orientation of these axes are controlled via the [`FlexDirection`] field of this struct.
141///
142/// This struct follows the [CSS equivalent](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) directly;
143/// information about the behavior on the web should transfer directly.
144///
145/// Detailed information about the exact behavior of each of these fields
146/// can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS) by searching for the field name.
147/// The distinction between margin, padding and border is explained well in
148/// this [introduction to the box model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model).
149///
150/// If the behavior does not match the flexbox layout algorithm on the web, please file a bug!
151#[derive(Clone, PartialEq, Debug)]
152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
153#[cfg_attr(feature = "serde", serde(default))]
154pub struct Style {
155 /// What layout strategy should be used?
156 pub display: Display,
157
158 // Overflow properties
159 /// How children overflowing their container should affect layout
160 pub overflow: Point<Overflow>,
161
162 // Position properties
163 /// What should the `position` value of this struct use as a base offset?
164 pub position: Position,
165 /// How should the position of this element be tweaked relative to the layout defined?
166 #[cfg_attr(feature = "serde", serde(default = "style_helpers::auto"))]
167 pub inset: Rect<LengthPercentageAuto>,
168
169 // Size properies
170 /// Sets the initial size of the item
171 #[cfg_attr(feature = "serde", serde(default = "style_helpers::auto"))]
172 pub size: Size<Dimension>,
173 /// Controls the minimum size of the item
174 #[cfg_attr(feature = "serde", serde(default = "style_helpers::auto"))]
175 pub min_size: Size<Dimension>,
176 /// Controls the maximum size of the item
177 #[cfg_attr(feature = "serde", serde(default = "style_helpers::auto"))]
178 pub max_size: Size<Dimension>,
179 /// Sets the preferred aspect ratio for the item
180 ///
181 /// The ratio is calculated as width divided by height.
182 pub aspect_ratio: Option<f32>,
183
184 // Spacing Properties
185 /// How large should the margin be on each side?
186 #[cfg_attr(feature = "serde", serde(default = "style_helpers::zero"))]
187 pub margin: Rect<LengthPercentageAuto>,
188 /// How large should the padding be on each side?
189 #[cfg_attr(feature = "serde", serde(default = "style_helpers::zero"))]
190 pub padding: Rect<LengthPercentage>,
191 /// How large should the border be on each side?
192 #[cfg_attr(feature = "serde", serde(default = "style_helpers::zero"))]
193 pub border: Rect<LengthPercentage>,
194
195 // Alignment properties
196 /// How this node's children aligned in the cross/block axis?
197 #[cfg(any(feature = "flexbox", feature = "grid"))]
198 pub align_items: Option<AlignItems>,
199 /// How this node should be aligned in the cross/block axis
200 /// Falls back to the parents [`AlignItems`] if not set
201 #[cfg(any(feature = "flexbox", feature = "grid"))]
202 pub align_self: Option<AlignSelf>,
203 /// How this node's children should be aligned in the inline axis
204 #[cfg(feature = "grid")]
205 pub justify_items: Option<AlignItems>,
206 /// How this node should be aligned in the inline axis
207 /// Falls back to the parents [`JustifyItems`] if not set
208 #[cfg(feature = "grid")]
209 pub justify_self: Option<AlignSelf>,
210 /// How should content contained within this item be aligned in the cross/block axis
211 #[cfg(any(feature = "flexbox", feature = "grid"))]
212 pub align_content: Option<AlignContent>,
213 /// How should contained within this item be aligned in the main/inline axis
214 #[cfg(any(feature = "flexbox", feature = "grid"))]
215 pub justify_content: Option<JustifyContent>,
216 /// How large should the gaps between items in a grid or flex container be?
217 #[cfg(any(feature = "flexbox", feature = "grid"))]
218 #[cfg_attr(feature = "serde", serde(default = "style_helpers::zero"))]
219 pub gap: Size<LengthPercentage>,
220
221 // Flexbox properies
222 /// Which direction does the main axis flow in?
223 #[cfg(feature = "flexbox")]
224 pub flex_direction: FlexDirection,
225 /// Should elements wrap, or stay in a single line?
226 #[cfg(feature = "flexbox")]
227 pub flex_wrap: FlexWrap,
228 /// Sets the initial main axis size of the item
229 #[cfg(feature = "flexbox")]
230 pub flex_basis: Dimension,
231 /// The relative rate at which this item grows when it is expanding to fill space
232 ///
233 /// 0.0 is the default value, and this value must be positive.
234 #[cfg(feature = "flexbox")]
235 pub flex_grow: f32,
236 /// The relative rate at which this item shrinks when it is contracting to fit into space
237 ///
238 /// 1.0 is the default value, and this value must be positive.
239 #[cfg(feature = "flexbox")]
240 pub flex_shrink: f32,
241
242 // Grid container properies
243 /// Defines the track sizing functions (widths) of the grid rows
244 #[cfg(feature = "grid")]
245 pub grid_template_rows: GridTrackVec<TrackSizingFunction>,
246 /// Defines the track sizing functions (heights) of the grid columns
247 #[cfg(feature = "grid")]
248 pub grid_template_columns: GridTrackVec<TrackSizingFunction>,
249 /// Defines the size of implicitly created rows
250 #[cfg(feature = "grid")]
251 pub grid_auto_rows: GridTrackVec<NonRepeatedTrackSizingFunction>,
252 /// Defined the size of implicitly created columns
253 #[cfg(feature = "grid")]
254 pub grid_auto_columns: GridTrackVec<NonRepeatedTrackSizingFunction>,
255 /// Controls how items get placed into the grid for auto-placed items
256 #[cfg(feature = "grid")]
257 pub grid_auto_flow: GridAutoFlow,
258
259 // Grid child properties
260 /// Defines which row in the grid the item should start and end at
261 #[cfg(feature = "grid")]
262 pub grid_row: Line<GridPlacement>,
263 /// Defines which column in the grid the item should start and end at
264 #[cfg(feature = "grid")]
265 pub grid_column: Line<GridPlacement>,
266}
267
268impl Style {
269 /// The [`Default`] layout, in a form that can be used in const functions
270 pub const DEFAULT: Style = Style {
271 display: Display::DEFAULT,
272 overflow: Point { x: Overflow::Visible, y: Overflow::Visible },
273 position: Position::Relative,
274 inset: Rect::auto(),
275 margin: Rect::zero(),
276 padding: Rect::zero(),
277 border: Rect::zero(),
278 size: Size::auto(),
279 min_size: Size::auto(),
280 max_size: Size::auto(),
281 aspect_ratio: None,
282 #[cfg(any(feature = "flexbox", feature = "grid"))]
283 gap: Size::zero(),
284 // Aligment
285 #[cfg(any(feature = "flexbox", feature = "grid"))]
286 align_items: None,
287 #[cfg(any(feature = "flexbox", feature = "grid"))]
288 align_self: None,
289 #[cfg(feature = "grid")]
290 justify_items: None,
291 #[cfg(feature = "grid")]
292 justify_self: None,
293 #[cfg(any(feature = "flexbox", feature = "grid"))]
294 align_content: None,
295 #[cfg(any(feature = "flexbox", feature = "grid"))]
296 justify_content: None,
297 // Flexbox
298 #[cfg(feature = "flexbox")]
299 flex_direction: FlexDirection::Row,
300 #[cfg(feature = "flexbox")]
301 flex_wrap: FlexWrap::NoWrap,
302 #[cfg(feature = "flexbox")]
303 flex_grow: 0.0,
304 #[cfg(feature = "flexbox")]
305 flex_shrink: 1.0,
306 #[cfg(feature = "flexbox")]
307 flex_basis: Dimension::Auto,
308 // Grid
309 #[cfg(feature = "grid")]
310 grid_template_rows: GridTrackVec::new(),
311 #[cfg(feature = "grid")]
312 grid_template_columns: GridTrackVec::new(),
313 #[cfg(feature = "grid")]
314 grid_auto_rows: GridTrackVec::new(),
315 #[cfg(feature = "grid")]
316 grid_auto_columns: GridTrackVec::new(),
317 #[cfg(feature = "grid")]
318 grid_auto_flow: GridAutoFlow::Row,
319 #[cfg(feature = "grid")]
320 grid_row: Line { start: GridPlacement::Auto, end: GridPlacement::Auto },
321 #[cfg(feature = "grid")]
322 grid_column: Line { start: GridPlacement::Auto, end: GridPlacement::Auto },
323 };
324}
325
326impl Default for Style {
327 fn default() -> Self {
328 Style::DEFAULT
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::Style;
335 use crate::geometry::*;
336
337 #[test]
338 fn defaults_match() {
339 #[cfg(feature = "grid")]
340 use super::GridPlacement;
341
342 let old_defaults = Style {
343 display: Default::default(),
344 overflow: Default::default(),
345 position: Default::default(),
346 #[cfg(feature = "flexbox")]
347 flex_direction: Default::default(),
348 #[cfg(feature = "flexbox")]
349 flex_wrap: Default::default(),
350 #[cfg(any(feature = "flexbox", feature = "grid"))]
351 align_items: Default::default(),
352 #[cfg(any(feature = "flexbox", feature = "grid"))]
353 align_self: Default::default(),
354 #[cfg(feature = "grid")]
355 justify_items: Default::default(),
356 #[cfg(feature = "grid")]
357 justify_self: Default::default(),
358 #[cfg(any(feature = "flexbox", feature = "grid"))]
359 align_content: Default::default(),
360 #[cfg(any(feature = "flexbox", feature = "grid"))]
361 justify_content: Default::default(),
362 inset: Rect::auto(),
363 margin: Rect::zero(),
364 padding: Rect::zero(),
365 border: Rect::zero(),
366 gap: Size::zero(),
367 #[cfg(feature = "flexbox")]
368 flex_grow: 0.0,
369 #[cfg(feature = "flexbox")]
370 flex_shrink: 1.0,
371 #[cfg(feature = "flexbox")]
372 flex_basis: super::Dimension::Auto,
373 size: Size::auto(),
374 min_size: Size::auto(),
375 max_size: Size::auto(),
376 aspect_ratio: Default::default(),
377 #[cfg(feature = "grid")]
378 grid_template_rows: Default::default(),
379 #[cfg(feature = "grid")]
380 grid_template_columns: Default::default(),
381 #[cfg(feature = "grid")]
382 grid_auto_rows: Default::default(),
383 #[cfg(feature = "grid")]
384 grid_auto_columns: Default::default(),
385 #[cfg(feature = "grid")]
386 grid_auto_flow: Default::default(),
387 #[cfg(feature = "grid")]
388 grid_row: Line { start: GridPlacement::Auto, end: GridPlacement::Auto },
389 #[cfg(feature = "grid")]
390 grid_column: Line { start: GridPlacement::Auto, end: GridPlacement::Auto },
391 };
392
393 assert_eq!(Style::DEFAULT, Style::default());
394 assert_eq!(Style::DEFAULT, old_defaults);
395 }
396
397 // NOTE: Please feel free the update the sizes in this test as required. This test is here to prevent unintentional size changes
398 // and to serve as accurate up-to-date documentation on the sizes.
399 #[test]
400 fn style_sizes() {
401 use super::*;
402
403 fn assert_type_size<T>(expected_size: usize) {
404 let name = ::core::any::type_name::<T>();
405 let name = name.replace("taffy::geometry::", "");
406 let name = name.replace("taffy::style::dimension::", "");
407 let name = name.replace("taffy::style::alignment::", "");
408 let name = name.replace("taffy::style::flex::", "");
409 let name = name.replace("taffy::style::grid::", "");
410
411 assert_eq!(
412 ::core::mem::size_of::<T>(),
413 expected_size,
414 "Expected {} for be {} byte(s) but it was {} byte(s)",
415 name,
416 expected_size,
417 ::core::mem::size_of::<T>(),
418 );
419 }
420
421 // Display and Position
422 assert_type_size::<Display>(1);
423 assert_type_size::<Position>(1);
424 assert_type_size::<Overflow>(1);
425
426 // Dimensions and aggregations of Dimensions
427 assert_type_size::<f32>(4);
428 assert_type_size::<LengthPercentage>(8);
429 assert_type_size::<LengthPercentageAuto>(8);
430 assert_type_size::<Dimension>(8);
431 assert_type_size::<Size<LengthPercentage>>(16);
432 assert_type_size::<Size<LengthPercentageAuto>>(16);
433 assert_type_size::<Size<Dimension>>(16);
434 assert_type_size::<Rect<LengthPercentage>>(32);
435 assert_type_size::<Rect<LengthPercentageAuto>>(32);
436 assert_type_size::<Rect<Dimension>>(32);
437
438 // Alignment
439 assert_type_size::<AlignContent>(1);
440 assert_type_size::<AlignItems>(1);
441 assert_type_size::<Option<AlignItems>>(1);
442
443 // Flexbox Container
444 assert_type_size::<FlexDirection>(1);
445 assert_type_size::<FlexWrap>(1);
446
447 // CSS Grid Container
448 assert_type_size::<GridAutoFlow>(1);
449 assert_type_size::<MinTrackSizingFunction>(8);
450 assert_type_size::<MaxTrackSizingFunction>(12);
451 assert_type_size::<NonRepeatedTrackSizingFunction>(20);
452 assert_type_size::<TrackSizingFunction>(32);
453 assert_type_size::<Vec<NonRepeatedTrackSizingFunction>>(24);
454 assert_type_size::<Vec<TrackSizingFunction>>(24);
455
456 // CSS Grid Item
457 assert_type_size::<GridPlacement>(4);
458 assert_type_size::<Line<GridPlacement>>(8);
459
460 // Overall
461 assert_type_size::<Style>(344);
462 }
463}