use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
text::{Line, Span},
widgets::Widget,
};
#[derive(Debug, Clone)]
pub struct TextInput {
pub content: String,
pub cursor: usize,
pub focused: bool,
pub placeholder: String,
pub label: String,
}
impl TextInput {
pub fn new() -> Self {
Self {
content: String::new(),
cursor: 0,
focused: false,
placeholder: String::new(),
label: String::new(),
}
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.placeholder = placeholder.into();
self
}
pub fn focused(mut self, focused: bool) -> Self {
self.focused = focused;
self
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = content.into();
self.cursor = self.content.len();
self
}
pub fn insert(&mut self, c: char) {
self.content.insert(self.cursor, c);
self.cursor += 1;
}
pub fn backspace(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
self.content.remove(self.cursor);
}
}
pub fn delete(&mut self) {
if self.cursor < self.content.len() {
self.content.remove(self.cursor);
}
}
pub fn move_left(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
}
pub fn move_right(&mut self) {
if self.cursor < self.content.len() {
self.cursor += 1;
}
}
pub fn move_start(&mut self) {
self.cursor = 0;
}
pub fn move_end(&mut self) {
self.cursor = self.content.len();
}
pub fn clear(&mut self) {
self.content.clear();
self.cursor = 0;
}
pub fn value(&self) -> &str {
&self.content
}
}
impl Default for TextInput {
fn default() -> Self {
Self::new()
}
}
impl Widget for TextInput {
fn render(self, area: Rect, buf: &mut Buffer) {
let label_width = if self.label.is_empty() {
0
} else {
self.label.len() + 2
};
let input_start = area.x + label_width as u16;
let _input_width = area.width.saturating_sub(label_width as u16);
if !self.label.is_empty() {
let label_line = Line::from(vec![
Span::styled(&self.label, Style::default().fg(Color::Cyan)),
Span::raw(": "),
]);
buf.set_line(area.x, area.y, &label_line, label_width as u16);
}
let display_text = if self.content.is_empty() && !self.focused {
self.placeholder.clone()
} else {
self.content.clone()
};
let text_style = if self.content.is_empty() && !self.focused {
Style::default().fg(Color::Yellow)
} else if self.focused {
Style::default().fg(Color::White)
} else {
Style::default().fg(Color::Yellow)
};
buf.set_string(input_start, area.y, &display_text, text_style);
if self.focused {
let cursor_x = input_start + self.cursor as u16;
if cursor_x < area.x + area.width {
let cursor_char = if self.cursor < self.content.len() {
self.content.chars().nth(self.cursor).unwrap_or('_')
} else {
'_'
};
buf.set_string(
cursor_x,
area.y,
cursor_char.to_string(),
Style::default().fg(Color::Black).bg(Color::Cyan),
);
}
}
}
}