retroglyph_widgets/widget/
paragraph.rs1use retroglyph_core::layout::TextLayout;
10use retroglyph_core::text::{Line, Span};
11use retroglyph_core::{Rect, Style};
12
13use super::{Measure, Widget};
14use crate::Surface;
15
16#[derive(Clone, Copy, Debug)]
38pub struct Paragraph<'a> {
39 text: &'a str,
40 style: Style,
41}
42
43impl<'a> Paragraph<'a> {
44 #[must_use]
46 pub fn new(text: &'a str) -> Self {
47 Self {
48 text,
49 style: Style::new(),
50 }
51 }
52
53 #[must_use]
55 pub const fn style(mut self, style: Style) -> Self {
56 self.style = style;
57 self
58 }
59
60 fn line(&self) -> Line {
61 Line::from(Span::styled(self.text, self.style))
62 }
63}
64
65impl Measure for Paragraph<'_> {
66 fn height_for(&self, width: u16) -> u16 {
67 let line = self.line();
68 TextLayout::new(&line)
69 .rect(Rect::new(0, 0, width, u16::MAX))
70 .measure()
71 .height
72 }
73}
74
75impl Widget for Paragraph<'_> {
76 fn render(&self, area: Rect, surface: &mut Surface<'_>) {
77 let line = self.line();
78 let layer = surface.layer();
79 TextLayout::new(&line)
80 .rect(area)
81 .render_to_grid(surface.grid_mut(), layer);
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use retroglyph_core::{Grid, Pos};
88
89 use super::*;
90
91 #[test]
92 fn height_for_matches_wrapped_line_count() {
93 let p = Paragraph::new("the quick brown fox jumps");
94 assert_eq!(p.height_for(10), 3); assert_eq!(p.height_for(100), 1);
96 }
97
98 #[test]
99 fn height_for_respects_hard_newlines() {
100 let p = Paragraph::new("first\nsecond\nthird");
103 assert_eq!(p.height_for(100), 3);
104 }
105
106 #[test]
107 fn render_draws_one_line_per_wrapped_row() {
108 let area = Rect::new(0, 0, 10, 5);
109 let mut grid = Grid::new(10, 5);
110 Paragraph::new("the quick brown fox jumps")
111 .render(area, &mut Surface::new(&mut grid, area, 0));
112
113 let row0: String = (0..10).map(|x| grid[Pos::new(x, 0)].glyph()).collect();
114 let row1: String = (0..10).map(|x| grid[Pos::new(x, 1)].glyph()).collect();
115 let row2: String = (0..10).map(|x| grid[Pos::new(x, 2)].glyph()).collect();
116 assert!(row0.starts_with("the quick"));
117 assert!(row1.starts_with("brown fox"));
118 assert!(row2.starts_with("jumps"));
119 }
120
121 #[test]
122 fn render_stops_at_the_area_bottom() {
123 let area = Rect::new(0, 0, 10, 1);
125 let mut grid = Grid::new(10, 2);
126 Paragraph::new("the quick brown fox jumps")
127 .render(area, &mut Surface::new(&mut grid, area, 0));
128
129 let row1: String = (0..10).map(|x| grid[Pos::new(x, 1)].glyph()).collect();
130 assert_eq!(row1.trim(), "");
131 }
132}