diff --git a/kkdisp/src/component.rs b/kkdisp/src/component.rs index 3728ec6..efc6606 100644 --- a/kkdisp/src/component.rs +++ b/kkdisp/src/component.rs @@ -2,15 +2,39 @@ use crate::{ theme::{Color, ColorSet}, token::Token, }; +use std::iter::Extend; #[derive(Eq, Debug, Clone)] pub enum Component { + NextLine, X(usize), String(String), Fg(Color), Bg(Color), } +impl Component { + fn make_for_line( + comp: Vec, + line: usize, + offset: usize, + ) -> String { + comp.into_iter() + .map(|c| match c { + Component::X(x) => termion::cursor::Goto( + (x + offset) as u16, + line as u16, + ) + .into(), + Component::String(s) => s, + Component::Fg(c) => c.fg(), + Component::Bg(c) => c.bg(), + Component::NextLine => "\r\n".into(), + }) + .collect() + } +} + impl PartialEq for Component { fn eq(&self, other: &Self) -> bool { match self { @@ -30,6 +54,10 @@ impl PartialEq for Component { Self::Bg(other_c) => c == other_c, _ => false, }, + Self::NextLine => match other { + Self::NextLine => true, + _ => false, + }, } } } @@ -86,7 +114,7 @@ impl Widget { } fn get_line(&self, line: usize) -> Option<&Token> { - self.per_line.get(line - 1) + self.per_line.get(line) } } @@ -131,11 +159,62 @@ impl PartialEq for Instruction { } impl Instruction { - pub fn make( + pub fn into_components( self, - (term_width, term_height): (u16, u16), - ) -> String { - todo!() + line_width: usize, + height_left: usize, + ) -> Vec { + if height_left == 0 { + return vec![]; + } + match self { + Instruction::FixedHeight(next, lines, wdg) => (0..lines) + .map(|line| { + let mut offset = 0; + (&wdg) + .into_iter() + .map(|w| { + let (width, token) = ( + w.want_width.abs_size(line_width), + w.get_line(line).map(|t| t.clone()), + ); + let mut result = match token { + Some(tok) => tok + .with_width(width) + .into_iter() + .map(|comp| { + if let Component::X(x) = comp + { + Component::X(offset + x) + } else { + comp + } + }) + .collect(), + None => Vec::new(), + }; + offset += width; + if offset < line_width { + result.push(Component::X(offset)); + } + result + }) + .flatten() + .chain(vec![Component::NextLine]) + .collect::>() + }) + .flatten() + .chain( + next.into_components( + line_width, + height_left - lines, + ), + ) + .collect(), + Instruction::End => (0..height_left) + .map(|_| Component::NextLine) + .collect(), + } } pub fn start() -> Self { @@ -251,13 +330,11 @@ impl Plan { mod tests { use super::*; - #[test] - fn test_plan_to_instructions() { - const HEIGHT: usize = 40; - let (widget_1, widget_2, widget_3) = ( + fn test_widgets() -> (Widget, Widget, Widget) { + ( Widget::new( SectionWidth::Third, - vec![Token::text("hello")], + vec![Token::text("hello").centered()], ), Widget::new( SectionWidth::Third, @@ -269,7 +346,108 @@ mod tests { SectionWidth::Third, vec![Token::text("hello").limited(16).padded(20)], ), - ); + ) + } + + #[test] + fn test_instructions_to_components() { + const WIDTH: usize = 120; + const HEIGHT: usize = 40; + let (w1, w2, w3) = test_widgets(); + vec![ + ( + "all the widgets, 30 lines", + Instruction::start().fixed( + 30, + vec![w1.clone(), w2.clone(), w3.clone()], + ), + w1.clone() + .get_line(0) + .unwrap() + .clone() + .with_width(WIDTH / 3) + .into_iter() + .chain(vec![Component::X(WIDTH / 3)]) + .chain( + w2.clone() + .get_line(0) + .unwrap() + .clone() + .with_width(WIDTH / 3), + ) + .chain(vec![Component::X((WIDTH / 3) * 2)]) + .chain( + w3.clone() + .get_line(0) + .unwrap() + .clone() + .with_width(WIDTH / 3), + ) + .chain(vec![Component::NextLine]) + .chain( + (1..30) + .map(|_| { + vec![ + Component::X(WIDTH / 3), + Component::X((WIDTH / 3) * 2), + Component::NextLine, + ] + }) + .flatten(), + ) + .chain( + (0..(HEIGHT - 30)) + .map(|_| Component::NextLine), + ) + .collect::>(), + ), + ( + "Single widget, single 10 lines section", + Instruction::start().fixed(10, vec![w1.clone()]), + w1.clone() + .get_line(0) + .unwrap() + .clone() + .with_width(WIDTH / 3) + .into_iter() + .chain( + (0..10) + .map(|_| { + vec![ + Component::X(WIDTH / 3), + Component::NextLine, + ] + }) + .flatten(), + ) + .chain( + (0..(HEIGHT - 10)) + .map(|_| Component::NextLine), + ) + .collect(), + ), + ] + .into_iter() + .for_each(|(name, instruction, expected)| { + let actual = instruction.into_components(WIDTH, HEIGHT); + assert!( + expected == actual, + "<{}>: + expected({}):\n{:#?} + actual({}):\n{:#?}", + name, + expected.len(), + expected, + actual.len(), + actual, + ); + }); + } + + #[test] + fn test_plan_to_instructions() { + const HEIGHT: usize = 40; + let (widget_1, widget_2, widget_3) = test_widgets(); vec![ ("end -> end", Plan::start(), Instruction::End), diff --git a/kkdisp/src/token.rs b/kkdisp/src/token.rs index c155093..638dd3f 100644 --- a/kkdisp/src/token.rs +++ b/kkdisp/src/token.rs @@ -76,7 +76,7 @@ impl Token { Self::Bg(Box::new(self), c) } - fn with_width(self, width: usize) -> Vec { + pub fn with_width(self, width: usize) -> Vec { match self { Token::String(t, s) => vec![Component::String(s)] .into_iter()