use termion::{clear, cursor}; const ESTIMATED_FRAME_BIT_SIZE: usize = 11; use std::io::Write; use crate::display::debug::debug; use super::theme::Theme; const FRAME_CHAR_HORIZONTAL: char = ' '; const FRAME_CHAR_VERTICAL: char = ' '; pub enum FrameSize { ByAbsolute(u16, u16), ByPercent(u16, u16), } #[derive(Debug, Clone, Copy)] pub struct Frame { // Both are (w, h) start: (u16, u16), end: (u16, u16), width: u16, theme: Theme, } impl Frame { #[inline(always)] pub fn goto(&self, x: u16, y: u16) -> String { let cg = cursor::Goto( (self.width + self.start.0 + x).min(self.end.0 - self.width), (self.width + self.start.1 + y).min(self.end.1 - self.width), ); cg.into() } #[inline(always)] fn goto_internal(&self, x: u16, y: u16) -> String { cursor::Goto( (self.start.0 + x).min(self.end.0), (self.start.1 + y).min(self.end.1), ) .into() } pub fn writeln(&self, s: &str, x: u16, y: u16) -> String { let words = s.split('\n').collect::>(); let mut out_words = Vec::with_capacity(words.len()); for i in 0..words.len() { out_words.push(format!( "{ret}{str}", str = words[i], ret = self.goto(x, y + i as u16), )); } out_words.concat() } #[inline(always)] pub fn size(&self) -> (u16, u16) { (self.end.0 - self.start.0, self.end.1 - self.start.1) } #[inline(always)] pub fn from_terminal_size(theme: Theme, width: u16) -> Result { let term = termion::terminal_size()?; // Swap term dimensions to use x/y let term = (term.1, term.0); Ok(Self::from_bottom_right(term, term, width, theme)) } #[inline(always)] fn from_bottom_right( (pos_h, pos_w): (u16, u16), (term_h, term_w): (u16, u16), width: u16, theme: Theme, ) -> Self { Self { start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)), end: (pos_w, pos_h), width, theme, } } pub fn frame_str(&self, body_theme: String) -> String { if self.width == 0 { return body_theme + &clear::All.to_string(); } self.goto_internal(0, 0); let (w_len, h_len) = self.size(); let width_str = FRAME_CHAR_HORIZONTAL.to_string().repeat(w_len as usize + 1); let mut frame = Vec::with_capacity(h_len as usize + 4); let frame_theme = self.theme.frame(); let body_clear = body_theme.clone() + &clear::CurrentLine.to_string(); frame.push(frame_theme.clone()); let make_line = |y: u16| format!("{left}{}", width_str, left = cursor::Goto(self.start.0, y)); for y in 0..self.width { frame.push(make_line(self.start.1 + y)); } for y in self.width + self.start.1..self.end.1 - self.width { frame.push(format!( "{left}{body_clear}{frame_theme}{char}{right}{char}", body_clear = &body_clear, frame_theme = &frame_theme, left = cursor::Goto(self.start.0, y), right = cursor::Goto(self.start.0 + self.end.0 - self.width, y), char = FRAME_CHAR_VERTICAL.to_string().repeat(self.width as usize), )); } for y in self.end.1 - self.width - 1..self.end.1 { frame.push(make_line(self.start.1 + y)); } frame.push(make_line(self.end.1)); frame.push(self.goto(1, 1)); frame.push(body_theme); frame.concat() } } impl FrameSize { #[inline(always)] fn abs_size(&self, theme: Theme, width: u16) -> Frame { let (term_height, term_width) = termion::terminal_size().expect("could not get terminal size"); let pos = match self { FrameSize::ByAbsolute(h, w) => ((*h).min(term_height), (*w).min(term_width)), FrameSize::ByPercent(h, w) => { // term_height = 100% // x = h% // // term_height * h / 100 = x ( term_height * (*h).min(100) / 100, term_width * (*w).min(100) / 100, ) } }; Frame::from_bottom_right(pos, (term_height, term_width), width, theme) } }