glues-tui 0.8.1

TUI and WASM frontends for the Glues note-taking experience
Documentation
use {
    crate::{
        context::{Context, notebook::ContextState},
        theme::THEME,
    },
    ratatui::{
        Frame,
        layout::Rect,
        style::{Style, Stylize},
        text::{Line, Span},
        widgets::{Block, Padding},
    },
    tui_textarea::TextArea,
};

const NOTE_SYMBOL: &str = "󱇗 ";
const SAMPLE_NOTE: [&str; 7] = [
    "Welcome to Glues!",
    "",
    "Press `?` to see keymaps and shortcuts.",
    "Press `m` in the note tree to create notes or directories.",
    "Press `Enter` on a note to open it and start writing.",
    "",
    "GitHub: https://github.com/gluesql/glues",
];

pub fn draw(frame: &mut Frame, area: Rect, context: &mut Context) {
    context.notebook.editor_height = area.height - 2;

    let (title, mut bottom_left) = if let Some(tab_index) = context.notebook.tab_index {
        let mut title = vec![];
        for (i, tab) in context.notebook.tabs.iter().enumerate() {
            let name = format!(" {NOTE_SYMBOL}{} ", tab.note.name);
            let name = if i == tab_index {
                if context.notebook.state.is_editor() {
                    name.fg(THEME.accent_text).bg(THEME.accent)
                } else {
                    name.fg(THEME.accent_text).bg(THEME.inactive_bg)
                }
            } else {
                name.fg(THEME.hint)
            };

            title.push(name);
        }

        let title = Line::from(title);
        let mut breadcrumb = vec![];
        let last_index = context.notebook.tabs[tab_index].breadcrumb.len() - 1;

        for (i, name) in context.notebook.tabs[tab_index]
            .breadcrumb
            .iter()
            .enumerate()
        {
            let (color_a, color_b) = if i % 2 == 0 {
                (THEME.crumb_a, THEME.crumb_b)
            } else {
                (THEME.crumb_b, THEME.crumb_a)
            };

            let name = if i == 0 {
                breadcrumb.push(Span::raw("  󰝰 ").fg(THEME.crumb_icon).bg(color_a));
                format!("{name} ")
            } else if i == last_index {
                format!(" 󱇗 {name} ")
            } else {
                breadcrumb.push(Span::raw(" 󰝰 ").fg(THEME.crumb_icon).bg(color_a));
                format!("{name} ")
            };

            breadcrumb.push(Span::raw(name).fg(THEME.text).bg(color_a));

            if i < last_index {
                breadcrumb.push(Span::raw("").fg(color_a).bg(color_b));
            } else {
                breadcrumb.push(Span::raw("").fg(color_a).bg(THEME.background));
            }
        }

        (title, breadcrumb)
    } else {
        (
            Line::from("[Editor]".fg(THEME.inactive_text)),
            vec![Span::default()],
        )
    };

    let (mode, bg) = match context.notebook.state {
        ContextState::EditorNormalMode { .. } => (
            Span::raw(" NORMAL ").fg(THEME.text).bg(THEME.background),
            THEME.background,
        ),
        ContextState::EditorInsertMode => (
            Span::raw(" INSERT ").fg(THEME.accent_text).bg(THEME.accent),
            THEME.accent,
        ),
        ContextState::EditorVisualMode => (
            Span::raw(" VISUAL ").fg(THEME.error_text).bg(THEME.error),
            THEME.error,
        ),
        _ => (Span::raw("        ").bg(THEME.surface), THEME.surface),
    };

    if context.notebook.tab_index.is_some() {
        bottom_left.insert(0, mode);
        bottom_left.insert(1, Span::raw("").fg(bg).bg(THEME.crumb_a));
    }

    let bottom_left = Line::from(bottom_left);
    let block = Block::new().title(title).title_bottom(bottom_left);
    let block = match (
        context.last_log.as_ref(),
        context.notebook.editors.iter().any(|(_, item)| item.dirty),
    ) {
        (_, true) => block.title_bottom(
            Line::from(vec![
                Span::raw("").fg(THEME.accent).bg(THEME.background),
                Span::raw(" 󰔚 Saving... ")
                    .fg(THEME.accent_text)
                    .bg(THEME.accent),
            ])
            .right_aligned(),
        ),
        (Some((log, _)), false) => block.title_bottom(
            Line::from(format!(" {log} ").fg(THEME.success_text).bg(THEME.success)).right_aligned(),
        ),
        (None, false) => block,
    }
    .fg(THEME.text)
    .bg(THEME.background)
    .padding(if context.notebook.show_line_number {
        Padding::ZERO
    } else {
        Padding::left(1)
    });

    let show_line_number = context.notebook.show_line_number;
    let state = context.notebook.state;
    let mut editor = TextArea::from(SAMPLE_NOTE);
    let editor = if context.notebook.tab_index.is_some() {
        context.notebook.get_editor_mut()
    } else {
        &mut editor
    };

    editor.set_block(block);

    let (cursor_style, cursor_line_style) = match state {
        ContextState::EditorNormalMode { .. }
        | ContextState::EditorInsertMode
        | ContextState::EditorVisualMode => (
            Style::default().fg(THEME.accent_text).bg(THEME.accent),
            Style::default().underlined(),
        ),
        _ => (Style::default(), Style::default()),
    };

    editor.set_cursor_style(cursor_style);
    editor.set_cursor_line_style(cursor_line_style);
    editor.set_selection_style(Style::default().fg(THEME.accent_text).bg(THEME.accent));
    if show_line_number {
        editor.set_line_number_style(Style::default().fg(THEME.inactive_text));
    } else {
        editor.remove_line_number();
    }

    frame.render_widget(&*editor, area);
}