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

View File

@ -1,14 +1,121 @@
use std::{
iter::Map,
ops::{Deref, Range},
vec::IntoIter,
};
use super::{frame::Frame, theme::ColorSet}; 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)] #[derive(Clone, Debug)]
pub enum Component { pub enum Component {
String(String), String(Text),
Goto(u16, u16), Goto(u16, u16),
Theme(ColorSet), Theme(ColorSet),
Padding(u16), Padding(u16),
@ -19,9 +126,9 @@ pub enum Component {
} }
impl Component { impl Component {
pub fn prepare_for(self, frame: &Frame) -> String { pub fn make(self, frame: &Frame) -> String {
match self { match self {
Component::String(s) => s, Component::String(s) => s.make(frame),
Component::Goto(x, y) => frame.goto(x, y), Component::Goto(x, y) => frame.goto(x, y),
Component::Theme(c) => c.to_string(), Component::Theme(c) => c.to_string(),
Component::Clear => termion::clear::All.to_string(), Component::Clear => termion::clear::All.to_string(),
@ -58,27 +165,48 @@ impl From<Component> for Components {
} }
impl 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 { pub fn str_len(&self) -> u16 {
(&self.0) (&self.0)
.into_iter() .into_iter()
.map(|comp| { .map(|comp| {
if let Component::String(s) = comp { if let Component::String(s) = comp {
s.len() as u16 s.len()
} else { } else {
0 0
} }
}) })
.sum() .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; const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use std::{
io::{Stdout, Write}, use crate::display::compose::Text;
slice::Iter,
vec::IntoIter,
};
use super::{ use super::{
compose::{Component, Components}, compose::{Component, Components},
theme::{ColorSet, Theme}, theme::ColorSet,
}; };
const FRAME_CHAR_HORIZONTAL: char = ' '; const FRAME_CHAR_HORIZONTAL: char = ' ';
@ -60,6 +57,20 @@ impl Frame {
.into() .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)] #[inline(always)]
fn goto_internal(&self, x: u16, y: u16) -> Component { fn goto_internal(&self, x: u16, y: u16) -> Component {
Component::Internal( Component::Internal(
@ -73,7 +84,7 @@ impl Frame {
} }
#[inline(always)] #[inline(always)]
fn write_centered_clear(&self, s: &str) -> Components { fn write_centered_clear(&self, s: &str) -> Component {
let width = self.size().0; let width = self.size().0;
let limit = width - self.border * 2; let limit = width - self.border * 2;
let s = if s.len() > limit as usize { let s = if s.len() > limit as usize {
@ -83,12 +94,11 @@ impl Frame {
}; };
let len = s.len() as u16; let len = s.len() as u16;
let base_size = ((width - len) / 2) - self.border; let base_size = ((width - len) / 2) - self.border;
vec![ Component::String(super::compose::Text::PaddedBothSides(
Component::Padding(base_size + ((width - len) % 2)), base_size + ((width - len) % 2),
Component::String(s.into()), s.into(),
Component::Padding(base_size), base_size,
] ))
.into()
} }
#[inline(always)] #[inline(always)]
@ -100,58 +110,39 @@ impl Frame {
.into() .into()
} }
#[inline(always)] #[inline]
fn write_clear_to_end(&self, s: &str) -> Components { pub fn to_end(&self, s: &str, x: u16, y: u16) -> Components {
let limit = (self.size().0 - self.border * 2) as usize; // FIXME: handle the fucking newlines grrrr i hate this
let s = if s.len() > limit { &s[..limit] } else { s }; let width = self.inner_size().0;
vec![ vec![
Component::String(s.into()), Component::Goto(x, y),
Component::Padding( Component::String(Text::LimitedOrPadded(
self.size().0 - s.len() as u16 - (self.border * 2), s.into(),
), ' ',
width - x,
)),
] ]
.into() .into()
} }
pub fn writeln(&self, s: &str, x: u16, y: u16) -> Components { pub fn make(&self, comp: Components) -> String {
let mut index: u16 = 0; comp.0.into_iter().map(|c| c.make(self)).collect()
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()
} }
#[inline(always)] #[inline(always)]
pub fn size(&self) -> (u16, u16) { 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) (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)] #[inline(always)]
pub fn from_terminal_size( pub fn from_terminal_size(
theme: ColorSet, theme: ColorSet,
@ -225,8 +216,9 @@ impl Frame {
frame frame
} }
#[inline(always)]
pub fn frame_str(&self, body_theme: ColorSet) -> String { 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 // compose generates a collection of components for drawing
@ -240,13 +232,10 @@ impl Frame {
let (w_len, h_len) = self.size(); let (w_len, h_len) = self.size();
let frame_theme = Component::Internal(self.theme.to_string()); let frame_theme = Component::Internal(self.theme.to_string());
let body_clear = Component::Internal( let body_clear =
Into::<Components>::into(vec![ Component::Internal(self.make(Into::<Components>::into(
body_theme.clone(), vec![body_theme.clone(), Component::Padding(w_len)],
Component::Padding(w_len), )));
])
.concat_for(self),
);
let border = let border =
Component::Repeated(FRAME_CHAR_VERTICAL, self.border); Component::Repeated(FRAME_CHAR_VERTICAL, self.border);