TextSpan instead of strings

This commit is contained in:
veeso 2021-03-06 20:34:32 +01:00
parent db0c54b781
commit 55f74a8244
3 changed files with 126 additions and 31 deletions

View file

@ -118,7 +118,7 @@ impl FileList {
// Initialize states
let mut states: OwnStates = OwnStates::default();
// Set list length
states.set_list_len(match &props.texts.body {
states.set_list_len(match &props.texts.rows {
Some(tokens) => tokens.len(),
None => 0,
});
@ -136,11 +136,11 @@ impl Component for FileList {
false => None,
true => {
// Make list
let list_item: Vec<ListItem> = match self.props.texts.body.as_ref() {
let list_item: Vec<ListItem> = match self.props.texts.rows.as_ref() {
None => vec![],
Some(lines) => lines
.iter()
.map(|line: &String| ListItem::new(Span::from(line.to_string())))
.map(|line| ListItem::new(Span::from(line.content.to_string())))
.collect(),
};
let (fg, bg): (Color, Color) = match self.states.focus {
@ -186,7 +186,7 @@ impl Component for FileList {
fn update(&mut self, props: Props) -> Msg {
self.props = props;
// re-Set list length
self.states.set_list_len(match &self.props.texts.body {
self.states.set_list_len(match &self.props.texts.rows {
Some(tokens) => tokens.len(),
None => 0,
});
@ -287,7 +287,7 @@ impl Component for FileList {
mod tests {
use super::*;
use crate::ui::layout::props::TextParts;
use crate::ui::layout::props::{TextParts, TextSpan};
use crossterm::event::KeyEvent;
@ -298,7 +298,7 @@ mod tests {
PropsBuilder::default()
.with_texts(TextParts::new(
Some(String::from("filelist")),
Some(vec![String::from("file1"), String::from("file2")]),
Some(vec![TextSpan::from("file1"), TextSpan::from("file2")]),
))
.build(),
);
@ -323,9 +323,9 @@ mod tests {
.with_texts(TextParts::new(
Some(String::from("filelist")),
Some(vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
TextSpan::from("file1"),
TextSpan::from("file2"),
TextSpan::from("file3"),
]),
))
.build(),

View file

@ -24,6 +24,7 @@
*/
// locals
use super::super::props::TextSpan;
use super::{Component, InputEvent, Msg, Payload, PropValue, Props, PropsBuilder, Render};
// ext
use crossterm::event::KeyCode;
@ -73,6 +74,13 @@ impl OwnStates {
self.choice -= 1;
}
}
/// ### make_choices
///
/// Set OwnStates choices from a vector of text spans
pub fn make_choices(&mut self, spans: &Vec<TextSpan>) {
self.choices = spans.iter().map(|x| x.content.clone()).collect();
}
}
// -- component
@ -92,8 +100,8 @@ impl RadioGroup {
pub fn new(props: Props) -> Self {
// Make states
let mut states: OwnStates = OwnStates::default();
// Update choices
states.choices = props.texts.body.clone().unwrap_or(Vec::new());
// Update choices (vec of TextSpan to String)
states.make_choices(props.texts.rows.as_ref().unwrap_or(&Vec::new()));
// Get value
if let PropValue::Unsigned(choice) = props.value {
states.choice = choice;
@ -164,7 +172,8 @@ impl Component for RadioGroup {
/// Returns a Msg to the view
fn update(&mut self, props: Props) -> Msg {
// Reset choices
self.states.choices = props.texts.body.clone().unwrap_or(Vec::new());
self.states
.make_choices(props.texts.rows.as_ref().unwrap_or(&Vec::new()));
// Get value
if let PropValue::Unsigned(choice) = props.value {
self.states.choice = choice;
@ -256,7 +265,7 @@ impl Component for RadioGroup {
mod tests {
use super::*;
use crate::ui::layout::props::TextParts;
use crate::ui::layout::props::{TextParts, TextSpan};
use crossterm::event::KeyEvent;
@ -268,9 +277,9 @@ mod tests {
.with_texts(TextParts::new(
Some(String::from("yes or no?")),
Some(vec![
String::from("Yes!"),
String::from("No"),
String::from("Maybe"),
TextSpan::from("Yes!"),
TextSpan::from("No"),
TextSpan::from("Maybe"),
]),
))
.with_value(PropValue::Unsigned(1))

View file

@ -235,15 +235,15 @@ impl Default for PropsBuilder {
#[derive(Clone)]
pub struct TextParts {
pub title: Option<String>,
pub body: Option<Vec<String>>,
pub rows: Option<Vec<TextSpan>>,
}
impl TextParts {
/// ### new
///
/// Instantiates a new TextParts entity
pub fn new(title: Option<String>, body: Option<Vec<String>>) -> Self {
TextParts { title, body }
pub fn new(title: Option<String>, rows: Option<Vec<TextSpan>>) -> Self {
TextParts { title, rows }
}
}
@ -251,7 +251,46 @@ impl Default for TextParts {
fn default() -> Self {
TextParts {
title: None,
body: None,
rows: None,
}
}
}
/// ### TextSpan
///
/// TextSpan is a "cell" of text with its attributes
#[derive(Clone, std::fmt::Debug)]
pub struct TextSpan {
pub content: String,
pub color: Color,
pub bold: bool,
pub italic: bool,
pub underlined: bool,
}
impl TextSpan {
/// ### new
///
/// Instantiates a new TextSpan with all the attributes
pub fn new(content: String, color: Color, bold: bool, italic: bool, underlined: bool) -> Self {
TextSpan {
content,
color,
bold,
italic,
underlined,
}
}
}
impl From<&str> for TextSpan {
fn from(txt: &str) -> Self {
TextSpan {
content: txt.to_string(),
color: Color::Reset,
bold: false,
italic: false,
underlined: false,
}
}
}
@ -301,7 +340,7 @@ mod tests {
assert_eq!(props.input_type, InputType::Text);
assert!(props.input_len.is_none());
assert_eq!(props.value, PropValue::None);
assert!(props.texts.body.is_none());
assert!(props.texts.rows.is_none());
}
#[test]
@ -326,7 +365,7 @@ mod tests {
.underlined()
.with_texts(TextParts::new(
Some(String::from("hello")),
Some(vec![String::from("hey")]),
Some(vec![TextSpan::from("hey")]),
))
.with_input(InputType::Password)
.with_input_len(16)
@ -345,7 +384,15 @@ mod tests {
panic!("Expected value to be a string");
}
assert_eq!(
props.texts.body.as_ref().unwrap().get(0).unwrap().as_str(),
props
.texts
.rows
.as_ref()
.unwrap()
.get(0)
.unwrap()
.content
.as_str(),
"hey"
);
assert_eq!(props.underlined, true);
@ -359,7 +406,7 @@ mod tests {
.underlined()
.with_texts(TextParts::new(
Some(String::from("hello")),
Some(vec![String::from("hey")]),
Some(vec![TextSpan::from("hey")]),
))
.build();
assert_eq!(props.background, Color::Blue);
@ -368,7 +415,15 @@ mod tests {
assert_eq!(props.italic, true);
assert_eq!(props.texts.title.as_ref().unwrap().as_str(), "hello");
assert_eq!(
props.texts.body.as_ref().unwrap().get(0).unwrap().as_str(),
props
.texts
.rows
.as_ref()
.unwrap()
.get(0)
.unwrap()
.content
.as_str(),
"hey"
);
assert_eq!(props.underlined, true);
@ -389,7 +444,7 @@ mod tests {
.underlined()
.with_texts(TextParts::new(
Some(String::from("hello")),
Some(vec![String::from("hey")]),
Some(vec![TextSpan::from("hey")]),
));
// Rebuild
let _ = builder.build();
@ -406,7 +461,7 @@ mod tests {
.underlined()
.with_texts(TextParts::new(
Some(String::from("hello")),
Some(vec![String::from("hey")]),
Some(vec![TextSpan::from("hey")]),
))
.build();
// Ok, now make a builder from properties
@ -418,15 +473,29 @@ mod tests {
fn test_ui_layout_props_text_parts_with_values() {
let parts: TextParts = TextParts::new(
Some(String::from("Hello world!")),
Some(vec![String::from("row1"), String::from("row2")]),
Some(vec![TextSpan::from("row1"), TextSpan::from("row2")]),
);
assert_eq!(parts.title.as_ref().unwrap().as_str(), "Hello world!");
assert_eq!(
parts.body.as_ref().unwrap().get(0).unwrap().as_str(),
parts
.rows
.as_ref()
.unwrap()
.get(0)
.unwrap()
.content
.as_str(),
"row1"
);
assert_eq!(
parts.body.as_ref().unwrap().get(1).unwrap().as_str(),
parts
.rows
.as_ref()
.unwrap()
.get(1)
.unwrap()
.content
.as_str(),
"row2"
);
}
@ -435,6 +504,23 @@ mod tests {
fn test_ui_layout_props_text_parts_default() {
let parts: TextParts = TextParts::default();
assert!(parts.title.is_none());
assert!(parts.body.is_none());
assert!(parts.rows.is_none());
}
#[test]
fn test_ui_layout_props_text_span() {
let span: TextSpan = TextSpan::from("Hello!");
assert_eq!(span.content.as_str(), "Hello!");
assert_eq!(span.bold, false);
assert_eq!(span.color, Color::Reset);
assert_eq!(span.italic, false);
assert_eq!(span.underlined, false);
// With attributes
let span: TextSpan = TextSpan::new(String::from("Error"), Color::Red, true, true, true);
assert_eq!(span.content.as_str(), "Error");
assert_eq!(span.bold, true);
assert_eq!(span.color, Color::Red);
assert_eq!(span.italic, true);
assert_eq!(span.underlined, true);
}
}