tui_realm_stdlib/components/
paragraph.rs

1//! ## Paragraph
2//!
3//! `Paragraph` represents a read-only text component inside a container, the text is wrapped inside the container automatically
4//! using the [textwrap](https://docs.rs/textwrap/0.13.4/textwrap/) crate.
5//! The textarea supports multi-style spans.
6//! The component is not scrollable and doesn't handle any input. The text must then fit into the area.
7//! If you want scroll support, use a `Textarea` instead.
8
9use tuirealm::command::{Cmd, CmdResult};
10use tuirealm::props::{
11    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
12    TextModifiers, TextSpan,
13};
14use tuirealm::ratatui::text::Line as Spans;
15use tuirealm::ratatui::{
16    layout::Rect,
17    text::Span,
18    widgets::{Paragraph as TuiParagraph, Wrap},
19};
20use tuirealm::{Frame, MockComponent, State};
21
22// -- Component
23
24/// ## Paragraph
25///
26/// represents a read-only text component without any container.
27#[derive(Default)]
28pub struct Paragraph {
29    props: Props,
30}
31
32impl Paragraph {
33    pub fn foreground(mut self, fg: Color) -> Self {
34        self.attr(Attribute::Foreground, AttrValue::Color(fg));
35        self
36    }
37
38    pub fn background(mut self, bg: Color) -> Self {
39        self.attr(Attribute::Background, AttrValue::Color(bg));
40        self
41    }
42
43    pub fn modifiers(mut self, m: TextModifiers) -> Self {
44        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
45        self
46    }
47
48    pub fn borders(mut self, b: Borders) -> Self {
49        self.attr(Attribute::Borders, AttrValue::Borders(b));
50        self
51    }
52
53    pub fn alignment(mut self, a: Alignment) -> Self {
54        self.attr(Attribute::Alignment, AttrValue::Alignment(a));
55        self
56    }
57
58    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
59        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
60        self
61    }
62
63    pub fn text(mut self, s: &[TextSpan]) -> Self {
64        self.attr(
65            Attribute::Text,
66            AttrValue::Payload(PropPayload::Vec(
67                s.iter().cloned().map(PropValue::TextSpan).collect(),
68            )),
69        );
70        self
71    }
72
73    pub fn wrap(mut self, wrap: bool) -> Self {
74        self.attr(Attribute::TextWrap, AttrValue::Flag(wrap));
75        self
76    }
77}
78
79impl MockComponent for Paragraph {
80    fn view(&mut self, render: &mut Frame, area: Rect) {
81        // Make a Span
82        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
83            // Make text items
84            let text: Vec<Spans> = match self.props.get(Attribute::Text).map(|x| x.unwrap_payload())
85            {
86                Some(PropPayload::Vec(spans)) => spans
87                    .iter()
88                    .cloned()
89                    .map(|x| x.unwrap_text_span())
90                    .map(|x| {
91                        let (fg, bg, modifiers) =
92                            crate::utils::use_or_default_styles(&self.props, &x);
93                        Spans::from(vec![Span::styled(
94                            x.content,
95                            Style::default().add_modifier(modifiers).fg(fg).bg(bg),
96                        )])
97                    })
98                    .collect(),
99                _ => Vec::new(),
100            };
101            // Text properties
102            let alignment: Alignment = self
103                .props
104                .get_or(Attribute::Alignment, AttrValue::Alignment(Alignment::Left))
105                .unwrap_alignment();
106            // Wrap
107            let trim = self
108                .props
109                .get_or(Attribute::TextWrap, AttrValue::Flag(false))
110                .unwrap_flag();
111            let foreground = self
112                .props
113                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
114                .unwrap_color();
115            let background = self
116                .props
117                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
118                .unwrap_color();
119            let modifiers = self
120                .props
121                .get_or(
122                    Attribute::TextProps,
123                    AttrValue::TextModifiers(TextModifiers::empty()),
124                )
125                .unwrap_text_modifiers();
126            let borders = self
127                .props
128                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
129                .unwrap_borders();
130            let title = self.props.get(Attribute::Title).map(|x| x.unwrap_title());
131            let div = crate::utils::get_block(borders, title, true, None);
132            render.render_widget(
133                TuiParagraph::new(text)
134                    .block(div)
135                    .style(
136                        Style::default()
137                            .fg(foreground)
138                            .bg(background)
139                            .add_modifier(modifiers),
140                    )
141                    .alignment(alignment)
142                    .wrap(Wrap { trim }),
143                area,
144            );
145        }
146    }
147
148    fn query(&self, attr: Attribute) -> Option<AttrValue> {
149        self.props.get(attr)
150    }
151
152    fn attr(&mut self, attr: Attribute, value: AttrValue) {
153        self.props.set(attr, value)
154    }
155
156    fn state(&self) -> State {
157        State::None
158    }
159
160    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
161        CmdResult::None
162    }
163}
164
165#[cfg(test)]
166mod tests {
167
168    use super::*;
169
170    use pretty_assertions::assert_eq;
171
172    #[test]
173    fn test_components_paragraph() {
174        let component = Paragraph::default()
175            .background(Color::Blue)
176            .foreground(Color::Red)
177            .modifiers(TextModifiers::BOLD)
178            .alignment(Alignment::Center)
179            .text(&[
180                TextSpan::from("Press "),
181                TextSpan::from("<ESC>").fg(Color::Cyan).bold(),
182                TextSpan::from(" to quit"),
183            ])
184            .wrap(true)
185            .title("title", Alignment::Center);
186        // Get value
187        assert_eq!(component.state(), State::None);
188    }
189}