taffy/style/
mod.rs

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