use crate::fragment::Fragment;
use crate::geometry::{Point, Rect};
use crate::style::{Clear, Float};
#[derive(Debug, Clone)]
pub struct FloatContext {
containing_width: f32,
left_floats: Vec<FloatBox>,
right_floats: Vec<FloatBox>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct FloatBox {
rect: Rect,
}
impl FloatContext {
pub fn new(containing_width: f32) -> Self {
Self {
containing_width,
left_floats: Vec::new(),
right_floats: Vec::new(),
}
}
pub fn place_float(
&mut self,
mut fragment: Fragment,
float_type: Float,
cursor_y: f32,
) -> Fragment {
let margin_box = fragment.margin_box();
let width = margin_box.width;
let height = margin_box.height;
let min_y = self.compute_min_y_for_float(&float_type, cursor_y);
let mut y = min_y;
loop {
let (left_offset, available_width) = self.available_width_at(y, height);
if available_width >= width {
let x = match float_type {
Float::Left => left_offset,
Float::Right => left_offset + available_width - width,
Float::None => {
left_offset
}
};
let border_x = x + fragment.margin.left;
let border_y = y + fragment.margin.top;
fragment.position = Point::new(border_x, border_y);
let float_box = FloatBox {
rect: Rect::new(x, y, width, height),
};
match float_type {
Float::Left => self.left_floats.push(float_box),
Float::Right => self.right_floats.push(float_box),
Float::None => {}
}
return fragment;
}
y = self.next_y_position(y, height);
if y > cursor_y + 100000.0 {
let x = match float_type {
Float::Left => 0.0,
Float::Right => self.containing_width - width,
Float::None => 0.0,
}
.max(0.0);
let border_x = x + fragment.margin.left;
let border_y = y + fragment.margin.top;
fragment.position = Point::new(border_x, border_y);
let float_box = FloatBox {
rect: Rect::new(x, y, width, height),
};
match float_type {
Float::Left => self.left_floats.push(float_box),
Float::Right => self.right_floats.push(float_box),
Float::None => {}
}
return fragment;
}
}
}
pub fn available_width_at(&self, y: f32, height: f32) -> (f32, f32) {
let bottom = y + height;
let left_edge = self
.left_floats
.iter()
.filter(|f| f.rect.y < bottom && f.rect.bottom() > y)
.map(|f| f.rect.right())
.fold(0.0_f32, f32::max);
let right_edge = self
.right_floats
.iter()
.filter(|f| f.rect.y < bottom && f.rect.bottom() > y)
.map(|f| f.rect.x)
.fold(self.containing_width, f32::min);
let available = (right_edge - left_edge).max(0.0);
(left_edge, available)
}
pub fn clear(&self, clear: Clear) -> f32 {
match clear {
Clear::None => 0.0,
Clear::Left => self.bottom_of_floats(&self.left_floats),
Clear::Right => self.bottom_of_floats(&self.right_floats),
Clear::Both => {
let left_bottom = self.bottom_of_floats(&self.left_floats);
let right_bottom = self.bottom_of_floats(&self.right_floats);
left_bottom.max(right_bottom)
}
}
}
pub fn clear_all(&self) -> f32 {
let left_bottom = self.bottom_of_floats(&self.left_floats);
let right_bottom = self.bottom_of_floats(&self.right_floats);
left_bottom.max(right_bottom)
}
fn compute_min_y_for_float(&self, float_type: &Float, cursor_y: f32) -> f32 {
let same_side_bottom = match float_type {
Float::Left => self.bottom_of_floats(&self.left_floats),
Float::Right => self.bottom_of_floats(&self.right_floats),
Float::None => 0.0,
};
cursor_y.max(same_side_bottom)
}
fn next_y_position(&self, current_y: f32, height: f32) -> f32 {
let bottom = current_y + height;
let mut next_positions = Vec::new();
for float_box in self.left_floats.iter().chain(self.right_floats.iter()) {
if float_box.rect.y < bottom && float_box.rect.bottom() > current_y {
next_positions.push(float_box.rect.bottom());
}
if float_box.rect.y >= current_y {
next_positions.push(float_box.rect.y);
}
}
next_positions
.into_iter()
.filter(|&y| y > current_y)
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(current_y + 1.0)
}
fn bottom_of_floats(&self, floats: &[FloatBox]) -> f32 {
floats
.iter()
.map(|f| f.rect.bottom())
.fold(0.0_f32, f32::max)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fragment::FragmentKind;
use crate::geometry::{Edges, Size};
use crate::tree::NodeId;
fn create_fragment(width: f32, height: f32, margin: f32) -> Fragment {
let mut frag = Fragment::new(NodeId(0), FragmentKind::Box);
frag.size = Size::new(width, height);
frag.margin = Edges::all(margin);
frag
}
#[test]
fn test_new_float_context() {
let ctx = FloatContext::new(800.0);
assert_eq!(ctx.containing_width, 800.0);
assert_eq!(ctx.left_floats.len(), 0);
assert_eq!(ctx.right_floats.len(), 0);
}
#[test]
fn test_available_width_no_floats() {
let ctx = FloatContext::new(800.0);
let (left, width) = ctx.available_width_at(0.0, 100.0);
assert_eq!(left, 0.0);
assert_eq!(width, 800.0);
}
#[test]
fn test_place_left_float() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 10.0);
let placed = ctx.place_float(frag, Float::Left, 0.0);
assert_eq!(placed.position.x, 10.0);
assert_eq!(placed.position.y, 10.0);
assert_eq!(ctx.left_floats.len(), 1);
let margin_box = placed.margin_box();
assert_eq!(margin_box.x, 0.0);
assert_eq!(margin_box.y, 0.0);
assert_eq!(margin_box.width, 120.0); assert_eq!(margin_box.height, 70.0); }
#[test]
fn test_place_right_float() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 10.0);
let placed = ctx.place_float(frag, Float::Right, 0.0);
assert_eq!(placed.position.x, 690.0);
assert_eq!(placed.position.y, 10.0);
assert_eq!(ctx.right_floats.len(), 1);
}
#[test]
fn test_two_left_floats_side_by_side() {
let mut ctx = FloatContext::new(800.0);
let frag1 = create_fragment(100.0, 50.0, 0.0);
let placed1 = ctx.place_float(frag1, Float::Left, 0.0);
assert_eq!(placed1.position.x, 0.0);
assert_eq!(placed1.position.y, 0.0);
let frag2 = create_fragment(100.0, 50.0, 0.0);
let placed2 = ctx.place_float(frag2, Float::Left, 0.0);
assert_eq!(placed2.position.x, 0.0);
assert_eq!(placed2.position.y, 50.0);
}
#[test]
fn test_left_float_wraps_when_no_space() {
let mut ctx = FloatContext::new(200.0);
let frag1 = create_fragment(150.0, 50.0, 0.0);
ctx.place_float(frag1, Float::Left, 0.0);
let frag2 = create_fragment(100.0, 40.0, 0.0);
let placed2 = ctx.place_float(frag2, Float::Left, 0.0);
assert_eq!(placed2.position.x, 0.0);
assert_eq!(placed2.position.y, 50.0); }
#[test]
fn test_available_width_with_left_float() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(200.0, 100.0, 0.0);
ctx.place_float(frag, Float::Left, 0.0);
let (left, width) = ctx.available_width_at(50.0, 10.0);
assert_eq!(left, 200.0); assert_eq!(width, 600.0); }
#[test]
fn test_available_width_with_right_float() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(200.0, 100.0, 0.0);
ctx.place_float(frag, Float::Right, 0.0);
let (left, width) = ctx.available_width_at(50.0, 10.0);
assert_eq!(left, 0.0);
assert_eq!(width, 600.0); }
#[test]
fn test_available_width_with_both_floats() {
let mut ctx = FloatContext::new(800.0);
let left_frag = create_fragment(150.0, 100.0, 0.0);
ctx.place_float(left_frag, Float::Left, 0.0);
let right_frag = create_fragment(200.0, 100.0, 0.0);
ctx.place_float(right_frag, Float::Right, 0.0);
let (left, width) = ctx.available_width_at(50.0, 10.0);
assert_eq!(left, 150.0); assert_eq!(width, 450.0); }
#[test]
fn test_available_width_below_floats() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(200.0, 100.0, 0.0);
ctx.place_float(frag, Float::Left, 0.0);
let (left, width) = ctx.available_width_at(150.0, 10.0);
assert_eq!(left, 0.0); assert_eq!(width, 800.0);
}
#[test]
fn test_clear_none() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 0.0);
ctx.place_float(frag, Float::Left, 0.0);
assert_eq!(ctx.clear(Clear::None), 0.0);
}
#[test]
fn test_clear_left() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 0.0);
ctx.place_float(frag, Float::Left, 0.0);
assert_eq!(ctx.clear(Clear::Left), 50.0);
}
#[test]
fn test_clear_right() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 60.0, 0.0);
ctx.place_float(frag, Float::Right, 0.0);
assert_eq!(ctx.clear(Clear::Right), 60.0);
}
#[test]
fn test_clear_both() {
let mut ctx = FloatContext::new(800.0);
let left_frag = create_fragment(100.0, 50.0, 0.0);
ctx.place_float(left_frag, Float::Left, 0.0);
let right_frag = create_fragment(100.0, 80.0, 0.0);
ctx.place_float(right_frag, Float::Right, 0.0);
assert_eq!(ctx.clear(Clear::Both), 80.0);
}
#[test]
fn test_clear_all() {
let mut ctx = FloatContext::new(800.0);
let left_frag = create_fragment(100.0, 50.0, 0.0);
ctx.place_float(left_frag, Float::Left, 0.0);
let right_frag = create_fragment(100.0, 80.0, 0.0);
ctx.place_float(right_frag, Float::Right, 0.0);
assert_eq!(ctx.clear_all(), 80.0);
}
#[test]
fn test_float_respects_cursor_y() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 0.0);
let placed = ctx.place_float(frag, Float::Left, 100.0);
assert!(placed.position.y >= 100.0);
}
#[test]
fn test_float_with_margins() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(100.0, 50.0, 20.0);
let placed = ctx.place_float(frag, Float::Left, 0.0);
assert_eq!(placed.position.x, 20.0);
assert_eq!(placed.position.y, 20.0);
let margin_box = placed.margin_box();
assert_eq!(margin_box.x, 0.0);
assert_eq!(margin_box.y, 0.0);
}
#[test]
fn test_multiple_left_floats_stacking() {
let mut ctx = FloatContext::new(500.0);
let frag1 = create_fragment(100.0, 50.0, 0.0);
let placed1 = ctx.place_float(frag1, Float::Left, 0.0);
assert_eq!(placed1.position.x, 0.0);
assert_eq!(placed1.position.y, 0.0);
let frag2 = create_fragment(100.0, 50.0, 0.0);
let placed2 = ctx.place_float(frag2, Float::Left, 0.0);
assert_eq!(placed2.position.x, 0.0);
assert_eq!(placed2.position.y, 50.0);
let frag3 = create_fragment(100.0, 50.0, 0.0);
let placed3 = ctx.place_float(frag3, Float::Left, 0.0);
assert_eq!(placed3.position.x, 0.0);
assert_eq!(placed3.position.y, 100.0);
}
#[test]
fn test_float_below_previous_same_side() {
let mut ctx = FloatContext::new(800.0);
let frag1 = create_fragment(100.0, 50.0, 0.0);
ctx.place_float(frag1, Float::Left, 0.0);
let frag2 = create_fragment(100.0, 30.0, 0.0);
let placed2 = ctx.place_float(frag2, Float::Left, 0.0);
assert!(placed2.position.y >= 0.0);
}
#[test]
fn test_narrow_containing_block() {
let mut ctx = FloatContext::new(100.0);
let frag = create_fragment(150.0, 50.0, 0.0);
let placed = ctx.place_float(frag, Float::Left, 0.0);
assert!(placed.position.y > 100000.0);
assert_eq!(placed.position.x, 0.0);
}
#[test]
fn test_available_width_partial_overlap() {
let mut ctx = FloatContext::new(800.0);
let frag = create_fragment(200.0, 100.0, 0.0);
ctx.place_float(frag, Float::Left, 50.0);
let (left, width) = ctx.available_width_at(0.0, 100.0);
assert_eq!(left, 200.0); assert_eq!(width, 600.0);
let (left, width) = ctx.available_width_at(0.0, 40.0);
assert_eq!(left, 0.0); assert_eq!(width, 800.0);
}
#[test]
fn test_overlapping_left_and_right_floats() {
let mut ctx = FloatContext::new(800.0);
let left = create_fragment(350.0, 100.0, 0.0);
ctx.place_float(left, Float::Left, 0.0);
let right = create_fragment(350.0, 100.0, 0.0);
ctx.place_float(right, Float::Right, 0.0);
let (left_offset, width) = ctx.available_width_at(50.0, 10.0);
assert_eq!(left_offset, 350.0);
assert_eq!(width, 100.0); }
#[test]
fn test_zero_available_width() {
let mut ctx = FloatContext::new(400.0);
let left = create_fragment(250.0, 100.0, 0.0);
ctx.place_float(left, Float::Left, 0.0);
let right = create_fragment(250.0, 100.0, 0.0);
ctx.place_float(right, Float::Right, 0.0);
let (_, width) = ctx.available_width_at(50.0, 10.0);
assert_eq!(width, 150.0);
}
}