use crate::{Page, types::Vector2};
use crossterm::{
ExecutableCommand, cursor,
event::{self, Event, KeyCode, KeyEvent},
terminal::{self, enable_raw_mode},
};
use std::io::{self, Write};
pub struct Chaos<'a> {
paddings: ChaosPaddings,
stdout: io::Stdout,
input_label: &'a str,
dimensions: Vector2<u16>,
position: Vector2<u16>,
}
impl<'a> Chaos<'a> {
pub fn new(stdout: io::Stdout, options: ChaosOptions<'a>) -> Self {
enable_raw_mode().unwrap();
Self {
stdout,
input_label: options.input_label,
dimensions: Self::get_dimensions(),
position: Self::get_position(),
paddings: ChaosPaddings {
input: options.input_padding,
buffer: options.buffer_padding,
},
}
}
pub fn clear_terminal(&mut self) {
self.stdout
.execute(terminal::Clear(terminal::ClearType::All))
.unwrap();
}
fn last_character_pos(&self, input_len: usize) -> u16 {
self.paddings.input.x + self.input_label.len() as u16 + 1 + input_len as u16
}
pub fn get_input(&mut self, page: &mut Page) -> Result<String, io::Error> {
let mut input = String::new();
self.prepare_input();
loop {
match event::read()? {
Event::Resize(_, _) => {
self.update_dimensions();
page.align(&self);
self.clear_terminal();
self.print(page);
self.prepare_input();
let last_character_pos = self.last_character_pos(input.len());
if last_character_pos < self.dimensions.x {
print!("{input}");
self.move_cursor(last_character_pos, self.dimensions.y - 1);
self.update_position();
} else {
input = String::new();
}
}
Event::Key(KeyEvent {
code: KeyCode::Backspace,
..
}) if !input.is_empty() => {
self.move_cursor(self.position.x - 1, self.position.y);
print!(" ");
self.move_cursor(self.position.x - 1, self.position.y);
self.update_position();
input.pop();
}
Event::Key(KeyEvent {
code: KeyCode::Char(c),
..
}) if c.is_ascii()
&& self.dimensions.x - 1 > self.last_character_pos(input.len()) =>
{
print!("{c}");
self.move_cursor(self.position.x + 1, self.position.y);
self.update_position();
input.push(c);
}
Event::Key(KeyEvent {
code: KeyCode::Enter,
..
}) => break,
_ => (),
}
}
Ok(input)
}
fn prepare_input(&mut self) {
self.move_cursor(self.paddings.input.x, self.dimensions.y - 1);
print!("{}", self.input_label);
self.move_cursor(
self.paddings.input.x + self.input_label.len() as u16 + 1,
self.dimensions.y - 1,
);
self.update_position();
}
pub fn move_cursor(&mut self, x: u16, y: u16) {
self.stdout.execute(cursor::MoveTo(x, y)).unwrap();
self.stdout.flush().unwrap();
}
pub fn alternate_screen(&mut self, on: bool) {
if on {
self.stdout.execute(terminal::EnterAlternateScreen).unwrap();
} else {
self.stdout.execute(terminal::LeaveAlternateScreen).unwrap();
}
}
pub fn print(&mut self, page: &mut Page) {
let mut starting_line = self.paddings.buffer.y - 1;
self.move_cursor(starting_line, 0);
page.align(&self);
for index in 0..page.text().len() {
let string = &page.text()[index];
if index >= self.dimensions.y as usize - 1 {
continue;
}
starting_line += 1;
self.move_cursor(self.paddings.buffer.x / 2, starting_line);
print!("{string}");
}
}
pub fn position(&self) -> &Vector2<u16> {
&self.position
}
fn get_position() -> Vector2<u16> {
let (pos_x, pos_y) = cursor::position().unwrap();
Vector2::new(pos_x, pos_y)
}
fn update_position(&mut self) {
self.position = Self::get_position();
}
pub fn dimensions(&self) -> &Vector2<u16> {
&self.dimensions
}
fn get_dimensions() -> Vector2<u16> {
let (dim_x, dim_y) = terminal::size().unwrap();
Vector2::new(dim_x, dim_y)
}
fn update_dimensions(&mut self) {
self.dimensions = Self::get_dimensions();
}
pub fn paddings(&self) -> &ChaosPaddings {
&self.paddings
}
pub fn update_paddings(&mut self, padding: PaddingType, new_padding: Vector2<u16>) {
match padding {
PaddingType::Input => self.paddings.input = new_padding,
PaddingType::Buffer => self.paddings.buffer = new_padding,
}
}
#[cfg(feature = "test")]
pub fn test_setup(options: ChaosTestOptions<'a>) -> Self {
Self {
stdout: options.stdout,
input_label: options.input_label,
dimensions: options.dimensions,
position: options.position,
paddings: ChaosPaddings {
input: options.input_padding,
buffer: options.buffer_padding,
},
}
}
}
pub struct ChaosOptions<'a> {
pub input_padding: Vector2<u16>,
pub buffer_padding: Vector2<u16>,
pub input_label: &'a str,
}
impl<'a> Default for ChaosOptions<'a> {
fn default() -> Self {
ChaosOptions {
input_label: "Input:",
input_padding: Vector2::new(1, 0),
buffer_padding: Vector2::new(8, 2),
}
}
}
#[cfg(feature = "test")]
pub struct ChaosTestOptions<'a> {
pub stdout: std::io::Stdout,
pub input_label: &'a str,
pub dimensions: Vector2<u16>,
pub position: Vector2<u16>,
pub input_padding: Vector2<u16>,
pub buffer_padding: Vector2<u16>,
}
pub struct ChaosPaddings {
pub input: Vector2<u16>,
pub buffer: Vector2<u16>,
}
pub enum PaddingType {
Input,
Buffer,
}