kk/src/display/frame.rs

207 lines
5.8 KiB
Rust

use termion::{clear, cursor, raw::RawTerminal};
const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use std::io::{Stdout, Write};
use super::theme::{ColorSet, 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: ColorSet,
}
impl Frame {
#[inline(always)]
pub fn sub(
&self,
theme: ColorSet,
size: FrameSize,
width: u16,
) -> Frame {
let (p_width, p_height) = self.size();
size.abs_size((p_width - 1, p_height - 1), theme, width)
}
#[inline(always)]
pub fn goto(&self, x: u16, y: u16) -> String {
let cg = cursor::Goto(
self.width.max(
(self.width + self.start.0 + x)
.min(self.end.0 - self.width),
),
self.width.max(
(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()
}
#[inline(always)]
fn write_clear_to_end(&self, s: &str) -> String {
let clear_length = self.size().0 as usize
- s.len()
- (self.width * 2) as usize;
format!("{}{}", s, " ".repeat(clear_length))
}
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!(
"{ret}{str}",
str = self.write_clear_to_end(words[i]),
ret = self.goto(x, y + i as u16),
));
}
out_words.concat()
}
#[inline(always)]
fn write_frame_line(&self, y: u16, length: u16) -> String {
self.goto_internal(0, y)
+ &FRAME_CHAR_HORIZONTAL
.to_string()
.repeat(length as usize)
}
pub fn write_centered(&self, s: &str, y: u16) -> String {
let (width, _) = self.size();
let words = s.split('\n').collect::<Vec<&str>>();
let mut out_words = Vec::with_capacity(words.len());
for i in 0..words.len() {
let word = words[i];
let x = width - (word.len() as u16).min(width);
out_words.push(format!(
"{ret}{str}",
str = word,
ret = self.goto(x / 2, 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: ColorSet,
width: u16,
) -> 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, width, theme))
}
#[inline(always)]
fn from_bottom_right(
(pos_w, pos_h): (u16, u16),
(term_w, term_h): (u16, u16),
width: u16,
theme: ColorSet,
) -> 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.to_string();
let body_clear =
body_theme.clone() + &" ".repeat(w_len as usize);
frame.push(self.theme.to_string());
for y in 0..self.width {
frame.push(self.write_frame_line(y, w_len));
}
for y in self.width..h_len - self.width {
frame.push(format!(
"{left}{body_clear}{frame_theme}{left}{char}{right}{char}",
body_clear = &body_clear,
frame_theme = &frame_theme,
left = self.goto_internal(0, y),
right = self.goto_internal(w_len - self.width, y),
char = FRAME_CHAR_VERTICAL
.to_string()
.repeat(self.width as usize),
));
}
for y in h_len - self.width..h_len {
frame.push(self.write_frame_line(y, w_len));
}
frame.push(self.goto(0, 0));
frame.push(body_theme);
frame.concat()
}
}
impl FrameSize {
#[inline(always)]
fn abs_size(
&self,
(term_width, term_height): (u16, u16),
theme: ColorSet,
width: u16,
) -> Frame {
let pos = match self {
FrameSize::ByAbsolute(h, w) => {
((*w).min(term_width), (*h).min(term_height))
}
FrameSize::ByPercent(h, w) => {
// term_height = 100%
// x = h%
//
// term_height * h / 100 = x
(
term_width * (*w).min(100) / 100,
term_height * (*h).min(100) / 100,
)
}
};
Frame::from_bottom_right(
pos,
(term_width, term_height),
width,
theme,
)
}
}