extern crate textwrap;
extern crate unicode_width;
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, TextModifiers, TextSpan};
use tuirealm::Props;
use tuirealm::ratatui::style::{Color, Modifier, Style};
use tuirealm::ratatui::text::Line as Spans;
use tuirealm::ratatui::text::Span;
use tuirealm::ratatui::widgets::Block;
use unicode_width::UnicodeWidthStr;
pub fn wrap_spans<'a>(spans: &[TextSpan], width: usize, props: &Props) -> Vec<Spans<'a>> {
let mut res: Vec<Spans> = Vec::with_capacity(spans.len());
let mut line_width: usize = 0; let mut line_spans: Vec<Span> = Vec::new(); for span in spans.iter() {
let (fg, bg, tmod) = use_or_default_styles(props, span);
if line_width + span.content.width() > width {
if span.content.width() > width {
let span_lines = textwrap::wrap(span.content.as_str(), width);
for span_line in span_lines.iter() {
if line_width + span_line.width() > width {
res.push(Spans::from(line_spans));
line_width = 0;
line_spans = Vec::new();
}
line_width += span_line.width();
line_spans.push(Span::styled(
span_line.to_string(),
Style::default().fg(fg).bg(bg).add_modifier(tmod),
));
}
continue;
} else {
res.push(Spans::from(line_spans));
line_width = 0;
line_spans = Vec::new();
}
}
line_width += span.content.width();
line_spans.push(Span::styled(
span.content.to_string(),
Style::default().fg(fg).bg(bg).add_modifier(tmod),
));
}
if !line_spans.is_empty() {
res.push(Spans::from(line_spans));
}
res
}
pub fn use_or_default_styles(props: &Props, span: &TextSpan) -> (Color, Color, Modifier) {
(
match span.fg {
Color::Reset => props
.get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
.unwrap_color(),
_ => span.fg,
},
match span.bg {
Color::Reset => props
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
.unwrap_color(),
_ => span.bg,
},
match span.modifiers.is_empty() {
true => props
.get_or(
Attribute::TextProps,
AttrValue::TextModifiers(TextModifiers::empty()),
)
.unwrap_text_modifiers(),
false => span.modifiers,
},
)
}
pub fn get_block<'a>(
props: Borders,
title: Option<(String, Alignment)>,
focus: bool,
inactive_style: Option<Style>,
) -> Block<'a> {
let title = title.unwrap_or((String::default(), Alignment::Left));
Block::default()
.borders(props.sides)
.border_style(match focus {
true => props.style(),
false => {
inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
}
})
.border_type(props.modifiers)
.title(title.0)
.title_alignment(title.1)
}
pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
chars.iter().collect::<String>().width() as u16
}
#[cfg(test)]
mod test {
use super::*;
use tuirealm::props::{Alignment, BorderSides, BorderType, Props};
use pretty_assertions::assert_eq;
#[test]
fn test_components_utils_wrap_spans() {
let mut props: Props = Props::default();
props.set(
Attribute::TextProps,
AttrValue::TextModifiers(TextModifiers::BOLD),
);
props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
props.set(Attribute::Background, AttrValue::Color(Color::White));
let spans: Vec<TextSpan> = vec![TextSpan::from("hello, "), TextSpan::from("world!")];
assert_eq!(wrap_spans(&spans, 64, &props).len(), 1);
let spans: Vec<TextSpan> = vec![
TextSpan::from("Hello, everybody, I'm Uncle Camel!"),
TextSpan::from("How's it going today?"),
];
assert_eq!(wrap_spans(&spans, 32, &props).len(), 2);
let spans: Vec<TextSpan> = vec![TextSpan::from(
"Hello everybody! My name is Uncle Camel. How's it going today?",
)];
assert_eq!(wrap_spans(&spans, 16, &props).len(), 4);
let spans: Vec<TextSpan> = vec![
TextSpan::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
TextSpan::from("Canem!"),
TextSpan::from("In posuere sollicitudin vulputate"),
TextSpan::from("Sed vitae rutrum quam."),
];
assert_eq!(wrap_spans(&spans, 36, &props).len(), 4);
}
#[test]
fn test_components_utils_use_or_default_styles() {
let mut props: Props = Props::default();
props.set(
Attribute::TextProps,
AttrValue::TextModifiers(TextModifiers::BOLD),
);
props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
props.set(Attribute::Background, AttrValue::Color(Color::White));
let span: TextSpan = TextSpan::from("test")
.underlined()
.fg(Color::Yellow)
.bg(Color::Cyan);
let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
assert_eq!(fg, Color::Yellow);
assert_eq!(bg, Color::Cyan);
assert!(modifiers.intersects(Modifier::UNDERLINED));
let span: TextSpan = TextSpan::from("test");
let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
assert_eq!(fg, Color::Red);
assert_eq!(bg, Color::White);
assert!(modifiers.intersects(Modifier::BOLD));
}
#[test]
fn test_components_utils_get_block() {
let props = Borders::default()
.sides(BorderSides::ALL)
.color(Color::Red)
.modifiers(BorderType::Rounded);
get_block(
props.clone(),
Some(("title".to_string(), Alignment::Center)),
true,
None,
);
get_block(props, None, false, None);
}
#[test]
fn test_components_utils_calc_utf8_cursor_position() {
let chars: Vec<char> = vec!['v', 'e', 'e', 's', 'o'];
assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 5);
assert_eq!(calc_utf8_cursor_position(&chars[0..3]), 3);
let chars: Vec<char> = vec!['я', ' ', 'х', 'о', 'ч', 'у', ' ', 'с', 'п', 'а', 'т', 'ь'];
assert_eq!(calc_utf8_cursor_position(&chars[0..6]), 6);
let chars: Vec<char> = vec!['H', 'i', '😄'];
assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 4);
let chars: Vec<char> = vec!['我', '之', '😄'];
assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 6);
}
}