tui_additions/widgets/
textfield.rs1use std::fmt::Display;
2
3use ratatui::{
4 style::{Color, Style},
5 text::{Line, Span},
6 widgets::{Paragraph, Widget},
7};
8use unicode_segmentation::UnicodeSegmentation;
9
10#[derive(Clone)]
11pub struct TextField {
12 pub content: String,
13 pub scroll: usize,
14 pub cursor: usize,
15 pub style: Style,
16 pub text_style: Style,
17 pub cursor_style: Style,
18 pub width: Option<u16>,
19}
20
21impl Widget for TextField {
22 fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) {
23 if let Some(width) = self.width {
24 if width != area.width {
25 panic!("width mismatch");
26 }
27 } else {
28 panic!("unknown width");
29 }
30
31 let unicode = UnicodeSegmentation::graphemes(self.content.as_str(), true);
32
33 let cursor_at_end = self.cursor == unicode.clone().count();
34 let mut spans = vec![Span::styled(
35 unicode
36 .clone()
37 .skip(self.scroll)
38 .take(self.cursor - self.scroll)
39 .collect::<String>(),
40 self.text_style,
41 )];
42
43 if cursor_at_end {
44 spans.push(Span::styled(String::from(' '), self.cursor_style));
45 } else {
46 spans.push(Span::styled(
47 unicode
48 .clone()
49 .skip(self.cursor)
50 .take(1)
51 .collect::<String>(),
52 self.cursor_style,
53 ));
54 spans.push(Span::styled(
55 unicode.clone().skip(self.cursor + 1).collect::<String>(),
56 self.text_style,
57 ));
58 }
59
60 let paragraph = Paragraph::new(Line::from(spans)).style(self.style);
61 paragraph.render(area, buf);
62 }
63}
64
65impl Default for TextField {
66 fn default() -> Self {
67 Self {
68 content: String::default(),
69 scroll: 0,
70 cursor: 0,
71 style: Style::default(),
72 text_style: Style::default(),
73 cursor_style: Style::default().bg(Color::Gray),
74 width: None,
75 }
76 }
77}
78
79impl TextField {
80 pub fn insert(&mut self, index: usize, c: char) -> Result<(), TextFieldError> {
81 self.content = format!(
82 "{}{}{}",
83 UnicodeSegmentation::graphemes(self.content.as_str(), true)
84 .take(index)
85 .collect::<String>(),
86 c,
87 UnicodeSegmentation::graphemes(self.content.as_str(), true)
88 .skip(index)
89 .collect::<String>()
90 );
91 self.cursor += 1;
92 self.update()?;
93 Ok(())
94 }
95
96 pub fn remove(&mut self, index: usize) -> Result<(), TextFieldError> {
97 if self.cursor == 0 {
98 return Ok(());
99 }
100 let s = self.content.clone();
101 let mut s = UnicodeSegmentation::graphemes(s.as_str(), true).collect::<Vec<_>>();
102 s.remove(index - 1);
103 self.content = s.into_iter().collect();
104 self.cursor -= 1;
105 self.update()?;
106 Ok(())
107 }
108
109 pub fn push(&mut self, c: char) -> Result<(), TextFieldError> {
110 self.insert(self.cursor, c)
111 }
112
113 pub fn pop(&mut self) -> Result<(), TextFieldError> {
114 self.remove(self.cursor)
115 }
116
117 pub fn left(&mut self) -> Result<(), TextFieldError> {
118 if self.cursor == 0 {
119 return Ok(());
120 }
121
122 self.cursor -= 1;
123 self.update()
124 }
125
126 pub fn right(&mut self) -> Result<(), TextFieldError> {
127 if self.cursor == self.content.len() {
128 return Ok(());
129 }
130
131 self.cursor += 1;
132 self.update()
133 }
134
135 pub fn first(&mut self) -> Result<(), TextFieldError> {
136 self.cursor = 0;
137 self.update()
138 }
139
140 pub fn last(&mut self) -> Result<(), TextFieldError> {
141 self.cursor = self.content.len();
142 self.update()
143 }
144}
145
146impl TextField {
147 pub fn set_width(&mut self, width: u16) {
148 self.width = Some(width)
149 }
150
151 pub fn update(&mut self) -> Result<(), TextFieldError> {
152 let width = if let Some(width) = self.width {
153 width
154 } else {
155 return Err(TextFieldError::UnknownWidth);
156 };
157
158 if self.scroll > self.cursor {
159 self.scroll = self.cursor;
160 } else if self.scroll + width as usize - 1 < self.cursor {
161 self.scroll = self.cursor - width as usize + 1;
162 }
163
164 let len = UnicodeSegmentation::graphemes(self.content.as_str(), true).count();
165
166 if self.cursor > len {
167 self.cursor = len;
168 }
169
170 Ok(())
171 }
172}
173
174#[derive(Debug)]
175pub enum TextFieldError {
176 UnknownWidth,
177}
178
179impl Display for TextFieldError {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 f.write_fmt(format_args!("{:?}", self))
182 }
183}