a3s-tui 0.1.14

TEA (The Elm Architecture) framework for terminal user interfaces
Documentation
use a3s_tui::cmd;
use a3s_tui::components::{
    Breadcrumb, Confirm, KeyValue, Paragraph, Scrollbar, ToastKind, ToastManager, Tree, TreeNode,
};
use a3s_tui::element::{BoxElement, FlexDirection, TextElement};
use a3s_tui::style::Color;
use a3s_tui::{col, row, Element, ElementModel, ElementProgramBuilder, Event};
use std::time::Duration;

struct App {
    toasts: ToastManager,
    confirm: Option<Confirm>,
    tick_count: u64,
}

enum Msg {
    Quit,
    Tick,
    ShowConfirm,
    AddToast,
    Noop,
}

impl From<Event> for Msg {
    fn from(event: Event) -> Self {
        match &event {
            Event::Key(k) if k.is_char('q') => Msg::Quit,
            Event::Key(k) if k.is_ctrl('c') => Msg::Quit,
            Event::Key(k) if k.is_char('c') => Msg::ShowConfirm,
            Event::Key(k) if k.is_char('t') => Msg::AddToast,
            _ => Msg::Noop,
        }
    }
}

impl ElementModel for App {
    type Msg = Msg;

    fn init(&mut self) -> Option<cmd::Cmd<Msg>> {
        Some(cmd::tick(Duration::from_millis(100), Msg::Tick))
    }

    fn update(&mut self, msg: Msg) -> Option<cmd::Cmd<Msg>> {
        match msg {
            Msg::Quit => Some(cmd::quit()),
            Msg::Tick => {
                self.tick_count += 1;
                self.toasts.tick();
                Some(cmd::tick(Duration::from_millis(100), Msg::Tick))
            }
            Msg::ShowConfirm => {
                self.confirm = Some(Confirm::new("Are you sure?"));
                None
            }
            Msg::AddToast => {
                let kinds = [
                    ToastKind::Info,
                    ToastKind::Success,
                    ToastKind::Warning,
                    ToastKind::Error,
                ];
                let kind = kinds[self.tick_count as usize % 4];
                self.toasts
                    .push(kind, format!("Notification #{}", self.tick_count));
                None
            }
            Msg::Noop => None,
        }
    }

    fn view(&self) -> Element<Msg> {
        let header = Element::Box(
            BoxElement::new()
                .direction(FlexDirection::Row)
                .bg(Color::gray(30))
                .child(Element::Text(
                    TextElement::new(" Widgets Demo").bold().fg(Color::White),
                ))
                .child(Element::Spacer)
                .child(Element::Text(
                    TextElement::new("t:toast c:confirm q:quit ").fg(Color::gray(150)),
                )),
        );

        let breadcrumb = Breadcrumb::new(vec!["Home", "Components", "Widgets"])
            .active_color(Color::Cyan)
            .element();

        let tree = Tree::new(TreeNode::branch(
            "a3s-tui",
            vec![
                TreeNode::branch(
                    "src",
                    vec![
                        TreeNode::leaf("lib.rs"),
                        TreeNode::leaf("element.rs"),
                        TreeNode::branch(
                            "components",
                            vec![TreeNode::leaf("toast.rs"), TreeNode::leaf("tree.rs")],
                        ),
                    ],
                ),
                TreeNode::leaf("Cargo.toml"),
            ],
        ))
        .element();

        let kv = KeyValue::new()
            .pair("Version", "0.1.0")
            .pair("Components", "21")
            .pair("Tests", "184")
            .pair("Lines", "8,242")
            .key_color(Color::BrightBlack)
            .value_color(Color::Cyan)
            .element();

        let paragraph = Paragraph::new(
            "A3S TUI is a TEA framework for building terminal UIs with Flexbox layout, \
             incremental rendering, and a rich component library.",
        )
        .width(50)
        .indent(2)
        .color(Color::gray(200))
        .element();

        let scrollbar = Scrollbar::new(100, 20, (self.tick_count as usize / 5) % 81)
            .thumb_color(Color::Cyan)
            .element(10);

        let mut content = col![
            header,
            Element::Text(TextElement::new("")),
            Element::Text(TextElement::new("  ").fg(Color::White)),
            breadcrumb,
            Element::Text(TextElement::new("")),
            Element::Text(TextElement::new("  File Tree:").bold().fg(Color::White)),
            tree,
            Element::Text(TextElement::new("")),
            Element::Text(TextElement::new("  Stats:").bold().fg(Color::White)),
            kv,
            Element::Text(TextElement::new("")),
            Element::Text(TextElement::new("  About:").bold().fg(Color::White)),
            paragraph,
            Element::Text(TextElement::new("")),
            row![Element::Text(TextElement::new("  Scroll: ")), scrollbar,],
        ];

        if !self.toasts.is_empty() {
            content = col![
                content,
                Element::Text(TextElement::new("")),
                self.toasts.element(),
            ];
        }

        if let Some(ref confirm) = self.confirm {
            content = col![
                content,
                Element::Text(TextElement::new("")),
                confirm.element(),
            ];
        }

        content
    }
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let app = App {
        toasts: ToastManager::new().with_duration(Duration::from_secs(3)),
        confirm: None,
        tick_count: 0,
    };

    ElementProgramBuilder::new(app)
        .with_alt_screen()
        .with_fps(30)
        .run()
        .await
}