iced_pancurses/renderer/
text.rs1use crate::primitive::Primitive;
2use crate::renderer::PancursesRenderer;
3
4use iced_native::widget::text;
5use iced_native::{Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment};
6
7impl text::Renderer for PancursesRenderer {
8 fn default_size(&self) -> u16 {
9 1
10 }
11
12 fn measure(&self, content: &str, _size: u16, _font: Font, bounds: Size) -> (f32, f32) {
13 let content: String = content.into();
14 let max_x = bounds.width as u32;
15 let max_y = bounds.height as u32;
16 let layout = TextLayout::compute_layout(&content, max_x, max_y);
17 (layout.0 as f32, layout.1 as f32)
18 }
19
20 fn draw(
21 &mut self,
22 bounds: Rectangle,
23 content: &str,
24 _size: u16,
25 _font: Font,
26 color: Option<Color>,
27 horizontal_alignment: HorizontalAlignment,
28 _vertical_alignment: VerticalAlignment,
29 ) -> Self::Output {
30 let wrapped_content = TextLayout::wrap(
31 content,
32 bounds.width as u32,
33 bounds.height as u32,
34 horizontal_alignment,
35 );
36 Primitive::Text(wrapped_content, bounds, color.unwrap_or(Color::WHITE))
37 }
38}
39
40pub struct TextLayout;
41
42impl TextLayout {
43 pub fn compute_layout(content: &str, max_x: u32, max_y: u32) -> (u32, u32) {
47 let mut max_len = 0;
49
50 let lines: u32 = content
52 .lines()
53 .map(|l| {
54 let chars = l.chars().count() as u32;
55 max_len = max_len.max(chars);
56 let offset = if chars % max_x == 0 { 0 } else { 1 };
57 (chars / max_x) + offset
58 })
59 .sum();
60 (max_len.min(max_x), lines.min(max_y))
61 }
62
63 pub fn wrap(content: &str, max_x: u32, max_y: u32, align: HorizontalAlignment) -> Vec<String> {
67 let (wrapped_x, _) = TextLayout::compute_layout(content, max_x, max_y);
68 content
69 .lines()
70 .flat_map(|l| {
71 let len = l.chars().count() as u32;
72 if len > wrapped_x {
73 l.as_bytes()
74 .chunks(wrapped_x as usize)
75 .map(|bytes| String::from_utf8(bytes.to_vec()).unwrap())
76 .collect()
77 } else {
78 let diff = wrapped_x - len;
79 match align {
80 HorizontalAlignment::Left => {
81 let padding: String = (0..diff).map(|_| ' ').collect();
82 vec![format!("{}{}", l, padding)]
83 }
84 HorizontalAlignment::Center => {
85 let pad = diff / 2;
86 let padding: String = (0..pad).map(|_| ' ').collect();
87 let offset = if diff % 2 == 0 { "" } else { " " };
88 vec![format!("{}{}{}{}", offset, padding, l, padding)]
89 }
90 HorizontalAlignment::Right => {
91 let padding: String = (0..diff).map(|_| ' ').collect();
92 vec![format!("{}{}", padding, l)]
93 }
94 }
95 }
96 })
97 .collect()
98 }
99}
100
101#[cfg(test)]
102pub mod tests {
103
104 use super::TextLayout;
105 use iced_native::HorizontalAlignment;
106
107 #[test]
108 pub fn text_layout_compute_should_work() {
109 let content = "First line\ntest!";
110 assert_eq!(TextLayout::compute_layout(content, 10, 2), (10, 2));
117 assert_eq!(TextLayout::compute_layout(content, 15, 3), (10, 2));
118 }
119
120 #[test]
121 pub fn text_layout_compute_should_wrap() {
122 let content = "First line\ntest!";
123 assert_eq!(TextLayout::compute_layout(content, 5, 10), (5, 3));
131
132 assert_eq!(TextLayout::compute_layout(content, 4, 10), (4, 5));
140 }
141
142 #[test]
143 pub fn text_layout_wrap_should_work() {
144 let content = "First line\ntest!";
145
146 assert_eq!(
148 TextLayout::wrap(content, 10, 2, HorizontalAlignment::Left),
149 vec!["First line", "test! "]
150 );
151
152 assert_eq!(
154 TextLayout::wrap(content, 10, 2, HorizontalAlignment::Center),
155 vec!["First line", " test! "]
156 );
157
158 assert_eq!(
160 TextLayout::wrap(content, 10, 2, HorizontalAlignment::Right),
161 vec!["First line", " test!"]
162 )
163 }
164}