rat-widget 3.2.1

ratatui widgets extended edition
Documentation
#![allow(dead_code)]

use crate::mini_salsa::{MiniSalsaState, mock_init, run_ui, setup_logging};
use chrono::{Datelike, Local, Months, NaiveDate};
use pure_rust_locales::Locale;
use rat_event::{HandleEvent, Regular, try_flow};
use rat_focus::{Focus, FocusBuilder};
use rat_menu::event::MenuOutcome;
use rat_menu::menuline::{MenuLine, MenuLineState};
use rat_theme4::WidgetStyle;
use rat_widget::button::{Button, ButtonState};
use rat_widget::calendar::selection::RangeSelection;
use rat_widget::calendar::{CalendarState, Month, TodayPolicy};
use rat_widget::event::{ButtonOutcome, Outcome};
use rat_widget::statusline::StatusLineState;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Alignment, Constraint, Layout, Rect};
use ratatui_core::style::Style;
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::borders::Borders;
use std::collections::HashMap;

mod mini_salsa;

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

    let mut state = State::new();
    state.menu.focus.set(true);

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

struct State {
    calendar: CalendarState<3, RangeSelection>,

    prev: ButtonState,
    next: ButtonState,

    menu: MenuLineState,
    status: StatusLineState,
}

impl State {
    fn new() -> Self {
        let mut s = Self {
            calendar: Default::default(),
            prev: Default::default(),
            next: Default::default(),
            menu: Default::default(),
            status: Default::default(),
        };

        let today = Local::now().date_naive();
        s.calendar.set_today_policy(TodayPolicy::Index(1));
        s.calendar.set_primary_idx(1);
        s.calendar.set_start_date(today - Months::new(1));
        s.calendar.set_step(1);
        s
    }

    fn start_date(&self) -> NaiveDate {
        self.calendar.start_date()
    }

    fn prev_month(&mut self) {
        self.calendar.scroll_back(1);
    }

    fn next_month(&mut self) {
        self.calendar.scroll_forward(1);
    }
}

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

    let l2 = Layout::horizontal([
        Constraint::Length(5),
        Constraint::Fill(1),
        Constraint::Fill(1),
        Constraint::Fill(1),
        Constraint::Length(5),
    ])
    .spacing(1)
    .split(l1[3]);

    let l4 = Layout::horizontal([
        Constraint::Length(5),
        Constraint::Fill(1),
        Constraint::Fill(1),
        Constraint::Fill(1),
        Constraint::Length(5),
    ])
    .spacing(1)
    .split(l1[1]);

    let mut date_styles = HashMap::new();
    date_styles.insert(
        NaiveDate::from_ymd_opt(2024, 9, 1).expect("some"),
        Style::default().red(),
    );
    date_styles.insert(Local::now().date_naive(), ctx.theme.p.redpink(3));

    let title = if state.calendar.months[0].start_date().year()
        != state.calendar.months[2].start_date().year()
    {
        format!(
            "{} / {}",
            state.calendar.months[0]
                .start_date()
                .format("%Y")
                .to_string(),
            state.calendar.months[2]
                .start_date()
                .format("%Y")
                .to_string()
        )
    } else {
        state.calendar.months[0]
            .start_date()
            .format("%Y")
            .to_string()
    };

    Line::from(title)
        .alignment(Alignment::Center)
        .style(ctx.theme.p.limegreen(2))
        .render(l4[2], buf);

    Month::new()
        .locale(Locale::de_AT_euro)
        .styles(ctx.theme.style(WidgetStyle::MONTH))
        .title_align(Alignment::Left)
        .day_styles(&date_styles)
        .show_weekdays()
        .block(Block::bordered().borders(Borders::TOP))
        .render(l2[1], buf, &mut state.calendar.months[0]);

    Month::new()
        .locale(Locale::de_AT_euro)
        .styles(ctx.theme.style(WidgetStyle::MONTH))
        .title_align(Alignment::Left)
        .day_styles(&date_styles)
        .show_weekdays()
        .block(Block::bordered().borders(Borders::TOP))
        .render(l2[2], buf, &mut state.calendar.months[1]);

    Month::new()
        .locale(Locale::de_AT_euro)
        .styles(ctx.theme.style(WidgetStyle::MONTH))
        .title_align(Alignment::Left)
        .day_styles(&date_styles)
        .show_weekdays()
        .block(Block::bordered().borders(Borders::TOP))
        .render(l2[3], buf, &mut state.calendar.months[2]);

    Button::new("<<<")
        .styles(ctx.theme.style(WidgetStyle::BUTTON))
        .render(l4[1], buf, &mut state.prev);

    Button::new(">>>")
        .styles(ctx.theme.style(WidgetStyle::BUTTON))
        .render(l4[3], buf, &mut state.next);

    MenuLine::new()
        .title("|/\\|")
        .item_parsed("_Quit")
        .title_style(Style::default().black().on_yellow())
        .style(Style::default().black().on_dark_gray())
        .render(l1[5], buf, &mut state.menu);

    Ok(())
}

fn focus(state: &State) -> Focus {
    let mut builder = FocusBuilder::default();
    builder.widget(&state.calendar);
    builder.widget(&state.menu);
    let f = builder.build();
    f.enable_log();
    f
}

fn event(
    event: &Event,
    ctx: &mut MiniSalsaState,
    state: &mut State,
) -> Result<Outcome, anyhow::Error> {
    let mut focus = focus(state);

    ctx.focus_outcome = focus.handle(event, Regular);

    try_flow!(state.calendar.handle(event, Regular));

    try_flow!(match state.menu.handle(event, Regular) {
        MenuOutcome::Activated(0) => {
            ctx.quit = true;
            Outcome::Changed
        }
        _ => Outcome::Continue,
    });

    try_flow!(match state.prev.handle(event, Regular) {
        ButtonOutcome::Pressed => {
            state.prev_month();
            Outcome::Changed
        }
        r => r.into(),
    });
    try_flow!(match state.next.handle(event, Regular) {
        ButtonOutcome::Pressed => {
            state.next_month();
            Outcome::Changed
        }
        r => r.into(),
    });

    Ok(Outcome::Continue)
}