use ratatui::layout::{Direction, Rect};
use crate::core::protocol::common_types::{Align, Justify};
pub fn weighted_split(
direction: Direction,
area: Rect,
weights: &[Option<f64>],
) -> Vec<Rect> {
let n = weights.len();
if n == 0 {
return vec![];
}
let total_size = match direction {
Direction::Horizontal => area.width as u16,
Direction::Vertical => area.height as u16,
} as f64;
let effective: Vec<f64> = weights.iter().map(|w| w.unwrap_or(1.0)).collect();
let total_weight: f64 = effective.iter().sum();
if total_weight <= 0.0 {
return equal_split(direction, area, n);
}
let mut rects = Vec::with_capacity(n);
let mut offset: u16 = 0;
for (i, &w) in effective.iter().enumerate() {
let fraction = w / total_weight;
let raw = total_size * fraction;
let mut size = raw.floor() as u16;
if i == n - 1 {
let used: u16 = rects.iter().map(|r: &Rect| size_axis(r, direction)).sum();
size = total_size as u16 - used;
}
let rect = make_rect(direction, area, offset, size);
rects.push(rect);
offset += size;
}
rects
}
pub fn apply_justify(
justify: Justify,
items: &[(Rect, u16)],
total_area: Rect,
direction: Direction,
) -> Vec<Rect> {
let container_size = size_from_direction(total_area, direction);
let total_item_size: u16 = items.iter().map(|(_, s)| *s).sum();
match justify {
Justify::Start => {
items.iter().map(|(rect, _)| *rect).collect()
}
Justify::Center => {
let gap = container_size.saturating_sub(total_item_size);
let offset = gap / 2;
shift_items(items, total_area, direction, offset)
}
Justify::End => {
let gap = container_size.saturating_sub(total_item_size);
shift_items(items, total_area, direction, gap)
}
Justify::SpaceBetween => {
let count = items.len();
if count <= 1 {
return items.iter().map(|(rect, _)| *rect).collect();
}
let gap = container_size.saturating_sub(total_item_size);
let spacing = gap / (count as u16 - 1);
let mut result = Vec::with_capacity(count);
let mut offset: u16 = 0;
for (rect, size) in items {
result.push(set_offset(*rect, total_area, direction, offset));
offset += size + spacing;
}
result
}
}
}
pub fn apply_align(align: Align, item: Rect, container: Rect, direction: Direction) -> Rect {
let (cross_size, container_cross) = match direction {
Direction::Horizontal => (item.height, container.height),
Direction::Vertical => (item.width, container.width),
};
match align {
Align::Start => item,
Align::Center => {
let offset = container_cross.saturating_sub(cross_size) / 2;
set_cross_offset(item, container, direction, offset)
}
Align::End => {
let offset = container_cross.saturating_sub(cross_size);
set_cross_offset(item, container, direction, offset)
}
Align::Stretch => {
match direction {
Direction::Horizontal => Rect {
x: item.x,
y: container.y,
width: item.width,
height: container.height,
},
Direction::Vertical => Rect {
x: container.x,
y: item.y,
width: container.width,
height: item.height,
},
}
}
}
}
fn equal_split(direction: Direction, area: Rect, n: usize) -> Vec<Rect> {
if n == 0 {
return vec![];
}
let total = match direction {
Direction::Horizontal => area.width,
Direction::Vertical => area.height,
};
let each = total / n as u16;
let mut rects = Vec::with_capacity(n);
let base = match direction {
Direction::Horizontal => area.x,
Direction::Vertical => area.y,
};
for i in 0..n {
let offset = base + (each * i as u16);
let size = if i == n - 1 {
total - each * (n as u16 - 1)
} else {
each
};
rects.push(make_rect(direction, area, offset - base, size));
}
rects
}
fn size_axis(rect: &Rect, direction: Direction) -> u16 {
match direction {
Direction::Horizontal => rect.width,
Direction::Vertical => rect.height,
}
}
fn size_from_direction(area: Rect, direction: Direction) -> u16 {
match direction {
Direction::Horizontal => area.width,
Direction::Vertical => area.height,
}
}
fn make_rect(direction: Direction, area: Rect, offset: u16, size: u16) -> Rect {
match direction {
Direction::Horizontal => Rect {
x: area.x + offset,
y: area.y,
width: size,
height: area.height,
},
Direction::Vertical => Rect {
x: area.x,
y: area.y + offset,
width: area.width,
height: size,
},
}
}
fn shift_items(
items: &[(Rect, u16)],
total_area: Rect,
direction: Direction,
start_offset: u16,
) -> Vec<Rect> {
let base = match direction {
Direction::Horizontal => total_area.x,
Direction::Vertical => total_area.y,
};
let mut result = Vec::with_capacity(items.len());
let mut pos = base + start_offset;
for (rect, size) in items {
result.push(set_offset(*rect, total_area, direction, pos - base));
pos += size;
}
result
}
fn set_offset(rect: Rect, _total_area: Rect, direction: Direction, offset: u16) -> Rect {
match direction {
Direction::Horizontal => Rect {
x: _total_area.x + offset,
..rect
},
Direction::Vertical => Rect {
y: _total_area.y + offset,
..rect
},
}
}
fn set_cross_offset(item: Rect, container: Rect, direction: Direction, offset: u16) -> Rect {
match direction {
Direction::Horizontal => Rect {
y: container.y + offset,
..item
},
Direction::Vertical => Rect {
x: container.x + offset,
..item
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_area() -> Rect {
Rect::new(0, 0, 100, 30)
}
#[test]
fn weighted_split_equal_when_no_weights() {
let area = test_area();
let result = weighted_split(Direction::Horizontal, area, &[None, None, None]);
assert_eq!(result.len(), 3);
let total_width: u16 = result.iter().map(|r| r.width).sum();
assert_eq!(total_width, 100);
}
#[test]
fn weighted_split_respects_weights() {
let area = test_area();
let result = weighted_split(Direction::Vertical, area, &[Some(3.0), Some(1.0)]);
assert_eq!(result.len(), 2);
assert_eq!(result[0].height, 22); assert_eq!(result[1].height, 8); assert_eq!(result[0].height + result[1].height, 30);
}
#[test]
fn weighted_split_mixed_weights() {
let area = test_area();
let result = weighted_split(Direction::Horizontal, area, &[None, Some(2.0)]);
assert_eq!(result.len(), 2);
let total: u16 = result.iter().map(|r| r.width).sum();
assert_eq!(total, 100);
assert!(result[0].width < result[1].width);
}
#[test]
fn weighted_split_empty() {
let area = test_area();
let result = weighted_split(Direction::Horizontal, area, &[]);
assert!(result.is_empty());
}
#[test]
fn apply_align_stretch_horizontal() {
let container = Rect::new(0, 0, 100, 30);
let item = Rect::new(10, 5, 50, 10);
let result = apply_align(Align::Stretch, item, container, Direction::Horizontal);
assert_eq!(result.y, 0);
assert_eq!(result.height, 30);
assert_eq!(result.width, 50);
}
#[test]
fn apply_align_center_vertical() {
let container = Rect::new(0, 0, 100, 30);
let item = Rect::new(0, 0, 10, 10);
let result = apply_align(Align::Center, item, container, Direction::Vertical);
assert_eq!(result.x, 45); }
#[test]
fn apply_justify_space_between() {
let container = Rect::new(0, 0, 100, 30);
let items: Vec<(Rect, u16)> = vec![
(Rect::new(0, 0, 20, 30), 20),
(Rect::new(20, 0, 20, 30), 20),
(Rect::new(40, 0, 20, 30), 20),
];
let result = apply_justify(Justify::SpaceBetween, &items, container, Direction::Horizontal);
assert_eq!(result.len(), 3);
assert_eq!(result[0].x, 0);
assert_eq!(result[1].x, 40);
assert_eq!(result[2].x, 80);
}
#[test]
fn apply_justify_center() {
let container = Rect::new(0, 0, 100, 30);
let items: Vec<(Rect, u16)> = vec![
(Rect::new(0, 0, 20, 30), 20),
];
let result = apply_justify(Justify::Center, &items, container, Direction::Horizontal);
assert_eq!(result[0].x, 40); }
#[test]
fn apply_justify_end_vertical() {
let container = Rect::new(0, 0, 100, 30);
let items: Vec<(Rect, u16)> = vec![
(Rect::new(0, 0, 100, 10), 10),
];
let result = apply_justify(Justify::End, &items, container, Direction::Vertical);
assert_eq!(result[0].y, 20); }
}