1use std::cell::Cell;
2use std::rc::Rc;
3
4use either::Either;
5use unicode_segmentation::UnicodeSegmentation;
6use unicode_width::UnicodeWidthStr;
7
8use crate::buffer::Buffer;
9use crate::layout::{Alignment, Rect, ScrollMode};
10use crate::style::Style;
11use crate::widgets::reflow::{LineComposer, LineTruncator, Styled, WordWrapper};
12use crate::widgets::scroll::{OffsetScroller, ScrolledLine, Scroller, TailScroller};
13use crate::widgets::{Block, Text, Widget};
14
15fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
16 match alignment {
17 Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
18 Alignment::Right => text_area_width.saturating_sub(line_width),
19 Alignment::Left => 0,
20 }
21}
22
23pub struct Paragraph<'a, 't, T>
42where
43 T: Iterator<Item = &'t Text<'t>>,
44{
45 block: Option<Block<'a>>,
47 style: Style,
49 wrapping: bool,
51 text: T,
53 raw: bool,
55 scroll: u16,
57 scroll_mode: ScrollMode,
59 scroll_overflow_char: Option<char>,
60 alignment: Alignment,
62 has_overflown: Option<Rc<Cell<bool>>>,
65 at_top: Option<Rc<Cell<bool>>>,
66}
67
68impl<'a, 't, T> Paragraph<'a, 't, T>
69where
70 T: Iterator<Item = &'t Text<'t>>,
71{
72 pub fn new(text: T) -> Paragraph<'a, 't, T> {
73 Paragraph {
74 block: None,
75 style: Default::default(),
76 wrapping: false,
77 raw: false,
78 text,
79 scroll: 0,
80 scroll_mode: ScrollMode::Normal,
81 scroll_overflow_char: None,
82 alignment: Alignment::Left,
83 has_overflown: None,
84 at_top: None,
85 }
86 }
87
88 pub fn block(mut self, block: Block<'a>) -> Paragraph<'a, 't, T> {
89 self.block = Some(block);
90 self
91 }
92
93 pub fn style(mut self, style: Style) -> Paragraph<'a, 't, T> {
94 self.style = style;
95 self
96 }
97
98 pub fn wrap(mut self, flag: bool) -> Paragraph<'a, 't, T> {
99 self.wrapping = flag;
100 self
101 }
102
103 pub fn raw(mut self, flag: bool) -> Paragraph<'a, 't, T> {
104 self.raw = flag;
105 self
106 }
107
108 pub fn scroll(mut self, offset: u16) -> Paragraph<'a, 't, T> {
109 self.scroll = offset;
110 self
111 }
112
113 pub fn scroll_mode(mut self, scroll_mode: ScrollMode) -> Paragraph<'a, 't, T> {
114 self.scroll_mode = scroll_mode;
115 self
116 }
117
118 pub fn scroll_overflow_char(
119 mut self,
120 scroll_overflow_char: Option<char>,
121 ) -> Paragraph<'a, 't, T> {
122 self.scroll_overflow_char = scroll_overflow_char;
123 self
124 }
125
126 pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a, 't, T> {
127 self.alignment = alignment;
128 self
129 }
130
131 pub fn did_overflow(mut self, over: Rc<Cell<bool>>) -> Paragraph<'a, 't, T> {
132 self.has_overflown = Some(over);
133 self
134 }
135
136 pub fn at_top(mut self, top: Rc<Cell<bool>>) -> Paragraph<'a, 't, T> {
137 self.at_top = Some(top);
138 self
139 }
140}
141
142impl<'a, 't, 'b, T> Widget for Paragraph<'a, 't, T>
143where
144 T: Iterator<Item = &'t Text<'t>>,
145{
146 fn render(mut self, area: Rect, buf: &mut Buffer) {
147 let text_area = match self.block {
148 Some(ref mut b) => {
149 b.render(area, buf);
150 b.inner(area)
151 }
152 None => area,
153 };
154
155 if text_area.height < 1 {
156 return;
157 }
158
159 buf.set_background(text_area, self.style.bg);
160
161 let style = self.style;
162 let mut styled = self.text.by_ref().flat_map(|t| match *t {
163 Text::Raw(ref d) => {
164 let data: &'t str = d; Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| Styled(g, style)))
166 }
167 Text::Styled(ref d, s) => {
168 let data: &'t str = d; Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| Styled(g, s)))
170 }
171 });
172
173 let line_composer: Box<dyn LineComposer> = if self.wrapping {
174 Box::new(WordWrapper::new(&mut styled, text_area.width))
175 } else {
176 Box::new(LineTruncator::new(&mut styled, text_area.width))
177 };
178
179 let mut scrolled_lines: Box<dyn Scroller<'t>> = match self.scroll_mode {
180 ScrollMode::Normal => {
181 let scroller = OffsetScroller::new(self.scroll, line_composer);
182 Box::new(scroller)
183 }
184 ScrollMode::Tail => {
185 let over = self
186 .has_overflown
187 .unwrap_or_else(|| Rc::new(Cell::new(false)));
188
189 let scroller = TailScroller::new(
190 self.scroll,
191 line_composer,
192 text_area.height,
193 Rc::clone(&over),
194 );
195 Box::new(scroller)
196 }
197 };
198
199 for y in 0..text_area.height {
200 match scrolled_lines.next_line() {
201 Some(ScrolledLine::Line(current_line, current_line_width)) => {
202 let mut x =
203 get_line_offset(current_line_width, text_area.width, self.alignment);
204 for Styled(symbol, style) in current_line {
205 buf.get_mut(text_area.left() + x, text_area.top() + y)
206 .set_symbol(symbol)
207 .set_style(style);
208 x += symbol.width() as u16;
209 }
210 }
211 Some(ScrolledLine::Overflow) => {
212 if let Some(top) = self.at_top.as_ref() {
213 top.set(true);
214 }
215
216 if let Some(c) = self.scroll_overflow_char {
217 buf.get_mut(text_area.left(), text_area.top() + y)
218 .set_symbol(&c.to_string())
219 .set_style(style);
220 }
221 }
222 None => {}
223 }
224 }
225 }
226}