use crate::box_model::BoxModel;
use crate::fragment::Fragment;
use crate::geometry::{Point, Size};
use crate::style::{ComputedStyle, Position};
use crate::tree::BoxTree;
pub fn resolve_positioned(tree: &BoxTree, mut root: Fragment, viewport: Size) -> Fragment {
resolve_fragment(tree, &mut root, viewport, viewport);
root
}
fn resolve_fragment(
tree: &BoxTree,
fragment: &mut Fragment,
containing_block_size: Size,
viewport: Size,
) {
let style = tree.style(fragment.node);
match style.position {
Position::Relative => {
apply_relative_offset(fragment, style, containing_block_size);
}
Position::Absolute => {
resolve_absolute_position(fragment, style, containing_block_size);
}
Position::Fixed => {
resolve_absolute_position(fragment, style, viewport);
}
Position::Sticky => {
apply_relative_offset(fragment, style, containing_block_size);
}
Position::Static => {}
}
let child_cb = if style.position.is_positioned() || style.position == Position::Static {
Size::new(
fragment.size.width + fragment.padding.horizontal() + fragment.border.horizontal(),
fragment.size.height + fragment.padding.vertical() + fragment.border.vertical(),
)
} else {
containing_block_size
};
for child in &mut fragment.children {
resolve_fragment(tree, child, child_cb, viewport);
}
}
fn apply_relative_offset(fragment: &mut Fragment, style: &ComputedStyle, cb_size: Size) {
let dx = resolve_offset_pair(&style.left, &style.right, cb_size.width);
let dy = resolve_offset_pair(&style.top, &style.bottom, cb_size.height);
fragment.position.x += dx;
fragment.position.y += dy;
}
fn resolve_absolute_position(fragment: &mut Fragment, style: &ComputedStyle, cb_size: Size) {
let border = BoxModel::resolve_border(style);
let padding = BoxModel::resolve_padding(style, cb_size.width);
let (x, width) = resolve_absolute_axis(
&style.left,
&style.right,
&style.width,
&style.margin_left,
&style.margin_right,
border.left + padding.left,
border.right + padding.right,
cb_size.width,
fragment.size.width,
);
let (y, height) = resolve_absolute_axis(
&style.top,
&style.bottom,
&style.height,
&style.margin_top,
&style.margin_bottom,
border.top + padding.top,
border.bottom + padding.bottom,
cb_size.height,
fragment.size.height,
);
fragment.position = Point::new(x, y);
if width >= 0.0 {
fragment.size.width = width;
}
if height >= 0.0 {
fragment.size.height = height;
}
fragment.border = border;
fragment.padding = padding;
}
fn resolve_absolute_axis(
start: &crate::values::LengthPercentageAuto,
end: &crate::values::LengthPercentageAuto,
size: &crate::values::LengthPercentageAuto,
margin_start: &crate::values::LengthPercentageAuto,
margin_end: &crate::values::LengthPercentageAuto,
border_padding_start: f32,
border_padding_end: f32,
cb_size: f32,
intrinsic_size: f32,
) -> (f32, f32) {
let start_val = start.resolve(cb_size);
let end_val = end.resolve(cb_size);
let size_val = size.resolve(cb_size);
let ms = margin_start.resolve(cb_size).unwrap_or(0.0);
let me = margin_end.resolve(cb_size).unwrap_or(0.0);
match (start_val, end_val, size_val) {
(Some(s), Some(_e), Some(w)) => {
let pos = s + ms;
(pos, w)
}
(Some(s), _, Some(w)) => {
let pos = s + ms;
(pos, w)
}
(_, Some(e), Some(w)) => {
let pos = cb_size - e - me - w - border_padding_start - border_padding_end;
(pos, w)
}
(Some(s), Some(e), None) => {
let pos = s + ms;
let w = cb_size - s - e - ms - me - border_padding_start - border_padding_end;
(pos, w.max(0.0))
}
(Some(s), None, None) => {
let pos = s + ms;
(pos, intrinsic_size)
}
(None, Some(e), None) => {
let pos = cb_size - e - me - intrinsic_size - border_padding_start - border_padding_end;
(pos, intrinsic_size)
}
(None, None, Some(w)) => (ms, w),
(None, None, None) => (ms, intrinsic_size),
}
}
fn resolve_offset_pair(
start: &crate::values::LengthPercentageAuto,
end: &crate::values::LengthPercentageAuto,
reference: f32,
) -> f32 {
let s = start.resolve(reference);
let e = end.resolve(reference);
match (s, e) {
(Some(sv), _) => sv, (None, Some(ev)) => -ev, (None, None) => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::values::LengthPercentageAuto;
#[test]
fn test_relative_offset_left() {
let left = LengthPercentageAuto::px(10.0);
let right = LengthPercentageAuto::Auto;
assert_eq!(resolve_offset_pair(&left, &right, 800.0), 10.0);
}
#[test]
fn test_relative_offset_right() {
let left = LengthPercentageAuto::Auto;
let right = LengthPercentageAuto::px(10.0);
assert_eq!(resolve_offset_pair(&left, &right, 800.0), -10.0);
}
#[test]
fn test_relative_offset_both_start_wins() {
let left = LengthPercentageAuto::px(20.0);
let right = LengthPercentageAuto::px(10.0);
assert_eq!(resolve_offset_pair(&left, &right, 800.0), 20.0);
}
#[test]
fn test_absolute_position_left_top() {
let (x, w) = resolve_absolute_axis(
&LengthPercentageAuto::px(10.0),
&LengthPercentageAuto::Auto,
&LengthPercentageAuto::px(200.0),
&LengthPercentageAuto::px(0.0),
&LengthPercentageAuto::px(0.0),
0.0,
0.0,
800.0,
0.0,
);
assert_eq!(x, 10.0);
assert_eq!(w, 200.0);
}
#[test]
fn test_absolute_position_stretch() {
let (x, w) = resolve_absolute_axis(
&LengthPercentageAuto::px(10.0),
&LengthPercentageAuto::px(10.0),
&LengthPercentageAuto::Auto,
&LengthPercentageAuto::px(0.0),
&LengthPercentageAuto::px(0.0),
0.0,
0.0,
800.0,
0.0,
);
assert_eq!(x, 10.0);
assert_eq!(w, 780.0); }
}