kk/src/display/mod.rs

189 lines
4.7 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, SigninPage};
use self::frame::Frame;
use self::theme::{Color, Theme};
pub mod body;
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<KeyEvent>,
body: Body,
last_interrupt: Option<Instant>,
theme: Theme,
}
#[derive(Debug)]
enum KeyEvent {
Key(Key),
Interrupt,
}
#[derive(Debug)]
pub enum Event {
Key(Key),
}
impl From<KeyEvent> for Event {
fn from(value: KeyEvent) -> Self {
match value {
KeyEvent::Key(key) => Event::Key(key),
_ => panic!(
"FIXME: this event should be handled outside of children"
),
}
}
}
impl Display for Event {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
Event::Key(key) => {
write!(
f,
"{}",
format!("{:#?}", key)
)
}
}
}
}
impl From<Key> for KeyEvent {
fn from(value: Key) -> Self {
KeyEvent::Key(value)
}
}
pub trait Page {
fn init(
&mut 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 {
self.frame.frame_str(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.colors.frame, 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::<KeyEvent>(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;
match ev {
KeyEvent::Interrupt => {
if let Some(last) = self.last_interrupt {
if last.elapsed().as_millis() < 500 {
self.events_ch.close();
break;
}
}
self.last_interrupt = Some(Instant::now());
}
KeyEvent::Key(key) => self.body.receive_event(
env,
&mut scr,
Event::Key(key),
)?,
}
}
// 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<KeyEvent>) {
let input = io::stdin();
for key in input.keys() {
let event = match key.unwrap() {
Key::Ctrl(sub) => match sub {
'c' => KeyEvent::Interrupt,
key => KeyEvent::Key(Key::Ctrl(key)),
},
key => key.into(),
};
snd.send(event).await.unwrap();
}
}
}