use crate::{common::errors::*, msg::broker::Control as MediaControl, StringInfo};
use crossbeam_channel::{Receiver, Sender};
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::{self, Event, KeyCode, KeyEvent},
execute,
style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor, Stylize},
terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, SetTitle},
};
use std::{
io::{stdout, Write, Result as IOResult},
time::Duration,
};
#[derive(PartialEq)]
enum State {
Running,
Paused,
Stopped,
}
pub struct Terminal {
fg_color: Color,
bg_color: Color,
title: String,
state: State,
rx_buffer: Receiver<Option<StringInfo>>,
tx_control: Sender<MediaControl>,
use_grayscale: bool,
}
impl Terminal {
pub fn new(
title: String,
use_grayscale: bool,
rx_buffer: Receiver<Option<StringInfo>>,
tx_control: Sender<MediaControl>,
) -> Self {
Self {
fg_color: Color::White,
bg_color: Color::Black,
title,
state: State::Running,
rx_buffer,
tx_control,
use_grayscale,
}
}
pub fn run(&mut self, barrier: std::sync::Arc<std::sync::Barrier>) -> Result<(), MyError> {
execute!(stdout(), EnterAlternateScreen, SetTitle(&self.title))?;
terminal::enable_raw_mode()?;
self.clear()?;
let (width, height) = terminal::size()?;
self.send_control(MediaControl::Resize(width, height))?;
barrier.wait();
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.try_recv() {
self.draw(&s)?;
};
}
self.cleanup()?;
Ok(())
}
fn clear(&self) -> IOResult<()> {
execute!(
stdout(),
Clear(ClearType::All),
Hide,
SetForegroundColor(self.fg_color),
SetBackgroundColor(self.bg_color),
MoveTo(0, 0),
)?;
stdout().flush()?;
Ok(())
}
fn cleanup(&self) -> IOResult<()> {
execute!(
stdout(),
ResetColor,
Clear(ClearType::All),
Show,
LeaveAlternateScreen
)?;
terminal::disable_raw_mode()?;
Ok(())
}
fn draw(&self, (string, rgb_data): &StringInfo) -> IOResult<()> {
let print_string = |string: &str| {
let mut out = stdout();
execute!(out, MoveTo(0, 0), Print(string), MoveTo(0, 0))?;
out.flush()?;
Ok(())
};
if self.use_grayscale {
print_string(string)
} else {
let mut colored_string = String::with_capacity(string.len() * 10);
for (c, rgb) in string.chars().zip(rgb_data.chunks(3)) {
let color = Color::Rgb {
r: rgb[0],
g: rgb[1],
b: rgb[2],
};
colored_string.push_str(&format!("{}", c.stylize().with(color)));
}
print_string(&colored_string)
}
}
fn handle_event(&mut self, event: Event) -> IOResult<()> {
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(MediaControl::Exit)?;
}
Event::Key(KeyEvent {
code: KeyCode::Char(' '),
..
}) => {
self.send_control(MediaControl::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(MediaControl::Resize(width, height))?;
while self
.rx_buffer
.recv_timeout(Duration::from_millis(1))
.is_ok()
{ }
}
Event::Key(KeyEvent {
code: KeyCode::Char(digit),
..
}) if digit.is_ascii_digit() => {
self.send_control(MediaControl::SetCharMap(digit.to_digit(10).unwrap_or_else(
|| panic!("{error}: {digit:?}", error = ERROR_PARSE_DIGIT_FAILED),
)))?;
}
Event::Key(KeyEvent {
code: KeyCode::Char('g') | KeyCode::Char('G'),
..
}) => {
self.use_grayscale = !self.use_grayscale;
self.send_control(MediaControl::SetGrayscale(self.use_grayscale))?;
}
Event::Key(KeyEvent {
code: KeyCode::Char('m') | KeyCode::Char('M'),
..
}) => {
self.send_control(MediaControl::MuteUnmute)?;
}
_ => {}
}
Ok(())
}
fn send_control(&self, control: MediaControl) -> Result<(), MyError> {
self.tx_control
.send(control)
.map_err(|e| MyError::Terminal(format!("{error}: {e:?}", error = ERROR_CHANNEL, e = e)))
}
}