1use super::GridTrack;
3use crate::compute::common::sizing_keyword::{resolve_sizing_keyword, SizingKeywordResolution};
4use crate::compute::grid::OriginZeroLine;
5use crate::geometry::AbstractAxis;
6use crate::geometry::{Line, Point, Rect, Size};
7use crate::style::{AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, Overflow};
8use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
9use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
10use crate::{AlignItemsKeyword, BoxSizing, GridItemStyle, LengthPercentage};
11use core::ops::Range;
12
13#[derive(Debug)]
15pub(in super::super) struct GridItem {
16 pub node: NodeId,
18
19 pub source_order: u16,
24
25 pub row: Line<OriginZeroLine>,
28 pub column: Line<OriginZeroLine>,
31
32 pub is_compressible_replaced: bool,
35 pub overflow: Point<Overflow>,
37 pub box_sizing: BoxSizing,
39 pub size: Size<Dimension>,
41 pub min_size: Size<LengthPercentageAuto>,
43 pub max_size: Size<LengthPercentageAuto>,
45 pub aspect_ratio: Option<f32>,
47 pub padding: Rect<LengthPercentage>,
49 pub border: Rect<LengthPercentage>,
51 pub margin: Rect<LengthPercentageAuto>,
53 pub align_self: AlignSelf,
55 pub justify_self: AlignSelf,
57 pub baseline: Option<f32>,
59 pub baseline_shim: f32,
62
63 pub row_indexes: Line<u16>,
66 pub column_indexes: Line<u16>,
69
70 pub crosses_flexible_row: bool,
72 pub crosses_flexible_column: bool,
74 pub crosses_intrinsic_row: bool,
76 pub crosses_intrinsic_column: bool,
78
79 pub grid_area_size_cache: Option<Size<Option<f32>>>,
82 pub min_content_contribution_cache: Size<Option<f32>>,
84 pub minimum_contribution_cache: Size<Option<f32>>,
86 pub max_content_contribution_cache: Size<Option<f32>>,
88
89 pub y_position: f32,
91 pub height: f32,
93}
94
95impl GridItem {
96 pub fn new_with_placement_style_and_order<S: GridItemStyle>(
98 node: NodeId,
99 col_span: Line<OriginZeroLine>,
100 row_span: Line<OriginZeroLine>,
101 style: S,
102 parent_align_items: AlignItems,
103 parent_justify_items: AlignItems,
104 source_order: u16,
105 ) -> Self {
106 GridItem {
107 node,
108 source_order,
109 row: row_span,
110 column: col_span,
111 is_compressible_replaced: style.is_compressible_replaced(),
112 overflow: style.overflow(),
113 box_sizing: style.box_sizing(),
114 size: style.size(),
115 min_size: style.min_size(),
116 max_size: style.max_size(),
117 aspect_ratio: style.aspect_ratio(),
118 padding: style.padding(),
119 border: style.border(),
120 margin: style.margin(),
121 align_self: style.align_self().unwrap_or(parent_align_items),
122 justify_self: style.justify_self().unwrap_or(parent_justify_items),
123 baseline: None,
124 baseline_shim: 0.0,
125 row_indexes: Line { start: 0, end: 0 }, column_indexes: Line { start: 0, end: 0 }, crosses_flexible_row: false, crosses_flexible_column: false, crosses_intrinsic_row: false, crosses_intrinsic_column: false, grid_area_size_cache: None,
132 min_content_contribution_cache: Size::NONE,
133 max_content_contribution_cache: Size::NONE,
134 minimum_contribution_cache: Size::NONE,
135 y_position: 0.0,
136 height: 0.0,
137 }
138 }
139
140 #[inline(always)]
142 pub fn has_auto_block_margin(&self) -> bool {
143 self.margin.top.is_auto() || self.margin.bottom.is_auto()
144 }
145
146 #[inline(always)]
151 pub fn has_cyclic_block_size_dependency(&self) -> bool {
152 self.size.height.0.uses_percentage() && (self.crosses_intrinsic_row || self.crosses_flexible_row)
153 }
154
155 #[inline(always)]
159 pub fn participates_in_baseline_alignment(&self) -> bool {
160 self.align_self.keyword == AlignItemsKeyword::Baseline
161 && !self.has_auto_block_margin()
162 && !self.has_cyclic_block_size_dependency()
163 }
164
165 pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
167 match axis {
168 AbstractAxis::Block => self.row,
169 AbstractAxis::Inline => self.column,
170 }
171 }
172
173 pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
175 match axis {
176 AbstractAxis::Block => self.row_indexes,
177 AbstractAxis::Inline => self.column_indexes,
178 }
179 }
180
181 pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
185 let indexes = self.placement_indexes(axis);
186 (indexes.start as usize + 1)..(indexes.end as usize)
187 }
188
189 pub fn span(&self, axis: AbstractAxis) -> u16 {
191 match axis {
192 AbstractAxis::Block => self.row.span(),
193 AbstractAxis::Inline => self.column.span(),
194 }
195 }
196
197 pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
200 match axis {
201 AbstractAxis::Inline => self.crosses_flexible_column,
202 AbstractAxis::Block => self.crosses_flexible_row,
203 }
204 }
205
206 pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
209 match axis {
210 AbstractAxis::Inline => self.crosses_intrinsic_column,
211 AbstractAxis::Block => self.crosses_intrinsic_row,
212 }
213 }
214
215 pub fn spanned_track_limit(
218 &mut self,
219 axis: AbstractAxis,
220 axis_tracks: &[GridTrack],
221 axis_parent_size: Option<f32>,
222 resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
223 ) -> Option<f32> {
224 let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
225 let tracks_all_fixed = spanned_tracks.iter().all(|track| {
226 track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).is_some()
227 });
228 if tracks_all_fixed {
229 let limit: f32 = spanned_tracks
230 .iter()
231 .map(|track| {
232 track.max_track_sizing_function.definite_limit(axis_parent_size, resolve_calc_value).unwrap()
233 })
234 .sum();
235 Some(limit)
236 } else {
237 None
238 }
239 }
240
241 pub fn spanned_fixed_track_limit(
244 &mut self,
245 axis: AbstractAxis,
246 axis_tracks: &[GridTrack],
247 axis_parent_size: Option<f32>,
248 resolve_calc_value: &dyn Fn(*const (), f32) -> f32,
249 ) -> Option<f32> {
250 let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
251 let tracks_all_fixed = spanned_tracks.iter().all(|track| {
252 track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).is_some()
253 });
254 if tracks_all_fixed {
255 let limit: f32 = spanned_tracks
256 .iter()
257 .map(|track| {
258 track.max_track_sizing_function.definite_value(axis_parent_size, resolve_calc_value).unwrap()
259 })
260 .sum();
261 Some(limit)
262 } else {
263 None
264 }
265 }
266
267 fn known_dimensions(
271 &self,
272 tree: &mut impl LayoutPartialTree,
273 grid_area_size: Size<Option<f32>>,
274 ) -> Size<Option<f32>> {
275 let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
276
277 let aspect_ratio = self.aspect_ratio;
278 let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
285 let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
286 let padding_border_size = (padding + border).sum_axes();
287 let box_sizing_adjustment =
288 if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
289 let inherent_size = self
290 .size
291 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
292 .maybe_apply_aspect_ratio(aspect_ratio)
293 .maybe_add(box_sizing_adjustment);
294 let min_size = self
295 .min_size
296 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
297 .maybe_apply_aspect_ratio(aspect_ratio)
298 .maybe_add(box_sizing_adjustment);
299 let max_size = self
300 .max_size
301 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
302 .maybe_apply_aspect_ratio(aspect_ratio)
303 .maybe_add(box_sizing_adjustment);
304
305 let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);
306
307 let width = inherent_size.width.or_else(|| {
310 if self.size.width.is_sizing_keyword() {
313 return match resolve_sizing_keyword(
314 self.size.width,
315 grid_area_minus_item_margins_size.width,
316 grid_area_size.width,
317 ) {
318 Some(SizingKeywordResolution::Exact(width)) => Some(width),
319 _ => None,
320 };
321 }
322
323 if !self.margin.left.is_auto() && !self.margin.right.is_auto() && self.justify_self == AlignSelf::STRETCH {
328 return grid_area_minus_item_margins_size.width;
329 }
330
331 None
332 });
333 let Size { width, height } =
335 Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
336
337 let height = height.or_else(|| {
338 if self.size.height.is_sizing_keyword() {
341 return match resolve_sizing_keyword(
342 self.size.height,
343 grid_area_minus_item_margins_size.height,
344 grid_area_size.height,
345 ) {
346 Some(SizingKeywordResolution::Exact(height)) => Some(height),
347 _ => None,
348 };
349 }
350
351 if !self.margin.top.is_auto() && !self.margin.bottom.is_auto() && self.align_self == AlignSelf::STRETCH {
356 return grid_area_minus_item_margins_size.height;
357 }
358
359 None
360 });
361 let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
363
364 let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
366
367 Size { width, height }
368 }
369
370 pub fn grid_area_size(
385 &self,
386 axis: AbstractAxis,
387 axis_tracks: &[GridTrack],
388 other_axis_tracks: &[GridTrack],
389 available_space: Size<Option<f32>>,
390 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
391 resolve_calc_value: &impl Fn(*const (), f32) -> f32,
392 ) -> Size<Option<f32>> {
393 let mut size = Size::NONE;
394 size.set(
395 axis,
396 axis_tracks[self.track_range_excluding_lines(axis)]
397 .iter()
398 .map(|track| {
399 let min_size = track
400 .min_track_sizing_function
401 .definite_value(available_space.get(axis), resolve_calc_value)?;
402 let max_size = track
403 .max_track_sizing_function
404 .definite_value(available_space.get(axis), resolve_calc_value)?;
405
406 if min_size == max_size {
407 Some(track.base_size)
408 } else {
409 None
410 }
411 })
412 .sum::<Option<f32>>(),
413 );
414
415 size.set(
416 axis.other(),
417 other_axis_tracks[self.track_range_excluding_lines(axis.other())]
418 .iter()
419 .map(|track| {
420 get_track_size_estimate(track, available_space.get(axis.other()))
421 .map(|size| size + track.content_alignment_adjustment)
422 })
423 .sum::<Option<f32>>(),
424 );
425
426 size
427 }
428
429 pub fn grid_area_size_cached(
431 &mut self,
432 axis: AbstractAxis,
433 axis_tracks: &[GridTrack],
434 other_axis_tracks: &[GridTrack],
435 available_space: Size<Option<f32>>,
436 get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
437 resolve_calc_value: &impl Fn(*const (), f32) -> f32,
438 ) -> Size<Option<f32>> {
439 self.grid_area_size_cache.unwrap_or_else(|| {
440 let grid_area_size = self.grid_area_size(
441 axis,
442 axis_tracks,
443 other_axis_tracks,
444 available_space,
445 get_track_size_estimate,
446 resolve_calc_value,
447 );
448 self.grid_area_size_cache = Some(grid_area_size);
449 grid_area_size
450 })
451 }
452
453 #[inline(always)]
456 pub fn margins_axis_sums_with_baseline_shims(
457 &self,
458 inner_node_width: Option<f32>,
459 tree: &impl LayoutPartialTree,
460 ) -> Size<f32> {
461 Rect {
462 left: self.margin.left.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
463 right: self.margin.right.resolve_or_zero(Some(0.0), |val, basis| tree.calc(val, basis)),
464 top: self.margin.top.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis))
465 + self.baseline_shim,
466 bottom: self.margin.bottom.resolve_or_zero(inner_node_width, |val, basis| tree.calc(val, basis)),
467 }
468 .sum_axes()
469 }
470
471 pub fn min_content_contribution(
473 &self,
474 axis: AbstractAxis,
475 tree: &mut impl LayoutPartialTree,
476 grid_area_size: Size<Option<f32>>,
477 available_space: Size<Option<f32>>,
478 ) -> f32 {
479 let known_dimensions = self.known_dimensions(tree, grid_area_size);
480 tree.measure_child_size(
486 self.node,
487 known_dimensions,
488 grid_area_size,
489 self.keyword_adjusted_available_space(
490 grid_area_size,
491 available_space.map(|opt| match opt {
492 Some(size) => AvailableSpace::Definite(size),
493 None => AvailableSpace::MinContent,
494 }),
495 tree,
496 ),
497 SizingMode::InherentSize,
498 axis.as_abs_naive(),
499 Line::FALSE,
500 )
501 }
502
503 #[inline(always)]
505 pub fn min_content_contribution_cached(
506 &mut self,
507 axis: AbstractAxis,
508 tree: &mut impl LayoutPartialTree,
509 grid_area_size: Size<Option<f32>>,
510 available_space: Size<Option<f32>>,
511 ) -> f32 {
512 self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
513 let size = self.min_content_contribution(axis, tree, grid_area_size, available_space);
514 self.min_content_contribution_cache.set(axis, Some(size));
515 size
516 })
517 }
518
519 pub fn max_content_contribution(
521 &self,
522 axis: AbstractAxis,
523 tree: &mut impl LayoutPartialTree,
524 grid_area_size: Size<Option<f32>>,
525 available_space: Size<Option<f32>>,
526 ) -> f32 {
527 let known_dimensions = self.known_dimensions(tree, grid_area_size);
528 tree.measure_child_size(
532 self.node,
533 known_dimensions,
534 grid_area_size,
535 self.keyword_adjusted_available_space(
536 grid_area_size,
537 available_space.map(|opt| match opt {
538 Some(size) => AvailableSpace::Definite(size),
539 None => AvailableSpace::MaxContent,
540 }),
541 tree,
542 ),
543 SizingMode::InherentSize,
544 axis.as_abs_naive(),
545 Line::FALSE,
546 )
547 }
548
549 fn keyword_adjusted_available_space(
553 &self,
554 grid_area_size: Size<Option<f32>>,
555 available_space: Size<AvailableSpace>,
556 tree: &impl LayoutPartialTree,
557 ) -> Size<AvailableSpace> {
558 if !self.size.width.is_sizing_keyword() && !self.size.height.is_sizing_keyword() {
559 return available_space;
560 }
561 let margins = self.margins_axis_sums_with_baseline_shims(grid_area_size.width, tree);
562 let mut adjusted = available_space;
563 for axis in [AbstractAxis::Inline, AbstractAxis::Block] {
564 let size_style = self.size.get(axis);
565 if !size_style.is_sizing_keyword() {
566 continue;
567 }
568 let stretch_size = grid_area_size.get(axis).maybe_sub(margins.get(axis));
569 if let Some(SizingKeywordResolution::Measure(available)) =
570 resolve_sizing_keyword(size_style, stretch_size, grid_area_size.get(axis))
571 {
572 adjusted.set(axis, available);
573 }
574 }
575 adjusted
576 }
577
578 #[inline(always)]
580 pub fn max_content_contribution_cached(
581 &mut self,
582 axis: AbstractAxis,
583 tree: &mut impl LayoutPartialTree,
584 grid_area_size: Size<Option<f32>>,
585 available_space: Size<Option<f32>>,
586 ) -> f32 {
587 self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
588 let size = self.max_content_contribution(axis, tree, grid_area_size, available_space);
589 self.max_content_contribution_cache.set(axis, Some(size));
590 size
591 })
592 }
593
594 pub fn minimum_contribution(
603 &mut self,
604 tree: &mut impl LayoutPartialTree,
605 axis: AbstractAxis,
606 axis_tracks: &[GridTrack],
607 grid_area_size: Size<Option<f32>>,
608 inner_node_size: Size<Option<f32>>,
609 ) -> f32 {
610 let padding = self.padding.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
611 let border = self.border.resolve_or_zero(grid_area_size.width, |val, basis| tree.calc(val, basis));
612 let padding_border_size = (padding + border).sum_axes();
613 let box_sizing_adjustment =
614 if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
615 self.size
616 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
617 .maybe_apply_aspect_ratio(self.aspect_ratio)
618 .maybe_add(box_sizing_adjustment)
619 .get(axis)
620 .or_else(|| {
621 self.min_size
622 .maybe_resolve(grid_area_size, |val, basis| tree.calc(val, basis))
623 .maybe_apply_aspect_ratio(self.aspect_ratio)
624 .maybe_add(box_sizing_adjustment)
625 .get(axis)
626 })
627 .or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
628 .unwrap_or_else(|| {
629 let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
634
635 let spans_auto_min_track = axis_tracks
640 .iter()
641 .any(|track| track.min_track_sizing_function.is_auto());
643
644 let only_span_one_track = item_axis_tracks.len() == 1;
646 let spans_a_flexible_track = axis_tracks.iter().any(|track| track.max_track_sizing_function.is_fr());
647
648 let use_content_based_minimum =
649 spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
650
651 if use_content_based_minimum {
653 let mut minimum_contribution =
654 self.min_content_contribution_cached(axis, tree, grid_area_size, grid_area_size);
655
656 if self.is_compressible_replaced {
660 let size = self.size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
661 let max_size =
662 self.max_size.get(axis).maybe_resolve(Some(0.0), |val, basis| tree.calc(val, basis));
663 minimum_contribution = minimum_contribution.maybe_min(size).maybe_min(max_size);
664 }
665
666 let limit =
671 self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis), &|val, basis| {
672 tree.resolve_calc_value(val, basis)
673 });
674 minimum_contribution.maybe_min(limit)
675 } else {
676 0.0
677 }
678 })
679 }
680
681 #[inline(always)]
683 pub fn minimum_contribution_cached(
684 &mut self,
685 tree: &mut impl LayoutPartialTree,
686 axis: AbstractAxis,
687 axis_tracks: &[GridTrack],
688 grid_area_size: Size<Option<f32>>,
689 inner_node_size: Size<Option<f32>>,
690 ) -> f32 {
691 self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
692 let size = self.minimum_contribution(tree, axis, axis_tracks, grid_area_size, inner_node_size);
693 self.minimum_contribution_cache.set(axis, Some(size));
694 size
695 })
696 }
697}