finished body/frame refactoring

This commit is contained in:
emilis 2023-01-15 22:45:03 +00:00
parent 99808f3d90
commit ac9e337e73
3 changed files with 277 additions and 130 deletions

View File

@ -3,7 +3,7 @@ use std::io::{Stdout, Write};
use termion::{clear, cursor, raw::RawTerminal};
use super::{
compose::{Component, Components},
compose::{Component, Components, HorizontalAlignment, Text},
frame::Frame,
theme::ColorSet,
Environment, Event, Page,
@ -23,16 +23,14 @@ impl Body {
screen: &mut RawTerminal<Stdout>,
event: Event,
) -> Result<()> {
let event = format!("{}", event);
write!(
screen,
"{theme}{content}",
theme = env.theme.primary(),
content = env
.frame
.writeln(&format!("Event: {}", event), 0, 0)
.concat_for(&env.frame),
)?;
let echo_comps =
Components::break_into(format!("Event: {}", event))
.nextline_after_text(
0,
HorizontalAlignment::Left,
&env.frame,
);
write!(screen, "{}", env.frame.make(echo_comps))?;
screen.flush()?;
Ok(())
}
@ -112,43 +110,76 @@ impl SigninPage {
const HOSTNAME_Y: u16 = 3;
const OK_Y: u16 = 5;
let frame = self.frame.unwrap();
let fr_width = frame.size().0;
let total_width = fr_width / 3;
let (w, _) = frame.inner_size();
let exp_w = w / 3;
todo!()
// frame
// .write_centered("login kk", HEADER_Y)
// .into_iter()
// .chain(
// frame
// .write_centered(
// &self.field_str(
// highlight,
// total_width,
// "hostname",
// "",
// ),
// HOSTNAME_Y,
// )
// .into_iter(),
// )
// .chain(vec![Component::Theme(normal)].into_iter())
// .chain(frame.write_centered("[ok]", OK_Y).into_iter())
// .collect::<Vec<Component>>()
// .into()
vec![
frame
.prefix_centered_goto(
vec![Component::String(Text::Normal(
"login kk".into(),
))]
.into(),
HEADER_Y,
)
.0,
frame
.prefix_centered_goto(
self.field(
highlight,
normal,
exp_w,
"hostname",
&self.hostname,
),
HOSTNAME_Y,
)
.0,
frame
.prefix_centered_goto(
self.field(
highlight,
normal,
exp_w,
"username",
&self.username,
),
OK_Y,
)
.0,
]
.into_iter()
.flatten()
.collect::<Vec<Component>>()
.into()
}
#[inline]
fn field_str(
fn field<C>(
&self,
color: ColorSet,
width: u16,
field: &str,
field_content: &str,
) -> String {
let unpadded = format!("{field}: {field_content}",);
let len = field.len() + field_content.len() + 2;
unpadded + &"_".repeat(width as usize - len)
normal: ColorSet,
exp_width: u16,
field: C,
field_content: C,
) -> Components
where
C: Into<String>,
{
let (field, field_content) =
(field.into(), field_content.into());
let budget_for_content = exp_width - field.len() as u16 - 3;
vec![
Component::String((field + &": ").into()),
Component::Theme(color),
Component::String(Text::LimitedOrPadded(
field_content,
'_',
budget_for_content,
)),
Component::Theme(normal),
]
.into()
}
}
@ -197,11 +228,10 @@ impl Page for SigninPage {
.unwrap()
.frame_str(env.theme.colors.subwin),
hide_cursor = cursor::Hide,
content = "",
// content = self.draw(
// env.theme.colors.highlight,
// env.theme.colors.subwin
// ),
content = self.frame.unwrap().make(self.draw(
env.theme.colors.highlight,
env.theme.colors.subwin
))
)?;
screen.flush()?;

View File

@ -1,14 +1,121 @@
use std::{
iter::Map,
ops::{Deref, Range},
vec::IntoIter,
};
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,
frame: &Frame,
y: u16,
) -> Components {
let (width, _) = frame.size();
match self {
Self::Left => {
vec![Component::Goto(0, y), Component::String(s)]
}
Self::Right => {
vec![
Component::Goto(s.len() as u16 - width, y),
Component::String(s),
]
}
Self::Center => {
vec![
Component::Goto((width - s.len()) / 2, y),
Component::String(s),
]
}
}
.into()
}
}
#[derive(Clone, Debug)]
pub enum Component {
String(String),
String(Text),
Goto(u16, u16),
Theme(ColorSet),
Padding(u16),
@ -19,9 +126,9 @@ pub enum Component {
}
impl Component {
pub fn prepare_for(self, frame: &Frame) -> String {
pub fn make(self, frame: &Frame) -> String {
match self {
Component::String(s) => s,
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(),
@ -58,27 +165,48 @@ impl From<Component> for Components {
}
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,
frame: &Frame,
) -> 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, frame, 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() as u16
s.len()
} else {
0
}
})
.sum()
}
pub fn concat_for(self, frame: &Frame) -> String {
self.0
.into_iter()
.map(|component| component.prepare_for(frame))
.collect::<String>()
}
pub fn push(&mut self, comp: Component) {
self.0.push(comp)
}
}

