use async_channel::Sender;
use log::info;
use std::io;
use termit::prelude::*;
#[async_std::main]
async fn main() -> io::Result<()> {
env_logger::init();
let mut termit = Terminal::try_system_default()?
.into_termit::<NoAppEvent>()
.enter_raw_mode()?
.capture_mouse(true)?
.handle_focus_events(true)?
.use_alternate_screen(true)?;
let mut app = AppState::new(termit.refresh_time_sender());
let mut ui = Stack::layered_boxes(vec![])
.add_box(Canvas::new(Colors::default().height(9).left(10).right(10)).back(Color::black()))
.add_box(Nice::default())
.add_box(Exit);
while !app.is_terminating {
termit.step(&mut app, &mut ui).await?;
}
info!("done");
Ok(())
}
#[derive(Default)]
struct AppState {
pub is_terminating: bool,
pub refresh_sender: Option<Sender<Duration>>,
pub refresh_interval: u64,
}
impl AppState {
fn new(lag: Sender<Duration>) -> Self {
Self {
is_terminating: false,
refresh_sender: Some(lag),
refresh_interval: 1000,
}
}
fn refresh_every(&mut self, ms: u64) {
if ms != self.refresh_interval {
if let Some(sender) = &self.refresh_sender {
if let Ok(()) = sender.send_blocking(Duration::from_millis(ms)) {
self.refresh_interval = ms
}
}
}
}
}
struct Exit;
impl<A: AppEvent> Widget<AppState, A> for Exit {
fn update(
&mut self,
model: &mut AppState,
input: &Event<A>,
_screen: &mut Screen,
painter: &Painter,
) -> Window {
if matches!(
input,
Event::Key(KeyEvent {
keycode: KeyCode::Char('q'),
..
})
) {
info!("exit!");
model.is_terminating = true;
}
painter.scope().trim(point(0, 0))
}
}
#[derive(Default)]
struct Colors {
size: Point,
r: u8,
g: u8,
b: u8,
input: String,
}
impl Colors {
fn render(&mut self, screen: &mut Screen, painter: &Painter) -> Window {
let d = format!(
"Press numbers 123789 to change the RGB color, q to quit.\nr:{} g:{} b:{} \nlast input: {:?}\nsize: {:?}\ntime: {}",
self.r,self.g,self.b,
self.input,
self.size,
chrono::Local::now().format("%Y-%m-%d %H:%M:%S.%f")
);
painter
.back(Color::rgb(self.r, self.g, self.b))
.fill(screen);
painter
.with_scope(window(
painter.scope().position() + point(2, 1),
painter.scope().size() - point(4, 2),
))
.fill(screen);
painter
.with_scope(window(
painter.scope().position() + point(4, 2),
painter.scope().size() - point(8, 4),
))
.paint(&d, screen, 0, false);
painter.scope()
}
fn digest<X: AppEvent>(&mut self, input: &Event<X>) {
if !matches!(input, Event::Refresh(_)) {
self.input = format!("{:?}", input);
}
match input {
Event::Resize(size) => self.size = *size,
Event::Key(KeyEvent {
keycode: KeyCode::Char(c),
..
}) => match c {
'7' => self.r = self.r.wrapping_add(1),
'1' => self.r = self.r.wrapping_sub(1),
'8' => self.g = self.g.wrapping_add(1),
'2' => self.g = self.g.wrapping_sub(1),
'9' => self.b = self.b.wrapping_add(1),
'3' => self.b = self.b.wrapping_sub(1),
_ => {}
},
_ => {}
}
}
}
impl AnchorPlacementEnabled for Colors {}
impl<M, A: AppEvent> Widget<M, A> for Colors {
fn update(
&mut self,
_model: &mut M,
input: &Event<A>,
screen: &mut Screen,
painter: &Painter,
) -> Window {
self.digest(input);
self.render(screen, painter)
}
}
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
pub struct Nice {
durations: VecDeque<Duration>,
last: Instant,
show: bool,
control_refresh: bool,
}
impl Default for Nice {
fn default() -> Self {
Self {
durations: vec![Duration::ZERO; 600].into_iter().collect(),
last: Instant::now(),
show: true,
control_refresh: true,
}
}
}
impl<A: AppEvent> Widget<AppState, A> for Nice {
fn update(
&mut self,
model: &mut AppState,
input: &Event<A>,
screen: &mut Screen,
painter: &Painter,
) -> Window {
match input {
Event::Focus(focused) => {
if self.control_refresh {
model.refresh_every(if *focused { 1000 } else { 60000 });
}
}
_ => {}
}
if let (true, Event::Refresh(_beats)) = (self.show, input) {
self.durations.push_back(self.last.elapsed());
self.durations.pop_front();
self.last = Instant::now();
let (count, total_duration) =
self.durations.iter().rev().fold((0, 0.0), |(c, t), a| {
if t < 3.0 {
(c + 1, t + a.as_secs_f64())
} else {
(c, t)
}
});
let fps = count as f64 / total_duration;
format!("FPS:{fps:0.5}\nlag:{}", model.refresh_interval)
.update_asserted(model, input, screen, painter)
} else {
painter.scope().trim(point(0, 0))
}
}
}