1use crate::{Page, types::Vector2};
2use crossterm::{
3 ExecutableCommand, cursor,
4 event::{self, Event, KeyCode, KeyEvent},
5 terminal::{self, enable_raw_mode},
6};
7use std::io::{self, Write};
8
9pub struct Chaos<'a> {
24 paddings: ChaosPaddings,
25 stdout: io::Stdout,
26 input_label: &'a str,
27 dimensions: Vector2<u16>,
28 position: Vector2<u16>,
29}
30
31impl<'a> Chaos<'a> {
32 pub fn new(stdout: io::Stdout, options: ChaosOptions<'a>) -> Self {
36 enable_raw_mode().unwrap();
37
38 Self {
39 stdout,
40 input_label: options.input_label,
41 dimensions: Self::get_dimensions(),
42 position: Self::get_position(),
43 paddings: ChaosPaddings {
44 input: options.input_padding,
45 buffer: options.buffer_padding,
46 },
47 }
48 }
49
50 pub fn clear_terminal(&mut self) {
56 self.stdout
57 .execute(terminal::Clear(terminal::ClearType::All))
58 .unwrap();
59 }
60
61 fn last_character_pos(&self, input_len: usize) -> u16 {
63 self.paddings.input.x + self.input_label.len() as u16 + 1 + input_len as u16
65 }
66
67 pub fn get_input(&mut self, page: &mut Page) -> Result<String, io::Error> {
98 let mut input = String::new();
99 self.prepare_input();
100
101 loop {
102 match event::read()? {
103 Event::Resize(_, _) => {
104 self.update_dimensions();
105 page.align(&self);
106 self.clear_terminal();
107 self.print(page);
108 self.prepare_input();
109
110 let last_character_pos = self.last_character_pos(input.len());
111
112 if last_character_pos < self.dimensions.x {
113 print!("{input}");
114 self.move_cursor(last_character_pos, self.dimensions.y - 1);
115 self.update_position();
116 } else {
117 input = String::new();
118 }
119 }
120 Event::Key(KeyEvent {
121 code: KeyCode::Backspace,
122 ..
123 }) if !input.is_empty() => {
124 self.move_cursor(self.position.x - 1, self.position.y);
125 print!(" ");
126 self.move_cursor(self.position.x - 1, self.position.y);
127 self.update_position();
128 input.pop();
129 }
130 Event::Key(KeyEvent {
131 code: KeyCode::Char(c),
132 ..
133 }) if c.is_ascii()
134 && self.dimensions.x - 1 > self.last_character_pos(input.len()) =>
135 {
136 print!("{c}");
137 self.move_cursor(self.position.x + 1, self.position.y);
138 self.update_position();
139 input.push(c);
140 }
141 Event::Key(KeyEvent {
142 code: KeyCode::Enter,
143 ..
144 }) => break,
145 _ => (),
146 }
147 }
148
149 Ok(input)
150 }
151
152 fn prepare_input(&mut self) {
154 self.move_cursor(self.paddings.input.x, self.dimensions.y - 1);
155 print!("{}", self.input_label);
156 self.move_cursor(
157 self.paddings.input.x + self.input_label.len() as u16 + 1,
158 self.dimensions.y - 1,
159 );
160 self.update_position();
161 }
162
163 pub fn move_cursor(&mut self, x: u16, y: u16) {
169 self.stdout.execute(cursor::MoveTo(x, y)).unwrap();
170 self.stdout.flush().unwrap();
171 }
172
173 pub fn alternate_screen(&mut self, on: bool) {
183 if on {
184 self.stdout.execute(terminal::EnterAlternateScreen).unwrap();
185 } else {
186 self.stdout.execute(terminal::LeaveAlternateScreen).unwrap();
187 }
188 }
189
190 pub fn print(&mut self, page: &mut Page) {
195 let mut starting_line = self.paddings.buffer.y - 1;
196 self.move_cursor(starting_line, 0);
197 page.align(&self);
198
199 for index in 0..page.text().len() {
200 let string = &page.text()[index];
201 if index >= self.dimensions.y as usize - 1 {
202 continue;
203 }
204 starting_line += 1;
205 self.move_cursor(self.paddings.buffer.x / 2, starting_line);
206 print!("{string}");
207 }
208 }
209
210 pub fn position(&self) -> &Vector2<u16> {
212 &self.position
213 }
214
215 fn get_position() -> Vector2<u16> {
217 let (pos_x, pos_y) = cursor::position().unwrap();
218 Vector2::new(pos_x, pos_y)
219 }
220
221 fn update_position(&mut self) {
223 self.position = Self::get_position();
224 }
225
226 pub fn dimensions(&self) -> &Vector2<u16> {
228 &self.dimensions
229 }
230
231 fn get_dimensions() -> Vector2<u16> {
237 let (dim_x, dim_y) = terminal::size().unwrap();
238 Vector2::new(dim_x, dim_y)
239 }
240
241 fn update_dimensions(&mut self) {
243 self.dimensions = Self::get_dimensions();
244 }
245
246 pub fn paddings(&self) -> &ChaosPaddings {
248 &self.paddings
249 }
250
251 pub fn update_paddings(&mut self, padding: PaddingType, new_padding: Vector2<u16>) {
253 match padding {
254 PaddingType::Input => self.paddings.input = new_padding,
255 PaddingType::Buffer => self.paddings.buffer = new_padding,
256 }
257 }
258
259 #[cfg(feature = "test")]
260 pub fn test_setup(options: ChaosTestOptions<'a>) -> Self {
261 Self {
262 stdout: options.stdout,
263 input_label: options.input_label,
264 dimensions: options.dimensions,
265 position: options.position,
266 paddings: ChaosPaddings {
267 input: options.input_padding,
268 buffer: options.buffer_padding,
269 },
270 }
271 }
272}
273
274pub struct ChaosOptions<'a> {
288 pub input_padding: Vector2<u16>,
289 pub buffer_padding: Vector2<u16>,
290 pub input_label: &'a str,
291}
292
293impl<'a> Default for ChaosOptions<'a> {
294 fn default() -> Self {
295 ChaosOptions {
296 input_label: "Input:",
297 input_padding: Vector2::new(1, 0),
298 buffer_padding: Vector2::new(8, 2),
299 }
300 }
301}
302
303#[cfg(feature = "test")]
304pub struct ChaosTestOptions<'a> {
305 pub stdout: std::io::Stdout,
306 pub input_label: &'a str,
307 pub dimensions: Vector2<u16>,
308 pub position: Vector2<u16>,
309 pub input_padding: Vector2<u16>,
310 pub buffer_padding: Vector2<u16>,
311}
312
313pub struct ChaosPaddings {
315 pub input: Vector2<u16>,
316 pub buffer: Vector2<u16>,
317}
318
319pub enum PaddingType {
321 Input,
322 Buffer,
323}