1extern crate textwrap;
7extern crate unicode_width;
8use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, TextModifiers, TextSpan};
10use tuirealm::Props;
11use tuirealm::ratatui::style::{Color, Modifier, Style};
13use tuirealm::ratatui::text::Line as Spans;
14use tuirealm::ratatui::text::Span;
15use tuirealm::ratatui::widgets::Block;
16use unicode_width::UnicodeWidthStr;
17
18pub fn wrap_spans<'a>(spans: &[TextSpan], width: usize, props: &Props) -> Vec<Spans<'a>> {
23 let mut res: Vec<Spans> = Vec::with_capacity(spans.len());
25 let mut line_width: usize = 0; let mut line_spans: Vec<Span> = Vec::new(); for span in spans.iter() {
29 let (fg, bg, tmod) = use_or_default_styles(props, span);
31 if line_width + span.content.width() > width {
33 if span.content.width() > width {
35 let span_lines = textwrap::wrap(span.content.as_str(), width);
37 for span_line in span_lines.iter() {
39 if line_width + span_line.width() > width {
41 res.push(Spans::from(line_spans));
43 line_width = 0;
44 line_spans = Vec::new();
45 }
46 line_width += span_line.width();
48 line_spans.push(Span::styled(
50 span_line.to_string(),
51 Style::default().fg(fg).bg(bg).add_modifier(tmod),
52 ));
53 }
54 continue;
56 } else {
57 res.push(Spans::from(line_spans));
59 line_width = 0;
60 line_spans = Vec::new();
61 }
62 }
63 line_width += span.content.width();
65 line_spans.push(Span::styled(
66 span.content.to_string(),
67 Style::default().fg(fg).bg(bg).add_modifier(tmod),
68 ));
69 }
70 if !line_spans.is_empty() {
72 res.push(Spans::from(line_spans));
73 }
74 res
76}
77
78pub fn use_or_default_styles(props: &Props, span: &TextSpan) -> (Color, Color, Modifier) {
83 (
84 match span.fg {
85 Color::Reset => props
86 .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
87 .unwrap_color(),
88 _ => span.fg,
89 },
90 match span.bg {
91 Color::Reset => props
92 .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
93 .unwrap_color(),
94 _ => span.bg,
95 },
96 match span.modifiers.is_empty() {
97 true => props
98 .get_or(
99 Attribute::TextProps,
100 AttrValue::TextModifiers(TextModifiers::empty()),
101 )
102 .unwrap_text_modifiers(),
103 false => span.modifiers,
104 },
105 )
106}
107
108pub fn get_block<'a>(
113 props: Borders,
114 title: Option<(String, Alignment)>,
115 focus: bool,
116 inactive_style: Option<Style>,
117) -> Block<'a> {
118 let title = title.unwrap_or((String::default(), Alignment::Left));
119 Block::default()
120 .borders(props.sides)
121 .border_style(match focus {
122 true => props.style(),
123 false => {
124 inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
125 }
126 })
127 .border_type(props.modifiers)
128 .title(title.0)
129 .title_alignment(title.1)
130}
131
132pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
137 chars.iter().collect::<String>().width() as u16
138}
139
140#[cfg(test)]
141mod test {
142
143 use super::*;
144 use tuirealm::props::{Alignment, BorderSides, BorderType, Props};
145
146 use pretty_assertions::assert_eq;
147
148 #[test]
149 fn test_components_utils_wrap_spans() {
150 let mut props: Props = Props::default();
151 props.set(
152 Attribute::TextProps,
153 AttrValue::TextModifiers(TextModifiers::BOLD),
154 );
155 props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
156 props.set(Attribute::Background, AttrValue::Color(Color::White));
157 let spans: Vec<TextSpan> = vec![TextSpan::from("hello, "), TextSpan::from("world!")];
159 assert_eq!(wrap_spans(&spans, 64, &props).len(), 1);
160 let spans: Vec<TextSpan> = vec![
162 TextSpan::from("Hello, everybody, I'm Uncle Camel!"),
163 TextSpan::from("How's it going today?"),
164 ];
165 assert_eq!(wrap_spans(&spans, 32, &props).len(), 2);
166 let spans: Vec<TextSpan> = vec![TextSpan::from(
168 "Hello everybody! My name is Uncle Camel. How's it going today?",
169 )];
170 assert_eq!(wrap_spans(&spans, 16, &props).len(), 4);
172 let spans: Vec<TextSpan> = vec![
174 TextSpan::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
175 TextSpan::from("Canem!"),
176 TextSpan::from("In posuere sollicitudin vulputate"),
177 TextSpan::from("Sed vitae rutrum quam."),
178 ];
179 assert_eq!(wrap_spans(&spans, 36, &props).len(), 4);
181 }
182
183 #[test]
184 fn test_components_utils_use_or_default_styles() {
185 let mut props: Props = Props::default();
186 props.set(
187 Attribute::TextProps,
188 AttrValue::TextModifiers(TextModifiers::BOLD),
189 );
190 props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
191 props.set(Attribute::Background, AttrValue::Color(Color::White));
192 let span: TextSpan = TextSpan::from("test")
193 .underlined()
194 .fg(Color::Yellow)
195 .bg(Color::Cyan);
196 let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
198 assert_eq!(fg, Color::Yellow);
199 assert_eq!(bg, Color::Cyan);
200 assert!(modifiers.intersects(Modifier::UNDERLINED));
201 let span: TextSpan = TextSpan::from("test");
203 let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
204 assert_eq!(fg, Color::Red);
205 assert_eq!(bg, Color::White);
206 assert!(modifiers.intersects(Modifier::BOLD));
207 }
208
209 #[test]
210 fn test_components_utils_get_block() {
211 let props = Borders::default()
212 .sides(BorderSides::ALL)
213 .color(Color::Red)
214 .modifiers(BorderType::Rounded);
215 get_block(
216 props.clone(),
217 Some(("title".to_string(), Alignment::Center)),
218 true,
219 None,
220 );
221 get_block(props, None, false, None);
222 }
223
224 #[test]
225 fn test_components_utils_calc_utf8_cursor_position() {
226 let chars: Vec<char> = vec!['v', 'e', 'e', 's', 'o'];
227 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 5);
229 assert_eq!(calc_utf8_cursor_position(&chars[0..3]), 3);
230 let chars: Vec<char> = vec!['я', ' ', 'х', 'о', 'ч', 'у', ' ', 'с', 'п', 'а', 'т', 'ь'];
232 assert_eq!(calc_utf8_cursor_position(&chars[0..6]), 6);
233 let chars: Vec<char> = vec!['H', 'i', '😄'];
234 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 4);
235 let chars: Vec<char> = vec!['我', '之', '😄'];
236 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 6);
237 }
238}