stynx_code_tui/widgets/
input_box.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Modifier, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, Paragraph, Widget},
7};
8
9use crate::state::{InputMode, InputState};
10use crate::theme;
11
12pub struct InputBox<'a> {
13 pub state: &'a InputState,
14 pub focused: bool,
15}
16
17impl<'a> InputBox<'a> {
18 pub fn new(state: &'a InputState, focused: bool) -> Self {
19 Self { state, focused }
20 }
21}
22
23impl<'a> Widget for InputBox<'a> {
24 fn render(self, area: Rect, buf: &mut Buffer) {
25 let (mode_label, mode_color) = match self.state.mode {
26 InputMode::Insert => (" INSERT ", theme::PINE()),
27 InputMode::Normal => (" NORMAL ", theme::GOLD()),
28 };
29
30 let border_color = if self.focused { theme::PINE() } else { theme::HL_MED() };
31
32 let block = Block::default()
33 .borders(Borders::ALL)
34 .border_style(Style::default().fg(border_color))
35 .title(Span::styled(
36 mode_label,
37 Style::default().fg(mode_color).add_modifier(Modifier::BOLD),
38 ));
39
40 let inner = block.inner(area);
41
42 let buffer_lines: Vec<&str> = if self.state.buffer.is_empty() {
43 Vec::new()
44 } else {
45 self.state.buffer.split('\n').collect()
46 };
47
48 let lines: Vec<Line<'static>> = if buffer_lines.is_empty() {
49 let hint = if self.state.suggestion.is_empty() {
50 "Type a messageā¦".to_string()
51 } else {
52 self.state.suggestion.clone()
53 };
54 vec![Line::from(Span::styled(
55 hint,
56 Style::default().fg(theme::MUTED()),
57 ))]
58 } else {
59 buffer_lines
60 .iter()
61 .enumerate()
62 .map(|(i, l)| {
63 if i == buffer_lines.len() - 1 && !self.state.suggestion.is_empty() {
64 Line::from(vec![
65 Span::styled(l.to_string(), Style::default().fg(theme::TEXT())),
66 Span::styled(
67 self.state.suggestion.clone(),
68 Style::default()
69 .fg(theme::MUTED())
70 .add_modifier(Modifier::DIM),
71 ),
72 ])
73 } else {
74 Line::from(Span::styled(l.to_string(), Style::default().fg(theme::TEXT())))
75 }
76 })
77 .collect()
78 };
79
80 Paragraph::new(lines)
81 .block(block)
82 .style(Style::default().bg(theme::SURFACE()))
83 .render(area, buf);
84
85 if self.focused && inner.width > 0 && inner.height > 0 {
86 let (line, col) = self.state.cursor_line_col();
87 let cx = inner.x.saturating_add(u16::try_from(col).unwrap_or(u16::MAX));
88 let cy = inner.y.saturating_add(u16::try_from(line).unwrap_or(u16::MAX));
89 let right = inner.x.saturating_add(inner.width);
90 let bottom = inner.y.saturating_add(inner.height);
91 if cx < right && cy < bottom {
92 buf[(cx, cy)].set_style(Style::default().bg(theme::HL_HIGH()).fg(theme::TEXT()));
93 }
94 }
95 }
96}