envelope_cli/tui/widgets/
input.rs1use ratatui::{
6 buffer::Buffer,
7 layout::Rect,
8 style::{Color, Style},
9 text::{Line, Span},
10 widgets::Widget,
11};
12
13#[derive(Debug, Clone)]
15pub struct TextInput {
16 pub content: String,
18 pub cursor: usize,
20 pub focused: bool,
22 pub placeholder: String,
24 pub label: String,
26}
27
28impl TextInput {
29 pub fn new() -> Self {
31 Self {
32 content: String::new(),
33 cursor: 0,
34 focused: false,
35 placeholder: String::new(),
36 label: String::new(),
37 }
38 }
39
40 pub fn label(mut self, label: impl Into<String>) -> Self {
42 self.label = label.into();
43 self
44 }
45
46 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
48 self.placeholder = placeholder.into();
49 self
50 }
51
52 pub fn focused(mut self, focused: bool) -> Self {
54 self.focused = focused;
55 self
56 }
57
58 pub fn content(mut self, content: impl Into<String>) -> Self {
60 self.content = content.into();
61 self.cursor = self.content.len();
62 self
63 }
64
65 pub fn insert(&mut self, c: char) {
67 self.content.insert(self.cursor, c);
68 self.cursor += 1;
69 }
70
71 pub fn backspace(&mut self) {
73 if self.cursor > 0 {
74 self.cursor -= 1;
75 self.content.remove(self.cursor);
76 }
77 }
78
79 pub fn delete(&mut self) {
81 if self.cursor < self.content.len() {
82 self.content.remove(self.cursor);
83 }
84 }
85
86 pub fn move_left(&mut self) {
88 if self.cursor > 0 {
89 self.cursor -= 1;
90 }
91 }
92
93 pub fn move_right(&mut self) {
95 if self.cursor < self.content.len() {
96 self.cursor += 1;
97 }
98 }
99
100 pub fn move_start(&mut self) {
102 self.cursor = 0;
103 }
104
105 pub fn move_end(&mut self) {
107 self.cursor = self.content.len();
108 }
109
110 pub fn clear(&mut self) {
112 self.content.clear();
113 self.cursor = 0;
114 }
115
116 pub fn value(&self) -> &str {
118 &self.content
119 }
120}
121
122impl Default for TextInput {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl Widget for TextInput {
129 fn render(self, area: Rect, buf: &mut Buffer) {
130 let label_width = if self.label.is_empty() {
132 0
133 } else {
134 self.label.len() + 2
135 };
136
137 let input_start = area.x + label_width as u16;
138 let _input_width = area.width.saturating_sub(label_width as u16);
139
140 if !self.label.is_empty() {
142 let label_line = Line::from(vec![
143 Span::styled(&self.label, Style::default().fg(Color::Cyan)),
144 Span::raw(": "),
145 ]);
146 buf.set_line(area.x, area.y, &label_line, label_width as u16);
147 }
148
149 let display_text = if self.content.is_empty() && !self.focused {
151 self.placeholder.clone()
152 } else {
153 self.content.clone()
154 };
155
156 let text_style = if self.content.is_empty() && !self.focused {
157 Style::default().fg(Color::Yellow)
158 } else if self.focused {
159 Style::default().fg(Color::White)
160 } else {
161 Style::default().fg(Color::Yellow)
162 };
163
164 buf.set_string(input_start, area.y, &display_text, text_style);
166
167 if self.focused {
169 let cursor_x = input_start + self.cursor as u16;
170 if cursor_x < area.x + area.width {
171 let cursor_char = if self.cursor < self.content.len() {
172 self.content.chars().nth(self.cursor).unwrap_or('_')
173 } else {
174 '_'
175 };
176 buf.set_string(
177 cursor_x,
178 area.y,
179 cursor_char.to_string(),
180 Style::default().fg(Color::Black).bg(Color::Cyan),
181 );
182 }
183 }
184 }
185}