tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81414 Definiciones

⎇ 30f83642887f3f0d5e5e6235e3de9700a5e30495 src/style/grid.rs Rust · 506 líneas · 21.3 KB Historia Quién tocó qué crudo

Implement CSS Grid (#205) 6fd2c6bcb · Nico Burns ·

1//! Style types for CSS Grid layout
2use super::{AlignContent, LengthPercentage, Style};
3use crate::axis::{AbsoluteAxis, AbstractAxis};
4use crate::geometry::{Line, MinMax};
5use crate::style::AvailableSpace;
6use crate::style_helpers::*;
7use crate::sys::GridTrackVec;
8use core::cmp::{max, min};
9
10/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
11///
12/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
13///
14/// Defaults to [`GridAutoFlow::Row`]
15///
16/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
17#[derive(Copy, Clone, PartialEq, Eq, Debug)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub enum GridAutoFlow {
20    /// Items are placed by filling each row in turn, adding new rows as necessary
21    Row,
22    /// Items are placed by filling each column in turn, adding new columns as necessary.
23    Column,
24    /// Combines `Row` with the dense packing algorithm.
25    RowDense,
26    /// Combines `Column` with the dense packing algorithm.
27    ColumnDense,
28}
29
30impl Default for GridAutoFlow {
31    fn default() -> Self {
32        Self::Row
33    }
34}
35
36impl GridAutoFlow {
37    /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
38    /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values
39    pub fn is_dense(&self) -> bool {
40        match self {
41            Self::Row | Self::Column => false,
42            Self::RowDense | Self::ColumnDense => true,
43        }
44    }
45
46    /// Whether grid auto placement fills areas row-wise or column-wise
47    /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values
48    pub fn primary_axis(&self) -> AbsoluteAxis {
49        match self {
50            Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
51            Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
52        }
53    }
54}
55
56/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
57///
58/// Defaults to [`GridLine::Auto`]
59///
60/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
61#[derive(Copy, Clone, PartialEq, Eq, Debug)]
62#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
63pub enum GridPlacement {
64    /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
65    Auto,
66    /// Place item at specified line (column or row) index
67    Line(i16),
68    /// Item should span specified number of tracks (columns or rows)
69    Span(u16),
70}
71impl TaffyAuto for GridPlacement {
72    const AUTO: Self = Self::Auto;
73}
74impl TaffyGridLine for GridPlacement {
75    fn from_line_index(index: i16) -> Self {
76        match index {
77            0 => GridPlacement::Auto,
78            _ => GridPlacement::Line(index),
79        }
80    }
81}
82impl TaffyGridLine for Line<GridPlacement> {
83    fn from_line_index(index: i16) -> Self {
84        Line { start: GridPlacement::from_line_index(index), end: GridPlacement::Auto }
85    }
86}
87impl TaffyGridSpan for GridPlacement {
88    fn from_span(span: u16) -> Self {
89        GridPlacement::Span(span)
90    }
91}
92impl TaffyGridSpan for Line<GridPlacement> {
93    fn from_span(span: u16) -> Self {
94        Line { start: GridPlacement::from_span(span), end: GridPlacement::Auto }
95    }
96}
97
98impl Default for GridPlacement {
99    fn default() -> Self {
100        Self::Auto
101    }
102}
103
104impl GridPlacement {
105    /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
106    pub fn map_track(&self, map_fn: impl FnOnce(i16) -> i16) -> Self {
107        use GridPlacement::*;
108        match *self {
109            Auto => Auto,
110            Span(span) => Span(span),
111            Line(track) => Line(map_fn(track)),
112        }
113    }
114}
115
116impl Line<GridPlacement> {
117    #[inline]
118    /// Whether the track position is definite in this axis (or the item will need auto placement)
119    /// The track position is definite if least one of the start and end positions is a track index
120    pub fn is_definite(&self) -> bool {
121        use GridPlacement::*;
122        matches!((self.start, self.end), (Line(_), _) | (_, Line(_)))
123    }
124
125    /// If at least one of the of the start and end positions is a track index then the other end can be resolved
126    /// into a track index purely based on the information contained with the placement specification
127    pub fn resolve_definite_grid_tracks(&self) -> Line<i16> {
128        use GridPlacement as GP;
129        match (self.start, self.end) {
130            (GP::Line(track1), GP::Line(track2)) if track1 != 0 && track2 != 0 => {
131                if track1 == track2 {
132                    Line { start: track1, end: track1 + 1 }
133                } else {
134                    Line { start: min(track1, track2), end: max(track1, track2) }
135                }
136            }
137            (GP::Line(track), GP::Span(span)) => Line { start: track, end: track + span as i16 },
138            (GP::Line(track), GP::Auto | GP::Line(0)) => Line { start: track, end: track + 1_i16 },
139            (GP::Span(span), GP::Line(track)) => Line { start: track - span as i16, end: track },
140            (GP::Auto | GP::Line(0), GP::Line(track)) => Line { start: track - 1_i16, end: track },
141            _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
142        }
143    }
144
145    /// For absolutely positioned items:
146    ///   - Tracks resolve to definite tracks
147    ///   - For Spans:
148    ///      - If the other position is a Track, they resolve to a definite track relative to the other track
149    ///      - Else resolve to None
150    ///   - Auto resolves to None
151    ///
152    /// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
153    /// container's border box on that side.
154    pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<i16>> {
155        use GridPlacement as GP;
156        match (self.start, self.end) {
157            (GP::Line(track1), GP::Line(track2)) if track1 != 0 && track2 != 0 => {
158                if track1 == track2 {
159                    Line { start: Some(track1), end: Some(track1 + 1) }
160                } else {
161                    Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
162                }
163            }
164            (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span as i16) },
165            (GP::Line(track), GP::Auto | GP::Line(0)) => Line { start: Some(track), end: None },
166            (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span as i16), end: Some(track) },
167            (GP::Auto | GP::Line(0), GP::Line(track)) => Line { start: None, end: Some(track) },
168            _ => Line { start: None, end: None },
169        }
170    }
171
172    /// If neither of the start and end positions is a track index then the other end can be resolved
173    /// into a track index if a definite start position is supplied externally
174    pub fn resolve_indefinite_grid_tracks(&self, start: i16) -> Line<i16> {
175        use GridPlacement as GP;
176        match (self.start, self.end) {
177            (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
178            (GP::Span(span), GP::Auto) => Line { start, end: start + span as i16 },
179            (GP::Auto, GP::Span(span)) => Line { start, end: start + span as i16 },
180            (GP::Span(span), GP::Span(_)) => Line { start, end: start + span as i16 },
181            _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
182        }
183    }
184
185    /// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
186    /// Panics if called on a definite placement
187    pub fn indefinite_span(&self) -> u16 {
188        use GridPlacement as GP;
189        match (self.start, self.end) {
190            (GP::Line(_), GP::Auto) => 1,
191            (GP::Auto, GP::Line(_)) => 1,
192            (GP::Auto, GP::Auto) => 1,
193            (GP::Line(_), GP::Span(span)) => span,
194            (GP::Span(span), GP::Line(_)) => span,
195            (GP::Span(span), GP::Auto) => span,
196            (GP::Auto, GP::Span(span)) => span,
197            (GP::Span(span), GP::Span(_)) => span,
198            (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
199        }
200    }
201}
202
203/// Represents the start and end points of a GridItem within a given axis
204impl Default for Line<GridPlacement> {
205    fn default() -> Self {
206        Line { start: GridPlacement::Auto, end: GridPlacement::Auto }
207    }
208}
209
210/// Maximum track sizing function
211///
212/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
213/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
214/// See https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns
215#[derive(Copy, Clone, PartialEq, Debug)]
216pub enum MaxTrackSizingFunction {
217    /// Track maximum size should be a fixed points or percentage value
218    Fixed(LengthPercentage),
219    /// Track maximum size should be content sized under a min-content constraint
220    MinContent,
221    /// Track maximum size should be content sized under a max-content constraint
222    MaxContent,
223    /// Track maximum size should be sized according to the fit-content formula
224    FitContent(LengthPercentage),
225    /// Track maximum size should be automatically sized
226    Auto,
227    /// The dimension as a fraction of the total available grid space.
228    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
229    /// Spec: https://www.w3.org/TR/css3-grid-layout/#fr-unit
230    Flex(f32),
231}
232impl TaffyAuto for MaxTrackSizingFunction {
233    const AUTO: Self = Self::Auto;
234}
235impl TaffyMinContent for MaxTrackSizingFunction {
236    const MIN_CONTENT: Self = Self::MinContent;
237}
238impl TaffyMaxContent for MaxTrackSizingFunction {
239    const MAX_CONTENT: Self = Self::MaxContent;
240}
241impl TaffyFitContent for MaxTrackSizingFunction {
242    fn fit_content(argument: LengthPercentage) -> Self {
243        Self::FitContent(argument)
244    }
245}
246impl TaffyZero for MaxTrackSizingFunction {
247    const ZERO: Self = Self::Fixed(LengthPercentage::ZERO);
248}
249impl FromPoints for MaxTrackSizingFunction {
250    fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
251        Self::Fixed(LengthPercentage::from_points(points))
252    }
253}
254impl FromPercent for MaxTrackSizingFunction {
255    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
256        Self::Fixed(LengthPercentage::from_percent(percent))
257    }
258}
259impl FromFlex for MaxTrackSizingFunction {
260    fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
261        Self::Flex(flex.into())
262    }
263}
264
265impl MaxTrackSizingFunction {
266    /// Returns true if the max track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
267    #[inline(always)]
268    pub fn is_intrinsic(&self) -> bool {
269        matches!(self, Self::MinContent | Self::MaxContent | Self::FitContent(_) | Self::Auto)
270    }
271
272    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
273    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
274    /// See: https://www.w3.org/TR/css-grid-1/#algo-terms
275    #[inline(always)]
276    pub fn is_max_content_alike(&self) -> bool {
277        matches!(self, Self::MaxContent | Self::FitContent(_) | Self::Auto)
278    }
279
280    /// Returns true if the max track sizing function is `Flex`, else false.
281    #[inline(always)]
282    pub fn is_flexible(&self) -> bool {
283        matches!(self, Self::Flex(_))
284    }
285
286    /// Returns fixed point values directly. Attempts to resolve percentage values against
287    /// the passed available_space and returns if this results in a concrete value (which it
288    /// will if the available_space is `Some`). Otherwise returns None.
289    #[inline(always)]
290    pub fn definite_value(self, available_space: AvailableSpace) -> Option<f32> {
291        use MaxTrackSizingFunction::{Auto, *};
292        match self {
293            Fixed(LengthPercentage::Points(size)) => Some(size),
294            Fixed(LengthPercentage::Percent(fraction)) => match available_space {
295                AvailableSpace::Definite(available_size) => Some(fraction * available_size),
296                _ => None,
297            },
298            MinContent | MaxContent | FitContent(_) | Auto | Flex(_) => None,
299        }
300    }
301}
302
303/// Minimum track sizing function
304///
305/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
306/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
307/// See https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns
308#[derive(Copy, Clone, PartialEq, Debug)]
309pub enum MinTrackSizingFunction {
310    /// Track minimum size should be a fixed points or percentage value
311    Fixed(LengthPercentage),
312    /// Track minimum size should be content sized under a min-content constraint
313    MinContent,
314    /// Track minimum size should be content sized under a max-content constraint
315    MaxContent,
316    /// Track minimum size should be automatically sized
317    Auto,
318}
319impl TaffyAuto for MinTrackSizingFunction {
320    const AUTO: Self = Self::Auto;
321}
322impl TaffyMinContent for MinTrackSizingFunction {
323    const MIN_CONTENT: Self = Self::MinContent;
324}
325impl TaffyMaxContent for MinTrackSizingFunction {
326    const MAX_CONTENT: Self = Self::MaxContent;
327}
328impl TaffyZero for MinTrackSizingFunction {
329    const ZERO: Self = Self::Fixed(LengthPercentage::ZERO);
330}
331impl FromPoints for MinTrackSizingFunction {
332    fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
333        Self::Fixed(LengthPercentage::from_points(points))
334    }
335}
336impl FromPercent for MinTrackSizingFunction {
337    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
338        Self::Fixed(LengthPercentage::from_percent(percent))
339    }
340}
341
342impl MinTrackSizingFunction {
343    /// Returns fixed point values directly. Attempts to resolve percentage values against
344    /// the passed available_space and returns if this results in a concrete value (which it
345    /// will if the available_space is `Some`). Otherwise returns `None`.
346    #[inline(always)]
347    pub fn definite_value(self, available_space: AvailableSpace) -> Option<f32> {
348        use MinTrackSizingFunction::{Auto, *};
349        match self {
350            Fixed(LengthPercentage::Points(size)) => Some(size),
351            Fixed(LengthPercentage::Percent(fraction)) => match available_space {
352                AvailableSpace::Definite(available_size) => Some(fraction * available_size),
353                _ => None,
354            },
355            MinContent | MaxContent | Auto => None,
356        }
357    }
358}
359
360/// The sizing function for a grid track (row/column) (either auto-track or template track)
361/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
362/// or a scalar value which applies to both track sizing functions.
363pub type NonRepeatedTrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
364impl NonRepeatedTrackSizingFunction {
365    /// Extract the min track sizing function
366    pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
367        self.min
368    }
369    /// Extract the max track sizing function
370    pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
371        self.max
372    }
373    /// Determine whether at least one of the components ("min" and "max") are fixed sizing function
374    pub fn has_fixed_component(&self) -> bool {
375        matches!(self.min, MinTrackSizingFunction::Fixed(_)) || matches!(self.max, MaxTrackSizingFunction::Fixed(_))
376    }
377}
378impl TaffyAuto for NonRepeatedTrackSizingFunction {
379    const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
380}
381impl TaffyMinContent for NonRepeatedTrackSizingFunction {
382    const MIN_CONTENT: Self =
383        Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
384}
385impl TaffyMaxContent for NonRepeatedTrackSizingFunction {
386    const MAX_CONTENT: Self =
387        Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
388}
389impl TaffyFitContent for NonRepeatedTrackSizingFunction {
390    fn fit_content(argument: LengthPercentage) -> Self {
391        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::FitContent(argument) }
392    }
393}
394impl TaffyZero for NonRepeatedTrackSizingFunction {
395    const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
396}
397impl FromPoints for NonRepeatedTrackSizingFunction {
398    fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
399        Self { min: MinTrackSizingFunction::from_points(points), max: MaxTrackSizingFunction::from_points(points) }
400    }
401}
402impl FromPercent for NonRepeatedTrackSizingFunction {
403    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
404        Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
405    }
406}
407impl FromFlex for NonRepeatedTrackSizingFunction {
408    fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
409        Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_flex(flex) }
410    }
411}
412
413/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
414///
415/// See https://www.w3.org/TR/css-grid-1/#auto-repeat for an explanation of how auto-repeated track definitions work
416/// and the difference between AutoFit and AutoFill.
417#[derive(Clone, Copy, Debug, PartialEq, Eq)]
418pub enum GridTrackRepetition {
419    /// Auto-repeating track should be generated to fit the container
420    /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill
421    AutoFill,
422    /// Auto-repeating track should be generated to fit the container
423    /// See: https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit
424    AutoFit,
425}
426
427/// The sizing function for a grid track (row/column)
428/// See https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns
429#[derive(Clone, PartialEq, Debug)]
430pub enum TrackSizingFunction {
431    /// A single non-repeated track
432    Single(NonRepeatedTrackSizingFunction),
433    /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
434    /// Only valid if every track in template (not just the repitition) has a fixed size.
435    AutoRepeat(GridTrackRepetition, GridTrackVec<NonRepeatedTrackSizingFunction>),
436}
437impl TrackSizingFunction {
438    /// Whether the track definition is a auto-repeated fragment
439    pub fn is_auto_repetition(&self) -> bool {
440        matches!(self, Self::AutoRepeat(_, _))
441    }
442}
443impl TaffyAuto for TrackSizingFunction {
444    const AUTO: Self = Self::Single(NonRepeatedTrackSizingFunction::AUTO);
445}
446impl TaffyMinContent for TrackSizingFunction {
447    const MIN_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MIN_CONTENT);
448}
449impl TaffyMaxContent for TrackSizingFunction {
450    const MAX_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MAX_CONTENT);
451}
452impl TaffyFitContent for TrackSizingFunction {
453    fn fit_content(argument: LengthPercentage) -> Self {
454        Self::Single(NonRepeatedTrackSizingFunction::fit_content(argument))
455    }
456}
457impl TaffyZero for TrackSizingFunction {
458    const ZERO: Self = Self::Single(NonRepeatedTrackSizingFunction::ZERO);
459}
460impl FromPoints for TrackSizingFunction {
461    fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
462        Self::Single(NonRepeatedTrackSizingFunction::from_points(points))
463    }
464}
465impl FromPercent for TrackSizingFunction {
466    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
467        Self::Single(NonRepeatedTrackSizingFunction::from_percent(percent))
468    }
469}
470impl FromFlex for TrackSizingFunction {
471    fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
472        Self::Single(NonRepeatedTrackSizingFunction::from_flex(flex))
473    }
474}
475impl From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for TrackSizingFunction {
476    fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
477        Self::Single(input)
478    }
479}
480
481// Grid extensions to the Style struct
482impl Style {
483    /// Get a grid item's row or column placement depending on the axis passed
484    pub(crate) fn grid_template_tracks(&self, axis: AbsoluteAxis) -> &GridTrackVec<TrackSizingFunction> {
485        match axis {
486            AbsoluteAxis::Horizontal => &self.grid_template_columns,
487            AbsoluteAxis::Vertical => &self.grid_template_rows,
488        }
489    }
490
491    /// Get a grid item's row or column placement depending on the axis passed
492    pub(crate) fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement> {
493        match axis {
494            AbsoluteAxis::Horizontal => self.grid_column,
495            AbsoluteAxis::Vertical => self.grid_row,
496        }
497    }
498
499    /// Get a grid container's align-content or justify-content alignment depending on the axis passed
500    pub(crate) fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
501        match axis {
502            AbstractAxis::Inline => self.justify_content.unwrap_or(AlignContent::Stretch),
503            AbstractAxis::Block => self.align_content.unwrap_or(AlignContent::Stretch),
504        }
505    }
506}