1extern crate textwrap;
7extern crate unicode_width;
8use tuirealm::Props;
10use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, TextModifiers, TextSpan};
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
18#[must_use]
23pub fn wrap_spans<'a>(spans: &[&TextSpan], width: usize, props: &Props) -> Vec<Spans<'a>> {
24 let mut res: Vec<Spans> = Vec::with_capacity(spans.len());
26 let mut line_width: usize = 0; let mut line_spans: Vec<Span> = Vec::new(); for span in spans {
30 let (fg, bg, tmod) = use_or_default_styles(props, span);
32 if line_width + span.content.width() > width {
34 if span.content.width() > width {
36 let span_lines = textwrap::wrap(span.content.as_str(), width);
38 for span_line in span_lines {
40 if line_width + span_line.width() > width {
42 res.push(Spans::from(line_spans));
44 line_width = 0;
45 line_spans = Vec::new();
46 }
47 line_width += span_line.width();
49 line_spans.push(Span::styled(
51 span_line.to_string(),
52 Style::default().fg(fg).bg(bg).add_modifier(tmod),
53 ));
54 }
55 continue;
57 }
58 res.push(Spans::from(line_spans));
60 line_width = 0;
61 line_spans = Vec::new();
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
78#[must_use]
83pub fn use_or_default_styles(props: &Props, span: &TextSpan) -> (Color, Color, Modifier) {
84 (
85 match span.fg {
86 Color::Reset => props
87 .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
88 .unwrap_color(),
89 _ => span.fg,
90 },
91 match span.bg {
92 Color::Reset => props
93 .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
94 .unwrap_color(),
95 _ => span.bg,
96 },
97 if span.modifiers.is_empty() {
98 props
99 .get_or(
100 Attribute::TextProps,
101 AttrValue::TextModifiers(TextModifiers::empty()),
102 )
103 .unwrap_text_modifiers()
104 } else {
105 span.modifiers
106 },
107 )
108}
109
110#[must_use]
115pub fn get_block<T: AsRef<str>>(
116 props: Borders,
117 title: Option<&(T, Alignment)>,
118 focus: bool,
119 inactive_style: Option<Style>,
120) -> Block {
121 let title = title.map_or(("", Alignment::Left), |v| (v.0.as_ref(), v.1));
122 Block::default()
123 .borders(props.sides)
124 .border_style(if focus {
125 props.style()
126 } else {
127 inactive_style.unwrap_or_else(|| Style::default().fg(Color::Reset).bg(Color::Reset))
128 })
129 .border_type(props.modifiers)
130 .title(title.0)
131 .title_alignment(title.1)
132}
133
134#[must_use]
136pub fn get_title_or_center(props: &Props) -> (&str, Alignment) {
137 props
138 .get_ref(Attribute::Title)
139 .and_then(|v| v.as_title())
140 .map_or(("", Alignment::Center), |v| (v.0.as_str(), v.1))
141}
142
143#[must_use]
148pub fn calc_utf8_cursor_position(chars: &[char]) -> u16 {
149 chars.iter().collect::<String>().width() as u16
150}
151
152#[cfg(test)]
153mod test {
154
155 use super::*;
156 use tuirealm::props::{Alignment, BorderSides, BorderType, Props};
157
158 use pretty_assertions::assert_eq;
159
160 #[test]
161 fn test_components_utils_wrap_spans() {
162 let mut props: Props = Props::default();
163 props.set(
164 Attribute::TextProps,
165 AttrValue::TextModifiers(TextModifiers::BOLD),
166 );
167 props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
168 props.set(Attribute::Background, AttrValue::Color(Color::White));
169 let spans: Vec<TextSpan> = vec![TextSpan::from("hello, "), TextSpan::from("world!")];
171 let spans: Vec<&TextSpan> = spans.iter().collect();
172 assert_eq!(wrap_spans(&spans, 64, &props).len(), 1);
173 let spans: Vec<TextSpan> = vec![
175 TextSpan::from("Hello, everybody, I'm Uncle Camel!"),
176 TextSpan::from("How's it going today?"),
177 ];
178 let spans: Vec<&TextSpan> = spans.iter().collect();
179 assert_eq!(wrap_spans(&spans, 32, &props).len(), 2);
180 let spans: Vec<TextSpan> = vec![TextSpan::from(
182 "Hello everybody! My name is Uncle Camel. How's it going today?",
183 )];
184 let spans: Vec<&TextSpan> = spans.iter().collect();
185 assert_eq!(wrap_spans(&spans, 16, &props).len(), 4);
187 let spans: Vec<TextSpan> = vec![
189 TextSpan::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
190 TextSpan::from("Canem!"),
191 TextSpan::from("In posuere sollicitudin vulputate"),
192 TextSpan::from("Sed vitae rutrum quam."),
193 ];
194 let spans: Vec<&TextSpan> = spans.iter().collect();
195 assert_eq!(wrap_spans(&spans, 36, &props).len(), 4);
197 }
198
199 #[test]
200 fn test_components_utils_use_or_default_styles() {
201 let mut props: Props = Props::default();
202 props.set(
203 Attribute::TextProps,
204 AttrValue::TextModifiers(TextModifiers::BOLD),
205 );
206 props.set(Attribute::Foreground, AttrValue::Color(Color::Red));
207 props.set(Attribute::Background, AttrValue::Color(Color::White));
208 let span: TextSpan = TextSpan::from("test")
209 .underlined()
210 .fg(Color::Yellow)
211 .bg(Color::Cyan);
212 let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
214 assert_eq!(fg, Color::Yellow);
215 assert_eq!(bg, Color::Cyan);
216 assert!(modifiers.intersects(Modifier::UNDERLINED));
217 let span: TextSpan = TextSpan::from("test");
219 let (fg, bg, modifiers) = use_or_default_styles(&props, &span);
220 assert_eq!(fg, Color::Red);
221 assert_eq!(bg, Color::White);
222 assert!(modifiers.intersects(Modifier::BOLD));
223 }
224
225 #[test]
226 fn test_components_utils_get_block() {
227 let props = Borders::default()
228 .sides(BorderSides::ALL)
229 .color(Color::Red)
230 .modifiers(BorderType::Rounded);
231 let _ = get_block(
232 props.clone(),
233 Some(&("title", Alignment::Center)),
234 true,
235 None,
236 );
237 let _ = get_block::<&str>(props, None, false, None);
238 }
239
240 #[test]
241 fn test_components_utils_calc_utf8_cursor_position() {
242 let chars: Vec<char> = vec!['v', 'e', 'e', 's', 'o'];
243 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 5);
245 assert_eq!(calc_utf8_cursor_position(&chars[0..3]), 3);
246 let chars: Vec<char> = vec!['я', ' ', 'х', 'о', 'ч', 'у', ' ', 'с', 'п', 'а', 'т', 'ь'];
248 assert_eq!(calc_utf8_cursor_position(&chars[0..6]), 6);
249 let chars: Vec<char> = vec!['H', 'i', '😄'];
250 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 4);
251 let chars: Vec<char> = vec!['我', '之', '😄'];
252 assert_eq!(calc_utf8_cursor_position(chars.as_slice()), 6);
253 }
254}