futtlui 0.1.0

A high-level Rust UI library built on top of FUTTL.
Documentation
use futtl::Futtl;

#[derive(Copy, Clone)]
pub enum Layout {
    Vh(i32),
    Vw(i32),
    Percent(i32),
    Cell(i32),
    Auto
}

#[derive(Copy, Clone)]
pub struct LayoutVec2 {
    pub x: Layout,
    pub y: Layout
}

impl LayoutVec2 {
    pub fn new(x: Layout, y: Layout) -> Self {
        Self {x, y}
    }
}

pub struct Style {
    pub position: LayoutVec2,
    pub size: LayoutVec2,
    pub margin: LayoutVec2,
    pub color: futtl::ColorSet,
    pub attributes: futtl::Attr
}

pub enum WidgetCtx {
    Box(usize),
    None
}

pub struct Text {
    pub text: String,
    pub style: Style
}

pub struct Widget {
    pub ctx: WidgetCtx,
    pub style: Style,
    pub children: Vec<Widget>,
    pub content: Vec<Text>
}

pub struct Win {
    pub ctx: futtl::Win,
    pub children: Vec<Widget>,
    pub color: futtl::ColorSet
}

impl Style {
    pub fn new() -> Self {
        Self {
            position: LayoutVec2 {x: Layout::Auto, y: Layout::Auto},
            size: LayoutVec2 {x: Layout::Auto, y: Layout::Auto},
            margin: LayoutVec2 {x: Layout::Auto, y: Layout::Auto},
            color: futtl::ColorSet::new_default(),
            attributes: futtl::Attr::NONE
        }
    }
}


fn evaluate_size(term_size: futtl::Vec2, parent_size: futtl::Vec2, size_obj: LayoutVec2) -> futtl::Vec2 {
    let x = match size_obj.x {
        Layout::Auto => term_size.x,
        Layout::Vw(n) => parent_size.x * n / 100,
        Layout::Vh(_) => 0,
        Layout::Percent(n) => term_size.x * n / 100,
        Layout::Cell(n) => n
    };
    let y = match size_obj.y {
        Layout::Auto => term_size.y,
        Layout::Vw(_) => 0,
        Layout::Vh(n) => parent_size.y * n / 100,
        Layout::Percent(n) => term_size.y * n / 100,
        Layout::Cell(n) => n
    };

    futtl::Vec2::new(x, y)
}

fn evaluate_pos(term_size: futtl::Vec2, parent_size: futtl::Vec2, pos_obj: LayoutVec2) -> futtl::Vec2 {
    let x = match pos_obj.x {
        Layout::Auto => term_size.x,
        Layout::Vw(n) => parent_size.x * n / 100,
        Layout::Vh(_) => 0,
        Layout::Percent(n) => term_size.x * n / 100,
        Layout::Cell(n) => n
    };
    let y = match pos_obj.y {
        Layout::Auto => term_size.y,
        Layout::Vw(_) => 0,
        Layout::Vh(n) => parent_size.y * n / 100,
        Layout::Percent(n) => term_size.y * n / 100,
        Layout::Cell(n) => n
    };

    futtl::Vec2::new(x, y)
}

impl Win {
    pub fn new() -> Self {
        Self {
            ctx: futtl::Win::new(256, futtl::Attr::NONE),
            children: Vec::new(),
            color: futtl::ColorSet::new(
                futtl::Color::ANSI(futtl::AnsiColor::DefaultFG),
                futtl::Color::ANSI(futtl::AnsiColor::DefaultBG),
            ),
        }
    }

    pub fn pack(&mut self, widget: Widget) {
        self.children.push(widget);
    }

    fn evaluate_members(&mut self) {
        let term_size = self.ctx.get_size();

        for child in self.children.iter_mut() {
            let child_size = evaluate_size(term_size, term_size, child.style.size);

            let x_size = child_size.x.min(term_size.x);
            let y_size = child_size.y.min(term_size.y);

            let idx = self.ctx.create_container(x_size, y_size);

            child.ctx = WidgetCtx::Box(idx);
        }
    }

