tui_temp_fork/widgets/
paragraph.rs

1use either::Either;
2use unicode_segmentation::UnicodeSegmentation;
3use unicode_width::UnicodeWidthStr;
4
5use crate::buffer::Buffer;
6use crate::layout::{Alignment, Rect};
7use crate::style::Style;
8use crate::widgets::reflow::{LineComposer, LineTruncator, Styled, WordWrapper};
9use crate::widgets::{Block, Text, Widget};
10
11fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
12    match alignment {
13        Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
14        Alignment::Right => text_area_width.saturating_sub(line_width),
15        Alignment::Left => 0,
16    }
17}
18
19/// A widget to display some text.
20///
21/// # Examples
22///
23/// ```
24/// # use tui_temp_fork::widgets::{Block, Borders, Paragraph, Text};
25/// # use tui_temp_fork::style::{Style, Color};
26/// # use tui_temp_fork::layout::{Alignment};
27/// # fn main() {
28/// let text = [
29///     Text::raw("First line\n"),
30///     Text::styled("Second line\n", Style::default().fg(Color::Red))
31/// ];
32/// Paragraph::new(text.iter())
33///     .block(Block::default().title("Paragraph").borders(Borders::ALL))
34///     .style(Style::default().fg(Color::White).bg(Color::Black))
35///     .alignment(Alignment::Center)
36///     .wrap(true);
37/// # }
38/// ```
39pub struct Paragraph<'a, 't, T>
40where
41    T: Iterator<Item = &'t Text<'t>>,
42{
43    /// A block to wrap the widget in
44    block: Option<Block<'a>>,
45    /// Widget style
46    style: Style,
47    /// Wrap the text or not
48    wrapping: bool,
49    /// The text to display
50    text: T,
51    /// Should we parse the text for embedded commands
52    raw: bool,
53    /// Scroll
54    scroll: u16,
55    /// Aligenment of the text
56    alignment: Alignment,
57}
58
59impl<'a, 't, T> Paragraph<'a, 't, T>
60where
61    T: Iterator<Item = &'t Text<'t>>,
62{
63    pub fn new(text: T) -> Paragraph<'a, 't, T> {
64        Paragraph {
65            block: None,
66            style: Default::default(),
67            wrapping: false,
68            raw: false,
69            text,
70            scroll: 0,
71            alignment: Alignment::Left,
72        }
73    }
74
75    pub fn block(mut self, block: Block<'a>) -> Paragraph<'a, 't, T> {
76        self.block = Some(block);
77        self
78    }
79
80    pub fn style(mut self, style: Style) -> Paragraph<'a, 't, T> {
81        self.style = style;
82        self
83    }
84
85    pub fn wrap(mut self, flag: bool) -> Paragraph<'a, 't, T> {
86        self.wrapping = flag;
87        self
88    }
89
90    pub fn raw(mut self, flag: bool) -> Paragraph<'a, 't, T> {
91        self.raw = flag;
92        self
93    }
94
95    pub fn scroll(mut self, offset: u16) -> Paragraph<'a, 't, T> {
96        self.scroll = offset;
97        self
98    }
99
100    pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a, 't, T> {
101        self.alignment = alignment;
102        self
103    }
104}
105
106impl<'a, 't, 'b, T> Widget for Paragraph<'a, 't, T>
107where
108    T: Iterator<Item = &'t Text<'t>>,
109{
110    fn draw(&mut self, area: Rect, buf: &mut Buffer) {
111        let text_area = match self.block {
112            Some(ref mut b) => {
113                b.draw(area, buf);
114                b.inner(area)
115            }
116            None => area,
117        };
118
119        if text_area.height < 1 {
120            return;
121        }
122
123        self.background(text_area, buf, self.style.bg);
124
125        let style = self.style;
126        let mut styled = self.text.by_ref().flat_map(|t| match *t {
127            Text::Raw(ref d) => {
128                let data: &'t str = d; // coerce to &str
129                Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| Styled(g, style)))
130            }
131            Text::Styled(ref d, s) => {
132                let data: &'t str = d; // coerce to &str
133                Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| Styled(g, s)))
134            }
135        });
136
137        let mut line_composer: Box<dyn LineComposer> = if self.wrapping {
138            Box::new(WordWrapper::new(&mut styled, text_area.width))
139        } else {
140            Box::new(LineTruncator::new(&mut styled, text_area.width))
141        };
142        let mut y = 0;
143        while let Some((current_line, current_line_width)) = line_composer.next_line() {
144            if y >= self.scroll {
145                let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
146                for Styled(symbol, style) in current_line {
147                    buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll)
148                        .set_symbol(symbol)
149                        .set_style(*style);
150                    x += symbol.width() as u16;
151                }
152            }
153            y += 1;
154            if y >= text_area.height + self.scroll {
155                break;
156            }
157        }
158    }
159}