rat-widget 3.0.2

ratatui widgets extended edition
Documentation
use crate::mini_salsa::{MiniSalsaState, run_ui, setup_logging};
use rat_event::{ct_event, try_flow};
use rat_menu::event::MenuOutcome;
use rat_menu::menuline;
use rat_menu::menuline::{MenuLine, MenuLineState};
use rat_theme4::palette::Colors;
use rat_theme4::{StyleName, WidgetStyle, create_salsa_theme};
use rat_widget::event::Outcome;
use rat_widget::layout::layout_middle;
use rat_widget::msgdialog;
use rat_widget::msgdialog::{MsgDialog, MsgDialogState};
use rat_widget::statusline_stacked::{SLANT_BL_TR, SLANT_TL_BR, StatusLineStacked};
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Layout, Rect};
use ratatui_core::style::Style;
use ratatui_core::text::Span;
use ratatui_core::widgets::{StatefulWidget, Widget};
use ratatui_crossterm::crossterm::event::Event;
use std::iter::repeat_with;

mod mini_salsa;

fn main() -> Result<(), anyhow::Error> {
    setup_logging()?;

    let mut state = State {
        menu: Default::default(),
        msg: Default::default(),
        msg_count: Default::default(),
        mode: Default::default(),
        status_styling: Default::default(),
    };

    run_ui(
        "menu_status2", //
        init,
        event,
        render,
        &mut state,
    )
}

struct State {
    menu: MenuLineState,
    msg: MsgDialogState,
    msg_count: u32,

    mode: i32,
    status_styling: i32,
}

fn init(ctx: &mut MiniSalsaState, _state: &mut State) -> Result<(), anyhow::Error> {
    ctx.theme = create_salsa_theme("Radium Shell");
    ctx.hide_status = true;
    Ok(())
}

fn render(
    buf: &mut Buffer,
    area: Rect,
    ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<(), anyhow::Error> {
    let l1 = Layout::vertical([
        Constraint::Length(1), //
        Constraint::Fill(1),
        Constraint::Length(1),
    ])
    .split(area);

    MenuLine::new()
        .item_parsed(mtxt("STATE\u{FF3F}_1", 0, state).as_str())
        .item_parsed(mtxt("STATE\u{FF3F}_2", 1, state).as_str())
        .item_parsed(mtxt("STATE\u{FF3F}_3", 2, state).as_str())
        .item_parsed(mtxt2("_MESSAGE|F1", "_MESSAGE", 3, state).as_str())
        .item_parsed(mtxt("_STYLE", 4, state).as_str())
        .item_parsed(mtxt("_QUIT", 5, state).as_str())
        .styles(ctx.theme.style(WidgetStyle::MENU))
        .focus_style(ctx.theme.p.primary(7))
        .render(l1[0], buf, &mut state.menu);

    match state.status_styling {
        0 => stacked_1(buf, l1[2], ctx, state),
        1 => stacked_2(buf, l1[2], ctx, state),
        _ => unreachable!(),
    }

    if state.msg.active() {
        let l_msg = layout_middle(
            l1[1],
            Constraint::Percentage(19),
            Constraint::Percentage(19),
            Constraint::Percentage(19),
            Constraint::Percentage(19),
        );
        MsgDialog::new()
            .styles(ctx.theme.style(WidgetStyle::MSG_DIALOG))
            .render(l_msg, buf, &mut state.msg);
    }

    Ok(())
}

fn mtxt(txt: &str, idx: usize, state: &mut State) -> String {
    if state.menu.selected == Some(idx) {
        format!("[{}]", txt).to_string()
    } else {
        txt.to_string()
    }
}

fn mtxt2(txt: &str, txt_sel: &str, idx: usize, state: &mut State) -> String {
    if state.menu.selected == Some(idx) {
        format!("[{}]", txt_sel).to_string()
    } else {
        txt.to_string()
    }
}

fn stacked_1(buf: &mut Buffer, area: Rect, ctx: &mut MiniSalsaState, state: &mut State) {
    let pal = &ctx.theme.p;
    let color_0 = pal.color(Colors::Gray, 3);
    let color_1 = match state.mode {
        0 => pal.color(Colors::Green, 3),
        1 => pal.color(Colors::Yellow, 3),
        2 => pal.color(Colors::Red, 3),
        _ => unreachable!(),
    };
    let color_3 = pal.color(Colors::Cyan, 0);
    let color_4 = pal.color(Colors::Cyan, 7);

    let mode_str = match state.mode {
        0 => " OPERATIONAL ",
        1 => " DIRE ",
        2 => " EVACUATE ",
        _ => unreachable!(),
    };

    StatusLineStacked::new()
        .style(ctx.theme.style(Style::STATUS_BASE))
        .start(
            Span::from(" WESTINGHOUSE[STATUS]2 ")
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_0)),
            Span::from(SLANT_TL_BR).style(Style::new().fg(color_0).bg(color_1)),
        )
        .start(
            Span::from(mode_str).style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_1)),
            Span::from(SLANT_TL_BR).style(Style::new().fg(color_1)),
        )
        .center_margin(1)
        .center(ctx.status[0].as_str())
        .end(
            Span::from(format!("R[{}][{:.0?} ", ctx.frame, ctx.last_render))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
            Span::from(SLANT_BL_TR).style(Style::new().fg(color_3).bg(color_4)),
        )
        .end(
            "",
            Span::from(SLANT_BL_TR).style(Style::new().fg(color_4).bg(color_3)),
        )
        .end(
            Span::from(format!("E[{}][{:.0?}", ctx.event_cnt, ctx.last_event))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
            Span::from(SLANT_BL_TR).style(Style::new().fg(color_3).bg(color_4)),
        )
        .end(
            "",
            Span::from(SLANT_BL_TR).style(Style::new().fg(color_4).bg(color_3)),
        )
        .end(
            Span::from(format!("MSG[{}", state.msg_count))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
            Span::from(SLANT_BL_TR).style(Style::new().fg(color_3).bg(color_4)),
        )
        .end("", Span::from(SLANT_BL_TR).style(Style::new().fg(color_4)))
        .render(area, buf);
}

