tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 8TOML 3CSS 1317 Definitions

⎇ 08376be245a487c3d3438dfd5108a67f48222739 src/style/mod.rs Rust · 472 lines · 19.1 KB History Who touched what raw

Support `Overflow::Scroll` (#446) daf416438 · Nico Burns ·

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