View File

@ -1,15 +1,12 @@
use termion::{clear, cursor, raw::RawTerminal};
use termion::cursor;
const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use std::{
io::{Stdout, Write},
slice::Iter,
vec::IntoIter,
};
use crate::display::compose::Text;
use super::{
compose::{Component, Components},
theme::{ColorSet, Theme},
theme::ColorSet,
};
const FRAME_CHAR_HORIZONTAL: char = ' ';
@ -60,6 +57,20 @@ impl Frame {
.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(
@ -73,7 +84,7 @@ impl Frame {
}
#[inline(always)]
fn write_centered_clear(&self, s: &str) -> Components {
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 {
@ -83,12 +94,11 @@ impl Frame {
};
let len = s.len() as u16;
let base_size = ((width - len) / 2) - self.border;
vec![
Component::Padding(base_size + ((width - len) % 2)),
Component::String(s.into()),
Component::Padding(base_size),
]
.into()
Component::String(super::compose::Text::PaddedBothSides(
base_size + ((width - len) % 2),
s.into(),
base_size,
))
}
#[inline(always)]
@ -100,58 +110,39 @@ impl Frame {
.into()
}
#[inline(always)]
fn write_clear_to_end(&self, s: &str) -> Components {
let limit = (self.size().0 - self.border * 2) as usize;
let s = if s.len() > limit { &s[..limit] } else { s };
#[inline]
pub fn to_end(&self, s: &str, x: u16, y: u16) -> Components {
// FIXME: handle the fucking newlines grrrr i hate this
let width = self.inner_size().0;
vec![
Component::String(s.into()),
Component::Padding(
self.size().0 - s.len() as u16 - (self.border * 2),
),
Component::Goto(x, y),
Component::String(Text::LimitedOrPadded(
s.into(),
' ',
width - x,
)),
]
.into()
}
pub fn writeln(&self, s: &str, x: u16, y: u16) -> Components {
let mut index: u16 = 0;
s.split('\n')
.flat_map(|wrd| {
index += 1;
vec![
vec![Component::Goto(x, y + index - 1)],
self.write_clear_to_end(wrd).0,
]
})
.flatten()
.collect::<Vec<Component>>()
.into()
}
pub fn write_centered(&self, s: &str, y: u16) -> Components {
let mut index: u16 = 0;
s.split('\n')
.flat_map(|wrd| {
index += 1;
vec![
vec![Component::Goto(0, y + index - 1)],
self.write_centered_clear(wrd).0,
]
})
.flatten()
.collect::<Vec<Component>>()
.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) {
eprintln!(
"end.0: {}, start.0: {}, end.1: {}, start.1: {}",
self.end.0, self.start.0, self.end.1, self.start.1
);
(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,
@ -225,8 +216,9 @@ impl Frame {
frame
}
#[inline(always)]
pub fn frame_str(&self, body_theme: ColorSet) -> String {
self.compose(body_theme).concat_for(self)
self.make(self.compose(body_theme))
}
// compose generates a collection of components for drawing
@ -240,13 +232,10 @@ impl Frame {
let (w_len, h_len) = self.size();
let frame_theme = Component::Internal(self.theme.to_string());
let body_clear = Component::Internal(
Into::<Components>::into(vec![
body_theme.clone(),
Component::Padding(w_len),
])
.concat_for(self),
);
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);