retroglyph_widgets/widget/
print_line.rs1use retroglyph_core::Rect;
3use retroglyph_core::text::Line;
4use unicode_width::UnicodeWidthStr;
5
6use super::Widget;
7use crate::Align;
8use crate::Surface;
9use crate::text::truncate as truncate_to_cols;
10
11#[derive(Clone, Copy, Debug)]
34pub struct PrintLine<'a> {
35 line: &'a Line,
36 align: Align,
37}
38
39impl<'a> PrintLine<'a> {
40 #[must_use]
43 pub const fn new(line: &'a Line) -> Self {
44 Self {
45 line,
46 align: Align::Left,
47 }
48 }
49
50 #[must_use]
53 pub const fn align(mut self, align: Align) -> Self {
54 self.align = align;
55 self
56 }
57}
58
59impl Widget for PrintLine<'_> {
60 fn render(&self, area: Rect, surface: &mut Surface<'_>) {
61 let max_width = area.width();
62 let right = area.left() + max_width;
63 #[allow(clippy::cast_possible_truncation)]
69 let line_width = self
70 .line
71 .spans
72 .iter()
73 .fold(0u16, |acc, s| acc.saturating_add(s.content.width() as u16))
74 .min(max_width);
75 let mut x = area.left() + self.align.offset(max_width, line_width);
76 for span in &self.line.spans {
77 if x >= right {
78 break;
79 }
80 let remaining = (right - x) as usize;
81 let text = truncate_to_cols(&span.content, remaining);
82 surface.print((x, area.top()), text, span.style);
83 #[allow(clippy::cast_possible_truncation)]
86 let text_w = text.width() as u16;
87 x += text_w;
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use retroglyph_core::text::Span;
95 use retroglyph_core::{Grid, Pos};
96
97 use super::*;
98
99 #[test]
100 fn prints_every_span() {
101 let line = Line::from(vec![Span::raw("hi "), Span::raw("there")]);
102 let area = Rect::new(0, 0, 20, 1);
103 let mut grid = Grid::new(20, 1);
104 PrintLine::new(&line).render(area, &mut Surface::new(&mut grid, area, 0));
105
106 let row: String = (0..20).map(|x| grid[Pos::new(x, 0)].glyph()).collect();
107 assert!(row.starts_with("hi there"));
108 }
109
110 #[test]
111 fn right_align_places_the_whole_line_against_the_right_edge() {
112 let line = Line::from(vec![Span::raw("hi "), Span::raw("there")]);
113 let area = Rect::new(0, 0, 20, 1);
114 let mut grid = Grid::new(20, 1);
115 PrintLine::new(&line)
116 .align(Align::Right)
117 .render(area, &mut Surface::new(&mut grid, area, 0));
118
119 let row: String = (0..20).map(|x| grid[Pos::new(x, 0)].glyph()).collect();
121 assert!(row.ends_with("hi there"), "row was {row:?}");
122 }
123
124 #[test]
125 fn clips_to_max_width() {
126 let line = Line::raw("a much longer message than fits");
127 let area = Rect::new(0, 0, 5, 1);
128 let mut grid = Grid::new(5, 1);
129 PrintLine::new(&line).render(area, &mut Surface::new(&mut grid, area, 0));
130
131 assert_eq!(grid[Pos::new(4, 0)].glyph(), 'c');
133 }
134}