rat-widget 3.2.1

ratatui widgets extended edition
Documentation
use crate::mini_salsa::{MiniSalsaState, mock_init, run_ui, setup_logging};
use chrono::{Local, NaiveTime};
use format_num_pattern::NumberFormat;
use rat_event::util::{Clicks, MouseFlags, set_double_click_timeout};
use rat_event::{Outcome, ct_event, try_flow};
use rat_widget::layout::layout_as_grid;
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::Widget;
use ratatui_crossterm::crossterm::event::{Event, KeyModifiers, MouseEvent, MouseEventKind};
use std::time::{Duration, SystemTime};

mod mini_salsa;

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

    let mut state = State {
        area: Default::default(),
        mouse: Default::default(),
        flip: false,
        flip2: false,
        drag_pos: None,

        journal: Default::default(),
    };

    set_double_click_timeout(350);

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

enum Journal {
    Mouse(MouseEvent, Option<SystemTime>, Clicks),
    DoubleClick(),
}

struct State {
    area: Rect,
    mouse: MouseFlags,
    flip: bool,
    flip2: bool,
    drag_pos: Option<(u16, u16)>,
    journal: Vec<(NaiveTime, Journal)>,
}

fn render(
    buf: &mut Buffer,
    area: Rect,
    _ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<(), anyhow::Error> {
    let l = layout_as_grid(
        area,
        Layout::horizontal([
            Constraint::Length(10),
            Constraint::Length(10),
            Constraint::Fill(1),
            Constraint::Length(5),
        ]),
        Layout::vertical([
            Constraint::Length(1),
            Constraint::Length(5),
            Constraint::Fill(1),
            Constraint::Length(5),
            Constraint::Length(1),
        ]),
    );

    if state.flip2 {
        if state.flip {
            buf.set_style(l.widget_for((1, 2)), Style::new().on_white());
        } else {
            buf.set_style(l.widget_for((1, 2)), Style::new().on_red());
        }
    } else {
        if state.flip {
            buf.set_style(l.widget_for((1, 2)), Style::new().on_green());
        } else {
            buf.set_style(l.widget_for((1, 2)), Style::new().on_blue());
        }
    }
    state.area = l.widget_for((1, 2));

    if state.mouse.drag.get() {
        if let Some((c, r)) = state.drag_pos {
            let numf = NumberFormat::new("###")?;
            let drag = Span::from(
                format!(
                    " DRAG: {}:{}",
                    numf.fmt_u(c as isize - state.area.x as isize),
                    numf.fmt_u(r as isize - state.area.y as isize)
                )
                .to_string(),
            );
            drag.render(l.widget_for((3, 2)), buf);
        }
    }

    if state.journal.len() > 0 {
        let numf = NumberFormat::new("##,###,###")?;

        let off = state
            .journal
            .len()
            .saturating_sub(l.widget_for((2, 2)).height as usize);
        let journal = &state.journal[off..];

        let zero = off.saturating_sub(1);
        let mut prev_time = state.journal[zero].0.clone();

        for (n, (time, event)) in journal.iter().enumerate() {
            let journal_area = l.widget_for((2, 2));
            let row_area = Rect::new(
                journal_area.x,
                journal_area.y + n as u16,
                journal_area.width,
                1,
            );

            let dur = time.signed_duration_since(prev_time);

            let msg = match event {
                Journal::Mouse(event, sys, click) => {
                    let delta = match sys {
                        None => 0,
                        Some(v) => v
                            .duration_since(SystemTime::UNIX_EPOCH)
                            .unwrap_or(Duration::default())
                            .as_millis(),
                    };

                    Span::from(
                        format!(
                            "{:>20} {:02}:{:02} {:>15} {:9} {:?}",
                            numf.fmt_u(dur.num_microseconds().expect("duration")),
                            event.column,
                            event.row,
                            format!("{:?}", event.kind),
                            delta,
                            click
                        )
                        .to_string(),
                    )
                }
                Journal::DoubleClick() => Span::from(
                    format!(
                        "{:>20}   :   CLICK CLICK",
                        numf.fmt_u(dur.num_microseconds().expect("duration")),
                    )
                    .to_string(),
                ),
            };
            msg.render(row_area, buf);

            prev_time = time.clone();
        }
    }

    Ok(())
}

fn event(
    event: &Event,
    _ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<Outcome, anyhow::Error> {
    try_flow!(match event {
        ct_event!(mouse any for m) if state.mouse.doubleclick(state.area, m) => {
            state
                .journal
                .push((Local::now().time(), Journal::DoubleClick()));
            state.flip = !state.flip;
            Outcome::Changed
        }
        ct_event!(mouse any for m)
            if state
                .mouse
                .doubleclick2(state.area, m, KeyModifiers::CONTROL) =>
        {
            state
                .journal
                .push((Local::now().time(), Journal::DoubleClick()));
            state.flip2 = !state.flip2;
            Outcome::Changed
        }
        ct_event!(mouse any for m) if state.mouse.drag(state.area, m) => {
            state.drag_pos = Some(state.mouse.pos_of(m));
            Outcome::Changed
        }
        _ => Outcome::Continue,
    });

    try_flow!(match event {
        Event::Mouse(
            m @ MouseEvent {
                kind: MouseEventKind::Up(_) | MouseEventKind::Down(_) | MouseEventKind::Drag(_),
                ..
            },
        ) => {
            if state.area.contains((m.column, m.row).into()) {
                state.journal.push((
                    Local::now().time(),
                    Journal::Mouse(m.clone(), state.mouse.time.get(), state.mouse.click.get()),
                ));
                Outcome::Changed
            } else {
                Outcome::Unchanged
            }
        }
        _ => Outcome::Continue,
    });

    Ok(Outcome::Continue)
}