kk/src/display/frame.rs

291 lines
8.1 KiB
Rust

use termion::cursor;
const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use crate::display::compose::Text;
use super::{
compose::{Component, Components},
theme::ColorSet,
};
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),
border: u16,
theme: ColorSet,
}
impl Frame {
#[inline(always)]
pub fn sub(
&self,
theme: ColorSet,
size: FrameSize,
border: u16,
) -> Frame {
let (p_width, p_height) = self.size();
let sub_size = size.abs_size((
p_width - self.border * 2,
p_height - self.border * 2,
));
self.child_centered(sub_size, border, theme)
}
#[inline(always)]
pub fn goto(&self, x: u16, y: u16) -> String {
cursor::Goto(
self.border.max(
(self.border + self.start.0 + x)
.min(self.end.0 - self.border),
),
self.border.max(
(self.border + self.start.1 + y)
.min(self.end.1 - self.border),
),
)
.into()
}
#[inline(always)]
pub fn prefix_centered_goto(
&self,
comp: Components,
line_num: u16,
) -> Components {
let x = (self.inner_size().0 - comp.str_len()) / 2;
vec![Component::Goto(x, line_num)]
.into_iter()
.chain(comp.0.into_iter())
.collect::<Vec<Component>>()
.into()
}
#[inline(always)]
fn goto_internal(&self, x: u16, y: u16) -> Component {
Component::Internal(
cursor::Goto(
(self.start.0 + x).min(self.end.0),
(self.start.1 + y).min(self.end.1),
)
.into(),
)
.into()
}
#[inline(always)]
fn write_centered_clear(&self, s: &str) -> Component {
let width = self.size().0;
let limit = width - self.border * 2;
let s = if s.len() > limit as usize {
&s[..limit as usize]
} else {
s
};
let len = s.len() as u16;
let base_size = ((width - len) / 2) - self.border;
Component::String(super::compose::Text::PaddedBothSides(
base_size + ((width - len) % 2),
s.into(),
base_size,
))
}
#[inline(always)]
fn write_frame_line(&self, y: u16, length: u16) -> Components {
vec![
self.goto_internal(0, y),
Component::Repeated(FRAME_CHAR_HORIZONTAL, length),
]
.into()
}
pub fn make(&self, comp: Components) -> String {
comp.0.into_iter().map(|c| c.make(self)).collect()
}
#[inline(always)]
pub fn size(&self) -> (u16, u16) {
(self.end.0 - self.start.0, self.end.1 - self.start.1)
}
// Size within the borders
#[inline(always)]
pub fn inner_size(&self) -> (u16, u16) {
(
self.end.0 - self.start.0 - self.border * 2,
self.end.1 - self.start.1 - self.border * 2,
)
}
#[inline(always)]
pub fn from_terminal_size(
theme: ColorSet,
width: u16,
) -> Result<Self, anyhow::Error> {
let term = termion::terminal_size()?;
Ok(Self::from_bottom_right(term, term, width, theme))
}
#[inline(always)]
fn child_centered(
&self,
(pos_w, pos_h): (u16, u16),
border: u16,
theme: ColorSet,
) -> Frame {
let parent_offset = self.border / 2;
let (parent_w, parent_h) = self.size();
let (parent_w, parent_h) =
(parent_w - self.border, parent_h - self.border);
let start = (
1.max(
(parent_w - pos_w) / 2
+ ((parent_w - pos_w) % 2)
+ parent_offset,
),
1.max(
(parent_h - pos_h) / 2
+ ((parent_h - pos_h) % 2)
+ parent_offset,
),
);
let frame = Self {
end: (start.0 + pos_w, start.1 + pos_h),
start,
border,
theme,
};
frame
}
#[inline(always)]
fn from_bottom_right(
(pos_w, pos_h): (u16, u16),
(term_w, term_h): (u16, u16),
border: u16,
theme: ColorSet,
) -> Self {
let start = (1.max(term_w - pos_w), 1.max(term_h - pos_h));
let frame = Self {
end: (start.0 + pos_w, start.1 + pos_h),
start,
border,
theme,
};
if frame.start.0 > frame.end.0 || frame.start.1 > frame.end.1
{
eprintln!(
"pos_w {}, pos_h {}, term_w {}, term_h {}, border {}",
pos_w, pos_h, term_w, term_h, border
);
panic!(
"start.0: {} end.0: {}, start.1: {}, end.1: {}",
frame.start.0,
frame.end.0,
frame.start.1,
frame.end.1
);
}
frame
}
#[inline(always)]
pub fn frame_str(&self, body_theme: ColorSet) -> String {
self.make(self.compose(body_theme))
}
// compose generates a collection of components for drawing
// the frame within a screen
pub fn compose(&self, body_theme: ColorSet) -> Components {
let body_theme = Component::Internal(body_theme.to_string());
let (w_len, h_len) = self.size();
let frame_theme = Component::Internal(self.theme.to_string());
let body_clear =
Component::Internal(self.make(Into::<Components>::into(
vec![body_theme.clone(), Component::Padding(w_len)],
)));
let border =
Component::Repeated(FRAME_CHAR_VERTICAL, self.border);
// This seems cursed but, I assure you, it's fine
vec![vec![frame_theme.clone()]]
.into_iter()
// Top horizontal borders
.chain(
(0..self.border)
.into_iter()
.map(|y: u16| self.write_frame_line(y, w_len).0),
)
// Frame body + vertical frame borders
.chain(
(self.border..h_len - self.border).into_iter().map(
|y: u16| {
let left = self.goto_internal(0, y);
let right = self
.goto_internal(w_len - self.border, y);
vec![
left.clone(),
body_clear.clone(),
frame_theme.clone(),
left,
border.clone(),
right,
border.clone(),
]
},
),
)
// Bottom horizontal border
.chain(
(h_len - self.border..h_len)
.into_iter()
.map(|y: u16| self.write_frame_line(y, w_len).0),
)
// Theme reset + move to beginning of inside frame
.chain(
vec![vec![Component::Goto(0, 0), body_theme]]
.into_iter(),
)
.flatten()
.collect::<Vec<Component>>()
.into()
}
}
impl FrameSize {
#[inline(always)]
fn abs_size(
&self,
(term_width, term_height): (u16, u16),
) -> (u16, u16) {
match self {
FrameSize::ByAbsolute(w, h) => {
((*w).min(term_width), (*h).min(term_height))
}
FrameSize::ByPercent(w, h) => {
// term_height = 100%
// x = h%
//
// term_height * h / 100 = x
(
term_width * (*w).min(100) / 100,
term_height * (*h).min(100) / 100,
)
}
}
}
}