189 lines
4.7 KiB
Rust
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();
|
|
}
|
|
}
|
|
}
|