Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
366 changes: 365 additions & 1 deletion moli-layout/src/positioned.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,275 @@
use taffy::Line;
use taffy::{
AbsoluteAxis, AlignContent, AlignContentKeyword, AlignItems, AlignItemsKeyword, AlignSelf,
AlignmentSafety, Direction, FlexWrap, Line, Point, Rect, Size, WritingMode,
};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum LogicalStaticEdge {
Start,
Center,
End,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HorizontalStaticEdge {
Left,
Center,
Right,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum VerticalStaticEdge {
Top,
Center,
Bottom,
}

/// Physical static-position contract consumed by absolute positioning.
///
/// A point alone is insufficient: a centered point denotes the center of the
/// margin box, while an end point denotes its far edge. This is the same
/// distinction represented by Blink's `PhysicalStaticPosition`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct PhysicalStaticPosition {
point: Point<f32>,
horizontal_edge: HorizontalStaticEdge,
vertical_edge: VerticalStaticEdge,
}

impl PhysicalStaticPosition {
pub(crate) const fn new(
point: Point<f32>,
horizontal_edge: HorizontalStaticEdge,
vertical_edge: VerticalStaticEdge,
) -> Self {
Self {
point,
horizontal_edge,
vertical_edge,
}
}

pub(crate) fn relative_to(self, origin: Point<f32>) -> Self {
Self {
point: Point {
x: self.point.x - origin.x,
y: self.point.y - origin.y,
},
..self
}
}

pub(crate) fn margin_box_origin(self, box_size: Size<f32>, margin: Rect<f32>) -> Point<f32> {
let x = match self.horizontal_edge {
HorizontalStaticEdge::Left => self.point.x + margin.left,
HorizontalStaticEdge::Center => {
self.point.x - box_size.width / 2.0 + (margin.left - margin.right) / 2.0
}
HorizontalStaticEdge::Right => self.point.x - box_size.width - margin.right,
};
let y = match self.vertical_edge {
VerticalStaticEdge::Top => self.point.y + margin.top,
VerticalStaticEdge::Center => {
self.point.y - box_size.height / 2.0 + (margin.top - margin.bottom) / 2.0
}
VerticalStaticEdge::Bottom => self.point.y - box_size.height - margin.bottom,
};
Point { x, y }
}
}

pub(crate) fn flex_main_axis_static_edge(
justify_content: Option<AlignContent>,
is_reverse: bool,
) -> LogicalStaticEdge {
match justify_content
.unwrap_or(AlignContent::FLEX_START)
.keyword()
{
AlignContentKeyword::FlexEnd => {
if is_reverse {
LogicalStaticEdge::Start
} else {
LogicalStaticEdge::End
}
}
AlignContentKeyword::Center
| AlignContentKeyword::SpaceAround
| AlignContentKeyword::SpaceEvenly => LogicalStaticEdge::Center,
AlignContentKeyword::Start => LogicalStaticEdge::Start,
AlignContentKeyword::End => LogicalStaticEdge::End,
AlignContentKeyword::FlexStart
| AlignContentKeyword::Stretch
| AlignContentKeyword::SpaceBetween => {
if is_reverse {
LogicalStaticEdge::End
} else {
LogicalStaticEdge::Start
}
}
}
}

pub(crate) struct FlexCrossAxisStaticContext {
pub(crate) align_self: Option<AlignSelf>,
pub(crate) align_items: Option<AlignItems>,
pub(crate) flex_wrap: FlexWrap,
pub(crate) child_writing_mode: WritingMode,
pub(crate) child_direction: Direction,
pub(crate) container_writing_mode: WritingMode,
pub(crate) container_direction: Direction,
pub(crate) physical_axis: AbsoluteAxis,
pub(crate) overflows: bool,
}

impl FlexCrossAxisStaticContext {
pub(crate) fn resolve(self) -> LogicalStaticEdge {
let alignment = self
.align_self
.or(self.align_items)
.unwrap_or(AlignItems::STRETCH);
let mut keyword = if alignment.safety == AlignmentSafety::Safe && self.overflows {
AlignItemsKeyword::Start
} else {
alignment.keyword()
};

keyword =
match keyword {
AlignItemsKeyword::Start => AlignItemsKeyword::FlexStart,
AlignItemsKeyword::End => AlignItemsKeyword::FlexEnd,
AlignItemsKeyword::SelfStart | AlignItemsKeyword::SelfEnd => {
let child_start_reversed = self
.child_writing_mode
.is_axis_flow_reversed(self.physical_axis, self.child_direction);
let container_start_reversed = self
.container_writing_mode
.is_axis_flow_reversed(self.physical_axis, self.container_direction);
let starts_match = child_start_reversed == container_start_reversed;
match (keyword, starts_match) {
(AlignItemsKeyword::SelfStart, true)
| (AlignItemsKeyword::SelfEnd, false) => AlignItemsKeyword::FlexStart,
(AlignItemsKeyword::SelfStart, false)
| (AlignItemsKeyword::SelfEnd, true) => AlignItemsKeyword::FlexEnd,
_ => unreachable!("self-relative alignment was matched above"),
}
}
keyword => keyword,
};

if self.flex_wrap == FlexWrap::WrapReverse {
keyword = match keyword {
AlignItemsKeyword::FlexStart => AlignItemsKeyword::FlexEnd,
AlignItemsKeyword::FlexEnd => AlignItemsKeyword::FlexStart,
keyword => keyword,
};
}

match keyword {
AlignItemsKeyword::Center => LogicalStaticEdge::Center,
AlignItemsKeyword::FlexEnd => LogicalStaticEdge::End,
AlignItemsKeyword::Stretch if self.flex_wrap == FlexWrap::WrapReverse => {
LogicalStaticEdge::End
}
AlignItemsKeyword::Start
| AlignItemsKeyword::End
| AlignItemsKeyword::FlexStart
| AlignItemsKeyword::SelfStart
| AlignItemsKeyword::SelfEnd
| AlignItemsKeyword::Baseline
| AlignItemsKeyword::Stretch => LogicalStaticEdge::Start,
}
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PhysicalAxisStaticEdge {
Min,
Center,
Max,
}

fn physical_axis_static_position(
min: f32,
size: f32,
edge: LogicalStaticEdge,
start_is_reversed: bool,
) -> (f32, PhysicalAxisStaticEdge) {
match (edge, start_is_reversed) {
(LogicalStaticEdge::Start, false) | (LogicalStaticEdge::End, true) => {
(min, PhysicalAxisStaticEdge::Min)
}
(LogicalStaticEdge::Center, _) => (min + size / 2.0, PhysicalAxisStaticEdge::Center),
(LogicalStaticEdge::End, false) | (LogicalStaticEdge::Start, true) => {
(min + size, PhysicalAxisStaticEdge::Max)
}
}
}

pub(crate) fn physical_static_position_from_logical(
content_origin: Point<f32>,
content_size: Size<f32>,
writing_mode: WritingMode,
direction: Direction,
inline_edge: LogicalStaticEdge,
block_edge: LogicalStaticEdge,
) -> PhysicalStaticPosition {
let inline_axis = writing_mode.inline_axis();
let (inline_offset, inline_physical_edge) = physical_axis_static_position(
match inline_axis {
AbsoluteAxis::Horizontal => content_origin.x,
AbsoluteAxis::Vertical => content_origin.y,
},
content_size.get_abs(inline_axis),
inline_edge,
writing_mode.is_inline_flow_reversed(direction),
);
let block_axis = writing_mode.block_axis();
let (block_offset, block_physical_edge) = physical_axis_static_position(
match block_axis {
AbsoluteAxis::Horizontal => content_origin.x,
AbsoluteAxis::Vertical => content_origin.y,
},
content_size.get_abs(block_axis),
block_edge,
writing_mode.is_block_flow_reversed(),
);

match inline_axis {
AbsoluteAxis::Horizontal => PhysicalStaticPosition::new(
Point {
x: inline_offset,
y: block_offset,
},
match inline_physical_edge {
PhysicalAxisStaticEdge::Min => HorizontalStaticEdge::Left,
PhysicalAxisStaticEdge::Center => HorizontalStaticEdge::Center,
PhysicalAxisStaticEdge::Max => HorizontalStaticEdge::Right,
},
match block_physical_edge {
PhysicalAxisStaticEdge::Min => VerticalStaticEdge::Top,
PhysicalAxisStaticEdge::Center => VerticalStaticEdge::Center,
PhysicalAxisStaticEdge::Max => VerticalStaticEdge::Bottom,
},
),
AbsoluteAxis::Vertical => PhysicalStaticPosition::new(
Point {
x: block_offset,
y: inline_offset,
},
match block_physical_edge {
PhysicalAxisStaticEdge::Min => HorizontalStaticEdge::Left,
PhysicalAxisStaticEdge::Center => HorizontalStaticEdge::Center,
PhysicalAxisStaticEdge::Max => HorizontalStaticEdge::Right,
},
match inline_physical_edge {
PhysicalAxisStaticEdge::Min => VerticalStaticEdge::Top,
PhysicalAxisStaticEdge::Center => VerticalStaticEdge::Center,
PhysicalAxisStaticEdge::Max => VerticalStaticEdge::Bottom,
},
),
}
}

/// Resolve auto margins in one physical axis of an absolutely positioned box.
///
Expand Down Expand Up @@ -67,6 +338,99 @@ mod tests {
end: Some(0.0),
};

#[test]
fn centered_static_position_centers_the_margin_box() {
let position = PhysicalStaticPosition::new(
Point { x: 100.0, y: 50.0 },
HorizontalStaticEdge::Center,
VerticalStaticEdge::Center,
);
assert_eq!(
position.margin_box_origin(
Size {
width: 20.0,
height: 10.0,
},
Rect {
left: 4.0,
right: 8.0,
top: 2.0,
bottom: 6.0,
},
),
Point { x: 88.0, y: 43.0 }
);
}

#[test]
fn logical_static_position_respects_vertical_flow_and_rtl() {
let position = physical_static_position_from_logical(
Point { x: 20.0, y: 10.0 },
Size {
width: 160.0,
height: 80.0,
},
WritingMode::VerticalRl,
Direction::Rtl,
LogicalStaticEdge::Start,
LogicalStaticEdge::Start,
);
assert_eq!(
position,
PhysicalStaticPosition::new(
Point { x: 180.0, y: 90.0 },
HorizontalStaticEdge::Right,
VerticalStaticEdge::Bottom,
)
);
}

#[test]
fn flex_static_edges_distinguish_flow_and_flex_relative_values() {
assert_eq!(
flex_main_axis_static_edge(Some(AlignContent::START), true),
LogicalStaticEdge::Start
);
assert_eq!(
flex_main_axis_static_edge(Some(AlignContent::FLEX_START), true),
LogicalStaticEdge::End
);
assert_eq!(
flex_main_axis_static_edge(Some(AlignContent::FLEX_END), true),
LogicalStaticEdge::Start
);
assert_eq!(
FlexCrossAxisStaticContext {
align_self: None,
align_items: None,
flex_wrap: FlexWrap::WrapReverse,
child_writing_mode: WritingMode::HorizontalTb,
child_direction: Direction::Ltr,
container_writing_mode: WritingMode::HorizontalTb,
container_direction: Direction::Ltr,
physical_axis: AbsoluteAxis::Vertical,
overflows: false,
}
.resolve(),
LogicalStaticEdge::End
);
assert_eq!(
FlexCrossAxisStaticContext {
align_self: Some(AlignItems::SAFE_CENTER),
align_items: None,
flex_wrap: FlexWrap::NoWrap,
child_writing_mode: WritingMode::HorizontalTb,
child_direction: Direction::Ltr,
container_writing_mode: WritingMode::HorizontalTb,
container_direction: Direction::Ltr,
physical_axis: AbsoluteAxis::Vertical,
overflows: true,
}
.resolve(),
LogicalStaticEdge::Start
);
}

#[test]
fn positive_space_is_shared_even_when_the_box_is_wider_than_that_space() {
assert_eq!(
Expand Down
Loading
Loading