television_screen/
input.rs1use 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#[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 let inner_input_chunks = RatatuiLayout::default()
50 .direction(Direction::Horizontal)
51 .constraints([
52 Constraint::Length(2),
54 Constraint::Fill(1),
56 Constraint::Length(
58 3 * (u16::try_from((total_count.max(1)).ilog10()).unwrap()
59 + 1)
60 + 3,
61 ),
62 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 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 f.set_cursor_position((
121 inner_input_chunks[1].x
123 + u16::try_from(input_state.visual_cursor().max(scroll) - scroll)?,
124 inner_input_chunks[1].y,
126 ));
127 Ok(())
128}