Skip to main content

layout/flexbox/
geom.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! <https://drafts.csswg.org/css-flexbox/#box-model>
6
7use malloc_size_of_derive::MallocSizeOf;
8use style::properties::longhands::flex_direction::computed_value::T as FlexDirection;
9
10use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
11
12#[derive(Clone, Copy, Debug, Default)]
13pub(super) struct FlexRelativeVec2<T> {
14    pub main: T,
15    pub cross: T,
16}
17
18#[derive(Clone, Copy, Debug)]
19pub(super) struct FlexRelativeSides<T> {
20    pub cross_start: T,
21    pub main_start: T,
22    pub cross_end: T,
23    pub main_end: T,
24}
25
26pub(super) struct FlexRelativeRect<T> {
27    pub start_corner: FlexRelativeVec2<T>,
28    pub size: FlexRelativeVec2<T>,
29}
30
31impl<T> std::ops::Add for FlexRelativeVec2<T>
32where
33    T: std::ops::Add,
34{
35    type Output = FlexRelativeVec2<T::Output>;
36    fn add(self, rhs: Self) -> Self::Output {
37        FlexRelativeVec2 {
38            main: self.main + rhs.main,
39            cross: self.cross + rhs.cross,
40        }
41    }
42}
43
44impl<T> std::ops::Sub for FlexRelativeVec2<T>
45where
46    T: std::ops::Sub,
47{
48    type Output = FlexRelativeVec2<T::Output>;
49    fn sub(self, rhs: Self) -> Self::Output {
50        FlexRelativeVec2 {
51            main: self.main - rhs.main,
52            cross: self.cross - rhs.cross,
53        }
54    }
55}
56
57impl<T> FlexRelativeSides<T> {
58    pub fn sum_by_axis(self) -> FlexRelativeVec2<T::Output>
59    where
60        T: std::ops::Add,
61    {
62        FlexRelativeVec2 {
63            main: self.main_start + self.main_end,
64            cross: self.cross_start + self.cross_end,
65        }
66    }
67
68    pub fn cross_sum(self) -> T::Output
69    where
70        T: std::ops::Add,
71    {
72        self.cross_start + self.cross_end
73    }
74}
75
76/// One of the two bits set by the `flex-direction` property
77/// (The other is "forward" v.s. reverse.)
78#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
79pub(super) enum FlexAxis {
80    /// The main axis is the inline axis of the container (not necessarily of flex items!),
81    /// cross is block.
82    Row,
83    /// The main axis is the block axis, cross is inline.
84    Column,
85}
86
87/// Which flow-relative sides map to the main-start and cross-start sides, respectively.
88/// See <https://drafts.csswg.org/css-flexbox/#box-model>
89#[derive(Clone, Copy, Debug, MallocSizeOf)]
90pub(super) enum MainStartCrossStart {
91    InlineStartBlockStart,
92    InlineStartBlockEnd,
93    BlockStartInlineStart,
94    BlockStartInlineEnd,
95    InlineEndBlockStart,
96    InlineEndBlockEnd,
97    BlockEndInlineStart,
98    BlockEndInlineEnd,
99}
100
101impl FlexAxis {
102    pub fn from(flex_direction: FlexDirection) -> Self {
103        match flex_direction {
104            FlexDirection::Row | FlexDirection::RowReverse => FlexAxis::Row,
105            FlexDirection::Column | FlexDirection::ColumnReverse => FlexAxis::Column,
106        }
107    }
108
109    pub fn vec2_to_flex_relative<T>(self, flow_relative: LogicalVec2<T>) -> FlexRelativeVec2<T> {
110        let LogicalVec2 { inline, block } = flow_relative;
111        match self {
112            FlexAxis::Row => FlexRelativeVec2 {
113                main: inline,
114                cross: block,
115            },
116            FlexAxis::Column => FlexRelativeVec2 {
117                main: block,
118                cross: inline,
119            },
120        }
121    }
122
123    pub fn vec2_to_flow_relative<T>(self, flex_relative: FlexRelativeVec2<T>) -> LogicalVec2<T> {
124        let FlexRelativeVec2 { main, cross } = flex_relative;
125        match self {
126            FlexAxis::Row => LogicalVec2 {
127                inline: main,
128                block: cross,
129            },
130            FlexAxis::Column => LogicalVec2 {
131                block: main,
132                inline: cross,
133            },
134        }
135    }
136}
137
138macro_rules! sides_mapping_methods {
139    (
140        $(
141            $variant: path => {
142                $( $flex_relative_side: ident <=> $flow_relative_side: ident, )+
143            },
144        )+
145    ) => {
146        pub fn sides_to_flex_relative<T>(self, flow_relative: LogicalSides<T>) -> FlexRelativeSides<T> {
147            match self {
148                $(
149                    $variant => FlexRelativeSides {
150                        $( $flex_relative_side: flow_relative.$flow_relative_side, )+
151                    },
152                )+
153            }
154        }
155
156        pub fn sides_to_flow_relative<T>(self, flex_relative: FlexRelativeSides<T>) -> LogicalSides<T> {
157            match self {
158                $(
159                    $variant => LogicalSides {
160                        $( $flow_relative_side: flex_relative.$flex_relative_side, )+
161                    },
162                )+
163            }
164        }
165    }
166}
167
168impl MainStartCrossStart {
169    pub fn from(flex_direction: FlexDirection, flex_wrap_reverse: bool) -> Self {
170        match (flex_direction, flex_wrap_reverse) {
171            // See definition of each keyword in
172            // https://drafts.csswg.org/css-flexbox/#flex-direction-property and
173            // https://drafts.csswg.org/css-flexbox/#flex-wrap-property,
174            // or the tables (though they map to physical rather than flow-relative) at
175            // https://drafts.csswg.org/css-flexbox/#axis-mapping
176            (FlexDirection::Row, true) => MainStartCrossStart::InlineStartBlockEnd,
177            (FlexDirection::Row, false) => MainStartCrossStart::InlineStartBlockStart,
178            (FlexDirection::Column, true) => MainStartCrossStart::BlockStartInlineEnd,
179            (FlexDirection::Column, false) => MainStartCrossStart::BlockStartInlineStart,
180            (FlexDirection::RowReverse, true) => MainStartCrossStart::InlineEndBlockEnd,
181            (FlexDirection::RowReverse, false) => MainStartCrossStart::InlineEndBlockStart,
182            (FlexDirection::ColumnReverse, true) => MainStartCrossStart::BlockEndInlineEnd,
183            (FlexDirection::ColumnReverse, false) => MainStartCrossStart::BlockEndInlineStart,
184        }
185    }
186
187    sides_mapping_methods! {
188        MainStartCrossStart::InlineStartBlockStart => {
189            main_start <=> inline_start,
190            cross_start <=> block_start,
191            main_end <=> inline_end,
192            cross_end <=> block_end,
193        },
194        MainStartCrossStart::InlineStartBlockEnd => {
195            main_start <=> inline_start,
196            cross_start <=> block_end,
197            main_end <=> inline_end,
198            cross_end <=> block_start,
199        },
200        MainStartCrossStart::BlockStartInlineStart => {
201            main_start <=> block_start,
202            cross_start <=> inline_start,
203            main_end <=> block_end,
204            cross_end <=> inline_end,
205        },
206        MainStartCrossStart::BlockStartInlineEnd => {
207            main_start <=> block_start,
208            cross_start <=> inline_end,
209            main_end <=> block_end,
210            cross_end <=> inline_start,
211        },
212        MainStartCrossStart::InlineEndBlockStart => {
213            main_start <=> inline_end,
214            cross_start <=> block_start,
215            main_end <=> inline_start,
216            cross_end <=> block_end,
217        },
218        MainStartCrossStart::InlineEndBlockEnd => {
219            main_start <=> inline_end,
220            cross_start <=> block_end,
221            main_end <=> inline_start,
222            cross_end <=> block_start,
223        },
224        MainStartCrossStart::BlockEndInlineStart => {
225            main_start <=> block_end,
226            cross_start <=> inline_start,
227            main_end <=> block_start,
228            cross_end <=> inline_end,
229        },
230        MainStartCrossStart::BlockEndInlineEnd => {
231            main_start <=> block_end,
232            cross_start <=> inline_end,
233            main_end <=> block_start,
234            cross_end <=> inline_start,
235        },
236    }
237}
238
239/// The start corner coordinates in both the input rectangle and output rectangle
240/// are relative to some “base rectangle” whose size is passed here.
241pub(super) fn rect_to_flow_relative<T>(
242    flex_axis: FlexAxis,
243    main_start_cross_start_sides_are: MainStartCrossStart,
244    base_rect_size: FlexRelativeVec2<T>,
245    rect: FlexRelativeRect<T>,
246) -> LogicalRect<T>
247where
248    T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T>,
249{
250    // First, convert from (start corner, size) to offsets from the edges of the base rectangle
251
252    let end_corner_position = rect.start_corner + rect.size;
253    let end_corner_offsets = base_rect_size - end_corner_position;
254    // No-ops, but hopefully clarifies to human readers:
255    let start_corner_position = rect.start_corner;
256    let start_corner_offsets = start_corner_position;
257
258    // Then, convert to flow-relative using methods above
259    let flow_relative_offsets =
260        main_start_cross_start_sides_are.sides_to_flow_relative(FlexRelativeSides {
261            main_start: start_corner_offsets.main,
262            cross_start: start_corner_offsets.cross,
263            main_end: end_corner_offsets.main,
264            cross_end: end_corner_offsets.cross,
265        });
266    let flow_relative_base_rect_size = flex_axis.vec2_to_flow_relative(base_rect_size);
267
268    // Finally, convert back to (start corner, size)
269    let start_corner = LogicalVec2 {
270        inline: flow_relative_offsets.inline_start,
271        block: flow_relative_offsets.block_start,
272    };
273    let end_corner_position = LogicalVec2 {
274        inline: flow_relative_base_rect_size.inline - flow_relative_offsets.inline_end,
275        block: flow_relative_base_rect_size.block - flow_relative_offsets.block_end,
276    };
277    let size = end_corner_position - start_corner;
278    LogicalRect { start_corner, size }
279}