gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

⎇ 2b5366e4296fc2a5d84bfd8bc6967939a815c154 src/style/mod.rs Rust · 463 siq'ikuna · 18.3 KB Ñawpa willay Pi imata llamkarqan ch'uya

Refactor/module hierarchy (#443) 628d7e1bc · 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///
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}