diff --git a/src/display/body.rs b/src/display/body.rs index 7377556..44b23cc 100644 --- a/src/display/body.rs +++ b/src/display/body.rs @@ -3,7 +3,10 @@ use std::io::{Stdout, Write}; use termion::{clear, cursor, raw::RawTerminal}; use super::{ - frame::Frame, theme::ColorSet, Environment, Event, Page, + compose::{Component, Components}, + frame::Frame, + theme::ColorSet, + Environment, Event, Page, }; type Result = std::result::Result; @@ -25,8 +28,10 @@ impl Body { screen, "{theme}{content}", theme = env.theme.primary(), - content = - env.frame.writeln(&format!("Event: {}", event), 0, 0), + content = env + .frame + .writeln(&format!("Event: {}", event), 0, 0) + .concat_for(&env.frame), )?; screen.flush()?; Ok(()) @@ -42,7 +47,7 @@ impl Body { "{hide}{clear}{fr}{cursor}", clear = clear::All, hide = cursor::Hide, - fr = env.frame.frame_str(env.theme.primary()), + fr = env.frame.frame_str(env.theme.colors.primary), cursor = env.frame.goto(0, 0), )?; screen.flush()?; @@ -98,7 +103,11 @@ impl Default for SigninCursorLocation { impl SigninPage { #[inline] - fn draw(&self, highlight: ColorSet, normal: ColorSet) -> String { + fn draw( + &self, + highlight: ColorSet, + normal: ColorSet, + ) -> Components { const HEADER_Y: u16 = 1; const HOSTNAME_Y: u16 = 3; const OK_Y: u16 = 5; @@ -106,21 +115,27 @@ impl SigninPage { let fr_width = frame.size().0; let total_width = fr_width / 3; - format!( - "{header}{hostname}{retheme}{ok}", - header = frame.write_centered("login kk", HEADER_Y), - hostname = frame.write_centered( - &self.field_str( - highlight, - total_width, - "hostname", - "", - ), - HOSTNAME_Y, - ), - retheme = normal.to_string(), - ok = frame.write_centered("[ok]", OK_Y), - ) + 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::>() + // .into() } #[inline] @@ -176,16 +191,17 @@ impl Page for SigninPage { "{theme}{clear}{frame}{subframe}{content}{hide_cursor}", theme = env.theme.frame(), clear = clear::All, - frame = env.frame.frame_str(env.theme.primary()), + frame = env.frame.frame_str(env.theme.colors.primary), subframe = self .frame .unwrap() - .frame_str(env.theme.colors.subwin.to_string()), + .frame_str(env.theme.colors.subwin), hide_cursor = cursor::Hide, - content = self.draw( - env.theme.colors.highlight, - env.theme.colors.subwin - ), + content = "", + // content = self.draw( + // env.theme.colors.highlight, + // env.theme.colors.subwin + // ), )?; screen.flush()?; diff --git a/src/display/compose.rs b/src/display/compose.rs index c5d88b7..0044b44 100644 --- a/src/display/compose.rs +++ b/src/display/compose.rs @@ -1,4 +1,8 @@ -use std::ops::Deref; +use std::{ + iter::Map, + ops::{Deref, Range}, + vec::IntoIter, +}; use super::{frame::Frame, theme::ColorSet}; @@ -7,22 +11,31 @@ pub enum Component { String(String), Goto(u16, u16), Theme(ColorSet), + Padding(u16), + // Pre-formatted + Internal(String), + Repeated(char, u16), Clear, } impl Component { - pub fn prepare_for(&self, frame: &Frame) -> String { + pub fn prepare_for(self, frame: &Frame) -> String { match self { - Component::String(s) => s.to_owned(), - Component::Goto(x, y) => frame.goto(*x, *y), + Component::String(s) => s, + 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(Vec); +pub struct Components(pub Vec); impl Deref for Components { type Target = Vec; @@ -46,13 +59,16 @@ impl From for Components { impl Components { pub fn str_len(&self) -> u16 { - let mut len_cnt: u16 = 0; - for elem in &self.0 { - if let Component::String(s) = elem { - len_cnt += s.len() as u16; - } - } - len_cnt + (&self.0) + .into_iter() + .map(|comp| { + if let Component::String(s) = comp { + s.len() as u16 + } else { + 0 + } + }) + .sum() } pub fn concat_for(self, frame: &Frame) -> String { @@ -61,4 +77,8 @@ impl Components { .map(|component| component.prepare_for(frame)) .collect::() } + + pub fn push(&mut self, comp: Component) { + self.0.push(comp) + } } diff --git a/src/display/frame.rs b/src/display/frame.rs index 25e4a8e..e40cf7b 100644 --- a/src/display/frame.rs +++ b/src/display/frame.rs @@ -1,9 +1,16 @@ use termion::{clear, cursor, raw::RawTerminal}; const ESTIMATED_FRAME_BIT_SIZE: usize = 11; -use std::io::{Stdout, Write}; +use std::{ + io::{Stdout, Write}, + slice::Iter, + vec::IntoIter, +}; -use super::theme::{ColorSet, Theme}; +use super::{ + compose::{Component, Components}, + theme::{ColorSet, Theme}, +}; const FRAME_CHAR_HORIZONTAL: char = ' '; const FRAME_CHAR_VERTICAL: char = ' '; @@ -40,7 +47,7 @@ impl Frame { #[inline(always)] pub fn goto(&self, x: u16, y: u16) -> String { - let cg = cursor::Goto( + cursor::Goto( self.border.max( (self.border + self.start.0 + x) .min(self.end.0 - self.border), @@ -49,76 +56,91 @@ impl Frame { (self.border + self.start.1 + y) .min(self.end.1 - self.border), ), - ); - cg.into() - } - - #[inline(always)] - fn goto_internal(&self, x: u16, y: u16) -> String { - cursor::Goto( - (self.start.0 + x).min(self.end.0), - (self.start.1 + y).min(self.end.1), ) .into() } #[inline(always)] - fn write_centered_clear(&self, s: &str) -> String { - let width = self.size().0 as usize; - let limit = width - self.border as usize * 2; - let s = if s.len() > limit { &s[..limit] } else { s }; - let len = s.len(); - let base_size = ((width - len) / 2) - self.border as usize; - format!( - "{left}{line}{right}", - left = " ".repeat(base_size + ((width - len) % 2)), - line = s, - right = " ".repeat(base_size) + 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_frame_line(&self, y: u16, length: u16) -> String { - self.goto_internal(0, y) - + &FRAME_CHAR_HORIZONTAL - .to_string() - .repeat(length as usize) + fn write_centered_clear(&self, s: &str) -> Components { + let width = self.size().0; + let limit = width - self.border * 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.border; + vec![ + Component::Padding(base_size + ((width - len) % 2)), + Component::String(s.into()), + Component::Padding(base_size), + ] + .into() } #[inline(always)] - fn write_clear_to_end(&self, s: &str) -> String { + fn write_frame_line(&self, y: u16, length: u16) -> Components { + vec![ + self.goto_internal(0, y), + Component::Repeated(FRAME_CHAR_HORIZONTAL, length), + ] + .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 }; - let clear_length = self.size().0 as usize - - s.len() - - (self.border * 2) as usize; - format!("{}{}", s, " ".repeat(clear_length)) + vec![ + Component::String(s.into()), + Component::Padding( + self.size().0 - s.len() as u16 - (self.border * 2), + ), + ] + .into() } - pub fn writeln(&self, s: &str, x: u16, y: u16) -> String { - let words = s.split('\n').collect::>(); - let mut out_words = Vec::with_capacity(words.len()); - for i in 0..words.len() { - out_words.push(format!( - "{ret}{str}", - str = self.write_clear_to_end(words[i]), - ret = self.goto(x, y + i as u16), - )); - } - out_words.concat() + 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::>() + .into() } - pub fn write_centered(&self, s: &str, y: u16) -> String { - let words = s.split('\n').collect::>(); - let mut out_words = Vec::with_capacity(words.len()); - for i in 0..words.len() { - out_words.push(format!( - "{ret}{str}", - ret = self.goto(0, y + i as u16), - str = self.write_centered_clear(words[i]), - )); - } - out_words.concat() + 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::>() + .into() } #[inline(always)] @@ -203,42 +225,73 @@ impl Frame { frame } - pub fn frame_str(&self, body_theme: String) -> String { - if self.border == 0 { - return body_theme + &clear::All.to_string(); - } - self.goto_internal(0, 0); - let (w_len, h_len) = self.size(); - let width_str = FRAME_CHAR_HORIZONTAL - .to_string() - .repeat(w_len as usize + 1); - let mut frame = Vec::with_capacity(h_len as usize + 4); - let frame_theme = self.theme.to_string(); - let body_clear = - body_theme.clone() + &" ".repeat(w_len as usize); - frame.push(self.theme.to_string()); + pub fn frame_str(&self, body_theme: ColorSet) -> String { + self.compose(body_theme).concat_for(self) + } - for y in 0..self.border { - frame.push(self.write_frame_line(y, w_len)); + // 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()); + if self.border == 0 { + // FIXME: this will fuck up subframes + return vec![body_theme, Component::Clear].into(); } - for y in self.border..h_len - self.border { - frame.push(format!( - "{left}{body_clear}{frame_theme}{left}{char}{right}{char}", - body_clear = &body_clear, - frame_theme = &frame_theme, - left = self.goto_internal(0, y), - right = self.goto_internal(w_len - self.border, y), - char = FRAME_CHAR_VERTICAL - .to_string() - .repeat(self.border as usize), - )); - } - for y in h_len - self.border..h_len { - frame.push(self.write_frame_line(y, w_len)); - } - frame.push(self.goto(0, 0)); - frame.push(body_theme); - frame.concat() + let (w_len, h_len) = self.size(); + let frame_theme = Component::Internal(self.theme.to_string()); + + let body_clear = Component::Internal( + Into::::into(vec![ + body_theme.clone(), + Component::Padding(w_len), + ]) + .concat_for(self), + ); + + let border = + Component::Repeated(FRAME_CHAR_VERTICAL, self.border); + // This seems cursed but, I assure you, it's fine + vec![vec![frame_theme.clone()]] + .into_iter() + // Top horizontal borders + .chain( + (0..self.border) + .into_iter() + .map(|y: u16| self.write_frame_line(y, w_len).0), + ) + // Frame body + vertical frame borders + .chain( + (self.border..h_len - self.border).into_iter().map( + |y: u16| { + let left = self.goto_internal(0, y); + let right = self + .goto_internal(w_len - self.border, y); + vec![ + left.clone(), + body_clear.clone(), + frame_theme.clone(), + left, + border.clone(), + right, + border.clone(), + ] + }, + ), + ) + // Bottom horizontal border + .chain( + (h_len - self.border..h_len) + .into_iter() + .map(|y: u16| self.write_frame_line(y, w_len).0), + ) + // Theme reset + move to beginning of inside frame + .chain( + vec![vec![Component::Goto(0, 0), body_theme]] + .into_iter(), + ) + .flatten() + .collect::>() + .into() } } diff --git a/src/display/mod.rs b/src/display/mod.rs index 9e8cb04..4ff880f 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -89,7 +89,7 @@ pub struct Environment { impl Into for Environment { fn into(self) -> String { - self.frame.frame_str(self.theme.primary()) + self.frame.frame_str(self.theme.colors.primary) } } @@ -107,7 +107,7 @@ impl Environment { #[inline(always)] pub fn primary_frame(&self) -> String { - self.frame.frame_str(self.theme.primary()) + self.frame.frame_str(self.theme.colors.primary) } }