//! Fade through RGB colors showing off some layouting //! and event info 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()? //.controlling(fs::File::open("/dev/tty")?) .into_termit::() .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>, pub refresh_interval: u64, } impl AppState { fn new(lag: Sender) -> 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 Widget for Exit { fn update( &mut self, model: &mut AppState, input: &Event, _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(&mut self, input: &Event) { 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 Widget for Colors { fn update( &mut self, _model: &mut M, input: &Event, screen: &mut Screen, painter: &Painter, ) -> Window { self.digest(input); self.render(screen, painter) } } use std::{ collections::VecDeque, time::{Duration, Instant}, }; /// Reduce cycling if not in use pub struct Nice { durations: VecDeque, 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 Widget for Nice { fn update( &mut self, model: &mut AppState, input: &Event, 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) { //if *_beats != 0 { self.durations.push_back(self.last.elapsed()); self.durations.pop_front(); self.last = Instant::now(); //} // show stats over longer period 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 { // no space taken painter.scope().trim(point(0, 0)) } } }