termit-ui 0.0.1

Terminal UI with GUI-like layouts
Documentation
use crate::geometry::*;
use crate::widget::capture::CaptureState;
use crate::widget::placement::*;
use crate::widget::*;

pub trait Layout {
    fn layout(&self, size: Point, element: usize, total: usize) -> Window;
}

pub trait LayoutElement {
    fn widget<'a>(&'a self) -> &'a Widget;
    fn widget_mut<'a>(&'a mut self) -> &'a mut Widget;
    fn placement<'a>(&'a self) -> &'a Placement;
    fn state<'a>(&'a self) -> &'a CaptureState;
    fn state_mut<'a>(&'a mut self) -> &'a mut CaptureState;
}

pub trait IntoLayoutElement<T>: Sized
where
    T: LayoutElement,
{
    fn into_layout_element(self) -> T;
}

impl<W> IntoLayoutElement<(W, Place, CaptureState)> for W
where
    W: Widget,
{
    fn into_layout_element(self) -> (W, Place, CaptureState) {
        (self, Place, CaptureState::default())
    }
}

impl<W, P> IntoLayoutElement<(W, P, CaptureState)> for (W, P)
where
    W: Widget,
    P: Placement,
{
    fn into_layout_element(self) -> (W, P, CaptureState) {
        (self.0, self.1, CaptureState::default())
    }
}

impl<L> IntoLayoutElement<L> for L
where
    L: LayoutElement,
{
    fn into_layout_element(self) -> L {
        self
    }
}

impl<W> AnchorPlaced<(W, AnchorPlacement)> for W
where
    W: Widget,
{
    fn anchor_left(self, anchor: u16) -> (W, AnchorPlacement) {
        (self, Place.anchor_left(anchor))
    }
    fn left_size(self, size: u16) -> (W, AnchorPlacement) {
        (self, Place.left_size(size))
    }
    fn anchor_right(self, anchor: u16) -> (W, AnchorPlacement) {
        (self, Place.anchor_right(anchor))
    }
    fn right_size(self, size: u16) -> (W, AnchorPlacement) {
        (self, Place.right_size(size))
    }
    fn anchor_top(self, anchor: u16) -> (W, AnchorPlacement) {
        (self, Place.anchor_top(anchor))
    }
    fn top_size(self, size: u16) -> (W, AnchorPlacement) {
        (self, Place.top_size(size))
    }
    fn anchor_bottom(self, anchor: u16) -> (W, AnchorPlacement) {
        (self, Place.anchor_bottom(anchor))
    }
    fn bottom_size(self, size: u16) -> (W, AnchorPlacement) {
        (self, Place.bottom_size(size))
    }
    fn fill(self) -> (W, AnchorPlacement) {
        (self, Place.fill())
    }
}

impl<W> AnchorPlaced<(W, AnchorPlacement)> for (W, AnchorPlacement)
where
    W: Widget,
{
    fn anchor_left(self, anchor: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.anchor_left(anchor))
    }
    fn left_size(self, size: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.left_size(size))
    }
    fn anchor_right(self, anchor: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.anchor_right(anchor))
    }
    fn right_size(self, size: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.right_size(size))
    }
    fn anchor_top(self, anchor: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.anchor_top(anchor))
    }
    fn top_size(self, size: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.top_size(size))
    }
    fn anchor_bottom(self, anchor: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.anchor_bottom(anchor))
    }
    fn bottom_size(self, size: u16) -> (W, AnchorPlacement) {
        (self.0, self.1.bottom_size(size))
    }
    fn fill(self) -> (W, AnchorPlacement) {
        (self.0, self.1.fill())
    }
}

impl<P: Placement, W: Widget> LayoutElement for (W, P, CaptureState) {
    fn widget<'a>(&'a self) -> &'a Widget {
        &self.0
    }
    fn widget_mut<'a>(&'a mut self) -> &'a mut Widget {
        &mut self.0
    }
    fn placement<'a>(&'a self) -> &'a Placement {
        &self.1
    }
    fn state<'a>(&'a self) -> &'a CaptureState {
        &self.2
    }
    fn state_mut<'a>(&'a mut self) -> &'a mut CaptureState {
        &mut self.2
    }
}

pub struct FreeLayout;

pub struct GridLayout {
    rows: Vec<u8>,
    columns: Vec<u8>,
}

impl GridLayout {
    pub fn new(columns: &[u8], rows: &[u8]) -> Self {
        let columns = if columns.len() == 0 {
            vec![255]
        } else {
            columns.into()
        };
        let rows = if rows.len() == 0 {
            vec![255]
        } else {
            rows.into()
        };
        GridLayout { rows, columns }
    }
}

impl FreeLayout {
    pub fn with_grid(&self, columns: &[u8], rows: &[u8]) -> GridLayout {
        GridLayout::new(columns, rows)
    }
}

impl Layout for FreeLayout {
    fn layout(&self, size: Point, _element: usize, _total: usize) -> Window {
        window(Point::default(), size)
    }
}

impl Layout for GridLayout {
    fn layout(&self, size: Point, element: usize, total: usize) -> Window {
        let cols = self.columns.len();
        let rows = self.rows.len();

        if element >= cols * rows {
            // the element is outside of the grid, use free layout then
            return FreeLayout.layout(size, element, total);
        }

        let row = element / cols;
        let col = element % cols;

        let cell_x = cell(&self.columns, col, size.x.to_primitive());
        let cell_y = cell(&self.rows, row, size.y.to_primitive());

        window(
            point(x(cell_x.0), y(cell_y.0)),
            point(x(cell_x.1), y(cell_y.1)),
        )
    }
}

/// Calculate the start and width of the cell relative to the size
fn cell(grid: &[u8], idx: usize, size: u16) -> (u16, u16) {
    let mut total = 0u16;
    let mut start = 0u16;
    let mut cell = 0u16;
    for i in 0..grid.len() {
        let c = grid[i] as u16;
        total += c;
        if i < idx {
            start += c;
        } else if i == idx {
            cell = c;
        }
    }

    if total == 0 {
        // special case where total requested size is 0, distribute cells equally
        total = size;
        start = (total as usize / idx) as u16;
        cell = (size as usize / grid.len()) as u16;
    }

    if idx + 1 == grid.len() {
        // special case for last cell that takes the remainder
        (size * start / total, size - size * start / total)
    } else {
        (size * start / total, size * cell / total)
    }
}

#[test]
fn test_layout() {
    let sut = GridLayout {
        rows: vec![100, 100],
        columns: vec![100, 100, 100],
    };
    let size = point(x(10), y(13));
    assert_eq!(
        sut.layout(size, 0usize, 3),
        window(point(x(0), y(0)), point(x(3), y(6)))
    );
    assert_eq!(
        sut.layout(size, 5usize, 3),
        window(point(x(6), y(6)), point(x(4), y(7)))
    );
}