tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definiciones

⎇ 39cd0fcbbda7cd38cc6316e7339b1f3aed29e99f src/style.rs Rust · 490 líneas · 17.4 KB Historia Quién tocó qué crudo

Restore and fix gentest (#145) 39cd0fcbb · François ·

1//! A representation of [CSS layout properties](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) in Rust, used for flexbox layout
2
3use crate::geometry::{Rect, Size};
4use crate::number::Number;
5
6/// How [`Nodes`](crate::node::Node) are aligned relative to the cross axis
7///
8/// The default behavior is [`AlignItems::Stretch`].
9///
10/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-items-property)
11#[derive(Copy, Clone, PartialEq, Eq, Debug)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub enum AlignItems {
14    /// Items are packed toward the start of the cross axis
15    FlexStart,
16    /// Items are packed toward the end of the cross axis
17    FlexEnd,
18    /// Items are packed along the center of the cross axis
19    Center,
20    /// Items are aligned such as their baselines align
21    Baseline,
22    /// Stretch to fill the container
23    Stretch,
24}
25
26impl Default for AlignItems {
27    fn default() -> Self {
28        Self::Stretch
29    }
30}
31
32/// Overrides the inherited [`AlignItems`] behavior for this node.
33///
34/// The behavior of any child nodes will be controlled by this node's [`AlignItems`] value.
35///
36/// The default behavior is [`AlignSelf::Auto`].
37///
38/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-items-property)
39#[derive(Copy, Clone, PartialEq, Eq, Debug)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub enum AlignSelf {
42    /// Inherits the [`AlignItems`] behavior of the parent
43    Auto,
44    /// Items are packed toward the start of the cross axis
45    FlexStart,
46    /// Items are packed toward the end of the cross axis
47    FlexEnd,
48    /// Items are packed along the center of the cross axis
49    Center,
50    /// Items are aligned such as their baselines align
51    Baseline,
52    /// Distribute items evenly, but stretch them to fill the container
53    Stretch,
54}
55
56impl Default for AlignSelf {
57    fn default() -> Self {
58        Self::Auto
59    }
60}
61
62/// Sets the distribution of space between and around content items along the cross-axis
63///
64/// The default value is [`AlignContent::Stretch`].
65///
66/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-content-property)
67#[derive(Copy, Clone, PartialEq, Eq, Debug)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub enum AlignContent {
70    /// Items are packed toward the start of the cross axis
71    FlexStart,
72    /// Items are packed toward the end of the cross axis
73    FlexEnd,
74    /// Items are packed along the center of the cross axis
75    Center,
76    /// Distribute items evenly, but stretch them to fill the container
77    Stretch,
78    /// Distribute items evenly, such that the first and last item are aligned with the edges
79    SpaceBetween,
80    /// Distribute items evenly,
81    /// such that the space between items is the same as the space between the first and last item and the edges
82    SpaceAround,
83}
84
85impl Default for AlignContent {
86    fn default() -> Self {
87        Self::Stretch
88    }
89}
90
91/// Sets the layout used for the children of this node
92///
93/// [`Display::Flex`] is the default value.
94#[derive(Copy, Clone, PartialEq, Eq, Debug)]
95#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96pub enum Display {
97    /// The children will follow the flexbox layout algorithm
98    #[cfg_attr(feature = "serde", serde(rename = "flex"))]
99    Flex,
100    /// The children will not be laid out, and will follow absolute positioning
101    #[cfg_attr(feature = "serde", serde(rename = "none"))]
102    None,
103}
104
105impl Default for Display {
106    fn default() -> Self {
107        Self::Flex
108    }
109}
110
111/// The direction of the flexbox layout main axis.
112///
113/// There are always two perpendicular layout axes: main (or primary) and cross (or secondary).
114/// Adding items will cause them to be positioned adjacent to each other along the main axis.
115/// By varying this value throughout your tree, you can create complex axis-aligned layouts.
116///
117/// Items are always aligned relative to the cross axis, and justified relative to the main axis.
118///
119/// The default behavior is [`FlexDirection::Row`].
120///
121/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property)
122#[derive(Copy, Clone, PartialEq, Eq, Debug)]
123#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
124pub enum FlexDirection {
125    /// Defines +x as the main axis
126    ///
127    /// Items will be added from left to right in a row.
128    Row,
129    /// Defines +y as the main axis
130    ///
131    /// Items will be added from top to bottom in a column.
132    Column,
133    /// Defines -x as the main axis
134    ///
135    /// Items will be added from right to left in a row.
136    RowReverse,
137    /// Defines -y as the main axis
138    ///
139    /// Items will be added from bottom to top in a column.
140    ColumnReverse,
141}
142
143impl Default for FlexDirection {
144    fn default() -> Self {
145        Self::Row
146    }
147}
148
149impl FlexDirection {
150    #[inline]
151    /// Is the direction [`FlexDirection::Row`] or [`FlexDirection::RowReverse`]?
152    pub(crate) fn is_row(self) -> bool {
153        matches!(self, Self::Row | Self::RowReverse)
154    }
155
156    #[inline]
157    /// Is the direction [`FlexDirection::Column`] or [`FlexDirection::ColumnReverse`]?
158    pub(crate) fn is_column(self) -> bool {
159        matches!(self, Self::Column | Self::ColumnReverse)
160    }
161
162    #[inline]
163    /// Is the direction [`FlexDirection::RowReverse`] or [`FlexDirection::ColumnReverse`]?
164    pub(crate) fn is_reverse(self) -> bool {
165        matches!(self, Self::RowReverse | Self::ColumnReverse)
166    }
167}
168
169/// Sets the distribution of space between and around content items along the main-axis
170///
171/// The default value is [`JustifyContent::FlexStart`].
172///
173/// [Specification](https://www.w3.org/TR/css-flexbox-1/#justify-content-property)
174#[derive(Copy, Clone, PartialEq, Eq, Debug)]
175#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
176pub enum JustifyContent {
177    /// Items are packed toward the start of the main axis
178    FlexStart,
179    /// Items are packed toward the end of the main axis
180    FlexEnd,
181    /// Items are packed along the center of the main axis
182    Center,
183    /// Distribute items evenly, such that the first and last item are aligned with the edges
184    SpaceBetween,
185    /// Distribute items evenly,
186    /// such that the space between items is twice same as the space between the first and last item and the edges
187    SpaceAround,
188    /// Distribute items evenly,
189    /// such that the space between items is the same as the space between the first and last item and the edges
190    SpaceEvenly,
191}
192
193impl Default for JustifyContent {
194    fn default() -> Self {
195        Self::FlexStart
196    }
197}
198
199/// The positioning strategy for this item.
200///
201/// This controls both how the origin is determined for the [`Style::position`] field,
202/// and whether or not the item will be controlled by flexbox's layout algorithm.
203///
204/// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position),
205/// which can be unintuitive.
206///
207/// [`PositionType::Relative`] is the default value, in contrast to the default behavior in CSS.
208#[derive(Copy, Clone, PartialEq, Eq, Debug)]
209#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
210pub enum PositionType {
211    /// The offset is computed relative to the final position given by the layout algorithm.
212    /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end.
213    Relative,
214    /// The offset is computed relative to this item's closest positioned ancestor, if any.
215    /// Otherwise, it is placed relative to the origin.
216    /// No space is created for the item in the page layout, and its size will not be altered.
217    ///
218    /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object.
219    Absolute,
220}
221
222impl Default for PositionType {
223    fn default() -> Self {
224        Self::Relative
225    }
226}
227
228/// Controls whether flex items are forced onto one line or can wrap onto multiple lines.
229///
230/// Defaults to [`FlexWrap::NoWrap`]
231///
232/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property)
233#[derive(Copy, Clone, PartialEq, Eq, Debug)]
234#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
235pub enum FlexWrap {
236    /// Items will not wrap and stay on a single line
237    NoWrap,
238    /// Items will wrap according to this item's [`FlexDirection`]
239    Wrap,
240    /// Items will wrap in the oppposite direction to this item's [`FlexDirection`]
241    WrapReverse,
242}
243
244impl Default for FlexWrap {
245    fn default() -> Self {
246        Self::NoWrap
247    }
248}
249
250/// A unit of linear measurement
251///
252/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
253/// The default value is [`Dimension::Undefined`].
254#[derive(Copy, Clone, PartialEq, Debug)]
255#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
256pub enum Dimension {
257    /// The dimension is not given
258    Undefined,
259    /// The dimension should be automatically computed
260    Auto,
261    /// The dimension is stored in [points](https://en.wikipedia.org/wiki/Point_(typography))
262    ///
263    /// Each point is about 0.353 mm in size.
264    Points(f32),
265    /// The dimension is stored in percentage relative to the parent item.
266    Percent(f32),
267}
268
269impl Default for Dimension {
270    fn default() -> Self {
271        Self::Undefined
272    }
273}
274
275impl Dimension {
276    /// Converts the given [`Dimension`] into a concrete value of points
277    pub(crate) fn resolve(self, parent_dim: Number) -> Number {
278        match self {
279            Dimension::Points(points) => Number::Defined(points),
280            Dimension::Percent(percent) => parent_dim * percent,
281            _ => Number::Undefined,
282        }
283    }
284
285    /// Is this value defined?
286    pub(crate) fn is_defined(self) -> bool {
287        matches!(self, Dimension::Points(_) | Dimension::Percent(_))
288    }
289}
290
291impl Default for Rect<Dimension> {
292    fn default() -> Self {
293        Self { start: Default::default(), end: Default::default(), top: Default::default(), bottom: Default::default() }
294    }
295}
296
297impl Default for Size<Dimension> {
298    fn default() -> Self {
299        Self { width: Dimension::Auto, height: Dimension::Auto }
300    }
301}
302
303/// The flexbox layout information for a single [`Node`](crate::node::Node).
304///
305/// The most important idea in flexbox is the notion of a "main" and "cross" axis, which are always perpendicular to each other.
306/// The orientation of these axes are controlled via the [`FlexDirection`] field of this struct.
307///
308/// This struct follows the [CSS equivalent](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) directly;
309/// information about the behavior on the web should transfer directly.
310///
311/// Detailed information about the exact behavior of each of these fields
312/// can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS) by searching for the field name.
313/// The distinction between margin, padding and border is explained well in
314/// this [introduction to the box model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model).
315///
316/// If the behavior does not match the flexbox layout algorithm on the web, please file a bug!
317#[derive(Copy, Clone, PartialEq, Debug)]
318#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
319#[cfg_attr(feature = "serde", serde(default))]
320pub struct FlexboxLayout {
321    /// What layout strategy should be used?
322    pub display: Display,
323    /// What should the `position` value of this struct use as a base offset?
324    pub position_type: PositionType,
325    /// Which direction does the main axis flow in?
326    pub flex_direction: FlexDirection,
327    /// Should elements wrap, or stay in a single line?
328    pub flex_wrap: FlexWrap,
329    /// How should items be aligned relative to the cross axis?
330    pub align_items: AlignItems,
331    /// Should this item violate the cross axis alignment specified by its parent's [`AlignItems`]?
332    pub align_self: AlignSelf,
333    /// How should content contained within this item be aligned relative to the cross axis?
334    pub align_content: AlignContent,
335    /// How should items be aligned relative to the main axis?
336    pub justify_content: JustifyContent,
337    /// How should the position of this element be tweaked relative to the layout defined?
338    pub position: Rect<Dimension>,
339    /// How large should the margin be on each side?
340    pub margin: Rect<Dimension>,
341    /// How large should the padding be on each side?
342    pub padding: Rect<Dimension>,
343    /// How large should the border be on each side?
344    pub border: Rect<Dimension>,
345    /// The relative rate at which this item grows when it is expanding to fill space
346    ///
347    /// 0.0 is the default value, and this value must be positive.
348    pub flex_grow: f32,
349    /// The relative rate at which this item shrinks when it is contracting to fit into space
350    ///
351    /// 1.0 is the default value, and this value must be positive.
352    pub flex_shrink: f32,
353    /// Sets the initial main axis size of the item
354    pub flex_basis: Dimension,
355    /// Sets the initial size of the item
356    // TODO: why does this exist as distinct from flex_basis? How do they interact?
357    pub size: Size<Dimension>,
358    /// Controls the minimum size of the item
359    pub min_size: Size<Dimension>,
360    /// Controls the maximum size of the item
361    pub max_size: Size<Dimension>,
362    /// Sets the preferred aspect ratio for the item
363    ///
364    /// The ratio is calculated as width divided by height.
365    pub aspect_ratio: Number,
366}
367
368impl Default for FlexboxLayout {
369    fn default() -> Self {
370        Self {
371            display: Default::default(),
372            position_type: Default::default(),
373            flex_direction: Default::default(),
374            flex_wrap: Default::default(),
375            align_items: Default::default(),
376            align_self: Default::default(),
377            align_content: Default::default(),
378            justify_content: Default::default(),
379            position: Default::default(),
380            margin: Default::default(),
381            padding: Default::default(),
382            border: Default::default(),
383            flex_grow: 0.0,
384            flex_shrink: 1.0,
385            flex_basis: Dimension::Auto,
386            size: Default::default(),
387            min_size: Default::default(),
388            max_size: Default::default(),
389            aspect_ratio: Default::default(),
390        }
391    }
392}
393
394impl FlexboxLayout {
395    /// If the `direction` is row-oriented, the min width. Otherwise the min height
396    pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension {
397        if direction.is_row() {
398            self.min_size.width
399        } else {
400            self.min_size.height
401        }
402    }
403
404    /// If the `direction` is row-oriented, the max width. Otherwise the max height
405    pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension {
406        if direction.is_row() {
407            self.max_size.width
408        } else {
409            self.max_size.height
410        }
411    }
412
413    /// If the `direction` is row-oriented, the margin start. Otherwise the margin top
414    pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension {
415        if direction.is_row() {
416            self.margin.start
417        } else {
418            self.margin.top
419        }
420    }
421
422    /// If the `direction` is row-oriented, the margin end. Otherwise the margin bottom
423    pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension {
424        if direction.is_row() {
425            self.margin.end
426        } else {
427            self.margin.bottom
428        }
429    }
430
431    /// If the `direction` is row-oriented, the height. Otherwise the width
432    pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension {
433        if direction.is_row() {
434            self.size.height
435        } else {
436            self.size.width
437        }
438    }
439
440    /// If the `direction` is row-oriented, the min height. Otherwise the min width
441    pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension {
442        if direction.is_row() {
443            self.min_size.height
444        } else {
445            self.min_size.width
446        }
447    }
448
449    /// If the `direction` is row-oriented, the max height. Otherwise the max width
450    pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension {
451        if direction.is_row() {
452            self.max_size.height
453        } else {
454            self.max_size.width
455        }
456    }
457
458    /// If the `direction` is row-oriented, the margin top. Otherwise the margin start
459    pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension {
460        if direction.is_row() {
461            self.margin.top
462        } else {
463            self.margin.start
464        }
465    }
466
467    /// If the `direction` is row-oriented, the margin bottom. Otherwise the margin end
468    pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension {
469        if direction.is_row() {
470            self.margin.bottom
471        } else {
472            self.margin.end
473        }
474    }
475
476    /// Computes the final alignment of this item based on the parent's [`AlignItems`] and this item's [`AlignSelf`]
477    pub(crate) fn align_self(&self, parent: &FlexboxLayout) -> AlignSelf {
478        if self.align_self == AlignSelf::Auto {
479            match parent.align_items {
480                AlignItems::FlexStart => AlignSelf::FlexStart,
481                AlignItems::FlexEnd => AlignSelf::FlexEnd,
482                AlignItems::Center => AlignSelf::Center,
483                AlignItems::Baseline => AlignSelf::Baseline,
484                AlignItems::Stretch => AlignSelf::Stretch,
485            }
486        } else {
487            self.align_self
488        }
489    }
490}