aven 0.1.6

Local-first task manager CLI and sync server
Documentation
use ratatui::Frame;
use ratatui::style::Style;
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::Paragraph;

use super::super::dialog::{Dialog, dialog_hint_line};
use super::super::input::{InputWidth, clipped_input_line, input_cursor_spans, input_line};
use super::add_task::{
    add_task_hint_line, add_task_metadata_title, add_task_title_input_line, add_task_title_metadata,
};
use crate::tui::authoring::AddTaskStep;
use crate::tui::overlay::{OverlayRoute, TextInputView};
use crate::tui::theme::{FG, FG_DIM};

pub(in crate::tui::ui) fn render_text_input(frame: &mut Frame, state: &TextInputView) {
    if let Some(placeholder) = text_input_placeholder(state.route) {
        render_placeholder_text_input(frame, state, placeholder);
        return;
    }

    if let Some((project, priority)) = add_task_title_metadata(&state.title) {
        let dialog = Dialog::new("Add task", 74, 5);
        let width = dialog.area(frame).width;
        let dialog = dialog.right_title(add_task_metadata_title(
            project,
            "inbox",
            priority,
            &[],
            width,
        ));
        let content = dialog.render_block(frame);
        let input =
            add_task_title_input_line(&state.input, Some(state.cursor), content.width as usize);
        let text = Text::from(vec![
            input,
            Line::from(""),
            add_task_hint_line(AddTaskStep::Title, false, false),
        ]);
        frame.render_widget(
            Paragraph::new(text).style(Style::new().fg(FG).bg(crate::tui::theme::BG_ALT)),
            content,
        );
        return;
    }

    if state.prompt.is_empty() {
        let dialog = Dialog::new(&state.title, 54, 5);
        let content = dialog.render_block(frame);
        let input = clipped_input_line(&state.input, state.cursor, content.width as usize);
        let text = Text::from(vec![
            input,
            Line::from(""),
            dialog_hint_line(&[("Enter", "submit"), ("Esc", "cancel")]),
        ]);
        frame.render_widget(
            Paragraph::new(text).style(Style::new().fg(FG).bg(crate::tui::theme::BG_ALT)),
            content,
        );
        return;
    }

    let input = input_line("", &state.input, state.cursor);
    let lines = if state.route == OverlayRoute::DeleteProjectNameConfirm {
        vec![
            Line::from(Span::styled(&state.prompt, Style::new().fg(FG_DIM))),
            Line::from(""),
            input,
            Line::from(""),
            dialog_hint_line(&[("Enter", "submit"), ("Esc", "cancel")]),
        ]
    } else {
        vec![
            Line::from(Span::styled(&state.prompt, Style::new().fg(FG_DIM))),
            input,
            Line::from(""),
            dialog_hint_line(&[("Enter", "submit"), ("Esc", "cancel")]),
        ]
    };
    let height = if state.route == OverlayRoute::DeleteProjectNameConfirm {
        7
    } else {
        6
    };
    Dialog::new(&state.title, 54, height).render_text(frame, Text::from(lines));
}

pub(in crate::tui::ui) const ADD_PROJECT_NAME_PLACEHOLDER: &str = "Enter project name here...";
pub(in crate::tui::ui) const ADD_LABEL_NAME_PLACEHOLDER: &str = "Enter label name here...";
pub(in crate::tui::ui) const RENAME_PROJECT_NAME_PLACEHOLDER: &str = "Enter project name here...";
pub(in crate::tui::ui) const CONFLICT_MANUAL_VALUE_PLACEHOLDER: &str = "Enter manual value here...";

fn text_input_placeholder(route: OverlayRoute) -> Option<&'static str> {
    match route {
        OverlayRoute::AddProject => Some(ADD_PROJECT_NAME_PLACEHOLDER),
        OverlayRoute::AddLabel => Some(ADD_LABEL_NAME_PLACEHOLDER),
        OverlayRoute::RenameProjectName => Some(RENAME_PROJECT_NAME_PLACEHOLDER),
        OverlayRoute::ConflictManual => Some(CONFLICT_MANUAL_VALUE_PLACEHOLDER),
        _ => None,
    }
}

fn render_placeholder_text_input(
    frame: &mut Frame,
    state: &TextInputView,
    placeholder: &'static str,
) {
    let dialog = Dialog::new(&state.title, 54, 5);
    let content = dialog.render_block(frame);
    let text = Text::from(vec![
        placeholder_text_input_line(
            &state.input,
            state.cursor,
            content.width as usize,
            placeholder,
        ),
        Line::from(""),
        dialog_hint_line(&[("Enter", "submit"), ("Esc", "cancel")]),
    ]);
    frame.render_widget(
        Paragraph::new(text).style(Style::new().fg(FG).bg(crate::tui::theme::BG_ALT)),
        content,
    );
}

pub(in crate::tui::ui) fn placeholder_text_input_line(
    input: &str,
    cursor: usize,
    width: usize,
    placeholder: &'static str,
) -> Line<'static> {
    if input.is_empty() {
        return Line::from(vec![
            super::super::input::cursor_cell(&placeholder[..1]),
            Span::styled(&placeholder[1..], Style::new().fg(FG_DIM)),
        ]);
    }
    Line::from(input_cursor_spans(
        input,
        cursor,
        InputWidth::Clipped(width),
    ))
}