Validate tag when deserializing values (#805)
1//! Style types for CSS Grid layout
2use super::{
3 AlignContent, AlignItems, AlignSelf, CompactLength, CoreStyle, Dimension, JustifyContent, LengthPercentage,
4 LengthPercentageAuto, Style,
5};
6use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine};
7use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
8use crate::style_helpers::*;
9use crate::util::sys::GridTrackVec;
10use core::borrow::Borrow;
11use core::cmp::{max, min};
12use core::convert::Infallible;
13
14/// The set of styles required for a CSS Grid container
15pub trait GridContainerStyle: CoreStyle {
16 /// The type returned by grid_template_rows and grid_template_columns
17 type TemplateTrackList<'a>: Borrow<[TrackSizingFunction]>
18 where
19 Self: 'a;
20 /// The type returned by grid_auto_rows and grid_auto_columns
21 type AutoTrackList<'a>: Borrow<[NonRepeatedTrackSizingFunction]>
22 where
23 Self: 'a;
24
25 // FIXME: re-add default implemenations for grid_{template,auto}_{rows,columns} once the
26 // associated_type_defaults feature (https://github.com/rust-lang/rust/issues/29661) is stabilised.
27
28 /// Defines the track sizing functions (heights) of the grid rows
29 fn grid_template_rows(&self) -> Self::TemplateTrackList<'_>;
30 /// Defines the track sizing functions (widths) of the grid columns
31 fn grid_template_columns(&self) -> Self::TemplateTrackList<'_>;
32 /// Defines the size of implicitly created rows
33 fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
34 /// Defined the size of implicitly created columns
35 fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
36
37 /// Controls how items get placed into the grid for auto-placed items
38 #[inline(always)]
39 fn grid_auto_flow(&self) -> GridAutoFlow {
40 Style::DEFAULT.grid_auto_flow
41 }
42
43 /// How large should the gaps between items in a grid or flex container be?
44 #[inline(always)]
45 fn gap(&self) -> Size<LengthPercentage> {
46 Style::DEFAULT.gap
47 }
48
49 // Alignment properties
50
51 /// How should content contained within this item be aligned in the cross/block axis
52 #[inline(always)]
53 fn align_content(&self) -> Option<AlignContent> {
54 Style::DEFAULT.align_content
55 }
56 /// How should contained within this item be aligned in the main/inline axis
57 #[inline(always)]
58 fn justify_content(&self) -> Option<JustifyContent> {
59 Style::DEFAULT.justify_content
60 }
61 /// How this node's children aligned in the cross/block axis?
62 #[inline(always)]
63 fn align_items(&self) -> Option<AlignItems> {
64 Style::DEFAULT.align_items
65 }
66 /// How this node's children should be aligned in the inline axis
67 #[inline(always)]
68 fn justify_items(&self) -> Option<AlignItems> {
69 Style::DEFAULT.justify_items
70 }
71
72 /// Get a grid item's row or column placement depending on the axis passed
73 #[inline(always)]
74 fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Self::TemplateTrackList<'_> {
75 match axis {
76 AbsoluteAxis::Horizontal => self.grid_template_columns(),
77 AbsoluteAxis::Vertical => self.grid_template_rows(),
78 }
79 }
80
81 /// Get a grid container's align-content or justify-content alignment depending on the axis passed
82 #[inline(always)]
83 fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
84 match axis {
85 AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::Stretch),
86 AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::Stretch),
87 }
88 }
89}
90
91/// The set of styles required for a CSS Grid item (child of a CSS Grid container)
92pub trait GridItemStyle: CoreStyle {
93 /// Defines which row in the grid the item should start and end at
94 #[inline(always)]
95 fn grid_row(&self) -> Line<GridPlacement> {
96 Style::DEFAULT.grid_row
97 }
98 /// Defines which column in the grid the item should start and end at
99 #[inline(always)]
100 fn grid_column(&self) -> Line<GridPlacement> {
101 Style::DEFAULT.grid_column
102 }
103
104 /// How this node should be aligned in the cross/block axis
105 /// Falls back to the parents [`AlignItems`] if not set
106 #[inline(always)]
107 fn align_self(&self) -> Option<AlignSelf> {
108 Style::DEFAULT.align_self
109 }
110 /// How this node should be aligned in the inline axis
111 /// Falls back to the parents [`super::JustifyItems`] if not set
112 #[inline(always)]
113 fn justify_self(&self) -> Option<AlignSelf> {
114 Style::DEFAULT.justify_self
115 }
116
117 /// Get a grid item's row or column placement depending on the axis passed
118 #[inline(always)]
119 fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement> {
120 match axis {
121 AbsoluteAxis::Horizontal => self.grid_column(),
122 AbsoluteAxis::Vertical => self.grid_row(),
123 }
124 }
125}
126
127/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
128///
129/// 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.
130///
131/// Defaults to [`GridAutoFlow::Row`]
132///
133/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
134#[derive(Copy, Clone, PartialEq, Eq, Debug)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136pub enum GridAutoFlow {
137 /// Items are placed by filling each row in turn, adding new rows as necessary
138 Row,
139 /// Items are placed by filling each column in turn, adding new columns as necessary.
140 Column,
141 /// Combines `Row` with the dense packing algorithm.
142 RowDense,
143 /// Combines `Column` with the dense packing algorithm.
144 ColumnDense,
145}
146
147impl Default for GridAutoFlow {
148 fn default() -> Self {
149 Self::Row
150 }
151}
152
153impl GridAutoFlow {
154 /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
155 /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
156 pub fn is_dense(&self) -> bool {
157 match self {
158 Self::Row | Self::Column => false,
159 Self::RowDense | Self::ColumnDense => true,
160 }
161 }
162
163 /// Whether grid auto placement fills areas row-wise or column-wise
164 /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
165 pub fn primary_axis(&self) -> AbsoluteAxis {
166 match self {
167 Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
168 Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
169 }
170 }
171}
172
173/// A grid line placement specification which is generic over the coordinate system that it uses to define
174/// grid line positions.
175///
176/// GenericGridPlacement<GridLine> is aliased as GridPlacement and is exposed to users of Taffy to define styles.
177/// GenericGridPlacement<OriginZeroLine> is aliased as OriginZeroGridPlacement and is used internally for placement computations.
178///
179/// See [`crate::compute::grid::type::coordinates`] for documentation on the different coordinate systems.
180#[derive(Copy, Clone, PartialEq, Eq, Debug)]
181#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
182pub enum GenericGridPlacement<LineType: GridCoordinate> {
183 /// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
184 Auto,
185 /// Place item at specified line (column or row) index
186 Line(LineType),
187 /// Item should span specified number of tracks (columns or rows)
188 Span(u16),
189}
190
191/// A grid line placement using the normalized OriginZero coordinates to specify line positions.
192pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
193
194/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
195///
196/// Defaults to `GridPlacement::Auto`
197///
198/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
199pub type GridPlacement = GenericGridPlacement<GridLine>;
200impl TaffyAuto for GridPlacement {
201 const AUTO: Self = Self::Auto;
202}
203impl TaffyGridLine for GridPlacement {
204 fn from_line_index(index: i16) -> Self {
205 GridPlacement::Line(GridLine::from(index))
206 }
207}
208impl TaffyGridLine for Line<GridPlacement> {
209 fn from_line_index(index: i16) -> Self {
210 Line { start: GridPlacement::from_line_index(index), end: GridPlacement::Auto }
211 }
212}
213impl TaffyGridSpan for GridPlacement {
214 fn from_span(span: u16) -> Self {
215 GridPlacement::Span(span)
216 }
217}
218impl TaffyGridSpan for Line<GridPlacement> {
219 fn from_span(span: u16) -> Self {
220 Line { start: GridPlacement::from_span(span), end: GridPlacement::Auto }
221 }
222}
223
224impl Default for GridPlacement {
225 fn default() -> Self {
226 Self::Auto
227 }
228}
229
230impl GridPlacement {
231 /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
232 pub fn into_origin_zero_placement(self, explicit_track_count: u16) -> OriginZeroGridPlacement {
233 match self {
234 Self::Auto => OriginZeroGridPlacement::Auto,
235 Self::Span(span) => OriginZeroGridPlacement::Span(span),
236 // Grid line zero is an invalid index, so it gets treated as Auto
237 // See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
238 Self::Line(line) => match line.as_i16() {
239 0 => OriginZeroGridPlacement::Auto,
240 _ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
241 },
242 }
243 }
244}
245
246impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
247 /// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
248 /// Panics if called on a definite placement
249 pub fn indefinite_span(&self) -> u16 {
250 use GenericGridPlacement as GP;
251 match (self.start, self.end) {
252 (GP::Line(_), GP::Auto) => 1,
253 (GP::Auto, GP::Line(_)) => 1,
254 (GP::Auto, GP::Auto) => 1,
255 (GP::Line(_), GP::Span(span)) => span,
256 (GP::Span(span), GP::Line(_)) => span,
257 (GP::Span(span), GP::Auto) => span,
258 (GP::Auto, GP::Span(span)) => span,
259 (GP::Span(span), GP::Span(_)) => span,
260 (GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
261 }
262 }
263}
264
265impl Line<GridPlacement> {
266 #[inline]
267 /// Whether the track position is definite in this axis (or the item will need auto placement)
268 /// The track position is definite if least one of the start and end positions is a NON-ZERO track index
269 /// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
270 pub fn is_definite(&self) -> bool {
271 match (self.start, self.end) {
272 (GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
273 (_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
274 _ => false,
275 }
276 }
277
278 /// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
279 pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
280 Line {
281 start: self.start.into_origin_zero_placement(explicit_track_count),
282 end: self.end.into_origin_zero_placement(explicit_track_count),
283 }
284 }
285}
286
287impl Line<OriginZeroGridPlacement> {
288 #[inline]
289 /// Whether the track position is definite in this axis (or the item will need auto placement)
290 /// The track position is definite if least one of the start and end positions is a track index
291 pub fn is_definite(&self) -> bool {
292 matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
293 }
294
295 /// If at least one of the of the start and end positions is a track index then the other end can be resolved
296 /// into a track index purely based on the information contained with the placement specification
297 pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
298 use OriginZeroGridPlacement as GP;
299 match (self.start, self.end) {
300 (GP::Line(line1), GP::Line(line2)) => {
301 if line1 == line2 {
302 Line { start: line1, end: line1 + 1 }
303 } else {
304 Line { start: min(line1, line2), end: max(line1, line2) }
305 }
306 }
307 (GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
308 (GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
309 (GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
310 (GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
311 _ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
312 }
313 }
314
315 /// For absolutely positioned items:
316 /// - Tracks resolve to definite tracks
317 /// - For Spans:
318 /// - If the other position is a Track, they resolve to a definite track relative to the other track
319 /// - Else resolve to None
320 /// - Auto resolves to None
321 ///
322 /// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
323 /// container's border box on that side.
324 pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
325 use OriginZeroGridPlacement as GP;
326 match (self.start, self.end) {
327 (GP::Line(track1), GP::Line(track2)) => {
328 if track1 == track2 {
329 Line { start: Some(track1), end: Some(track1 + 1) }
330 } else {
331 Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
332 }
333 }
334 (GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
335 (GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
336 (GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
337 (GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
338 _ => Line { start: None, end: None },
339 }
340 }
341
342 /// If neither of the start and end positions is a track index then the other end can be resolved
343 /// into a track index if a definite start position is supplied externally
344 pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
345 use OriginZeroGridPlacement as GP;
346 match (self.start, self.end) {
347 (GP::Auto, GP::Auto) => Line { start, end: start + 1 },
348 (GP::Span(span), GP::Auto) => Line { start, end: start + span },
349 (GP::Auto, GP::Span(span)) => Line { start, end: start + span },
350 (GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
351 _ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
352 }
353 }
354}
355
356/// Represents the start and end points of a GridItem within a given axis
357impl Default for Line<GridPlacement> {
358 fn default() -> Self {
359 Line { start: GridPlacement::Auto, end: GridPlacement::Auto }
360 }
361}
362
363/// Maximum track sizing function
364///
365/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
366/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
367/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
368#[derive(Copy, Clone, PartialEq, Debug)]
369#[cfg_attr(feature = "serde", derive(Serialize))]
370pub struct MaxTrackSizingFunction(pub(crate) CompactLength);
371impl TaffyZero for MaxTrackSizingFunction {
372 const ZERO: Self = Self(CompactLength::ZERO);
373}
374impl TaffyAuto for MaxTrackSizingFunction {
375 const AUTO: Self = Self(CompactLength::AUTO);
376}
377impl TaffyMinContent for MaxTrackSizingFunction {
378 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
379}
380impl TaffyMaxContent for MaxTrackSizingFunction {
381 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
382}
383impl FromLength for MaxTrackSizingFunction {
384 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
385 Self::length(value.into())
386 }
387}
388impl FromPercent for MaxTrackSizingFunction {
389 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
390 Self::percent(value.into())
391 }
392}
393impl TaffyFitContent for MaxTrackSizingFunction {
394 fn fit_content(argument: LengthPercentage) -> Self {
395 Self(CompactLength::fit_content(argument))
396 }
397}
398impl FromFr for MaxTrackSizingFunction {
399 fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
400 Self::fr(value.into())
401 }
402}
403impl From<LengthPercentage> for MaxTrackSizingFunction {
404 fn from(input: LengthPercentage) -> Self {
405 Self(input.0)
406 }
407}
408impl From<LengthPercentageAuto> for MaxTrackSizingFunction {
409 fn from(input: LengthPercentageAuto) -> Self {
410 Self(input.0)
411 }
412}
413impl From<Dimension> for MaxTrackSizingFunction {
414 fn from(input: Dimension) -> Self {
415 Self(input.0)
416 }
417}
418impl From<MinTrackSizingFunction> for MaxTrackSizingFunction {
419 fn from(input: MinTrackSizingFunction) -> Self {
420 Self(input.0)
421 }
422}
423#[cfg(feature = "serde")]
424impl<'de> serde::Deserialize<'de> for MaxTrackSizingFunction {
425 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426 where
427 D: serde::Deserializer<'de>,
428 {
429 let inner = CompactLength::deserialize(deserializer)?;
430 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
431 if matches!(
432 inner.tag(),
433 CompactLength::LENGTH_TAG
434 | CompactLength::PERCENT_TAG
435 | CompactLength::AUTO_TAG
436 | CompactLength::MIN_CONTENT_TAG
437 | CompactLength::MAX_CONTENT_TAG
438 | CompactLength::FIT_CONTENT_PX_TAG
439 | CompactLength::FIT_CONTENT_PERCENT_TAG
440 | CompactLength::FR_TAG
441 ) {
442 Ok(Self(inner))
443 } else {
444 Err(serde::de::Error::custom("Invalid tag"))
445 }
446 }
447}
448
449impl MaxTrackSizingFunction {
450 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
451 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
452 #[inline(always)]
453 pub const fn length(val: f32) -> Self {
454 Self(CompactLength::length(val))
455 }
456
457 /// A percentage length relative to the size of the containing block.
458 ///
459 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
460 #[inline(always)]
461 pub const fn percent(val: f32) -> Self {
462 Self(CompactLength::percent(val))
463 }
464
465 /// The dimension should be automatically computed according to algorithm-specific rules
466 /// regarding the default size of boxes.
467 #[inline(always)]
468 pub const fn auto() -> Self {
469 Self(CompactLength::auto())
470 }
471
472 /// The size should be the "min-content" size.
473 /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
474 #[inline(always)]
475 pub const fn min_content() -> Self {
476 Self(CompactLength::min_content())
477 }
478
479 /// The size should be the "max-content" size.
480 /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
481 #[inline(always)]
482 pub const fn max_content() -> Self {
483 Self(CompactLength::max_content())
484 }
485
486 /// The size should be computed according to the "fit content" formula:
487 /// `max(min_content, min(max_content, limit))`
488 /// where:
489 /// - `min_content` is the [min-content](Self::min_content) size
490 /// - `max_content` is the [max-content](Self::max_content) size
491 /// - `limit` is a LENGTH value passed to this function
492 ///
493 /// The effect of this is that the item takes the size of `limit` clamped
494 /// by the min-content and max-content sizes.
495 #[inline(always)]
496 pub const fn fit_content_px(limit: f32) -> Self {
497 Self(CompactLength::fit_content_px(limit))
498 }
499
500 /// The size should be computed according to the "fit content" formula:
501 /// `max(min_content, min(max_content, limit))`
502 /// where:
503 /// - `min_content` is the [min-content](Self::min_content) size
504 /// - `max_content` is the [max-content](Self::max_content) size
505 /// - `limit` is a PERCENTAGE value passed to this function
506 ///
507 /// The effect of this is that the item takes the size of `limit` clamped
508 /// by the min-content and max-content sizes.
509 #[inline(always)]
510 pub const fn fit_content_percent(limit: f32) -> Self {
511 Self(CompactLength::fit_content_percent(limit))
512 }
513
514 /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
515 /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
516 /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
517 #[inline(always)]
518 pub const fn fr(val: f32) -> Self {
519 Self(CompactLength::fr(val))
520 }
521
522 /// A `calc()` value. The value passed here is treated as an opaque handle to
523 /// the actual calc representation and may be a pointer, index, etc.
524 ///
525 /// The low 3 bits are used as a tag value and will be returned as 0.
526 #[inline]
527 pub fn calc(ptr: *const ()) -> Self {
528 Self(CompactLength::calc(ptr))
529 }
530
531 /// Create a LengthPercentageAuto from a raw `CompactLength`.
532 /// # Safety
533 /// CompactLength must represent a valid variant for LengthPercentageAuto
534 #[allow(unsafe_code)]
535 pub unsafe fn from_raw(val: CompactLength) -> Self {
536 Self(val)
537 }
538
539 /// Get the underlying `CompactLength` representation of the value
540 pub fn into_raw(self) -> CompactLength {
541 self.0
542 }
543
544 /// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
545 #[inline(always)]
546 pub fn is_intrinsic(&self) -> bool {
547 self.0.is_intrinsic()
548 }
549
550 /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
551 /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
552 /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
553 #[inline(always)]
554 pub fn is_max_content_alike(&self) -> bool {
555 self.0.is_max_content_alike()
556 }
557
558 /// Returns true if the an Fr value, else false.
559 #[inline(always)]
560 pub fn is_fr(&self) -> bool {
561 self.0.is_fr()
562 }
563
564 /// Returns true if the is `Auto`, else false.
565 #[inline(always)]
566 pub fn is_auto(&self) -> bool {
567 self.0.is_auto()
568 }
569
570 /// Returns true if value is MinContent
571 #[inline(always)]
572 pub fn is_min_content(&self) -> bool {
573 self.0.is_min_content()
574 }
575
576 /// Returns true if value is MaxContent
577 #[inline(always)]
578 pub fn is_max_content(&self) -> bool {
579 self.0.is_max_content()
580 }
581
582 /// Returns true if value is FitContent(...)
583 #[inline(always)]
584 pub fn is_fit_content(&self) -> bool {
585 self.0.is_fit_content()
586 }
587
588 /// Returns true if value is MaxContent or FitContent(...)
589 #[inline(always)]
590 pub fn is_max_or_fit_content(&self) -> bool {
591 self.0.is_max_or_fit_content()
592 }
593
594 /// Returns whether the value can be resolved using `Self::definite_value`
595 #[inline(always)]
596 pub fn has_definite_value(self, parent_size: Option<f32>) -> bool {
597 match self.0.tag() {
598 CompactLength::LENGTH_TAG => true,
599 CompactLength::PERCENT_TAG => parent_size.is_some(),
600 _ if self.0.is_calc() => parent_size.is_some(),
601 _ => false,
602 }
603 }
604
605 /// Returns fixed point values directly. Attempts to resolve percentage values against
606 /// the passed available_space and returns if this results in a concrete value (which it
607 /// will if the available_space is `Some`). Otherwise returns None.
608 #[inline(always)]
609 pub fn definite_value(
610 self,
611 parent_size: Option<f32>,
612 calc_resolver: impl Fn(*const (), f32) -> f32,
613 ) -> Option<f32> {
614 match self.0.tag() {
615 CompactLength::LENGTH_TAG => Some(self.0.value()),
616 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
617 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
618 _ => None,
619 }
620 }
621
622 /// Resolve the maximum size of the track as defined by either:
623 /// - A fixed track sizing function
624 /// - A percentage track sizing function (with definite available space)
625 /// - A fit-content sizing function with fixed argument
626 /// - A fit-content sizing function with percentage argument (with definite available space)
627 /// All other kinds of track sizing function return None.
628 #[inline(always)]
629 pub fn definite_limit(
630 self,
631 parent_size: Option<f32>,
632 calc_resolver: impl Fn(*const (), f32) -> f32,
633 ) -> Option<f32> {
634 match self.0.tag() {
635 CompactLength::FIT_CONTENT_PX_TAG => Some(self.0.value()),
636 CompactLength::FIT_CONTENT_PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
637 _ => self.definite_value(parent_size, calc_resolver),
638 }
639 }
640
641 /// Resolve percentage values against the passed parent_size, returning Some(value)
642 /// Non-percentage values always return None.
643 #[inline(always)]
644 pub fn resolved_percentage_size(
645 self,
646 parent_size: f32,
647 calc_resolver: impl Fn(*const (), f32) -> f32,
648 ) -> Option<f32> {
649 self.0.resolved_percentage_size(parent_size, calc_resolver)
650 }
651
652 /// Whether the track sizing functions depends on the size of the parent node
653 #[inline(always)]
654 pub fn uses_percentage(self) -> bool {
655 self.0.uses_percentage()
656 }
657}
658
659/// Minimum track sizing function
660///
661/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
662/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
663/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
664#[derive(Copy, Clone, PartialEq, Debug)]
665#[cfg_attr(feature = "serde", derive(Serialize))]
666pub struct MinTrackSizingFunction(pub(crate) CompactLength);
667impl TaffyZero for MinTrackSizingFunction {
668 const ZERO: Self = Self(CompactLength::ZERO);
669}
670impl TaffyAuto for MinTrackSizingFunction {
671 const AUTO: Self = Self(CompactLength::AUTO);
672}
673impl TaffyMinContent for MinTrackSizingFunction {
674 const MIN_CONTENT: Self = Self(CompactLength::MIN_CONTENT);
675}
676impl TaffyMaxContent for MinTrackSizingFunction {
677 const MAX_CONTENT: Self = Self(CompactLength::MAX_CONTENT);
678}
679impl FromLength for MinTrackSizingFunction {
680 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
681 Self::length(value.into())
682 }
683}
684impl FromPercent for MinTrackSizingFunction {
685 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
686 Self::percent(value.into())
687 }
688}
689impl From<LengthPercentage> for MinTrackSizingFunction {
690 fn from(input: LengthPercentage) -> Self {
691 Self(input.0)
692 }
693}
694impl From<LengthPercentageAuto> for MinTrackSizingFunction {
695 fn from(input: LengthPercentageAuto) -> Self {
696 Self(input.0)
697 }
698}
699impl From<Dimension> for MinTrackSizingFunction {
700 fn from(input: Dimension) -> Self {
701 Self(input.0)
702 }
703}
704#[cfg(feature = "serde")]
705impl<'de> serde::Deserialize<'de> for MinTrackSizingFunction {
706 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
707 where
708 D: serde::Deserializer<'de>,
709 {
710 let inner = CompactLength::deserialize(deserializer)?;
711 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
712 if matches!(
713 inner.tag(),
714 CompactLength::LENGTH_TAG
715 | CompactLength::PERCENT_TAG
716 | CompactLength::AUTO_TAG
717 | CompactLength::MIN_CONTENT_TAG
718 | CompactLength::MAX_CONTENT_TAG
719 | CompactLength::FIT_CONTENT_PX_TAG
720 | CompactLength::FIT_CONTENT_PERCENT_TAG
721 ) {
722 Ok(Self(inner))
723 } else {
724 Err(serde::de::Error::custom("Invalid tag"))
725 }
726 }
727}
728
729impl MinTrackSizingFunction {
730 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
731 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
732 #[inline(always)]
733 pub const fn length(val: f32) -> Self {
734 Self(CompactLength::length(val))
735 }
736
737 /// A percentage length relative to the size of the containing block.
738 ///
739 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
740 #[inline(always)]
741 pub const fn percent(val: f32) -> Self {
742 Self(CompactLength::percent(val))
743 }
744
745 /// The dimension should be automatically computed according to algorithm-specific rules
746 /// regarding the default size of boxes.
747 #[inline(always)]
748 pub const fn auto() -> Self {
749 Self(CompactLength::auto())
750 }
751
752 /// The size should be the "min-content" size.
753 /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
754 #[inline(always)]
755 pub const fn min_content() -> Self {
756 Self(CompactLength::min_content())
757 }
758
759 /// The size should be the "max-content" size.
760 /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
761 #[inline(always)]
762 pub const fn max_content() -> Self {
763 Self(CompactLength::max_content())
764 }
765
766 /// A `calc()` value. The value passed here is treated as an opaque handle to
767 /// the actual calc representation and may be a pointer, index, etc.
768 ///
769 /// The low 3 bits are used as a tag value and will be returned as 0.
770 #[inline]
771 pub fn calc(ptr: *const ()) -> Self {
772 Self(CompactLength::calc(ptr))
773 }
774
775 /// Create a LengthPercentageAuto from a raw `CompactLength`.
776 /// # Safety
777 /// CompactLength must represent a valid variant for LengthPercentageAuto
778 #[allow(unsafe_code)]
779 pub unsafe fn from_raw(val: CompactLength) -> Self {
780 Self(val)
781 }
782
783 /// Get the underlying `CompactLength` representation of the value
784 pub fn into_raw(self) -> CompactLength {
785 self.0
786 }
787
788 /// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
789 #[inline(always)]
790 pub fn is_intrinsic(&self) -> bool {
791 self.0.is_intrinsic()
792 }
793
794 /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
795 #[inline(always)]
796 pub fn is_min_or_max_content(&self) -> bool {
797 self.0.is_min_or_max_content()
798 }
799
800 /// Returns true if the value is an fr value
801 #[inline(always)]
802 pub fn is_fr(&self) -> bool {
803 self.0.is_fr()
804 }
805
806 /// Returns true if the is `Auto`, else false.
807 #[inline(always)]
808 pub fn is_auto(&self) -> bool {
809 self.0.is_auto()
810 }
811
812 /// Returns true if value is MinContent
813 #[inline(always)]
814 pub fn is_min_content(&self) -> bool {
815 self.0.is_min_content()
816 }
817
818 /// Returns true if value is MaxContent
819 #[inline(always)]
820 pub fn is_max_content(&self) -> bool {
821 self.0.is_max_content()
822 }
823
824 /// Returns fixed point values directly. Attempts to resolve percentage values against
825 /// the passed available_space and returns if this results in a concrete value (which it
826 /// will if the available_space is `Some`). Otherwise returns `None`.
827 #[inline(always)]
828 pub fn definite_value(
829 self,
830 parent_size: Option<f32>,
831 calc_resolver: impl Fn(*const (), f32) -> f32,
832 ) -> Option<f32> {
833 match self.0.tag() {
834 CompactLength::LENGTH_TAG => Some(self.0.value()),
835 CompactLength::PERCENT_TAG => parent_size.map(|size| self.0.value() * size),
836 _ if self.0.is_calc() => parent_size.map(|size| calc_resolver(self.0.calc_value(), size)),
837 _ => None,
838 }
839 }
840
841 /// Resolve percentage values against the passed parent_size, returning Some(value)
842 /// Non-percentage values always return None.
843 #[inline(always)]
844 pub fn resolved_percentage_size(
845 self,
846 parent_size: f32,
847 calc_resolver: impl Fn(*const (), f32) -> f32,
848 ) -> Option<f32> {
849 self.0.resolved_percentage_size(parent_size, calc_resolver)
850 }
851
852 /// Whether the track sizing functions depends on the size of the parent node
853 #[inline(always)]
854 pub fn uses_percentage(self) -> bool {
855 matches!(self.0.tag(), CompactLength::PERCENT_TAG) || self.0.is_calc()
856 }
857}
858
859/// The sizing function for a grid track (row/column)
860///
861/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
862/// or a scalar value which applies to both track sizing functions.
863pub type NonRepeatedTrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
864impl NonRepeatedTrackSizingFunction {
865 /// Extract the min track sizing function
866 pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
867 self.min
868 }
869 /// Extract the max track sizing function
870 pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
871 self.max
872 }
873 /// Determine whether at least one of the components ("min" and "max") are fixed sizing function
874 pub fn has_fixed_component(&self) -> bool {
875 self.min.0.is_length_or_percentage() || self.max.0.is_length_or_percentage()
876 }
877}
878impl TaffyAuto for NonRepeatedTrackSizingFunction {
879 const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
880}
881impl TaffyMinContent for NonRepeatedTrackSizingFunction {
882 const MIN_CONTENT: Self =
883 Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
884}
885impl TaffyMaxContent for NonRepeatedTrackSizingFunction {
886 const MAX_CONTENT: Self =
887 Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
888}
889impl TaffyFitContent for NonRepeatedTrackSizingFunction {
890 fn fit_content(argument: LengthPercentage) -> Self {
891 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::fit_content(argument) }
892 }
893}
894impl TaffyZero for NonRepeatedTrackSizingFunction {
895 const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
896}
897impl FromLength for NonRepeatedTrackSizingFunction {
898 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
899 Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
900 }
901}
902impl FromPercent for NonRepeatedTrackSizingFunction {
903 fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
904 Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
905 }
906}
907impl FromFr for NonRepeatedTrackSizingFunction {
908 fn from_fr<Input: Into<f32> + Copy>(flex: Input) -> Self {
909 Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_fr(flex) }
910 }
911}
912impl From<LengthPercentage> for NonRepeatedTrackSizingFunction {
913 fn from(input: LengthPercentage) -> Self {
914 Self { min: input.into(), max: input.into() }
915 }
916}
917impl From<LengthPercentageAuto> for NonRepeatedTrackSizingFunction {
918 fn from(input: LengthPercentageAuto) -> Self {
919 Self { min: input.into(), max: input.into() }
920 }
921}
922impl From<Dimension> for NonRepeatedTrackSizingFunction {
923 fn from(input: Dimension) -> Self {
924 Self { min: input.into(), max: input.into() }
925 }
926}
927
928/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
929///
930/// See <https://www.w3.org/TR/css-grid-1/#auto-repeat> for an explanation of how auto-repeated track definitions work
931/// and the difference between AutoFit and AutoFill.
932#[derive(Clone, Copy, Debug, PartialEq, Eq)]
933#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
934pub enum GridTrackRepetition {
935 /// Auto-repeating tracks should be generated to fit the container
936 /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
937 AutoFill,
938 /// Auto-repeating tracks should be generated to fit the container
939 /// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
940 AutoFit,
941 /// The specified tracks should be repeated exacts N times
942 Count(u16),
943}
944impl TryFrom<u16> for GridTrackRepetition {
945 type Error = Infallible;
946 fn try_from(value: u16) -> Result<Self, Infallible> {
947 Ok(Self::Count(value))
948 }
949}
950
951/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
952/// either "auto-fit" or "auto-fill"
953#[derive(Debug)]
954pub struct InvalidStringRepetitionValue;
955#[cfg(feature = "std")]
956impl std::error::Error for InvalidStringRepetitionValue {}
957impl core::fmt::Display for InvalidStringRepetitionValue {
958 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
959 f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
960 }
961}
962impl TryFrom<&str> for GridTrackRepetition {
963 type Error = InvalidStringRepetitionValue;
964 fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
965 match value {
966 "auto-fit" => Ok(Self::AutoFit),
967 "auto-fill" => Ok(Self::AutoFill),
968 _ => Err(InvalidStringRepetitionValue),
969 }
970 }
971}
972
973/// The sizing function for a grid track (row/column)
974/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
975#[derive(Clone, PartialEq, Debug)]
976#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
977pub enum TrackSizingFunction {
978 /// A single non-repeated track
979 Single(NonRepeatedTrackSizingFunction),
980 /// Automatically generate grid tracks to fit the available space using the specified definite track lengths
981 /// Only valid if every track in template (not just the repetition) has a fixed size.
982 Repeat(GridTrackRepetition, GridTrackVec<NonRepeatedTrackSizingFunction>),
983}
984impl TrackSizingFunction {
985 /// Whether the track definition is a auto-repeated fragment
986 pub fn is_auto_repetition(&self) -> bool {
987 matches!(self, Self::Repeat(GridTrackRepetition::AutoFit | GridTrackRepetition::AutoFill, _))
988 }
989}
990impl TaffyAuto for TrackSizingFunction {
991 const AUTO: Self = Self::Single(NonRepeatedTrackSizingFunction::AUTO);
992}
993impl TaffyMinContent for TrackSizingFunction {
994 const MIN_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MIN_CONTENT);
995}
996impl TaffyMaxContent for TrackSizingFunction {
997 const MAX_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MAX_CONTENT);
998}
999impl TaffyFitContent for TrackSizingFunction {
1000 fn fit_content(argument: LengthPercentage) -> Self {
1001 Self::Single(NonRepeatedTrackSizingFunction::fit_content(argument))
1002 }
1003}
1004impl TaffyZero for TrackSizingFunction {
1005 const ZERO: Self = Self::Single(NonRepeatedTrackSizingFunction::ZERO);
1006}
1007impl FromLength for TrackSizingFunction {
1008 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
1009 Self::Single(NonRepeatedTrackSizingFunction::from_length(value))
1010 }
1011}
1012impl FromPercent for TrackSizingFunction {
1013 fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
1014 Self::Single(NonRepeatedTrackSizingFunction::from_percent(percent))
1015 }
1016}
1017impl FromFr for TrackSizingFunction {
1018 fn from_fr<Input: Into<f32> + Copy>(flex: Input) -> Self {
1019 Self::Single(NonRepeatedTrackSizingFunction::from_fr(flex))
1020 }
1021}
1022impl From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for TrackSizingFunction {
1023 fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
1024 Self::Single(input)
1025 }
1026}