//! A window is an abstraction for the UI. Buffers (like [Threads]) draw "to" windows. use std::io::Write; use termion::{color, cursor}; use unicode_segmentation::UnicodeSegmentation; #[derive(Clone, Copy)] pub struct Area { pub x: u16, pub y: u16, pub w: u16, pub h: u16, } pub enum Line { Normal(String), Highlight(String), } pub fn truncate_dots(s: &str, to: usize) -> String { //TODO Don't do this every tick let graphemes = s.graphemes(true).collect::>(); if graphemes.len() >= to { format!("{}...", graphemes.iter().take(to - 3).fold(String::new(), |acc, s| format!("{}{}", acc, s))) } else { graphemes.join("") } } pub struct Statusbar(String); impl Statusbar { pub fn new(s: String) -> Self { Self(s) } pub fn draw(&self, buf: &mut W, area: Area) -> Result<(), std::io::Error> { write!(buf, "{}", cursor::Goto(area.x, area.y))?; write!(buf, "{}", truncate_dots(&self.0, area.w as usize))?; Ok(()) } } pub struct Window { pub lines: Vec, scroll: usize, } impl Window { pub fn new() -> Self { Self { lines: Vec::new(), scroll: 0, } } pub fn draw(&mut self, buf: &mut W, area: Area) -> Result<(), std::io::Error> { let mut y = area.y; let mut lines = self.lines.iter().skip(self.scroll); while y < area.h + 1 { match lines.next() { Some(line) => { write!(buf, "{}", cursor::Goto(area.x, y))?; match line { Line::Normal(s) => write!(buf, "{}", truncate_dots(s, area.w as usize))?, Line::Highlight(s) => write!( buf, "{}{}{}", color::Fg(color::Red), truncate_dots(s, area.w as usize), color::Fg(color::Reset), )?, } y += 1; } None => break, } } self.lines.clear(); Ok(()) } pub fn scroll_down(&mut self, until: usize) { if self.scroll < until - 1 { self.scroll += 1; } } pub fn scroll_up(&mut self) { if self.scroll != 0 { self.scroll -= 1; } } }