tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

/// Board-underneath input modes (filter): the tab bar is omitted for a single
/// board; otherwise the layout is tab bar, hovered-issue title line, optional
/// filter row, board, and a one-line prompt at the bottom.
pub(in crate::app) fn render_board_input(
    app: &App,
    f: &mut ratatui::Frame,
    area: Rect,
    label: &str,
    buffer: &str,
) {
    let tab_h: u16 = if app.tabs.needs_bar() { TAB_HEIGHT } else { 0 };
    if area.height < tab_h + HOVER_HEIGHT + 1 {
        return;
    }
    let view_h: u16 = u16::from(app.view_indicator().is_some());
    let band_h = filter_band_height(app);
    let filter_h = band_h - view_h;
    let input_area = Rect {
        y: area.y + area.height - 1,
        height: 1,
        ..area
    };
    let tabs_area = Rect {
        y: area.y,
        height: tab_h,
        ..area
    };
    let view_area = Rect {
        y: area.y + tab_h + HOVER_HEIGHT,
        height: view_h,
        ..area
    };
    let filter_area = Rect {
        y: area.y + tab_h + HOVER_HEIGHT + view_h,
        height: filter_h,
        ..area
    };
    let content = Rect {
        y: area.y + tab_h + HOVER_HEIGHT + band_h,
        height: area
            .height
            .saturating_sub(tab_h + HOVER_HEIGHT + band_h + 1),
        ..area
    };
    if tab_h > 0 {
        app.tabs.render(f, tabs_area);
        for index in 0..app.tabs.len() {
            if let Some(rect) = app.tabs.tab_rect(index, tabs_area) {
                app.record_region(rect, Action::SwitchBoard(index));
            }
        }
    }
    render_hover_line(app, f, area);
    if view_h > 0 {
        render_view_row(app, f, view_area);
    }
    if filter_h > 0 {
        filter_row(app).render(f, filter_area);
    }
    let page_cards = board::render(
        &app.board,
        f,
        content,
        &app.status_msg,
        app.drag_preview_col,
        board::BoardDisplay::from(app.active_board_config()),
        &app.theme,
        Some(&app.wip),
        app.capacity_summaries(),
        app.marked_numbers(),
    );
    // The renderer reports what one screen of the active column holds; the page keys
    // read it back. Set through `&App` because the field is a `Cell` — the same reason
    // the help overlay's geometry is one (tix #1442 follow-up).
    app.board_page.set(page_cards);
    render_input_line(f, input_area, label, buffer);
}

/// Detail-underneath input modes (comment, asset): detail content + prompt.
pub(in crate::app) fn render_detail_input(
    app: &App,
    f: &mut ratatui::Frame,
    area: Rect,
    label: &str,
    buffer: &str,
) {
    if area.height < 2 {
        return;
    }
    let input_area = Rect {
        y: area.y + area.height - 1,
        height: 1,
        ..area
    };
    let content = Rect {
        height: area.height - 1,
        ..area
    };
    if let Some(ref d) = app.detail {
        detail::render(d, f, content, &app.theme);
    }
    render_input_line(f, input_area, label, buffer);
}

pub(in crate::app) fn render_input_line(
    f: &mut ratatui::Frame,
    area: Rect,
    label: &str,
    buffer: &str,
) {
    let text = format!("{label}: {buffer}_");
    let prompt = Paragraph::new(Line::from(Span::styled(
        text,
        Style::default().fg(Color::Yellow),
    )));
    f.render_widget(prompt, area);
}

pub(in crate::app) fn filter_row(app: &App) -> crate::ui::filter_row::FilterRow {
    crate::ui::filter_row::FilterRow {
        query: app.filter.clone(),
        structured: app.structured.clone(),
    }
}

pub(in crate::app) fn render_hint(f: &mut ratatui::Frame, area: Rect, app: &App, tag: &str) {
    let context = if tag == commandbar::MODE_DETAIL {
        // The related picker overlays the detail pane (#1513), so its own keys
        // are the ones to advertise while it is open. When it is not, the pane's
        // keys are — never `app.input_context()`, which would report the *help*
        // context while the overlay is drawn over a detail pane.
        if app.related_picker.is_some() {
            InputContext::RelatedPicker
        } else {
            InputContext::Detail
        }
    } else {
        commandbar::hint_context_for_mode(app.top_mode)
    };
    let mut bar = commandbar::hint_bar_for_with(&app.bindings, context, app.stats_help_tab());
    // The batch selection announces itself where the keys are: how many are
    // marked and the one key that empties the set. It is added only once a card
    // is marked, so the bar stays uncluttered until the user starts selecting.
    if context == InputContext::Board
        && let Some(selection) = app.bulk.as_ref().filter(|m| !m.selection.is_empty())
    {
        let clear = app
            .bindings
            .label(InputContext::Board, TuiAction::ClearMarks);
        bar = format!(
            "{} marked · {clear} clear · {bar}",
            selection.selection.len()
        );
        bar = crate::ui::board::truncate_to_cells(&bar, usize::from(area.width));
    }
    let para = Paragraph::new(Line::from(Span::styled(
        bar,
        Style::default().fg(Color::Gray),
    )));
    f.render_widget(para, area);
}