|
|
|
@ -1,11 +1,9 @@ |
|
|
|
|
use termion::{clear, cursor}; |
|
|
|
|
use termion::{clear, cursor, raw::RawTerminal}; |
|
|
|
|
|
|
|
|
|
const ESTIMATED_FRAME_BIT_SIZE: usize = 11; |
|
|
|
|
use std::io::Write; |
|
|
|
|
use std::io::{Stdout, Write}; |
|
|
|
|
|
|
|
|
|
use crate::display::debug::debug; |
|
|
|
|
|
|
|
|
|
use super::theme::Theme; |
|
|
|
|
use super::theme::{ColorSet, Theme}; |
|
|
|
|
|
|
|
|
|
const FRAME_CHAR_HORIZONTAL: char = ' '; |
|
|
|
|
const FRAME_CHAR_VERTICAL: char = ' '; |
|
|
|
@ -21,15 +19,32 @@ pub struct Frame { |
|
|
|
|
start: (u16, u16), |
|
|
|
|
end: (u16, u16), |
|
|
|
|
width: u16, |
|
|
|
|
theme: Theme, |
|
|
|
|
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 + self.start.0 + x).min(self.end.0 - self.width), |
|
|
|
|
(self.width + self.start.1 + y).min(self.end.1 - self.width), |
|
|
|
|
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() |
|
|
|
|
} |
|
|
|
@ -43,38 +58,73 @@ impl Frame { |
|
|
|
|
.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 = words[i], |
|
|
|
|
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: Theme, width: u16) -> Result<Self, anyhow::Error> { |
|
|
|
|
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); |
|
|
|
|
// 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), |
|
|
|
|
(pos_w, pos_h): (u16, u16), |
|
|
|
|
(term_w, term_h): (u16, u16), |
|
|
|
|
width: u16, |
|
|
|
|
theme: Theme, |
|
|
|
|
theme: ColorSet, |
|
|
|
|
) -> Self { |
|
|
|
|
Self { |
|
|
|
|
start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)), |
|
|
|
@ -90,32 +140,34 @@ impl Frame { |
|
|
|
|
} |
|
|
|
|
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 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 frame_theme = self.theme.to_string(); |
|
|
|
|
let body_clear = |
|
|
|
|
body_theme.clone() + &" ".repeat(w_len as usize); |
|
|
|
|
frame.push(self.theme.to_string()); |
|
|
|
|
|
|
|
|
|
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)); |
|
|
|
|
frame.push(self.write_frame_line(y, w_len)); |
|
|
|
|
} |
|
|
|
|
for y in self.width + self.start.1..self.end.1 - self.width { |
|
|
|
|
for y in self.width..h_len - self.width { |
|
|
|
|
frame.push(format!( |
|
|
|
|
"{left}{body_clear}{frame_theme}{char}{right}{char}", |
|
|
|
|
"{left}{body_clear}{frame_theme}{left}{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), |
|
|
|
|
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 self.end.1 - self.width - 1..self.end.1 { |
|
|
|
|
frame.push(make_line(self.start.1 + y)); |
|
|
|
|
for y in h_len - self.width..h_len { |
|
|
|
|
frame.push(self.write_frame_line(y, w_len)); |
|
|
|
|
} |
|
|
|
|
frame.push(make_line(self.end.1)); |
|
|
|
|
frame.push(self.goto(1, 1)); |
|
|
|
|
frame.push(self.goto(0, 0)); |
|
|
|
|
frame.push(body_theme); |
|
|
|
|
frame.concat() |
|
|
|
|
} |
|
|
|
@ -123,22 +175,32 @@ impl Frame { |
|
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
fn abs_size( |
|
|
|
|
&self, |
|
|
|
|
(term_width, term_height): (u16, u16), |
|
|
|
|
theme: ColorSet, |
|
|
|
|
width: u16, |
|
|
|
|
) -> Frame { |
|
|
|
|
let pos = match self { |
|
|
|
|
FrameSize::ByAbsolute(h, w) => ((*h).min(term_height), (*w).min(term_width)), |
|
|
|
|
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_height * (*h).min(100) / 100, |
|
|
|
|
term_width * (*w).min(100) / 100, |
|
|
|
|
term_height * (*h).min(100) / 100, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
Frame::from_bottom_right(pos, (term_height, term_width), width, theme) |
|
|
|
|
Frame::from_bottom_right( |
|
|
|
|
pos, |
|
|
|
|
(term_width, term_height), |
|
|
|
|
width, |
|
|
|
|
theme, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|