use smallvec::SmallVec;
use std::{
cmp,
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use super::{
template::{ComponentDef, DynamicTemplate},
Component,
};
use crate::terminal::{Canvas, Position, Rect, Size};
pub trait ComponentExt: Component {
fn with(properties: Self::Properties) -> Layout {
Layout(LayoutNode::Component(DynamicTemplate(Box::new(
ComponentDef::<Self>::new(None, properties),
))))
}
fn with_key(key: impl Into<ComponentKey>, properties: Self::Properties) -> Layout {
Layout(LayoutNode::Component(DynamicTemplate(Box::new(
ComponentDef::<Self>::new(Some(key.into()), properties),
))))
}
fn item_with(flex: FlexBasis, properties: Self::Properties) -> Item {
Item {
flex,
node: Layout(LayoutNode::Component(DynamicTemplate(Box::new(
ComponentDef::<Self>::new(None, properties),
)))),
}
}
fn item_with_key(
flex: FlexBasis,
key: impl Into<ComponentKey>,
properties: Self::Properties,
) -> Item {
Item {
flex,
node: Layout(LayoutNode::Component(DynamicTemplate(Box::new(
ComponentDef::<Self>::new(Some(key.into()), properties),
)))),
}
}
}
impl<T: Component> ComponentExt for T {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ComponentKey(usize);
impl From<usize> for ComponentKey {
fn from(key: usize) -> Self {
Self(key)
}
}
impl From<&str> for ComponentKey {
fn from(key: &str) -> Self {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
Self(hasher.finish() as usize)
}
}
pub struct Layout(pub(crate) LayoutNode);
impl Layout {
#[inline]
pub fn container(direction: FlexDirection, items: impl IntoIterator<Item = Item>) -> Self {
Container::new(direction, items).into()
}
#[inline]
pub fn column(items: impl IntoIterator<Item = Item>) -> Self {
Container::column(items).into()
}
#[inline]
pub fn column_reverse(items: impl IntoIterator<Item = Item>) -> Self {
Container::column_reverse(items).into()
}
#[inline]
pub fn row(items: impl IntoIterator<Item = Item>) -> Self {
Container::row(items).into()
}
#[inline]
pub fn row_reverse(items: impl IntoIterator<Item = Item>) -> Self {
Container::row_reverse(items).into()
}
}
pub(crate) enum LayoutNode {
Container(Box<Container>),
Component(DynamicTemplate),
Canvas(Canvas),
}
impl LayoutNode {
pub(crate) fn crawl(
&mut self,
frame: Rect,
position_hash: u64,
view_fn: &mut impl FnMut(LaidComponent),
draw_fn: &mut impl FnMut(LaidCanvas),
) {
let mut hasher = DefaultHasher::new();
hasher.write_u64(position_hash);
match self {
Self::Container(container) => {
hasher.write_u64(Self::CONTAINER_HASH);
if container.direction.is_reversed() {
let frames: SmallVec<[_; ITEMS_INLINE_SIZE]> =
splits_iter(frame, container.direction, container.children.iter().rev())
.collect();
for (child, frame) in container.children.iter_mut().rev().zip(frames) {
child.node.0.crawl(frame, hasher.finish(), view_fn, draw_fn);
}
} else {
let frames: SmallVec<[_; ITEMS_INLINE_SIZE]> =
splits_iter(frame, container.direction, container.children.iter())
.collect();
for (child, frame) in container.children.iter_mut().zip(frames) {
child.node.0.crawl(frame, hasher.finish(), view_fn, draw_fn);
}
}
}
Self::Component(template) => {
template.component_type_id().hash(&mut hasher);
if let Some(key) = template.key() {
key.hash(&mut hasher);
}
view_fn(LaidComponent {
frame,
position_hash: hasher.finish(),
template,
});
}
Self::Canvas(canvas) => {
draw_fn(LaidCanvas { frame, canvas });
}
};
}
const CONTAINER_HASH: u64 = 0x5aa2d5349a05cde8;
}
impl From<Canvas> for Layout {
fn from(canvas: Canvas) -> Self {
Self(LayoutNode::Canvas(canvas))
}
}
const ITEMS_INLINE_SIZE: usize = 4;
type Items = SmallVec<[Item; ITEMS_INLINE_SIZE]>;
pub struct Container {
children: Items,
direction: FlexDirection,
}
impl Container {
#[inline]
pub fn new(direction: FlexDirection, items: impl IntoIterator<Item = Item>) -> Self {
Self {
children: items.into_iter().collect(),
direction,
}
}
#[inline]
pub fn empty(direction: FlexDirection) -> Self {
Self {
children: SmallVec::new(),
direction,
}
}
#[inline]
pub fn push(&mut self, item: Item) -> &mut Self {
self.children.push(item);
self
}
#[inline]
pub fn column(items: impl IntoIterator<Item = Item>) -> Self {
Self::new(FlexDirection::Column, items)
}
#[inline]
pub fn column_reverse(items: impl IntoIterator<Item = Item>) -> Self {
Self::new(FlexDirection::ColumnReverse, items)
}
#[inline]
pub fn row(items: impl IntoIterator<Item = Item>) -> Self {
Self::new(FlexDirection::Row, items)
}
#[inline]
pub fn row_reverse(items: impl IntoIterator<Item = Item>) -> Self {
Self::new(FlexDirection::RowReverse, items)
}
}
impl From<Container> for Layout {
fn from(container: Container) -> Self {
Layout(LayoutNode::Container(Box::new(container)))
}
}
pub struct Item {
node: Layout,
flex: FlexBasis,
}
impl Item {
#[inline]
pub fn auto(layout: impl Into<Layout>) -> Item {
Item {
node: layout.into(),
flex: FlexBasis::Auto,
}
}
#[inline]
pub fn fixed<LayoutT>(size: usize) -> impl FnOnce(LayoutT) -> Item
where
LayoutT: Into<Layout>,
{
move |layout| Item {
node: layout.into(),
flex: FlexBasis::Fixed(size),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FlexBasis {
Auto,
Fixed(usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FlexDirection {
Column,
ColumnReverse,
Row,
RowReverse,
}
impl FlexDirection {
#[inline]
pub fn is_reversed(&self) -> bool {
match self {
FlexDirection::Column | FlexDirection::Row => false,
FlexDirection::ColumnReverse | FlexDirection::RowReverse => true,
}
}
#[inline]
pub(crate) fn dimension(self, size: Size) -> usize {
match self {
FlexDirection::Row => size.width,
FlexDirection::RowReverse => size.width,
FlexDirection::Column => size.height,
FlexDirection::ColumnReverse => size.height,
}
}
}
pub(crate) struct LaidComponent<'a> {
pub frame: Rect,
pub position_hash: u64,
pub template: &'a mut DynamicTemplate,
}
pub(crate) struct LaidCanvas<'a> {
pub frame: Rect,
pub canvas: &'a Canvas,
}
#[inline]
fn splits_iter<'a>(
frame: Rect,
direction: FlexDirection,
children: impl Iterator<Item = &'a Item> + Clone + 'a,
) -> impl Iterator<Item = Rect> + 'a {
let total_size = direction.dimension(frame.size);
let (stretched_budget, num_stretched_children, total_fixed_size) = {
let mut stretched_budget = total_size;
let mut num_stretched_children = 0;
let mut total_fixed_size = 0;
for child in children.clone() {
match child.flex {
FlexBasis::Auto => {
num_stretched_children += 1;
}
FlexBasis::Fixed(size) => {
stretched_budget = stretched_budget.saturating_sub(size);
total_fixed_size += size;
}
}
}
(stretched_budget, num_stretched_children, total_fixed_size)
};
let stretched_size = if num_stretched_children > 0 {
stretched_budget / num_stretched_children
} else {
0
};
let mut remainder =
total_size.saturating_sub(num_stretched_children * stretched_size + total_fixed_size);
let mut remaining_size = total_size;
children
.map(move |child| match child.flex {
FlexBasis::Auto => {
let offset = total_size - remaining_size;
let size = if remainder > 0 {
remainder -= 1;
stretched_size + 1
} else {
stretched_size
};
remaining_size -= size;
(offset, size)
}
FlexBasis::Fixed(size) => {
let offset = total_size - remaining_size;
let size = cmp::min(remaining_size, size);
remaining_size -= size;
(offset, size)
}
})
.map(move |(offset, size)| match direction {
FlexDirection::Row | FlexDirection::RowReverse => Rect::new(
Position::new(frame.origin.x + offset, frame.origin.y),
Size::new(size, frame.size.height),
),
FlexDirection::Column | FlexDirection::ColumnReverse => Rect::new(
Position::new(frame.origin.x, frame.origin.y + offset),
Size::new(frame.size.width, size),
),
})
}