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, BorderType, Borders, Paragraph, Widget},
7};
8
9use crate::state::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 border_color = if self.focused { theme::IRIS() } else { theme::OVERLAY() };
26
27 let hint = Span::styled(
28 " ↵ send esc · ",
29 Style::default().fg(theme::SUBTLE()).add_modifier(Modifier::DIM),
30 );
31
32 let block = Block::default()
33 .borders(Borders::ALL)
34 .border_type(BorderType::Rounded)
35 .border_style(Style::default().fg(border_color))
36 .title(Span::styled(
37 " › ",
38 Style::default().fg(theme::FOAM()).add_modifier(Modifier::BOLD),
39 ))
40 .title_bottom(hint);
41
42 let inner = block.inner(area);
43
44 let buffer_lines: Vec<&str> = if self.state.buffer.is_empty() {
45 Vec::new()
46 } else {
47 self.state.buffer.split('\n').collect()
48 };
49
50 let lines: Vec<Line<'static>> = if buffer_lines.is_empty() {
51 let hint = if self.state.suggestion.is_empty() {
52 " Type a message…".to_string()
53 } else {
54 format!(" {}", self.state.suggestion)
55 };
56 vec![Line::from(Span::styled(
57 hint,
58 Style::default().fg(theme::MUTED()).add_modifier(Modifier::ITALIC),
59 ))]
60 } else {
61 buffer_lines
62 .iter()
63 .enumerate()
64 .map(|(i, l)| {
65 if i == buffer_lines.len() - 1 && !self.state.suggestion.is_empty() {
66 Line::from(vec![
67 Span::styled(format!(" {l}"), Style::default().fg(theme::TEXT())),
68 Span::styled(
69 self.state.suggestion.clone(),
70 Style::default()
71 .fg(theme::MUTED())
72 .add_modifier(Modifier::DIM),
73 ),
74 ])
75 } else {
76 Line::from(Span::styled(format!(" {l}"), Style::default().fg(theme::TEXT())))
77 }
78 })
79 .collect()
80 };
81
82 Paragraph::new(lines)
83 .block(block)
84 .style(Style::default().bg(theme::BACKGROUND()))
85 .render(area, buf);
86
87 if self.focused && inner.width > 0 && inner.height > 0 {
88 let (line, col) = self.state.cursor_line_col();
89 let cx = inner.x.saturating_add(1).saturating_add(u16::try_from(col).unwrap_or(u16::MAX));
90 let cy = inner.y.saturating_add(u16::try_from(line).unwrap_or(u16::MAX));
91 let right = inner.x.saturating_add(inner.width);
92 let bottom = inner.y.saturating_add(inner.height);
93 if cx < right && cy < bottom {
94 buf[(cx, cy)].set_style(Style::default().bg(theme::HL_HIGH()).fg(theme::TEXT()));
95 }
96 }
97 }
98}