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
69/// One of the two bits set by the `flex-direction` property
70/// (The other is "forward" v.s. reverse.)
71#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
72pub(super) enum FlexAxis {
73    /// The main axis is the inline axis of the container (not necessarily of flex items!),
74    /// cross is block.
75    Row,
76    /// The main axis is the block axis, cross is inline.
77    Column,
78}
79
80/// Which flow-relative sides map to the main-start and cross-start sides, respectively.
81/// See <https://drafts.csswg.org/css-flexbox/#box-model>
82#[derive(Clone, Copy, Debug, MallocSizeOf)]
83pub(super) enum MainStartCrossStart {
84    InlineStartBlockStart,
85    InlineStartBlockEnd,
86    BlockStartInlineStart,
87    BlockStartInlineEnd,
88    InlineEndBlockStart,
89    InlineEndBlockEnd,
90    BlockEndInlineStart,
91    BlockEndInlineEnd,
92}
93
94impl FlexAxis {
95    pub fn from(flex_direction: FlexDirection) -> Self {
96        match flex_direction {
97            FlexDirection::Row | FlexDirection::RowReverse => FlexAxis::Row,
98            FlexDirection::Column | FlexDirection::ColumnReverse => FlexAxis::Column,
99        }
100    }
101
102    pub fn vec2_to_flex_relative<T>(self, flow_relative: LogicalVec2<T>) -> FlexRelativeVec2<T> {
103        let LogicalVec2 { inline, block } = flow_relative;
104        match self {
105            FlexAxis::Row => FlexRelativeVec2 {
106                main: inline,
107                cross: block,
108            },
109            FlexAxis::Column => FlexRelativeVec2 {
110                main: block,
111                cross: inline,
112            },
113        }
114    }
115
116    pub fn vec2_to_flow_relative<T>(self, flex_relative: FlexRelativeVec2<T>) -> LogicalVec2<T> {
117        let FlexRelativeVec2 { main, cross } = flex_relative;
118        match self {
119            FlexAxis::Row => LogicalVec2 {
120                inline: main,
121                block: cross,
122            },
123            FlexAxis::Column => LogicalVec2 {
124                block: main,
125                inline: cross,
126            },
127        }
128    }
129}
130
131macro_rules! sides_mapping_methods {
132    (
133        $(
134            $variant: path => {
135                $( $flex_relative_side: ident <=> $flow_relative_side: ident, )+
136            },
137        )+
138    ) => {
139        pub fn sides_to_flex_relative<T>(self, flow_relative: LogicalSides<T>) -> FlexRelativeSides<T> {
140            match self {
141                $(
142                    $variant => FlexRelativeSides {
143                        $( $flex_relative_side: flow_relative.$flow_relative_side, )+
144                    },
145                )+
146            }
147        }
148
149        pub fn sides_to_flow_relative<T>(self, flex_relative: FlexRelativeSides<T>) -> LogicalSides<T> {
150            match self {
151                $(
152                    $variant => LogicalSides {
153                        $( $flow_relative_side: flex_relative.$flex_relative_side, )+
154                    },
155                )+
156            }
157        }
158    }
159}
160
161impl MainStartCrossStart {
162    pub fn from(flex_direction: FlexDirection, flex_wrap_reverse: bool) -> Self {
163        match (flex_direction, flex_wrap_reverse) {
164            // See definition of each keyword in
165            // https://drafts.csswg.org/css-flexbox/#flex-direction-property and
166            // https://drafts.csswg.org/css-flexbox/#flex-wrap-property,
167            // or the tables (though they map to physical rather than flow-relative) at
168            // https://drafts.csswg.org/css-flexbox/#axis-mapping
169            (FlexDirection::Row, true) => MainStartCrossStart::InlineStartBlockEnd,
170            (FlexDirection::Row, false) => MainStartCrossStart::InlineStartBlockStart,
171            (FlexDirection::Column, true) => MainStartCrossStart::BlockStartInlineEnd,
172            (FlexDirection::Column, false) => MainStartCrossStart::BlockStartInlineStart,
173            (FlexDirection::RowReverse, true) => MainStartCrossStart::InlineEndBlockEnd,
174            (FlexDirection::RowReverse, false) => MainStartCrossStart::InlineEndBlockStart,
175            (FlexDirection::ColumnReverse, true) => MainStartCrossStart::BlockEndInlineEnd,
176            (FlexDirection::ColumnReverse, false) => MainStartCrossStart::BlockEndInlineStart,
177        }
178    }
179
180    sides_mapping_methods! {
181        MainStartCrossStart::InlineStartBlockStart => {
182            main_start <=> inline_start,
183            cross_start <=> block_start,
184            main_end <=> inline_end,
185            cross_end <=> block_end,
186        },
187        MainStartCrossStart::InlineStartBlockEnd => {
188            main_start <=> inline_start,
189            cross_start <=> block_end,
190            main_end <=> inline_end,
191            cross_end <=> block_start,
192        },
193        MainStartCrossStart::BlockStartInlineStart => {
194            main_start <=> block_start,
195            cross_start <=> inline_start,
196            main_end <=> block_end,
197            cross_end <=> inline_end,
198        },
199        MainStartCrossStart::BlockStartInlineEnd => {
200            main_start <=> block_start,
201            cross_start <=> inline_end,
202            main_end <=> block_end,
203            cross_end <=> inline_start,
204        },
205        MainStartCrossStart::InlineEndBlockStart => {
206            main_start <=> inline_end,
207            cross_start <=> block_start,
208            main_end <=> inline_start,
209            cross_end <=> block_end,
210        },
211        MainStartCrossStart::InlineEndBlockEnd => {
212            main_start <=> inline_end,
213            cross_start <=> block_end,
214            main_end <=> inline_start,
215            cross_end <=> block_start,
216        },
217        MainStartCrossStart::BlockEndInlineStart => {
218            main_start <=> block_end,
219            cross_start <=> inline_start,
220            main_end <=> block_start,
221            cross_end <=> inline_end,
222        },
223        MainStartCrossStart::BlockEndInlineEnd => {
224            main_start <=> block_end,
225            cross_start <=> inline_end,
226            main_end <=> block_start,
227            cross_end <=> inline_start,
228        },
229    }
230}
231
232/// The start corner coordinates in both the input rectangle and output rectangle
233/// are relative to some “base rectangle” whose size is passed here.
234pub(super) fn rect_to_flow_relative<T>(
235    flex_axis: FlexAxis,
236    main_start_cross_start_sides_are: MainStartCrossStart,
237    base_rect_size: FlexRelativeVec2<T>,
238    rect: FlexRelativeRect<T>,
239) -> LogicalRect<T>
240where
241    T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T>,
242{
243    // First, convert from (start corner, size) to offsets from the edges of the base rectangle
244
245    let end_corner_position = rect.start_corner + rect.size;
246    let end_corner_offsets = base_rect_size - end_corner_position;
247    // No-ops, but hopefully clarifies to human readers:
248    let start_corner_position = rect.start_corner;
249    let start_corner_offsets = start_corner_position;
250
251    // Then, convert to flow-relative using methods above
252    let flow_relative_offsets =
253        main_start_cross_start_sides_are.sides_to_flow_relative(FlexRelativeSides {
254            main_start: start_corner_offsets.main,
255            cross_start: start_corner_offsets.cross,
256            main_end: end_corner_offsets.main,
257            cross_end: end_corner_offsets.cross,
258        });
259    let flow_relative_base_rect_size = flex_axis.vec2_to_flow_relative(base_rect_size);
260
261    // Finally, convert back to (start corner, size)
262    let start_corner = LogicalVec2 {
263        inline: flow_relative_offsets.inline_start,
264        block: flow_relative_offsets.block_start,
265    };
266    let end_corner_position = LogicalVec2 {
267        inline: flow_relative_base_rect_size.inline - flow_relative_offsets.inline_end,
268        block: flow_relative_base_rect_size.block - flow_relative_offsets.block_end,
269    };
270    let size = end_corner_position - start_corner;
271    LogicalRect { start_corner, size }
272}