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