kk/src/display/mod.rs

159 lines
4.1 KiB
Rust

use std::fmt::Display;
use std::io::{self, Stdout, Write};
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
use termion::{clear, cursor};
use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::sync::Mutex;
use tokio::time::Instant;
use self::body::Body;
use self::frame::Frame;
use self::theme::{Color, Theme};
pub mod body;
pub mod debug;
pub mod frame;
pub mod theme;
type Result<T> = std::result::Result<T, anyhow::Error>;
pub struct Screen {
screen: Mutex<RawTerminal<Stdout>>,
events_ch: Receiver<Event>,
body: Body,
last_interrupt: Option<Instant>,
theme: Theme,
}
#[derive(Debug)]
pub enum Event {
Key(Key),
Interrupt,
}
impl Display for Event {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Event::Key(key) => write!(f, "{}", format!("{:#?}", key).replace('\n', "\r\n")),
Event::Interrupt => write!(f, "Interrupt"),
}
}
}
impl From<Key> for Event {
fn from(value: Key) -> Self {
Event::Key(value)
}
}
pub trait Page {
fn init(&self, env: Environment, screen: &mut RawTerminal<Stdout>) -> Result<()>;
fn receive_event(
&mut self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
event: Event,
) -> Result<()>;
}
#[derive(Debug, Clone, Copy)]
pub struct Environment {
pub theme: Theme,
pub frame: Frame,
}
impl Into<String> for Environment {
fn into(self) -> String {
format!(
"{frame}{theme}",
frame = self.frame.frame_str(self.theme.primary()),
theme = self.theme.primary(),
)
}
}
impl Environment {
fn initial_must(theme: Theme) -> Self {
let w: u16 = std::env::var("FRAME_WIDTH")
.map(|f| f.parse().unwrap_or(1))
.unwrap_or(1);
Self {
theme,
frame: Frame::from_terminal_size(theme, w).expect("could not get terminal size"),
}
}
#[inline(always)]
pub fn primary_frame(&self) -> String {
self.frame.frame_str(self.theme.primary())
}
}
impl Screen {
pub fn new(theme: Theme) -> Result<Self> {
let screen = Mutex::new(io::stdout().into_raw_mode()?);
let (send, recv) = mpsc::channel::<Event>(16);
tokio::spawn(async { Screen::input_loop(send).await });
// let body = Body::Signin(SigninPage::default());
let body = Body::Echo;
Ok(Self {
screen,
body,
theme,
events_ch: recv,
last_interrupt: None,
})
}
pub async fn start(mut self) -> Result<()> {
let env = Environment::initial_must(self.theme);
{
let mut scr = self.screen.lock().await;
self.body.init(env, &mut scr)?;
}
while let Some(ev) = self.events_ch.recv().await {
let mut scr = self.screen.lock().await;
if let Event::Interrupt = ev {
if let Some(last) = self.last_interrupt {
if last.elapsed().as_millis() < 500 {
self.events_ch.close();
break;
}
}
self.last_interrupt = Some(Instant::now());
}
self.body.receive_event(env, &mut scr, ev)?;
}
// Cleanup
let mut scr = self.screen.lock().await;
write!(
scr,
"{color}{clear}{show_cursor}{move_to_start}",
color = Into::<Color>::into("#000000").bg_string(),
move_to_start = cursor::Goto(1, 1),
clear = clear::All,
show_cursor = cursor::Show,
)?;
scr.flush();
Ok(())
}
async fn input_loop(snd: Sender<Event>) {
let input = io::stdin();
for key in input.keys() {
let event = match key.unwrap() {
Key::Ctrl(sub) => match sub {
'c' => Event::Interrupt,
key => Event::Key(Key::Ctrl(key)),
},
key => key.into(),
};
snd.send(event).await.unwrap();
}
}
}