Skip to main content

chatty_rs/app/ui/
input_box.rs

1use crate::models::Event;
2use ratatui::{
3    Frame,
4    layout::Rect,
5    style::{Color, Style, Stylize},
6    text::Line,
7    widgets::{Block, BorderType, Borders, Clear, Padding, Widget},
8};
9use tui_textarea::{CursorMove, TextArea};
10
11pub struct InputBox<'a> {
12    showing: bool,
13    text: String,
14    input: TextArea<'a>,
15
16    title: String,
17    placeholder: String,
18}
19
20impl<'a> InputBox<'a> {
21    pub fn with_title(mut self, title: &str) -> InputBox<'a> {
22        self.set_title(title);
23        self
24    }
25
26    pub fn with_placeholder(mut self, placeholder: &str) -> InputBox<'a> {
27        self.set_placeholder(placeholder);
28        self
29    }
30
31    pub fn set_title(&mut self, title: &str) {
32        if !title.is_empty() {
33            self.title = title.to_string();
34        }
35    }
36
37    pub fn set_placeholder(&mut self, placeholder: &str) {
38        if !placeholder.is_empty() {
39            self.placeholder = placeholder.to_string();
40        }
41    }
42
43    pub fn showing(&self) -> bool {
44        self.showing
45    }
46
47    pub fn open(&mut self, text: impl Into<String>) {
48        self.text = text.into();
49        self.input = self.build_input();
50        self.showing = true;
51    }
52
53    pub fn close(&mut self) -> Option<String> {
54        if self.showing {
55            self.showing = false;
56            let text = self.input.lines().join("\n");
57            return Some(text);
58        }
59        None
60    }
61
62    pub fn render(&mut self, f: &mut Frame, area: Rect) {
63        if !self.showing {
64            return;
65        }
66
67        f.render_widget(Clear, area);
68        self.input.render(area, f.buffer_mut());
69    }
70
71    pub fn handle_key_event(&mut self, event: &Event) {
72        match event {
73            Event::KeyboardCharInput(input) => {
74                self.input.input(input.clone());
75            }
76            Event::KeyboardPaste(text) => {
77                self.input.set_yank_text(text.replace('\r', "\n"));
78                self.input.paste();
79            }
80            _ => {}
81        }
82    }
83
84    fn build_input(&self) -> TextArea<'a> {
85        let mut text_area = TextArea::new(vec![self.text.clone()]);
86        let block = Block::default()
87            .title(Line::from(self.title.clone()).bold())
88            .borders(Borders::ALL)
89            .border_type(BorderType::Rounded)
90            .border_style(Style::default().fg(Color::LightMagenta))
91            .padding(Padding::symmetric(1, 0));
92        text_area.set_block(block);
93        text_area.set_placeholder_text(&self.placeholder);
94        text_area.move_cursor(CursorMove::End);
95        text_area
96    }
97}
98
99impl Default for InputBox<'_> {
100    fn default() -> Self {
101        Self {
102            showing: false,
103            text: String::new(),
104            input: TextArea::default(),
105            title: "Input".to_string(),
106            placeholder: "Type here...".to_string(),
107        }
108    }
109}
110
111pub fn build_area(area: Rect, width: u16) -> Rect {
112    let x = area.x + (area.width - width) / 2;
113    let y = area.y + (area.height - (area.height as f32 * 0.7).ceil() as u16) / 2;
114    Rect::new(x, y, width, 3)
115}