finished body/frame refactoring
This commit is contained in:
parent
99808f3d90
commit
ac9e337e73
|
@ -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()?;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue