use log::info;
use std::io;
use std::marker::PhantomData;
use termit::prelude::*;
#[async_std::main]
async fn main() -> io::Result<()> {
env_logger::init();
let mut app = AppState::default();
let mut termit = Terminal::try_system_default()?
.into_termit::<NoAppEvent>()
.enter_raw_mode()?
.capture_mouse(true)?
.handle_focus_events(true)?
.use_alternate_screen(true)?
.show_cursor(false)?
.true_color(true);
let mut ui = Stack::layered_boxes(vec![])
.add_box(Canvas::new(Ball::default()).back(Color::black()))
.add_box(Pretty::new(Fps::default()).front(Color::black()))
.add_box(Exit);
termit
.refresh_time_sender()
.send_blocking(Duration::from_millis(15))
.expect("refresh interval update");
while !app.is_terminating {
termit.step(&mut app, &mut ui).await?;
}
Ok(())
}
#[derive(Default)]
struct AppState {
pub is_terminating: bool,
}
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 Ball<M, A> {
pos: Point,
direction: bool,
phantom_model: PhantomData<M>,
phantom_appe_event: PhantomData<A>,
}
impl<M, A: AppEvent> Widget<M, A> for Ball<M, A> {
fn update(
&mut self,
_model: &mut M,
input: &Event<A>,
screen: &mut Screen,
painter: &Painter,
) -> Window {
if let Event::Refresh(beats) = input {
for row in painter.scope().row..painter.scope().height {
for col in painter.scope().col..painter.scope().width {
let dx = col as f32 - self.pos.col as f32;
let dy = 2.0 * row as f32 - 2.0 * self.pos.row as f32;
let d = (dx * dx + dy * dy).sqrt();
screen.set(
point(col, row),
&Style::DEFAULT.back(Color::rgb(
(255.0 * 10.0 / d).clamp(1.0, 254.0) as u8,
255 - (255.0 * self.pos.row as f32 / painter.scope().height as f32)
.clamp(1.0, 254.0) as u8,
(d * 5.0).clamp(1.0, 254.0) as u8,
)),
" ",
);
}
}
for _ in 0..*beats {
if self.pos.col >= painter.scope().width + painter.scope().row || self.pos.col <= 0
{
self.direction = !self.direction
}
self.pos.row = (painter.scope().height as f32 * (self.pos.col as f32 / 10.0).sin()
/ 4.0
+ painter.scope().row as f32
+ painter.scope().height as f32 / 2.0) as u16;
self.pos.col = (self.pos.col as i32)
.saturating_add(2 * (self.direction) as i32 - 1)
.clamp(0, screen.size().col as i32) as u16;
}
}
painter.scope()
}
}
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
pub struct Fps {
durations: VecDeque<Duration>,
last: Instant,
show: bool,
}
impl Default for Fps {
fn default() -> Self {
Self {
durations: vec![Duration::ZERO; 600].into_iter().collect(),
last: Instant::now(),
show: true,
}
}
}
impl<A: AppEvent> Widget<AppState, A> for Fps {
fn update(
&mut self,
model: &mut AppState,
input: &Event<A>,
screen: &mut Screen,
painter: &Painter,
) -> Window {
if let (Event::Refresh(_), true) = (input, self.show) {
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}").update_asserted(model, input, screen, painter)
} else {
painter.scope().trim(point(0, 0))
}
}
}