use std::mem;
use bevy::ecs::entity::Entity;
use bevy::math::Vec2;
use crate::layout::{
Layout, LayoutControl, LayoutOutput, ParagraphLayout, SpanLayout, StackLayout,
};
use super::{util::*, LayoutInfo, LayoutRange};
impl<D: Direction> Layout for StackLayout<D> {
fn place(
&self,
parent: &LayoutInfo,
entities: Vec<LayoutItem>,
range: &mut LayoutRange,
) -> LayoutOutput {
let margin = parent.margin;
range.resolve(entities.len());
stack::<D>(margin, &entities[range.to_range(entities.len())])
.normalized()
.with_max(entities.len())
}
fn dyn_clone(&self) -> Box<dyn Layout> {
Box::new(*self)
}
fn is_size_agnostic(&self) -> bool {
true
}
}
impl<D: StretchDir> Layout for SpanLayout<D> {
fn place(
&self,
parent: &LayoutInfo,
mut entities: Vec<LayoutItem>,
range: &mut LayoutRange,
) -> LayoutOutput {
let margin = parent.margin;
let dimension = parent.dimension;
range.resolve(entities.len());
let len = entities.len();
let entity_anchors = span::<D>(dimension, margin, &mut entities[range.to_range(len)]);
LayoutOutput {
entity_anchors,
dimension,
max_count: entities.len(),
}
.normalized()
.with_max(entities.len())
}
fn dyn_clone(&self) -> Box<dyn Layout> {
Box::new(*self)
}
}
impl<D1: StretchDir, D2: Direction> Layout for ParagraphLayout<D1, D2>
where
(D1, D2): DirectionPair,
{
fn place(
&self,
parent: &LayoutInfo,
entities: Vec<LayoutItem>,
_: &mut LayoutRange,
) -> LayoutOutput {
let margin = parent.margin;
let dim = parent.dimension;
paragraph::<D1, D2>(dim, margin, entities).normalized()
}
fn dyn_clone(&self) -> Box<dyn Layout> {
Box::new(*self)
}
}
fn trim<T>(slice: &[T], mut f: impl FnMut(&T) -> bool) -> &[T] {
let mut min = 0;
let mut max = slice.len();
for (i, v) in slice.iter().enumerate() {
min = i;
if !f(v) {
break;
}
}
for (i, v) in slice.iter().enumerate().skip(min).rev() {
if !f(v) {
break;
}
max = i;
}
&slice[min..max]
}
pub(crate) fn stack<D: Direction>(margin: Vec2, items: &[LayoutItem]) -> LayoutOutput {
let mut result = Vec::new();
let margin = D::main(margin);
let mut cursor = -margin;
let line_height = D::side_vec(1.0);
let mut max_len = Vec2::ZERO;
let items = trim(items, |x| x.control == LayoutControl::WhiteSpace);
for item in items {
cursor += margin;
let width = D::main(item.dimension);
let size = width + line_height;
max_len = max_len.max(item.dimension);
let anchor = cursor + (size / 2.0) + item.anchor * size.abs();
result.push((item.entity, anchor));
cursor += width;
}
let height_mult = D::side(max_len) + D::main(Vec2::ONE).abs();
result.iter_mut().for_each(|(_, x)| *x *= height_mult);
if cursor.cmplt(Vec2::ZERO).any() {
let roll = cursor.min(Vec2::ZERO);
result.iter_mut().for_each(|(_, x)| *x -= roll);
}
LayoutOutput {
entity_anchors: result,
dimension: cursor.abs() + height_mult,
max_count: items.len(),
}
}
pub(crate) fn span<D: StretchDir>(
size: Vec2,
margin: Vec2,
items: &mut [LayoutItem],
) -> Vec<(Entity, Vec2)> {
let mut result = Vec::new();
let major_dim = D::Pos::main(size);
let minor_dim = D::Pos::side(size);
let mut neg_len = 0usize;
let mut mid_len = 0usize;
let mut pos_len = 0usize;
if D::reversed() {
items.reverse();
}
items.iter().for_each(|x| match D::bucket(x.anchor) {
Trinary::Neg => neg_len += 1,
Trinary::Mid => mid_len += 1,
Trinary::Pos => pos_len += 1,
});
items.sort_by_cached_key(|x| match D::bucket(x.anchor) {
Trinary::Neg => 0,
Trinary::Mid => 1,
Trinary::Pos => 2,
});
let neg = trim(&items[0..neg_len], |x| {
x.control == LayoutControl::WhiteSpace
});
let mid = trim(&items[neg_len..neg_len + mid_len], |x| {
x.control == LayoutControl::WhiteSpace
});
let pos = trim(
&items[neg_len + mid_len..neg_len + mid_len + pos_len],
|x| x.control == LayoutControl::WhiteSpace,
);
let mut neg_cursor = Vec2::ZERO;
let mut mid_cursor = Vec2::ZERO;
let mut pos_cursor = Vec2::ZERO;
for item in neg {
let cell_size = D::Pos::main(item.dimension) + minor_dim;
result.push((item.entity, neg_cursor + cell_size * (item.anchor + 0.5)));
neg_cursor += D::Pos::main(item.dimension)
}
for item in mid {
let cell_size = D::Pos::main(item.dimension) + minor_dim;
result.push((item.entity, mid_cursor + cell_size * (item.anchor + 0.5)));
mid_cursor += D::Pos::main(item.dimension)
}
for item in pos {
let cell_size = D::Pos::main(item.dimension) + minor_dim;
result.push((item.entity, pos_cursor + cell_size * (item.anchor + 0.5)));
pos_cursor += D::Pos::main(item.dimension)
}
let margin = if D::STRETCH {
if result.len() <= 1 {
Vec2::ZERO
} else {
let remaining = major_dim - neg_cursor - mid_cursor - pos_cursor;
remaining / (result.len() - 1) as f32
}
} else {
D::Pos::main(margin)
};
neg_cursor += margin * neg.len().saturating_sub(1) as f32;
mid_cursor += margin * mid.len().saturating_sub(1) as f32;
pos_cursor += margin * pos.len().saturating_sub(1) as f32;
let mut neg_index = 0.0;
let mut mid_index = 0.0;
let mut pos_index = 0.0;
let neg_len = neg_cursor.max(Vec2::ZERO);
let pos_len = pos_cursor.max(Vec2::ZERO);
let pos_offset = major_dim - pos_len;
let mid_offset = (major_dim + neg_len - pos_len - mid_cursor) / 2.0;
let categories = std::iter::repeat_n(Trinary::Neg, neg.len())
.chain(std::iter::repeat_n(Trinary::Mid, mid.len()))
.chain(std::iter::repeat_n(Trinary::Pos, pos.len()));
for ((_, pos), category) in result.iter_mut().zip(categories) {
match category {
Trinary::Neg => {
*pos += margin * neg_index;
neg_index += 1.0;
}
Trinary::Mid => {
*pos += margin * mid_index + mid_offset;
mid_index += 1.0;
}
Trinary::Pos => {
*pos += margin * pos_index + pos_offset;
pos_index += 1.0;
}
}
}
result
}
pub(crate) fn paragraph<D1: StretchDir, D2: Direction>(
size: Vec2,
margin: Vec2,
items: impl IntoIterator<Item = LayoutItem>,
) -> LayoutOutput {
let margin_flat = D1::len(margin);
let total = D1::len(size);
let mut len = 0.0;
let mut result = Vec::new();
let mut buffer = Vec::new();
let mut cursor = Vec2::ZERO;
let mut last_linebreak = false;
let mut lines = 0;
for item in items {
if len + D1::len(item.dimension) > total
|| item.control == LayoutControl::LinebreakMarker
|| last_linebreak
{
last_linebreak = false;
let line_height = buffer
.iter()
.map(|x: &LayoutItem| D2::main(x.dimension).abs())
.fold(Vec2::ZERO, |a, b| a.max(b));
let line_size = D1::main(size) + line_height;
let mut span = span::<D1>(line_size, margin, &mut mem::take(&mut buffer));
let line_height = if item.control == LayoutControl::LinebreakMarker {
D2::main(line_height.max(item.dimension))
} else {
D2::main(line_height)
};
cursor += line_height.min(Vec2::ZERO);
span.iter_mut().for_each(|(_, x)| *x += cursor);
cursor += line_height.max(Vec2::ZERO);
cursor += D2::main(margin);
result.extend(span);
len = D1::len(item.dimension) + margin_flat;
lines += 1;
} else {
len += D1::len(item.dimension) + margin_flat;
}
if item.control == LayoutControl::Linebreak {
last_linebreak = true;
}
if item.control != LayoutControl::LinebreakMarker {
buffer.push(item)
}
}
if !buffer.is_empty() {
let line_height = buffer
.iter()
.map(|x: &LayoutItem| D2::main(x.dimension).abs())
.fold(Vec2::ZERO, |a, b| a.max(b));
let line_size = D1::main(size) + line_height;
let mut span = span::<D1>(line_size, margin, &mut buffer);
cursor += D2::main(line_height).min(Vec2::ZERO);
span.iter_mut().for_each(|(_, x)| *x += cursor);
cursor += D2::main(line_height).max(Vec2::ZERO);
result.extend(span);
lines += 1;
} else if cursor != Vec2::ZERO {
cursor -= D2::main(margin);
}
if cursor.cmplt(Vec2::ZERO).any() {
result
.iter_mut()
.for_each(|(_, x)| *x -= cursor.min(Vec2::ZERO))
}
LayoutOutput {
entity_anchors: result,
dimension: cursor.abs() + D1::main(size),
max_count: lines,
}
}