    pub fn mainloop(&mut self) {
        self.evaluate_members();

        let term_size = self.ctx.get_size();
        for child in self.children.iter_mut() {
            match child.ctx {
                WidgetCtx::None => {},
                WidgetCtx::Box(_) => {
                    child.show(term_size, &mut self.ctx);
                }
            }
        }

        futtl::flush();

        let c = self.ctx.get_in();
        if !((c == 'q' as i32) || (c == 27)) {
            self.mainloop();
        }
    }
}

pub trait Stylable: Sized {
    fn style(&mut self) -> &mut Style;

    fn foreground(mut self, col: futtl::Color) -> Self {
        self.style().color.fg = col;
        self
    }

    fn background(mut self, col: futtl::Color) -> Self {
        self.style().color.bg = col;
        self
    }

    fn position(mut self, x: Layout, y: Layout) -> Self {
        self.style().position.x = x;
        self.style().position.y = y;
        self
    }

    fn position_x(mut self, x: Layout) -> Self {
        self.style().position.x = x;
        self
    }

    fn position_y(mut self, y: Layout) -> Self {
        self.style().position.y = y;
        self
    }

    fn size(mut self, x: Layout, y: Layout) -> Self {
        self.style().size.x = x;
        self.style().size.y = y;
        self
    }

    fn size_x(mut self, x: Layout) -> Self {
        self.style().size.x = x;
        self
    }

    fn size_y(mut self, y: Layout) -> Self {
        self.style().size.y = y;
        self
    }

    fn margin(mut self, x: Layout, y: Layout) -> Self {
        self.style().margin.x = x;
        self.style().margin.y = y;
        self
    }

    fn margin_x(mut self, x: Layout) -> Self {
        self.style().margin.x = x;
        self
    }

    fn margin_y(mut self, y: Layout) -> Self {
        self.style().margin.y = y;
        self
    }

    fn attribute(mut self, attr: futtl::Attr) -> Self {
        self.style().attributes |= attr;
        self
    }

    fn remove_attribute(mut self, attr: futtl::Attr) -> Self {
        self.style().attributes &= !attr;
        self
    }
}

impl Stylable for Widget {
    fn style(&mut self) -> &mut Style {
        &mut self.style
    }
}

impl Stylable for Text {
    fn style(&mut self) -> &mut Style {
        &mut self.style
    }
}

impl Widget {
    pub fn new() -> Self {
        Self {
            ctx: WidgetCtx::None,
            style: Style::new(),
            children: Vec::new(),
            content: Vec::new()
        }
    }

    pub fn style(&mut self, style: Style) {
        self.style = style;
    }

    pub fn text(&mut self, text: String) -> &mut Text {
        self.content.push(Text {
            text,
            style: Style::new(),
        });

        self.content.last_mut().unwrap()
    }

    pub fn show(&mut self, term_size: futtl::Vec2, f_ctx: &mut futtl::Win) {
        match self.ctx {
            WidgetCtx::None => {}

            WidgetCtx::Box(ctx) => {
                let pos = evaluate_pos(
                    term_size,
                    term_size,
                    self.style.position,
                );

                let box_ctx = &mut f_ctx.children()[ctx];

                box_ctx.clear();
                box_ctx.set_curs(0, 0);

                box_ctx.add_color(self.style.color, 0);
                box_ctx.set_color(0);

                for item in self.content.iter() {
                    box_ctx.put_str(&item.text);
                }

                box_ctx.show(pos.x, pos.y);
            }
        }
    }
}


impl Default for Win {
    fn default() -> Self {
        Self::new()
    }
}

impl Default for Widget {
    fn default() -> Self {
        Self::new()
    }
}

impl Default for Style {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut ctx = Win::new();

        let mut text_widget = Widget::new()
            .position_x(Layout::Cell(0))
            .position_y(Layout::Cell(0))
            .size_x(Layout::Vw(100))
            .size_y(Layout::Vh(100));

        text_widget.text("test".to_string());

        ctx.pack(text_widget);

        ctx.mainloop();
    }
}