television_screen/
input.rs

1use color_eyre::Result;
2use ratatui::{
3    layout::{
4        Alignment, Constraint, Direction, Layout as RatatuiLayout, Rect,
5    },
6    style::{Style, Stylize},
7    text::Span,
8    widgets::{Block, BorderType, Borders, ListState, Paragraph},
9    Frame,
10};
11use television_utils::input::Input;
12
13use crate::{
14    colors::Colorscheme,
15    spinner::{Spinner, SpinnerState},
16};
17
18// TODO: refactor arguments (e.g. use a struct for the spinner+state, same
19#[allow(clippy::too_many_arguments)]
20pub fn draw_input_box(
21    f: &mut Frame,
22    rect: Rect,
23    results_count: u32,
24    total_count: u32,
25    input_state: &mut Input,
26    results_picker_state: &mut ListState,
27    matcher_running: bool,
28    spinner: &Spinner,
29    spinner_state: &mut SpinnerState,
30    colorscheme: &Colorscheme,
31) -> Result<()> {
32    let input_block = Block::default()
33        .borders(Borders::ALL)
34        .border_type(BorderType::Rounded)
35        .border_style(Style::default().fg(colorscheme.general.border_fg))
36        .style(
37            Style::default()
38                .bg(colorscheme.general.background.unwrap_or_default()),
39        );
40
41    let input_block_inner = input_block.inner(rect);
42    if input_block_inner.area() == 0 {
43        return Ok(());
44    }
45
46    f.render_widget(input_block, rect);
47
48    // split input block into 4 parts: prompt symbol, input, result count, spinner
49    let inner_input_chunks = RatatuiLayout::default()
50        .direction(Direction::Horizontal)
51        .constraints([
52            // prompt symbol
53            Constraint::Length(2),
54            // input field
55            Constraint::Fill(1),
56            // result count
57            Constraint::Length(
58                3 * (u16::try_from((total_count.max(1)).ilog10()).unwrap()
59                    + 1)
60                    + 3,
61            ),
62            // spinner
63            Constraint::Length(1),
64        ])
65        .split(input_block_inner);
66
67    let arrow_block = Block::default();
68    let arrow = Paragraph::new(Span::styled(
69        "> ",
70        Style::default().fg(colorscheme.input.input_fg).bold(),
71    ))
72    .block(arrow_block);
73    f.render_widget(arrow, inner_input_chunks[0]);
74
75    let interactive_input_block = Block::default();
76    // keep 2 for borders and 1 for cursor
77    let width = inner_input_chunks[1].width.max(3) - 3;
78    let scroll = input_state.visual_scroll(width as usize);
79    let input = Paragraph::new(input_state.value())
80        .scroll((0, u16::try_from(scroll)?))
81        .block(interactive_input_block)
82        .style(
83            Style::default()
84                .fg(colorscheme.input.input_fg)
85                .bold()
86                .italic(),
87        )
88        .alignment(Alignment::Left);
89    f.render_widget(input, inner_input_chunks[1]);
90
91    if matcher_running {
92        f.render_stateful_widget(
93            spinner,
94            inner_input_chunks[3],
95            spinner_state,
96        );
97    }
98
99    let result_count_block = Block::default();
100    let result_count_paragraph = Paragraph::new(Span::styled(
101        format!(
102            " {} / {} ",
103            if results_count == 0 {
104                0
105            } else {
106                results_picker_state.selected().unwrap_or(0) + 1
107            },
108            results_count,
109        ),
110        Style::default()
111            .fg(colorscheme.input.results_count_fg)
112            .italic(),
113    ))
114    .block(result_count_block)
115    .alignment(Alignment::Right);
116    f.render_widget(result_count_paragraph, inner_input_chunks[2]);
117
118    // Make the cursor visible and ask tui-rs to put it at the
119    // specified coordinates after rendering
120    f.set_cursor_position((
121        // Put cursor past the end of the input text
122        inner_input_chunks[1].x
123            + u16::try_from(input_state.visual_cursor().max(scroll) - scroll)?,
124        // Move one line down, from the border to the input line
125        inner_input_chunks[1].y,
126    ));
127    Ok(())
128}