gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 976HTML 921Markdown 12TOML 82979 Definiciones

⎇ ae87fd0283feff32a5ceffe4c77394ff6ca88d39 src/compute/grid/alignment.rs Rust · 278 líneas · 11.4 KB Historia Quién tocó qué crudo

Update 0.3.x for 0.3.11 (#455) 1cc940684 · Nico Burns ·

1//! Alignment of tracks and final positioning of items
2use super::types::GridTrack;
3use crate::axis::InBothAbsAxis;
4use crate::compute::common::alignment::compute_alignment_offset;
5use crate::compute::{GenericAlgorithm, LayoutAlgorithm};
6use crate::geometry::{Line, Point, Rect, Size};
7use crate::layout::{Layout, SizingMode};
8use crate::math::MaybeMath;
9use crate::node::Node;
10use crate::resolve::{MaybeResolve, ResolveOrZero};
11use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, Position};
12use crate::sys::{f32_max, f32_min};
13use crate::tree::LayoutTree;
14
15/// Align the grid tracks within the grid according to the align-content (rows) or
16/// justify-content (columns) property. This only does anything if the size of the
17/// grid is not equal to the size of the grid container in the axis being aligned.
18pub(super) fn align_tracks(
19    grid_container_content_box_size: f32,
20    padding: Line<f32>,
21    border: Line<f32>,
22    tracks: &mut [GridTrack],
23    track_alignment_style: AlignContent,
24) {
25    let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
26    let size_diff = grid_container_content_box_size - used_size;
27    let free_space = f32_max(size_diff, 0.0);
28    let overflow = f32_min(size_diff, 0.0);
29
30    // If the used_size > grid_container_size then the tracks must overflow their container
31    // The direction in which they do so is determined by the alignment style
32    let origin = padding.start
33        + border.start
34        + match track_alignment_style {
35            AlignContent::Start => 0.0,
36            AlignContent::FlexStart => 0.0,
37            AlignContent::End => overflow,
38            AlignContent::FlexEnd => overflow,
39            AlignContent::Center => overflow / 2.0,
40            AlignContent::Stretch => 0.0,
41            AlignContent::SpaceBetween => 0.0,
42            AlignContent::SpaceEvenly => 0.0,
43            AlignContent::SpaceAround => 0.0,
44        };
45
46    // Count the number of non-collapsed tracks (not counting gutters)
47    let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
48
49    // Grid layout treats gaps as full tracks rather than applying them at alignment so we
50    // simply pass zero here. Grid layout is never reversed.
51    let gap = 0.0;
52    let layout_is_reversed = false;
53
54    // Compute offsets
55    let mut total_offset = origin;
56    tracks.iter_mut().enumerate().for_each(|(i, track)| {
57        // Odd tracks are gutters (but slices are zero-indexed, so odd tracks have even indicies)
58        let is_gutter = i % 2 == 0;
59
60        // The first non-gutter track is index 1
61        let is_first = i == 1;
62
63        let offset = if is_gutter {
64            0.0
65        } else {
66            compute_alignment_offset(free_space, num_tracks, gap, track_alignment_style, layout_is_reversed, is_first)
67        };
68
69        track.offset = total_offset + offset;
70        total_offset = total_offset + offset + track.base_size;
71    });
72}
73
74/// Align and size a grid item into it's final position
75pub(super) fn align_and_position_item(
76    tree: &mut impl LayoutTree,
77    node: Node,
78    order: u32,
79    grid_area: Rect<f32>,
80    container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
81    baseline_shim: f32,
82) {
83    let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
84
85    let style = tree.style(node);
86    let aspect_ratio = style.aspect_ratio;
87    let justify_self = style.justify_self;
88    let align_self = style.align_self;
89
90    let position = style.position;
91    let inset_horizontal = style.inset.horizontal_components().map(|size| size.resolve_to_option(grid_area_size.width));
92    let inset_vertical = style.inset.vertical_components().map(|size| size.resolve_to_option(grid_area_size.height));
93    let padding = style.padding.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
94    let border = style.border.map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
95    let padding_border_size = (padding + border).sum_axes();
96    let inherent_size = style.size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
97    let min_size = style
98        .min_size
99        .maybe_resolve(grid_area_size)
100        .or(padding_border_size.map(Some))
101        .maybe_max(padding_border_size)
102        .maybe_apply_aspect_ratio(aspect_ratio);
103    let max_size = style.max_size.maybe_resolve(grid_area_size).maybe_apply_aspect_ratio(aspect_ratio);
104
105    // Resolve default alignment styles if they are set on neither the parent or the node itself
106    // Note: if the child has a preferred aspect ratio but neither width or height are set, then the width is stretched
107    // and the then height is calculated from the width according the aspect ratio
108    // See: https://www.w3.org/TR/css-grid-1/#grid-item-sizing
109    let alignment_styles = InBothAbsAxis {
110        horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
111            if inherent_size.width.is_some() {
112                AlignSelf::Start
113            } else {
114                AlignSelf::Stretch
115            }
116        }),
117        vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
118            if inherent_size.height.is_some() || aspect_ratio.is_some() {
119                AlignSelf::Start
120            } else {
121                AlignSelf::Stretch
122            }
123        }),
124    };
125
126    // Note: This is not a bug. It is part of the CSS spec that both horizontal and vertical margins
127    // resolve against the WIDTH of the grid area.
128    let margin = style.margin.map(|margin| margin.resolve_to_option(grid_area_size.width));
129
130    let grid_area_minus_item_margins_size = Size {
131        width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
132        height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
133    };
134
135    // If node is absolutely positioned and width is not set explicitly, then deduce it
136    // from left, right and container_content_box if both are set.
137    let width = inherent_size.width.or_else(|| {
138        // Apply width derived from both the left and right properties of an absolutely
139        // positioned element being set
140        if position == Position::Absolute {
141            if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
142                return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
143            }
144        }
145
146        // Apply width based on stretch alignment if:
147        //  - Alignment style is "stretch"
148        //  - The node is not absolutely positioned
149        //  - The node does not have auto margins in this axis.
150        if margin.left.is_some()
151            && margin.right.is_some()
152            && alignment_styles.horizontal == AlignSelf::Stretch
153            && position != Position::Absolute
154        {
155            return Some(grid_area_minus_item_margins_size.width);
156        }
157
158        None
159    });
160
161    // Reapply aspect ratio after stretch and absolute position width adjustments
162    let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
163
164    let height = height.or_else(|| {
165        if position == Position::Absolute {
166            if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
167                return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
168            }
169        }
170
171        // Apply height based on stretch alignment if:
172        //  - Alignment style is "stretch"
173        //  - The node is not absolutely positioned
174        //  - The node does not have auto margins in this axis.
175        if margin.top.is_some()
176            && margin.bottom.is_some()
177            && alignment_styles.vertical == AlignSelf::Stretch
178            && position != Position::Absolute
179        {
180            return Some(grid_area_minus_item_margins_size.height);
181        }
182
183        None
184    });
185    // Reapply aspect ratio after stretch and absolute position height adjustments
186    let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
187
188    // Clamp size by min and max width/height
189    let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
190
191    // Layout node
192    let measured_size_and_baselines = GenericAlgorithm::perform_layout(
193        tree,
194        node,
195        Size { width, height },
196        grid_area_size.map(Option::Some),
197        grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
198        SizingMode::InherentSize,
199    );
200
201    // Resolve final size
202    let Size { width, height } =
203        Size { width, height }.unwrap_or(measured_size_and_baselines.size).maybe_clamp(min_size, max_size);
204
205    let x = align_item_within_area(
206        Line { start: grid_area.left, end: grid_area.right },
207        justify_self.unwrap_or(alignment_styles.horizontal),
208        width,
209        position,
210        inset_horizontal,
211        margin.horizontal_components(),
212        0.0,
213    );
214    let y = align_item_within_area(
215        Line { start: grid_area.top, end: grid_area.bottom },
216        align_self.unwrap_or(alignment_styles.vertical),
217        height,
218        position,
219        inset_vertical,
220        margin.vertical_components(),
221        baseline_shim,
222    );
223
224    *tree.layout_mut(node) = Layout { order, size: Size { width, height }, location: Point { x, y } };
225}
226
227/// Align and size a grid item along a single axis
228pub(super) fn align_item_within_area(
229    grid_area: Line<f32>,
230    alignment_style: AlignSelf,
231    resolved_size: f32,
232    position: Position,
233    inset: Line<Option<f32>>,
234    margin: Line<Option<f32>>,
235    baseline_shim: f32,
236) -> f32 {
237    // Calculate grid area dimension in the axis
238    let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
239    let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
240    let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
241
242    // Expand auto margins to fill available space
243    let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
244    let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
245    let resolved_margin = Line {
246        start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
247        end: margin.end.unwrap_or(auto_margin_size),
248    };
249
250    // Compute offset in the axis
251    let alignment_based_offset = match alignment_style {
252        AlignSelf::Start | AlignSelf::FlexStart => resolved_margin.start,
253        AlignSelf::End | AlignSelf::FlexEnd => grid_area_size - resolved_size - resolved_margin.end,
254        AlignSelf::Center => (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0,
255        // TODO: Add support for baseline alignment. For now we treat it as "start".
256        AlignSelf::Baseline => resolved_margin.start,
257        AlignSelf::Stretch => resolved_margin.start,
258    };
259
260    let offset_within_area = if position == Position::Absolute {
261        if let Some(start) = inset.start {
262            start + non_auto_margin.start
263        } else if let Some(end) = inset.end {
264            grid_area_size - end - resolved_size - non_auto_margin.end
265        } else {
266            alignment_based_offset
267        }
268    } else {
269        alignment_based_offset
270    };
271
272    let mut start = grid_area.start + offset_within_area;
273    if position == Position::Relative {
274        start += inset.start.or(inset.end.map(|pos| -pos)).unwrap_or(0.0);
275    }
276
277    start
278}