diff --git a/kkdisp/src/token.rs b/kkdisp/src/token.rs index 50c9e24..274553e 100644 --- a/kkdisp/src/token.rs +++ b/kkdisp/src/token.rs @@ -8,16 +8,37 @@ pub enum Token { CharPad(Box, char, usize), Fg(Box, Color), Bg(Box, Color), - String(String), + String(Box, String), + End, } impl From for Token { fn from(value: String) -> Self { - Token::String(value) + Self::text(value) + } +} + +impl From<&str> for Token { + fn from(value: &str) -> Self { + Self::text(value) } } impl Token { + pub fn text(t: T) -> Self + where + T: Into, + { + Self::End.string(t) + } + + pub fn string(self, s: S) -> Self + where + S: Into, + { + Token::String(Box::new(self), s.into()) + } + pub fn centered(self) -> Self { Self::Centered(Box::new(self)) } @@ -31,7 +52,17 @@ impl Token { } pub fn str_len(&self) -> usize { - self.walk_to_end().len() + match self { + Token::String(t, s) => s.len() + t.str_len(), + Token::Fg(t, _) => t.str_len(), + Token::Bg(t, _) => t.str_len(), + Token::Centered(t) => t.str_len(), + Token::Limited(t, lim) => (*lim).min(t.str_len()), + Token::CharPad(t, _, pad_to) => { + (*pad_to).max(t.str_len()) + } + Token::End => 0, + } } pub fn pad_char(self, c: char, pad_to: usize) -> Self { @@ -46,38 +77,25 @@ impl Token { Self::Bg(Box::new(self), c) } - fn walk_to_end(&self) -> &String { - match self { - Token::String(s) => s, - Token::Fg(t, _) => t.walk_to_end(), - Token::Bg(t, _) => t.walk_to_end(), - Token::Centered(t) => t.walk_to_end(), - Token::Limited(t, _) => t.walk_to_end(), - Token::CharPad(t, _, _) => t.walk_to_end(), - } - } - fn with_width(self, width: usize) -> Vec { match self { - Token::String(s) => vec![Component::String(s)], - Token::Centered(cnt) => cnt - .with_width(width) + Token::String(t, s) => vec![Component::String(s)] .into_iter() - .map(|comp| { - if let Component::String(s) = comp { - let str_len = s.len(); - let x = if str_len > width { - 0 - } else { - (width - str_len) / 2 - }; - vec![Component::X(x), Component::String(s)] - } else { - vec![comp] - } - }) - .flatten() + .chain(t.with_width(width)) .collect(), + Token::Centered(cnt) => { + let mut str_len = cnt.str_len(); + let components = if str_len > width { + str_len = width; + cnt.limited(width).with_width(width) + } else { + cnt.with_width(width) + }; + vec![Component::X((width - str_len) / 2)] + .into_iter() + .chain(components) + .collect() + } Token::Limited(s, lim) => s .with_width(width) .into_iter() @@ -116,6 +134,7 @@ impl Token { .into_iter() .chain(t.with_width(width)) .collect(), + Token::End => vec![], } } } @@ -130,29 +149,25 @@ mod tests { vec![ ( "string -> string", - Token::String("hello".into()), + Token::text("hello"), vec![Component::String("hello".into())], ), ( "string -> limited", - Token::String( - "This string is too long for this!".into(), - ) - .limited(4), + Token::text("This string is too long for this!") + .limited(4), vec![Component::String("This".into())], ), ( "string -> limit -> pad_to", - Token::String( - "This string is too long, but some".into(), - ) - .limited(10) - .padded(15), + Token::text("This string is too long, but some") + .limited(10) + .padded(15), vec![Component::String("This strin ".into())], ), ( "center limited string", - Token::String("Ahhh this won't go far".into()) + Token::text("Ahhh this won't go far") .limited(10) .centered(), vec![ @@ -162,9 +177,7 @@ mod tests { ), ( "padded string with underscores is centered", - Token::String("It was...".into()) - .pad_char('_', 15) - .centered(), + Token::text("It was...").pad_char('_', 15).centered(), vec![ Component::X((WIDTH - 15) / 2), Component::String("It was...______".into()), @@ -172,7 +185,7 @@ mod tests { ), ( "prefixes color", - Token::String("this is red".into()).fg(Color::RED), + Token::text("this is red").fg(Color::RED), vec![ Component::Fg(Color::RED), Component::String("this is red".into()), @@ -180,9 +193,7 @@ mod tests { ), ( "color at beginning of line", - Token::String("colored".into()) - .centered() - .bg(Color::RED), + Token::text("colored").centered().bg(Color::RED), vec![ Component::Bg(Color::RED), Component::X((WIDTH - 7) / 2), @@ -191,7 +202,7 @@ mod tests { ), ( "color isnt part of limit", - Token::String("ten chars!".into()) + Token::text("ten chars!") .bg(Color::RED) .fg(Color::GREEN) .bg(Color::BLUE) @@ -205,6 +216,25 @@ mod tests { Component::String("ten chars!".into()), ], ), + ( + "multicolor string centered", + Token::text("end") + .fg(Color::RED) + .string("mid") + .fg(Color::BLUE) + .string("start") + .fg(Color::GREEN) + .centered(), + vec![ + Component::X(0), + Component::Fg(Color::GREEN), + Component::String("start".into()), + Component::Fg(Color::BLUE), + Component::String("mid".into()), + Component::Fg(Color::RED), + Component::String("end".into()), + ], + ), ] .into_iter() .for_each(|test| {