use termion::cursor; const ESTIMATED_FRAME_BIT_SIZE: usize = 11; const FRAME_CHAR: char = ' '; pub enum FrameSize { ByAbsolute(u16, u16), ByPercent(u16, u16), } #[derive(Debug, Clone, Copy)] pub struct Frame { start: (u16, u16), // (w, h) end: (u16, u16), // (w, h) } impl Frame { #[inline] pub fn goto(&self, x: u16, y: u16) -> String { let cg = cursor::Goto( (self.start.0 + x).min(self.end.0 - 1), (self.start.1 + y).min(self.end.1 - 1), ); // eprintln!( // "goto x: {x} y: {y} -> x: {nx} y: {ny}", // x = x, // y = y, // nx = cg.0, // ny = cg.1 // ); // eprintln!("self start: {:#?} self end: {:#?}", self.start, self.end); cg.into() } pub fn write(&self, s: &str, x: u16, y: u16) -> String { let words = s.split('\n'); let (width, height) = self.size(); let mut lines_down = 0; let mut curr_len = width; let mut write_parts = Vec::with_capacity(s.len() / 5); write_parts.push(self.goto(x, y)); for w in words { // if curr_len + (w.len() as u16) > width { // if y + 1 >= height { // panic!("out of vertical space") // } // lines_down += 1; // curr_len = 1; // } write_parts.push(self.goto(1, lines_down + y)); lines_down += 1; write_parts.push(w.to_string()); } write_parts.concat() } // (x, y) #[inline] pub fn size(&self) -> (u16, u16) { (self.end.0 - self.start.0, self.end.1 - self.start.1) } pub fn from_terminal_size() -> Result { let term = termion::terminal_size()?; Ok(Self::from_bottom_right(term, term)) } fn from_bottom_right((pos_h, pos_w): (u16, u16), (term_h, term_w): (u16, u16)) -> Self { Self { start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)), end: (pos_w, pos_h), } } pub fn frame_str(&self) -> String { let (w_len, h_len) = ( (self.end.0 - self.start.0) as usize, (self.end.1 - self.start.1 - 1) as usize, ); let width_str = FRAME_CHAR.to_string().repeat(w_len + 1); let mut frame = String::with_capacity((h_len * 2) + (w_len * 2) * ESTIMATED_FRAME_BIT_SIZE); let make_line = |y: u16| format!("{left}{}", width_str, left = cursor::Goto(self.start.0, y)); frame.push_str(&make_line(self.start.1)); for y in self.start.1 + 1..self.end.1 { frame.push_str(&format!( "{left}{char}{right}{char}", left = self.goto(self.start.0, y), right = self.goto(self.end.0, y), char = FRAME_CHAR, )); } frame.push_str(&make_line(self.end.1)); frame.push_str(&self.goto(1, 1)); frame } } impl FrameSize { fn abs_size(&self) -> 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% // x = term_height * h / 100 ( term_height * (*h).min(100) / 100, term_width * (*w).min(100) / 100, ) } }; Frame::from_bottom_right(pos, (term_height, term_width)) } }