mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
TextSpan instead of strings
This commit is contained in:
@@ -118,7 +118,7 @@ impl FileList {
|
|||||||
// Initialize states
|
// Initialize states
|
||||||
let mut states: OwnStates = OwnStates::default();
|
let mut states: OwnStates = OwnStates::default();
|
||||||
// Set list length
|
// Set list length
|
||||||
states.set_list_len(match &props.texts.body {
|
states.set_list_len(match &props.texts.rows {
|
||||||
Some(tokens) => tokens.len(),
|
Some(tokens) => tokens.len(),
|
||||||
None => 0,
|
None => 0,
|
||||||
});
|
});
|
||||||
@@ -136,11 +136,11 @@ impl Component for FileList {
|
|||||||
false => None,
|
false => None,
|
||||||
true => {
|
true => {
|
||||||
// Make list
|
// 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![],
|
None => vec![],
|
||||||
Some(lines) => lines
|
Some(lines) => lines
|
||||||
.iter()
|
.iter()
|
||||||
.map(|line: &String| ListItem::new(Span::from(line.to_string())))
|
.map(|line| ListItem::new(Span::from(line.content.to_string())))
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
let (fg, bg): (Color, Color) = match self.states.focus {
|
let (fg, bg): (Color, Color) = match self.states.focus {
|
||||||
@@ -186,7 +186,7 @@ impl Component for FileList {
|
|||||||
fn update(&mut self, props: Props) -> Msg {
|
fn update(&mut self, props: Props) -> Msg {
|
||||||
self.props = props;
|
self.props = props;
|
||||||
// re-Set list length
|
// 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(),
|
Some(tokens) => tokens.len(),
|
||||||
None => 0,
|
None => 0,
|
||||||
});
|
});
|
||||||
@@ -287,7 +287,7 @@ impl Component for FileList {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ui::layout::props::TextParts;
|
use crate::ui::layout::props::{TextParts, TextSpan};
|
||||||
|
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
|
|
||||||
@@ -298,7 +298,7 @@ mod tests {
|
|||||||
PropsBuilder::default()
|
PropsBuilder::default()
|
||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("filelist")),
|
Some(String::from("filelist")),
|
||||||
Some(vec![String::from("file1"), String::from("file2")]),
|
Some(vec![TextSpan::from("file1"), TextSpan::from("file2")]),
|
||||||
))
|
))
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
@@ -323,9 +323,9 @@ mod tests {
|
|||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("filelist")),
|
Some(String::from("filelist")),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
String::from("file1"),
|
TextSpan::from("file1"),
|
||||||
String::from("file2"),
|
TextSpan::from("file2"),
|
||||||
String::from("file3"),
|
TextSpan::from("file3"),
|
||||||
]),
|
]),
|
||||||
))
|
))
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// locals
|
// locals
|
||||||
|
use super::super::props::TextSpan;
|
||||||
use super::{Component, InputEvent, Msg, Payload, PropValue, Props, PropsBuilder, Render};
|
use super::{Component, InputEvent, Msg, Payload, PropValue, Props, PropsBuilder, Render};
|
||||||
// ext
|
// ext
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
@@ -73,6 +74,13 @@ impl OwnStates {
|
|||||||
self.choice -= 1;
|
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
|
// -- component
|
||||||
@@ -92,8 +100,8 @@ impl RadioGroup {
|
|||||||
pub fn new(props: Props) -> Self {
|
pub fn new(props: Props) -> Self {
|
||||||
// Make states
|
// Make states
|
||||||
let mut states: OwnStates = OwnStates::default();
|
let mut states: OwnStates = OwnStates::default();
|
||||||
// Update choices
|
// Update choices (vec of TextSpan to String)
|
||||||
states.choices = props.texts.body.clone().unwrap_or(Vec::new());
|
states.make_choices(props.texts.rows.as_ref().unwrap_or(&Vec::new()));
|
||||||
// Get value
|
// Get value
|
||||||
if let PropValue::Unsigned(choice) = props.value {
|
if let PropValue::Unsigned(choice) = props.value {
|
||||||
states.choice = choice;
|
states.choice = choice;
|
||||||
@@ -164,7 +172,8 @@ impl Component for RadioGroup {
|
|||||||
/// Returns a Msg to the view
|
/// Returns a Msg to the view
|
||||||
fn update(&mut self, props: Props) -> Msg {
|
fn update(&mut self, props: Props) -> Msg {
|
||||||
// Reset choices
|
// 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
|
// Get value
|
||||||
if let PropValue::Unsigned(choice) = props.value {
|
if let PropValue::Unsigned(choice) = props.value {
|
||||||
self.states.choice = choice;
|
self.states.choice = choice;
|
||||||
@@ -256,7 +265,7 @@ impl Component for RadioGroup {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ui::layout::props::TextParts;
|
use crate::ui::layout::props::{TextParts, TextSpan};
|
||||||
|
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
|
|
||||||
@@ -268,9 +277,9 @@ mod tests {
|
|||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("yes or no?")),
|
Some(String::from("yes or no?")),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
String::from("Yes!"),
|
TextSpan::from("Yes!"),
|
||||||
String::from("No"),
|
TextSpan::from("No"),
|
||||||
String::from("Maybe"),
|
TextSpan::from("Maybe"),
|
||||||
]),
|
]),
|
||||||
))
|
))
|
||||||
.with_value(PropValue::Unsigned(1))
|
.with_value(PropValue::Unsigned(1))
|
||||||
|
|||||||
@@ -235,15 +235,15 @@ impl Default for PropsBuilder {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextParts {
|
pub struct TextParts {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub body: Option<Vec<String>>,
|
pub rows: Option<Vec<TextSpan>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextParts {
|
impl TextParts {
|
||||||
/// ### new
|
/// ### new
|
||||||
///
|
///
|
||||||
/// Instantiates a new TextParts entity
|
/// Instantiates a new TextParts entity
|
||||||
pub fn new(title: Option<String>, body: Option<Vec<String>>) -> Self {
|
pub fn new(title: Option<String>, rows: Option<Vec<TextSpan>>) -> Self {
|
||||||
TextParts { title, body }
|
TextParts { title, rows }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +251,46 @@ impl Default for TextParts {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TextParts {
|
TextParts {
|
||||||
title: None,
|
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_eq!(props.input_type, InputType::Text);
|
||||||
assert!(props.input_len.is_none());
|
assert!(props.input_len.is_none());
|
||||||
assert_eq!(props.value, PropValue::None);
|
assert_eq!(props.value, PropValue::None);
|
||||||
assert!(props.texts.body.is_none());
|
assert!(props.texts.rows.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -326,7 +365,7 @@ mod tests {
|
|||||||
.underlined()
|
.underlined()
|
||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("hello")),
|
Some(String::from("hello")),
|
||||||
Some(vec![String::from("hey")]),
|
Some(vec![TextSpan::from("hey")]),
|
||||||
))
|
))
|
||||||
.with_input(InputType::Password)
|
.with_input(InputType::Password)
|
||||||
.with_input_len(16)
|
.with_input_len(16)
|
||||||
@@ -345,7 +384,15 @@ mod tests {
|
|||||||
panic!("Expected value to be a string");
|
panic!("Expected value to be a string");
|
||||||
}
|
}
|
||||||
assert_eq!(
|
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"
|
"hey"
|
||||||
);
|
);
|
||||||
assert_eq!(props.underlined, true);
|
assert_eq!(props.underlined, true);
|
||||||
@@ -359,7 +406,7 @@ mod tests {
|
|||||||
.underlined()
|
.underlined()
|
||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("hello")),
|
Some(String::from("hello")),
|
||||||
Some(vec![String::from("hey")]),
|
Some(vec![TextSpan::from("hey")]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert_eq!(props.background, Color::Blue);
|
assert_eq!(props.background, Color::Blue);
|
||||||
@@ -368,7 +415,15 @@ mod tests {
|
|||||||
assert_eq!(props.italic, true);
|
assert_eq!(props.italic, true);
|
||||||
assert_eq!(props.texts.title.as_ref().unwrap().as_str(), "hello");
|
assert_eq!(props.texts.title.as_ref().unwrap().as_str(), "hello");
|
||||||
assert_eq!(
|
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"
|
"hey"
|
||||||
);
|
);
|
||||||
assert_eq!(props.underlined, true);
|
assert_eq!(props.underlined, true);
|
||||||
@@ -389,7 +444,7 @@ mod tests {
|
|||||||
.underlined()
|
.underlined()
|
||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("hello")),
|
Some(String::from("hello")),
|
||||||
Some(vec![String::from("hey")]),
|
Some(vec![TextSpan::from("hey")]),
|
||||||
));
|
));
|
||||||
// Rebuild
|
// Rebuild
|
||||||
let _ = builder.build();
|
let _ = builder.build();
|
||||||
@@ -406,7 +461,7 @@ mod tests {
|
|||||||
.underlined()
|
.underlined()
|
||||||
.with_texts(TextParts::new(
|
.with_texts(TextParts::new(
|
||||||
Some(String::from("hello")),
|
Some(String::from("hello")),
|
||||||
Some(vec![String::from("hey")]),
|
Some(vec![TextSpan::from("hey")]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
// Ok, now make a builder from properties
|
// Ok, now make a builder from properties
|
||||||
@@ -418,15 +473,29 @@ mod tests {
|
|||||||
fn test_ui_layout_props_text_parts_with_values() {
|
fn test_ui_layout_props_text_parts_with_values() {
|
||||||
let parts: TextParts = TextParts::new(
|
let parts: TextParts = TextParts::new(
|
||||||
Some(String::from("Hello world!")),
|
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.title.as_ref().unwrap().as_str(), "Hello world!");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parts.body.as_ref().unwrap().get(0).unwrap().as_str(),
|
parts
|
||||||
|
.rows
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(0)
|
||||||
|
.unwrap()
|
||||||
|
.content
|
||||||
|
.as_str(),
|
||||||
"row1"
|
"row1"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parts.body.as_ref().unwrap().get(1).unwrap().as_str(),
|
parts
|
||||||
|
.rows
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.content
|
||||||
|
.as_str(),
|
||||||
"row2"
|
"row2"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -435,6 +504,23 @@ mod tests {
|
|||||||
fn test_ui_layout_props_text_parts_default() {
|
fn test_ui_layout_props_text_parts_default() {
|
||||||
let parts: TextParts = TextParts::default();
|
let parts: TextParts = TextParts::default();
|
||||||
assert!(parts.title.is_none());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user