servo-layout 0.1.0

A component of the servo web-engine.
Documentation
/* 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 }
}