kk/src/display/frame.rs

294 lines
8.4 KiB
Rust

use termion::cursor;
use super::{
compose::{Component, Components},
theme::{self, ColorSet},
};
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),
theme: theme::Frame,
}
impl Frame {
#[inline(always)]
pub fn sub(&self, theme: theme::Frame, size: FrameSize) -> Frame {
let (p_width, p_height) = self.size();
let sub_size = size.abs_size((
p_width - self.theme.width * 2,
p_height - self.theme.width * 2,
));
self.child_centered(sub_size, theme)
}
#[inline(always)]
pub fn goto(&self, x: u16, y: u16) -> String {
cursor::Goto(
self.theme.width.max(
(self.theme.width + self.start.0 + x)
.min(self.end.0 - self.theme.width),
),
self.theme.width.max(
(self.theme.width + self.start.1 + y)
.min(self.end.1 - self.theme.width),
),
)
.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.theme.width * 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.theme.width;
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,
c: char,
) -> Components {
vec![self.goto_internal(0, y), Component::Repeated(c, 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.theme.width * 2,
self.end.1 - self.start.1 - self.theme.width * 2,
)
}
#[inline(always)]
pub fn from_terminal_size(
theme: theme::Frame,
) -> Result<Self, anyhow::Error> {
let term = termion::terminal_size()?;
Ok(Self::from_bottom_right(term, term, theme))
}
#[inline(always)]
fn child_centered(
&self,
(pos_w, pos_h): (u16, u16),
theme: theme::Frame,
) -> Frame {
let parent_offset = self.theme.width / 2;
let (parent_w, parent_h) = self.size();
let (parent_w, parent_h) = (
parent_w - self.theme.width,
parent_h - self.theme.width,
);
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,
theme,
};
frame
}
#[inline(always)]
fn from_bottom_right(
(pos_w, pos_h): (u16, u16),
(term_w, term_h): (u16, u16),
theme: theme::Frame,
) -> 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,
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, theme.width,
);
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.color.to_string());
let body_clear =
Component::Internal(self.make(Into::<Components>::into(
vec![body_theme.clone(), Component::Padding(w_len)],
)));
let border_left = Component::Repeated(
self.theme.frame_chars.left,
self.theme.width,
);
let border_right = Component::Repeated(
self.theme.frame_chars.right,
self.theme.width,
);
// This seems cursed but, I assure you, it's fine
vec![vec![frame_theme.clone()]]
.into_iter()
// Top horizontal borders
.chain((0..self.theme.width).into_iter().map(|y: u16| {
self.write_frame_line(
y,
w_len,
self.theme.frame_chars.top,
)
.0
}))
// Frame body + vertical frame borders
.chain(
(self.theme.width..h_len - self.theme.width)
.into_iter()
.map(|y: u16| {
let left = self.goto_internal(0, y);
let right = self.goto_internal(
w_len - self.theme.width,
y,
);
vec![
left.clone(),
body_clear.clone(),
frame_theme.clone(),
left,
border_left.clone(),
right,
border_right.clone(),
]
}),
)
// Bottom horizontal border
.chain((h_len - self.theme.width..h_len).into_iter().map(
|y: u16| {
self.write_frame_line(
y,
w_len,
self.theme.frame_chars.bottom,
)
.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,
)
}
}
}
}