kk/src/display/frame.rs

114 lines
3.5 KiB
Rust

use termion::{clear, cursor};
const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use std::io::Write;
use super::theme::Theme;
const FRAME_CHAR_HORIZONTAL: char = 'h';
const FRAME_CHAR_VERTICAL: char = 'v';
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),
}
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),
);
cg.into()
}
pub fn writeln(&self, s: &str, x: u16, y: u16) -> String {
let words = s.split('\n').collect::<Vec<&str>>();
let mut out_words = Vec::with_capacity(words.len());
for i in 0..words.len() {
out_words.push(format!(
"{str}{ret}",
str = words[i],
ret = self.goto(1, y + i as u16 + 1)
));
}
out_words.concat()
}
#[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<Self, anyhow::Error> {
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))
}
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, theme: &Theme, body_theme: String) -> String {
self.goto(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 = 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));
frame.push(make_line(self.start.1));
for y in self.start.1 + 1..self.end.1 {
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, y),
char = FRAME_CHAR_VERTICAL,
));
}
frame.push(make_line(self.end.1));
frame.push(self.goto(1, 1));
frame.push(body_theme);
frame.concat()
}
}
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%
//
// 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))
}
}