servo/components/layout/flexbox/geom.rs
Martin Robinson 7787cab521
layout: Combine layout_2020 and layout_thread_2020 into a crate called layout (#36613)
Now that legacy layout has been removed, the name `layout_2020` doesn't
make much sense any longer, also it's 2025 now for better or worse. The
split between the "layout thread" and "layout" also doesn't make as much
sense since layout doesn't run on it's own thread. There's a possibility
that it will in the future, but that should be something that the user
of the crate controls rather than layout iself.

This is part of the larger layout interface cleanup and optimization
that
@Looriool and I are doing.

Testing: Covered by existing tests as this is just code movement.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
2025-04-19 10:17:03 +00:00

272 lines
9.3 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! <https://drafts.csswg.org/css-flexbox/#box-model>
use malloc_size_of_derive::MallocSizeOf;
use style::properties::longhands::flex_direction::computed_value::T as FlexDirection;
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
#[derive(Clone, Copy, Debug, Default)]
pub(super) struct FlexRelativeVec2<T> {
pub main: T,
pub cross: T,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct FlexRelativeSides<T> {
pub cross_start: T,
pub main_start: T,
pub cross_end: T,
pub main_end: T,
}
pub(super) struct FlexRelativeRect<T> {
pub start_corner: FlexRelativeVec2<T>,
pub size: FlexRelativeVec2<T>,
}
impl<T> std::ops::Add for FlexRelativeVec2<T>
where
T: std::ops::Add,
{
type Output = FlexRelativeVec2<T::Output>;
fn add(self, rhs: Self) -> Self::Output {
FlexRelativeVec2 {
main: self.main + rhs.main,
cross: self.cross + rhs.cross,
}
}
}
impl<T> std::ops::Sub for FlexRelativeVec2<T>
where
T: std::ops::Sub,
{
type Output = FlexRelativeVec2<T::Output>;
fn sub(self, rhs: Self) -> Self::Output {
FlexRelativeVec2 {
main: self.main - rhs.main,
cross: self.cross - rhs.cross,
}
}
}
impl<T> FlexRelativeSides<T> {
pub fn sum_by_axis(self) -> FlexRelativeVec2<T::Output>
where
T: std::ops::Add,
{
FlexRelativeVec2 {
main: self.main_start + self.main_end,
cross: self.cross_start + self.cross_end,
}
}
}
/// One of the two bits set by the `flex-direction` property
/// (The other is "forward" v.s. reverse.)
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
pub(super) enum FlexAxis {
/// The main axis is the inline axis of the container (not necessarily of flex items!),
/// cross is block.
Row,
/// The main axis is the block axis, cross is inline.
Column,
}
/// Which flow-relative sides map to the main-start and cross-start sides, respectively.
/// See <https://drafts.csswg.org/css-flexbox/#box-model>
#[derive(Clone, Copy, Debug, MallocSizeOf)]
pub(super) enum MainStartCrossStart {
InlineStartBlockStart,
InlineStartBlockEnd,
BlockStartInlineStart,
BlockStartInlineEnd,
InlineEndBlockStart,
InlineEndBlockEnd,
BlockEndInlineStart,
BlockEndInlineEnd,
}
impl FlexAxis {
pub fn from(flex_direction: FlexDirection) -> Self {
match flex_direction {
FlexDirection::Row | FlexDirection::RowReverse => FlexAxis::Row,
FlexDirection::Column | FlexDirection::ColumnReverse => FlexAxis::Column,
}
}
pub fn vec2_to_flex_relative<T>(self, flow_relative: LogicalVec2<T>) -> FlexRelativeVec2<T> {
let LogicalVec2 { inline, block } = flow_relative;
match self {
FlexAxis::Row => FlexRelativeVec2 {
main: inline,
cross: block,
},
FlexAxis::Column => FlexRelativeVec2 {
main: block,
cross: inline,
},
}
}
pub fn vec2_to_flow_relative<T>(self, flex_relative: FlexRelativeVec2<T>) -> LogicalVec2<T> {
let FlexRelativeVec2 { main, cross } = flex_relative;
match self {
FlexAxis::Row => LogicalVec2 {
inline: main,
block: cross,
},
FlexAxis::Column => LogicalVec2 {
block: main,
inline: cross,
},
}
}
}
macro_rules! sides_mapping_methods {
(
$(
$variant: path => {
$( $flex_relative_side: ident <=> $flow_relative_side: ident, )+
},
)+
) => {
pub fn sides_to_flex_relative<T>(self, flow_relative: LogicalSides<T>) -> FlexRelativeSides<T> {
match self {
$(
$variant => FlexRelativeSides {
$( $flex_relative_side: flow_relative.$flow_relative_side, )+
},
)+
}
}
pub fn sides_to_flow_relative<T>(self, flex_relative: FlexRelativeSides<T>) -> LogicalSides<T> {
match self {
$(
$variant => LogicalSides {
$( $flow_relative_side: flex_relative.$flex_relative_side, )+
},
)+
}
}
}
}
impl MainStartCrossStart {
pub fn from(flex_direction: FlexDirection, flex_wrap_reverse: bool) -> Self {
match (flex_direction, flex_wrap_reverse) {
// See definition of each keyword in
// https://drafts.csswg.org/css-flexbox/#flex-direction-property and
// https://drafts.csswg.org/css-flexbox/#flex-wrap-property,
// or the tables (though they map to physical rather than flow-relative) at
// https://drafts.csswg.org/css-flexbox/#axis-mapping
(FlexDirection::Row, true) => MainStartCrossStart::InlineStartBlockEnd,
(FlexDirection::Row, false) => MainStartCrossStart::InlineStartBlockStart,
(FlexDirection::Column, true) => MainStartCrossStart::BlockStartInlineEnd,
(FlexDirection::Column, false) => MainStartCrossStart::BlockStartInlineStart,
(FlexDirection::RowReverse, true) => MainStartCrossStart::InlineEndBlockEnd,
(FlexDirection::RowReverse, false) => MainStartCrossStart::InlineEndBlockStart,
(FlexDirection::ColumnReverse, true) => MainStartCrossStart::BlockEndInlineEnd,
(FlexDirection::ColumnReverse, false) => MainStartCrossStart::BlockEndInlineStart,
}
}
sides_mapping_methods! {
MainStartCrossStart::InlineStartBlockStart => {
main_start <=> inline_start,
cross_start <=> block_start,
main_end <=> inline_end,
cross_end <=> block_end,
},
MainStartCrossStart::InlineStartBlockEnd => {
main_start <=> inline_start,
cross_start <=> block_end,
main_end <=> inline_end,
cross_end <=> block_start,
},
MainStartCrossStart::BlockStartInlineStart => {
main_start <=> block_start,
cross_start <=> inline_start,
main_end <=> block_end,
cross_end <=> inline_end,
},
MainStartCrossStart::BlockStartInlineEnd => {
main_start <=> block_start,
cross_start <=> inline_end,
main_end <=> block_end,
cross_end <=> inline_start,
},
MainStartCrossStart::InlineEndBlockStart => {
main_start <=> inline_end,
cross_start <=> block_start,
main_end <=> inline_start,
cross_end <=> block_end,
},
MainStartCrossStart::InlineEndBlockEnd => {
main_start <=> inline_end,
cross_start <=> block_end,
main_end <=> inline_start,
cross_end <=> block_start,
},
MainStartCrossStart::BlockEndInlineStart => {
main_start <=> block_end,
cross_start <=> inline_start,
main_end <=> block_start,
cross_end <=> inline_end,
},
MainStartCrossStart::BlockEndInlineEnd => {
main_start <=> block_end,
cross_start <=> inline_end,
main_end <=> block_start,
cross_end <=> inline_start,
},
}
}
/// The start corner coordinates in both the input rectangle and output rectangle
/// are relative to some “base rectangle” whose size is passed here.
pub(super) fn rect_to_flow_relative<T>(
flex_axis: FlexAxis,
main_start_cross_start_sides_are: MainStartCrossStart,
base_rect_size: FlexRelativeVec2<T>,
rect: FlexRelativeRect<T>,
) -> LogicalRect<T>
where
T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T>,
{
// First, convert from (start corner, size) to offsets from the edges of the base rectangle
let end_corner_position = rect.start_corner + rect.size;
let end_corner_offsets = base_rect_size - end_corner_position;
// No-ops, but hopefully clarifies to human readers:
let start_corner_position = rect.start_corner;
let start_corner_offsets = start_corner_position;
// Then, convert to flow-relative using methods above
let flow_relative_offsets =
main_start_cross_start_sides_are.sides_to_flow_relative(FlexRelativeSides {
main_start: start_corner_offsets.main,
cross_start: start_corner_offsets.cross,
main_end: end_corner_offsets.main,
cross_end: end_corner_offsets.cross,
});
let flow_relative_base_rect_size = flex_axis.vec2_to_flow_relative(base_rect_size);
// Finally, convert back to (start corner, size)
let start_corner = LogicalVec2 {
inline: flow_relative_offsets.inline_start,
block: flow_relative_offsets.block_start,
};
let end_corner_position = LogicalVec2 {
inline: flow_relative_base_rect_size.inline - flow_relative_offsets.inline_end,
block: flow_relative_base_rect_size.block - flow_relative_offsets.block_end,
};
let size = end_corner_position - start_corner;
LogicalRect { start_corner, size }
}