294 lines
8.4 KiB
Rust
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,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|