use core::ops::Range;
use crate::{debug::debug_log, sys::Vec, AvailableSpace, Clear, FloatDirection, Point, Size};
#[derive(Debug, Clone, Copy, Default)]
pub struct ContentSlot {
pub segment_id: Option<usize>,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone, Default)]
pub struct PlacedFloatedBox {
pub width: f32,
pub height: f32,
pub x_inset: f32,
pub y: f32,
}
#[derive(Debug, Clone)]
struct Segment {
y: Range<f32>,
insets: [f32; 2],
}
impl Segment {
fn fits_float_width(&self, floated_box: Size<f32>, direction: FloatDirection, bfc_width: f32) -> bool {
let slot = direction as usize;
self.insets[slot] == 0.0 || (bfc_width - floated_box.width - self.inset_sum()) >= 0.0
}
#[inline(always)]
fn inset_sum(&self) -> f32 {
self.insets[0] + self.insets[1]
}
}
#[derive(Debug, Clone)]
struct FloatFitter {
bfc_width: f32,
slot_height: f64,
insets: [f32; 2],
}
impl FloatFitter {
fn new(bfc_width: f32, slot_height: f32, insets: [f32; 2]) -> Self {
Self { bfc_width, slot_height: slot_height as f64, insets }
}
fn union_insets(&mut self, insets: [f32; 2]) {
self.insets[0] = self.insets[0].max(insets[0]);
self.insets[1] = self.insets[1].max(insets[1]);
}
fn fits_horiontally(&self, width: f32) -> bool {
self.insets == [0.0, 0.0] || self.bfc_width - self.insets[0] - self.insets[1] - width >= 0.0
}
fn add_height(&mut self, height: f32) {
self.slot_height += height as f64;
}
fn fits_vertically(&mut self, height: f32) -> bool {
self.slot_height >= height as f64
}
}
#[derive(Debug, Clone)]
pub struct FloatContext {
available_width: f32,
has_floats: bool,
left_floats: Vec<PlacedFloatedBox>,
right_floats: Vec<PlacedFloatedBox>,
segments: Vec<Segment>,
last_placed_floats: [Range<usize>; 2],
}
impl Default for FloatContext {
fn default() -> Self {
Self {
available_width: 0.0,
has_floats: false,
left_floats: Vec::new(),
right_floats: Vec::new(),
segments: Vec::new(),
last_placed_floats: [0..0, 0..0], }
}
}
impl FloatContext {
pub fn new() -> Self {
Default::default()
}
#[inline(always)]
pub fn has_floats(&self) -> bool {
self.has_floats
}
#[inline(always)]
pub fn has_active_floats(&self, min_y: f32) -> bool {
self.has_floats && self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0) > min_y
}
pub fn set_width(&mut self, available_width: f32) {
self.available_width = available_width;
}
pub fn left_floats(&self) -> &[PlacedFloatedBox] {
&self.left_floats
}
pub fn right_floats(&self) -> &[PlacedFloatedBox] {
&self.right_floats
}
fn subdivide_segment(&mut self, idx: usize, divide_at_y: f32) {
let old_segment = &mut self.segments[idx];
let new_segment = Segment { insets: old_segment.insets, y: divide_at_y..old_segment.y.end };
if !old_segment.y.contains(÷_at_y) || old_segment.y.start == divide_at_y {
debug_log!("old_segment", dbg:&mut *old_segment);
debug_log!("divide_at_y", dbg:divide_at_y);
assert!(old_segment.y.contains(÷_at_y) && old_segment.y.start != divide_at_y);
}
old_segment.y.end = divide_at_y;
self.segments.splice((idx + 1)..(idx + 1), core::iter::once(new_segment));
}
fn update_last_placed_float(&mut self, direction: FloatDirection, placement: Range<usize>) {
let slot = direction as usize;
self.last_placed_floats[slot].start = self.last_placed_floats[slot].start.max(placement.start);
self.last_placed_floats[slot].end = self.last_placed_floats[slot].end.max(placement.end);
}
pub fn place_floated_box(
&mut self,
floated_box: Size<f32>,
min_y: f32,
containing_block_insets: [f32; 2],
direction: FloatDirection,
clear: Clear,
) -> Point<f32> {
self.has_floats = true;
let placed_floated_box =
self.place_floated_box_inner(floated_box, min_y, containing_block_insets, direction, clear);
let x_inset = placed_floated_box.x_inset;
let y = placed_floated_box.y;
match direction {
FloatDirection::Left => {
self.left_floats.push(placed_floated_box);
Point { x: x_inset, y }
}
FloatDirection::Right => {
self.right_floats.push(placed_floated_box);
Point { x: self.available_width - x_inset - floated_box.width, y }
}
}
}
fn place_floated_box_inner(
&mut self,
floated_box: Size<f32>,
min_y: f32,
containing_block_insets: [f32; 2],
direction: FloatDirection,
clear: Clear,
) -> PlacedFloatedBox {
let slot = direction as usize;
let hwm = match clear {
Clear::Left => {
let float_dir_start = self.last_placed_floats[slot].start;
let left_end = self.last_placed_floats[0].end;
float_dir_start.max(left_end + 1)
}
Clear::Right => {
let float_dir_start = self.last_placed_floats[slot].start;
let right_end = self.last_placed_floats[1].end;
float_dir_start.max(right_end + 1)
}
Clear::Both => {
let left_end = self.last_placed_floats[0].end;
let right_end = self.last_placed_floats[1].end;
left_end.max(right_end) + 1
}
Clear::None => {
self.last_placed_floats[slot].start
}
};
let start_idx = self
.segments
.get(hwm..)
.and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm));
let mut start_idx = start_idx.unwrap_or(self.segments.len());
let mut start_y = min_y;
let mut end_idx = start_idx;
let (start, mut end, placed_inset) = 'outer: loop {
let Some(start_segment) = self.segments.get(start_idx) else {
break (None, None, containing_block_insets[slot]);
};
if !start_segment.fits_float_width(floated_box, direction, self.available_width) {
start_idx += 1;
end_idx = end_idx.max(start_idx);
continue;
}
start_y = start_y.max(start_segment.y.start);
let available_height = start_segment.y.end - start_y;
let mut fitter = FloatFitter::new(self.available_width, available_height, containing_block_insets);
fitter.union_insets(start_segment.insets);
loop {
let Some(end_segment) = self.segments.get(end_idx) else {
let inset = fitter.insets[slot];
break 'outer (Some(start_idx), None, inset);
};
fitter.union_insets(end_segment.insets);
if !fitter.fits_horiontally(floated_box.width) {
start_idx += 1;
end_idx = end_idx.max(start_idx);
continue 'outer;
}
if end_idx != start_idx {
fitter.add_height(end_segment.y.end - end_segment.y.start);
}
if !fitter.fits_vertically(floated_box.height) {
end_idx += 1;
continue;
}
let inset = fitter.insets[slot];
break 'outer (Some(start_idx), Some(end_idx), inset);
}
};
if floated_box.width == 0.0 || floated_box.height == 0.0 {
return PlacedFloatedBox {
width: floated_box.width,
height: floated_box.height,
y: start_y,
x_inset: placed_inset,
};
}
if start.is_none() {
let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0);
if start_y > last_y_end {
self.segments.push(Segment { y: last_y_end..start_y, insets: [0.0, 0.0] });
}
let start_y = last_y_end.max(start_y);
let mut insets = containing_block_insets;
insets[slot] += floated_box.width;
self.segments.push(Segment { y: start_y..(start_y + floated_box.height), insets });
let start_idx = self.segments.len() - 1;
let end_idx = start_idx + 1;
self.update_last_placed_float(direction, start_idx..end_idx);
return PlacedFloatedBox {
width: floated_box.width,
height: floated_box.height,
y: start_y,
x_inset: containing_block_insets[slot],
};
}
let mut start_idx = start.unwrap();
if start_y != self.segments[start_idx].y.start {
self.subdivide_segment(start_idx, start_y);
start_idx += 1;
if let Some(end_idx) = end.as_mut() {
*end_idx += 1;
}
}
let end_idx = match end {
None => {
let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0);
if min_y > last_y_end {
self.segments.push(Segment { y: last_y_end..min_y, insets: [0.0, 0.0] });
}
self.segments.len() - 1
}
Some(end_idx) => {
let end_y = start_y + floated_box.height;
if end_y != self.segments[end_idx].y.end {
self.subdivide_segment(end_idx, end_y);
}
end_idx
}
};
let placed_inset_plus_width = placed_inset + floated_box.width;
for segment in &mut self.segments[start_idx..=end_idx] {
segment.insets[slot] = placed_inset_plus_width;
}
self.update_last_placed_float(direction, start_idx..(end_idx + 1));
PlacedFloatedBox { width: floated_box.width, height: floated_box.height, y: start_y, x_inset: placed_inset }
}
fn cleared_segment(&self, clear: Clear) -> Option<usize> {
match clear {
Clear::Left => Some(self.last_placed_floats[0].end),
Clear::Right => Some(self.last_placed_floats[1].end),
Clear::Both => {
let left_end = self.last_placed_floats[0].end;
let right_end = self.last_placed_floats[1].end;
Some(left_end.max(right_end))
}
Clear::None => None,
}
}
pub fn cleared_threshold(&self, clear: Clear) -> Option<f32> {
self.cleared_segment(clear).and_then(|idx| self.segments.get(idx.max(1) - 1)).map(|seg| seg.y.end)
}
pub fn find_content_slot(
&self,
min_y: f32,
containing_block_insets: [f32; 2],
clear: Clear,
after: Option<usize>,
) -> ContentSlot {
if !self.has_active_floats(min_y) {
return ContentSlot {
segment_id: None,
x: containing_block_insets[0],
y: min_y,
width: self.available_width - containing_block_insets[0] - containing_block_insets[1],
height: f32::INFINITY,
};
}
let at_least = after.map(|idx| idx + 1).unwrap_or(0);
let hwm = at_least.max(self.cleared_segment(clear).map(|idx| idx + 1).unwrap_or(0));
let start_idx = self
.segments
.get(hwm..)
.and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm));
let start_idx = start_idx.unwrap_or(self.segments.len());
let segment = self.segments.get(start_idx);
match segment {
Some(segment) => {
let inset_left = segment.insets[0].max(containing_block_insets[0]);
let inset_right = segment.insets[1].max(containing_block_insets[1]);
ContentSlot {
segment_id: Some(start_idx),
x: inset_left,
y: segment.y.start.max(min_y),
width: self.available_width - inset_left - inset_right,
height: f32::INFINITY,
}
}
None => ContentSlot {
segment_id: None,
x: containing_block_insets[0],
y: min_y,
width: self.available_width - containing_block_insets[0] - containing_block_insets[1],
height: f32::INFINITY,
},
}
}
}
pub struct FloatIntrinsicWidthCalculator {
available_width: AvailableSpace,
contribution: f32,
}
impl FloatIntrinsicWidthCalculator {
pub fn new(available_width: AvailableSpace) -> Self {
Self { available_width, contribution: 0.0 }
}
pub fn add_float(&mut self, width: f32, _direction: FloatDirection, _clear: Clear) {
match self.available_width {
AvailableSpace::Definite(_) => {
}
AvailableSpace::MinContent => self.contribution = self.contribution.max(width),
AvailableSpace::MaxContent => self.contribution += width,
};
}
pub fn result(&self) -> f32 {
self.contribution
}
}