use crate::common::errors::{MyError, ERROR_CHANNEL, ERROR_PARSE_DIGIT_FAILED};
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::{self, Event, KeyCode, KeyEvent},
execute,
style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
terminal,
terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, SetTitle},
Result as CTResult,
};
use std::{
io::{stdout, Write},
sync::{mpsc::Receiver, mpsc::Sender},
};
use std::time::Duration;
use crate::runner::Control;
#[derive(PartialEq)]
enum State {
Running,
Paused,
Stopped,
}
pub struct Terminal {
fg_color: Color,
bg_color: Color,
title: String,
state: State,
rx_buffer: Receiver<Option::<String>>,
tx_control: Sender<Control>,
}
impl Terminal {
pub fn new(title: String, rx_buffer: Receiver<Option<String>>, tx_control: Sender<Control>) -> Self {
Self {
fg_color: Color::White,
bg_color: Color::Black,
title,
state: State::Running,
rx_buffer,
tx_control,
}
}
fn clear(&self) -> CTResult<()> {
execute!(
stdout(),
Clear(ClearType::All),
Hide,
SetForegroundColor(self.fg_color),
SetBackgroundColor(self.bg_color),
MoveTo(0, 0),
)?;
stdout().flush()?;
Ok(())
}
fn cleanup(&self) -> CTResult<()> {
execute!(
stdout(),
ResetColor,
Clear(ClearType::All),
Show,
LeaveAlternateScreen
)?;
terminal::disable_raw_mode()?;
Ok(())
}
fn draw(&self, string: &String) -> CTResult<()> {
execute!(stdout(), MoveTo(0, 0), Print(string), MoveTo(0, 0),)?;
stdout().flush()?;
Ok(())
}
fn handle_event(&mut self, event: Event) -> CTResult<()> {
match event {
Event::Key(KeyEvent {
code: KeyCode::Char('q') | KeyCode::Char('Q'),
..
})
| Event::Key(KeyEvent {
code: KeyCode::Char('c') | KeyCode::Char('C'),
modifiers: event::KeyModifiers::CONTROL,
..
})
| Event::Key(KeyEvent {
code: KeyCode::Esc, ..
}) => {
self.state = State::Stopped;
self.send_control(Control::Exit)?;
}
Event::Key(KeyEvent {
code: KeyCode::Char(' '),
..
}) => {
self.send_control(Control::PauseContinue)?;
self.state = match self.state {
State::Running => State::Paused,
State::Paused => State::Running,
State::Stopped => State::Stopped,
};
}
Event::Resize(width, height) => {
self.send_control(Control::Resize(width, height))?;
while let Ok(_) = self.rx_buffer.recv_timeout(Duration::from_millis(1)){};
}
Event::Key(KeyEvent {
code: KeyCode::Char(digit),
..
}) if digit.is_ascii_digit() => {
self.send_control(Control::SetCharMap(digit.to_digit(10).unwrap_or_else(
|| panic!("{error}: {digit:?}", error = ERROR_PARSE_DIGIT_FAILED),
)))?;
}
_ => {}
}
Ok(())
}
fn send_control(&self, control: Control) -> Result<(), MyError> {
self.tx_control
.send(control)
.map_err(|e| MyError::Terminal(format!("{error}: {e:?}", error = ERROR_CHANNEL, e = e)))
}
pub fn run(&mut self) -> Result<(), MyError> {
execute!(stdout(), EnterAlternateScreen, SetTitle(&self.title))?;
terminal::enable_raw_mode()?;
self.clear()?;
let (width, height) = terminal::size()?;
self.send_control(Control::Resize(width, height))?;
while self.state != State::Stopped {
if event::poll(Duration::from_millis(0))? {
let ev = event::read()?;
self.handle_event(ev)?;
}
if let Ok(Some(s)) = self.rx_buffer.recv_timeout(Duration::from_millis(5)){
self.draw(&s)?;
};
}
self.cleanup()?;
Ok(())
}
}