rat-text 3.1.0

ratatui text input widgets
Documentation
use crate::mini_salsa::{MiniSalsaState, layout_grid, mock_init, run_ui, setup_logging};
use log::warn;
use rat_event::{HandleEvent, Regular, event_flow};
use rat_event::{Outcome, ct_event};
use rat_focus::{FocusBuilder, HasFocus};
use rat_text::HasScreenCursor;
use rat_text::text_input_mask::{MaskedInput, MaskedInputState};
use rat_theme4::StyleName;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Layout, Rect};
use ratatui_core::style::{Style, Stylize};
use ratatui_core::text::Line;
use ratatui_core::widgets::{StatefulWidget, Widget};
use ratatui_crossterm::crossterm::event::Event;
use ratatui_widgets::block::Block;
use ratatui_widgets::paragraph::Paragraph;
use std::fmt;

mod mini_salsa;

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

    let mut state = State {
        info: true,
        masked: Default::default(),
        mask_idx: 0,
    };

    run_ui("textmask1", mock_init, event, render, &mut state)
}

struct State {
    pub(crate) info: bool,
    pub(crate) masked: MaskedInputState,
    pub(crate) mask_idx: usize,
}

fn render(
    buf: &mut Buffer,
    area: Rect,
    ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<(), anyhow::Error> {
    let ll: [[Rect; 6]; 4] = layout_grid(
        area,
        Layout::horizontal([
            Constraint::Length(25),
            Constraint::Fill(1),
            Constraint::Length(1),
            Constraint::Length(25),
        ]),
        Layout::vertical([
            Constraint::Length(1),
            Constraint::Length(1),
            Constraint::Fill(1),
            Constraint::Length(1),
            Constraint::Length(1),
            Constraint::Length(1),
        ]),
    );

    MaskedInput::new()
        .block(Block::bordered().style(ctx.theme.style_style(Style::INPUT)))
        .style(ctx.theme.style_style(Style::INPUT))
        .focus_style(ctx.theme.style_style(Style::INPUT_FOCUS))
        .select_style(ctx.theme.style_style(Style::INPUT_SELECT))
        .text_style([
            Style::new().red(),
            Style::new().underlined(),
            Style::new().green(),
            Style::new().on_yellow(),
        ])
        .render(ll[1][2], buf, &mut state.masked);

    if let Some((cx, cy)) = state.masked.screen_cursor() {
        ctx.cursor = Some((cx, cy));
    }

    let info_area = Rect::new(ll[0][1].x + 1, ll[0][1].y + 1, ll[0][1].width - 2, 1);
    let info = Line::from("F2 next mask").black().on_cyan();
    info.render(info_area, buf);

    let mask_area = Rect::new(ll[0][2].x + 1, ll[0][2].y + 2, ll[0][2].width - 2, 1);
    let mask = Line::from(state.masked.mask()).black().on_cyan();
    mask.render(mask_area, buf);

    if state.info {
        use fmt::Write;
        let mut stats = String::new();
        _ = writeln!(&mut stats);
        _ = writeln!(&mut stats, "cursor: {:?}", state.masked.cursor(),);
        _ = writeln!(&mut stats, "anchor: {:?}", state.masked.anchor());

        _ = writeln!(&mut stats, "navig: {:?}", state.masked.navigable());

        let sel = state.masked.selection();
        _ = writeln!(&mut stats, "selection: {:?}", sel.clone());
        _ = writeln!(
            &mut stats,
            "curr-sec: {:?}",
            state.masked.section_range(state.masked.cursor())
        );
        _ = writeln!(
            &mut stats,
            "prev-sec: {:?}",
            state.masked.prev_section_range(sel.start)
        );
        _ = writeln!(
            &mut stats,
            "next-sec: {:?}",
            state.masked.next_section_range(sel.end)
        );

        if let Some((scx, scy)) = state.masked.screen_cursor() {
            _ = writeln!(&mut stats, "screen: {}:{}", scx, scy);
        } else {
            _ = writeln!(&mut stats, "screen: None",);
        }
        _ = writeln!(&mut stats, "width: {:?} ", state.masked.line_width());

        _ = writeln!(&mut stats, "mask: {:?}", state.masked.mask(),);
        _ = writeln!(&mut stats, "text: {:?}", state.masked.text(),);

        _ = write!(&mut stats, "cursor-styles: ",);
        let mut styles = Vec::new();
        let cursor_byte = state.masked.byte_at(state.masked.cursor());
        state.masked.styles_at(cursor_byte.start, &mut styles);
        for (_, s) in styles {
            _ = write!(&mut stats, "{}, ", s);
        }
        _ = writeln!(&mut stats);

        if let Some(st) = state.masked.value.styles() {
            _ = writeln!(&mut stats, "text-styles: {}", st.count());
        }
        if let Some(st) = state.masked.value.styles() {
            for r in st.take(20) {
                _ = writeln!(&mut stats, "    {:?}", r);
            }
        }

        let lx = ll[3][1].union(ll[3][2]).union(ll[3][3]).union(ll[3][4]);
        Paragraph::new(stats).render(lx, buf);
    }

    let ccursor = state.masked.selection();
    ctx.status[0] = format!("{}-{}", ccursor.start, ccursor.end);

    Ok(())
}

fn event(
    event: &Event,
    ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<Outcome, anyhow::Error> {
    let mut focus = {
        let mut fb = FocusBuilder::default();
        fb.widget(&state.masked);
        fb.build()
    };
    ctx.focus_outcome = focus.handle(event, Regular);

    event_flow!(state.masked.handle(event, Regular));

    match event {
        ct_event!(key press ALT-'0') => event_flow!({
            state.info = !state.info;
            Outcome::Changed
        }),
        ct_event!(keycode press F(2)) => event_flow!(next_mask(state)),
        ct_event!(keycode press SHIFT-F(2)) => event_flow!(prev_mask(state)),
        _ => {}
    }

    Ok(Outcome::Continue)
}

static MASKS: [&str; 39] = [
    "",
    "##\\/##\\/####",
    "##\\.##\\.####",
    "\\€ ###,##0.0#+",
    "\\€ ###,##0.0#-",
    "HH HH HH",
    "dd\\°dd\\'dd\\\"",
    "90\\°90\\'90\\\"",
    "#",
    "##",
    "###",
    "##0",
    "#00",
    "000",
    "###.#",
    "###.##",
    "###.###",
    "###.0",
    "###.0##",
    "###.00",
    "###.00#",
    "###.000",
    "##0.000",
    "#00.000",
    "990.000-",
    "990.000+",
    "-990.000-",
    "+990.000+",
    "###,##0.0##",
    "###,##0.0##-",
    "###,##0.0##+",
    "HHH",
    "dddd dddd dddd dddd \\c\\v\\c ddd",
    "\\C\\C aaaa \\R aaaa \\I ddd",
    "llllll",
    "aaaaaa",
    "cccccc",
    "______",
    "aaaddaaa",
];

fn next_mask(state: &mut State) -> Outcome {
    state.mask_idx = (state.mask_idx + 1) % MASKS.len();

    match state.masked.set_mask(MASKS[state.mask_idx]) {
        Ok(_) => {}
        Err(e) => {
            warn!("{:?}", e)
        }
    };
    Outcome::Changed
}

fn prev_mask(state: &mut State) -> Outcome {
    if state.mask_idx == 0 {
        state.mask_idx = MASKS.len() - 1;
    } else {
        state.mask_idx -= 1;
    }

    match state.masked.set_mask(MASKS[state.mask_idx]) {
        Ok(_) => {}
        Err(e) => {
            warn!("{:?}", e)
        }
    };
    Outcome::Changed
}