212 lines
5.3 KiB
Rust
212 lines
5.3 KiB
Rust
use super::{frame::Frame, theme::ColorSet};
|
|
use std::ops::Deref;
|
|
|
|
const ELLIPSES: char = '…';
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Text {
|
|
// Unrestricted
|
|
Normal(String),
|
|
// To length
|
|
Limited(String, u16),
|
|
// Limit or pad to length
|
|
LimitedOrPadded(String, char, u16),
|
|
PaddedBothSides(u16, String, u16),
|
|
}
|
|
|
|
impl From<String> for Text {
|
|
fn from(value: String) -> Self {
|
|
Self::Normal(value)
|
|
}
|
|
}
|
|
|
|
impl Text {
|
|
#[inline]
|
|
pub fn limit(self, limit: u16) -> Text {
|
|
match self {
|
|
Self::Normal(s) => Self::Limited(s, limit),
|
|
Self::Limited(s, _) => Self::Limited(s, limit),
|
|
Self::LimitedOrPadded(s, c, _) => {
|
|
Self::LimitedOrPadded(s, c, limit)
|
|
}
|
|
Self::PaddedBothSides(left, s, right) => todo!(),
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn len(&self) -> u16 {
|
|
match self {
|
|
Text::Normal(s) => s.len() as u16,
|
|
Text::Limited(s, max) => (*max).min(s.len() as u16),
|
|
Text::LimitedOrPadded(_, _, len) => *len,
|
|
Text::PaddedBothSides(left, s, right) => {
|
|
left + s.len() as u16 + right
|
|
}
|
|
}
|
|
}
|
|
#[inline]
|
|
fn conditional_truncate(s: String, len: u16) -> String {
|
|
let mut s = s;
|
|
if s.len() > len as usize {
|
|
s.truncate(len as usize - 1);
|
|
s.push(ELLIPSES);
|
|
}
|
|
s
|
|
}
|
|
|
|
#[inline]
|
|
pub fn make(self, frame: &Frame) -> String {
|
|
match self {
|
|
Text::Normal(s) => s,
|
|
Text::Limited(s, len) => {
|
|
Self::conditional_truncate(s, len)
|
|
}
|
|
Text::LimitedOrPadded(s, pad, len) => {
|
|
let s = Self::conditional_truncate(s, len);
|
|
let sln = s.len();
|
|
s + &pad.to_string().repeat(len as usize - sln)
|
|
}
|
|
Text::PaddedBothSides(left, s, right) => {
|
|
format!(
|
|
"{}{}{}",
|
|
" ".repeat(left as usize),
|
|
s,
|
|
" ".repeat(right as usize)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum HorizontalAlignment {
|
|
Left,
|
|
Right,
|
|
Center,
|
|
}
|
|
|
|
impl HorizontalAlignment {
|
|
pub fn align(
|
|
&self,
|
|
s: Text,
|
|
body_width: u16,
|
|
y: u16,
|
|
) -> Components {
|
|
match self {
|
|
Self::Left => {
|
|
vec![Component::Goto(0, y), Component::String(s)]
|
|
}
|
|
Self::Right => {
|
|
vec![
|
|
Component::Goto(s.len() as u16 - body_width, y),
|
|
Component::String(s),
|
|
]
|
|
}
|
|
Self::Center => {
|
|
vec![
|
|
Component::Goto((body_width - s.len()) / 2, y),
|
|
Component::String(s),
|
|
]
|
|
}
|
|
}
|
|
.into()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Component {
|
|
String(Text),
|
|
Goto(u16, u16),
|
|
Theme(ColorSet),
|
|
Padding(u16),
|
|
// Pre-formatted
|
|
Internal(String),
|
|
Repeated(char, u16),
|
|
Clear,
|
|
}
|
|
|
|
impl Component {
|
|
pub fn make(self, frame: &Frame) -> String {
|
|
match self {
|
|
Component::String(s) => s.make(frame),
|
|
Component::Goto(x, y) => frame.goto(x, y),
|
|
Component::Theme(c) => c.to_string(),
|
|
Component::Clear => termion::clear::All.to_string(),
|
|
Component::Padding(len) => " ".repeat(len as usize),
|
|
Component::Internal(i) => i,
|
|
Component::Repeated(ch, len) => {
|
|
ch.to_string().repeat(len as usize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Components(pub Vec<Component>);
|
|
|
|
impl Deref for Components {
|
|
type Target = Vec<Component>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl From<Vec<Component>> for Components {
|
|
fn from(value: Vec<Component>) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl From<Component> for Components {
|
|
fn from(value: Component) -> Self {
|
|
Self(vec![value])
|
|
}
|
|
}
|
|
|
|
impl Components {
|
|
// Consumes itself, returns a set of new components
|
|
// with gotos for the following line, starting at y (not y+1)
|
|
// if there's a goto, the goto is used as the last y.
|
|
// also applies alignment, relative to the given frame
|
|
pub fn nextline_after_text(
|
|
self,
|
|
y: u16,
|
|
align: HorizontalAlignment,
|
|
body_width: u16,
|
|
) -> Self {
|
|
let mut last_y = y;
|
|
let xxx = self.0.into_iter().map(|comp| match comp {
|
|
Component::String(s) => {
|
|
last_y += 1;
|
|
align.align(s, body_width, last_y).0
|
|
}
|
|
Component::Goto(_, y) => {
|
|
last_y = y;
|
|
vec![comp]
|
|
}
|
|
_ => vec![comp],
|
|
});
|
|
todo!()
|
|
}
|
|
|
|
pub fn break_into(s: String) -> Components {
|
|
s.split('\n')
|
|
.map(|pt| Component::String(Text::Normal(pt.into())))
|
|
.collect::<Vec<Component>>()
|
|
.into()
|
|
}
|
|
|
|
pub fn str_len(&self) -> u16 {
|
|
(&self.0)
|
|
.into_iter()
|
|
.map(|comp| {
|
|
if let Component::String(s) = comp {
|
|
s.len()
|
|
} else {
|
|
0
|
|
}
|
|
})
|
|
.sum()
|
|
}
|
|
}
|