fn stacked_2(buf: &mut Buffer, area: Rect, ctx: &mut MiniSalsaState, state: &mut State) {
    let pal = &ctx.theme.p;
    let color_0 = pal.color(Colors::Gray, 3);
    let color_1 = match state.mode {
        0 => pal.color(Colors::Green, 3),
        1 => pal.color(Colors::Yellow, 3),
        2 => pal.color(Colors::Red, 3),
        _ => unreachable!(),
    };
    let color_3 = pal.color(Colors::Cyan, 0);
    let color_4 = pal.color(Colors::Gray, 6);

    let mode_str = match state.mode {
        0 => " [OPERATIONAL] ",
        1 => " [DIRE] ",
        2 => " [EVACUATE] ",
        _ => unreachable!(),
    };

    StatusLineStacked::new()
        .style(Style::new().fg(pal.color(Colors::TextLight, 0)).bg(color_4))
        .start_bare(
            Span::from(" WESTINGHOUSE[STATUS]2 ")
                .style(Style::new().fg(pal.color(Colors::TextLight, 0)).bg(color_0)),
        )
        .start_bare(
            Span::from(mode_str).style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_1)),
        )
        .center_margin(1)
        .center(ctx.status[0].as_str())
        .end_bare(
            Span::from(format!("R[{}][{:.0?}] ", ctx.frame, ctx.last_render))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
        )
        .end_bare(
            Span::from(format!("E[{}][{:.0?}] ", ctx.event_cnt, ctx.last_event))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
        )
        .end_bare(
            Span::from(format!(" MSG[{}] ", state.msg_count))
                .style(Style::new().fg(pal.color(Colors::TextDark, 3)).bg(color_3)),
        )
        .render(area, buf);
}

fn event(
    event: &Event,
    ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<Outcome, anyhow::Error> {
    try_flow!(msgdialog::handle_dialog_events(&mut state.msg, event));

    try_flow!(match event {
        ct_event!(keycode press F(1)) => {
            state.msg_count += 1;
            state.msg.append(
                &repeat_with(|| "ПРИВІТ РЕАКТОР!\n------------\n")
                    .take(20)
                    .collect::<String>(),
            );
            state.msg.set_active(true);
            Outcome::Changed
        }
        _ => Outcome::Continue,
    });

    try_flow!(
        match menuline::handle_events(&mut state.menu, true, event) {
            MenuOutcome::Selected(v) => {
                ctx.status[0] = format!("SELECT {}", v);
                Outcome::Changed
            }
            MenuOutcome::Activated(0) => {
                state.mode = 0;
                Outcome::Changed
            }
            MenuOutcome::Activated(1) => {
                state.mode = 1;
                Outcome::Changed
            }
            MenuOutcome::Activated(2) => {
                state.mode = 2;
                Outcome::Changed
            }
            MenuOutcome::Activated(v) => {
                ctx.status[0] = format!("ACTIVATE {}", v);
                match v {
                    3 => {
                        state.msg.append(
                            &repeat_with(|| "HELLO REACTOR!\n------------\n")
                                .take(20)
                                .collect::<String>(),
                        );
                        state.msg_count += 1;
                        state.msg.set_active(true);
                        return Ok(Outcome::Changed);
                    }
                    4 => {
                        state.status_styling = (state.status_styling + 1) % 2;
                    }
                    5 => {
                        ctx.quit = true;
                        return Ok(Outcome::Changed);
                    }
                    _ => {}
                }
                Outcome::Changed
            }
            r => r.into(),
        }
    );

    Ok(Outcome::Continue)
}