glues-tui 0.8.1

TUI and WASM frontends for the Glues note-taking experience
Documentation
use {
    crate::theme::THEME,
    glues_core::types::{KeymapGroup, KeymapItem},
    ratatui::{
        Frame,
        layout::{
            Alignment,
            Constraint::{Length, Percentage},
            Flex, Layout,
        },
        style::{Style, Stylize},
        text::{Line, Span},
        widgets::{Block, Clear, Padding, Paragraph},
    },
    textwrap::wrap,
};

const KEYMAP_WIDTH: u16 = 46;
const KEY_WIDTH: u16 = 10;

pub fn draw(frame: &mut Frame, keymap: &[KeymapGroup]) {
    let [area] = Layout::horizontal([Length(KEYMAP_WIDTH)])
        .flex(Flex::End)
        .areas(frame.area());
    let [area] = Layout::vertical([Percentage(100)])
        .flex(Flex::Center)
        .areas(area);

    let block = Block::default()
        .fg(THEME.inactive_text)
        .bg(THEME.panel)
        .padding(Padding::new(2, 2, 1, 1))
        .title(
            Line::from(vec![
                Span::raw("").fg(THEME.success).bg(THEME.panel),
                Span::raw(" [?] Hide keymap ")
                    .fg(THEME.success_text)
                    .bg(THEME.success),
            ])
            .right_aligned(),
        );

    let inner_area = block.inner(area);
    let desc_width = inner_area.width.saturating_sub(KEY_WIDTH + 1);

    enum Row<'a> {
        Title(&'a str),
        Item(&'a KeymapItem),
    }

    let mut rows = Vec::new();
    for group in keymap {
        rows.push(Row::Title(&group.title));
        for item in &group.items {
            rows.push(Row::Item(item));
        }
    }

    let heights: Vec<u16> = rows
        .iter()
        .map(|row| match row {
            Row::Title(_) => 1,
            Row::Item(item) => wrap(&item.desc, desc_width as usize).len() as u16,
        })
        .collect();

    let row_constraints = heights.iter().map(|h| Length(*h)).collect::<Vec<_>>();
    let row_areas = Layout::vertical(row_constraints).split(inner_area);

    frame.render_widget(Clear, area);
    frame.render_widget(block, area);

    for (row_area, row) in row_areas.iter().zip(rows) {
        match row {
            Row::Title(title) => {
                let line = Line::from(title.to_string())
                    .fg(THEME.accent_text)
                    .bg(THEME.accent);
                let paragraph = Paragraph::new(line).alignment(Alignment::Left);
                frame.render_widget(paragraph, *row_area);
            }
            Row::Item(item) => {
                let [key_area, desc_area] =
                    Layout::horizontal([Length(KEY_WIDTH), Length(desc_width)]).areas(*row_area);

                let key_paragraph =
                    Paragraph::new(Line::from(vec![Span::raw(format!("[{}]", item.key))]))
                        .alignment(Alignment::Left);
                let desc_lines: Vec<Line> = wrap(&item.desc, desc_width as usize)
                    .into_iter()
                    .map(|c| Line::from(c.into_owned()))
                    .collect();
                let desc_paragraph = Paragraph::new(desc_lines)
                    .alignment(Alignment::Left)
                    .style(Style::default());

                frame.render_widget(key_paragraph, key_area);
                frame.render_widget(desc_paragraph, desc_area);
            }
        }
    }
}