Skip to main content

tui_realm_stdlib/components/
paragraph.rs

1use tuirealm::command::{Cmd, CmdResult};
2use tuirealm::component::Component;
3use tuirealm::props::{
4    AttrValue, Attribute, Borders, Color, HorizontalAlignment, Props, QueryResult, Style,
5    TextModifiers, TextStatic, Title,
6};
7use tuirealm::ratatui::Frame;
8use tuirealm::ratatui::layout::Rect;
9use tuirealm::ratatui::widgets::{Paragraph as TuiParagraph, Wrap};
10use tuirealm::state::State;
11
12use crate::prop_ext::CommonProps;
13use crate::utils;
14
15/// A Paragraph represents multi-line, multi-style, automatically wrapped text, with container support.
16///
17/// This component does not scroll. If scrolling is additionally wanted, use [`Textarea`](super::Textarea).
18///
19/// If single-style, single-line text is wanted, use [`Label`](super::Label).
20/// If multi-style, single-line text is wanted, use [`Span`](super::Span).
21#[derive(Debug, Default)]
22#[must_use]
23pub struct Paragraph {
24    common: CommonProps,
25    props: Props,
26}
27
28impl Paragraph {
29    /// Set the main foreground color. This may get overwritten by individual text styles.
30    pub fn foreground(mut self, fg: Color) -> Self {
31        self.attr(Attribute::Foreground, AttrValue::Color(fg));
32        self
33    }
34
35    /// Set the main background color. This may get overwritten by individual text styles.
36    pub fn background(mut self, bg: Color) -> Self {
37        self.attr(Attribute::Background, AttrValue::Color(bg));
38        self
39    }
40
41    /// Set the main text modifiers. This may get overwritten by individual text styles.
42    pub fn modifiers(mut self, m: TextModifiers) -> Self {
43        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
44        self
45    }
46
47    /// Set the main style. This may get overwritten by individual text styles.
48    ///
49    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background) and [`modifiers`](Self::modifiers)!
50    pub fn style(mut self, style: Style) -> Self {
51        self.attr(Attribute::Style, AttrValue::Style(style));
52        self
53    }
54
55    /// Add a border to the component.
56    pub fn borders(mut self, b: Borders) -> Self {
57        self.attr(Attribute::Borders, AttrValue::Borders(b));
58        self
59    }
60
61    /// Set the horizontal text alignment.
62    pub fn alignment_horizontal(mut self, a: HorizontalAlignment) -> Self {
63        self.attr(
64            Attribute::AlignmentHorizontal,
65            AttrValue::AlignmentHorizontal(a),
66        );
67        self
68    }
69
70    /// Add a title to the component.
71    pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
72        self.attr(Attribute::Title, AttrValue::Title(title.into()));
73        self
74    }
75
76    /// Set the text of the [`Paragraph`].
77    pub fn text<T>(mut self, text: T) -> Self
78    where
79        T: Into<TextStatic>,
80    {
81        self.attr(Attribute::Text, AttrValue::Text(text.into()));
82        self
83    }
84
85    /// Set wheter automatic wrap whitespace trimming should be enabled.
86    ///
87    /// Default: `false`
88    pub fn wrap_trim(mut self, wrap: bool) -> Self {
89        self.attr(Attribute::TextWrap, AttrValue::Flag(wrap));
90        self
91    }
92}
93
94impl Component for Paragraph {
95    fn view(&mut self, render: &mut Frame, area: Rect) {
96        if !self.common.display {
97            return;
98        }
99
100        // Make text items
101        let text = self
102            .props
103            .get(Attribute::Text)
104            .and_then(AttrValue::as_text)
105            .map(utils::borrow_clone_text)
106            .unwrap_or_default();
107
108        // Text properties
109        let alignment: HorizontalAlignment = self
110            .props
111            .get(Attribute::AlignmentHorizontal)
112            .and_then(AttrValue::as_alignment_horizontal)
113            .unwrap_or(HorizontalAlignment::Left);
114        // Wrap
115        let trim = self
116            .props
117            .get(Attribute::TextWrap)
118            .and_then(AttrValue::as_flag)
119            .unwrap_or_default();
120
121        let mut paragraph = TuiParagraph::new(text)
122            .style(self.common.style)
123            .alignment(alignment)
124            .wrap(Wrap { trim });
125
126        if let Some(block) = self.common.get_block() {
127            paragraph = paragraph.block(block);
128        }
129
130        render.render_widget(paragraph, area);
131    }
132
133    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
134        if let Some(value) = self.common.get_for_query(attr) {
135            return Some(value);
136        }
137
138        self.props.get_for_query(attr)
139    }
140
141    fn attr(&mut self, attr: Attribute, value: AttrValue) {
142        if let Some(value) = self.common.set(attr, value) {
143            self.props.set(attr, value);
144        }
145    }
146
147    fn state(&self) -> State {
148        State::None
149    }
150
151    fn perform(&mut self, cmd: Cmd) -> CmdResult {
152        CmdResult::Invalid(cmd)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158
159    use pretty_assertions::assert_eq;
160    use tuirealm::ratatui::style::Stylize;
161    use tuirealm::ratatui::text::{Line, Text};
162
163    use super::*;
164
165    #[test]
166    fn test_components_paragraph() {
167        let component = Paragraph::default()
168            .background(Color::Blue)
169            .foreground(Color::Red)
170            .modifiers(TextModifiers::BOLD)
171            .alignment_horizontal(HorizontalAlignment::Center)
172            .text(vec![
173                Line::from("Press "),
174                Line::from("<ESC>").fg(Color::Cyan).bold(),
175                Line::from(" to quit"),
176            ])
177            .wrap_trim(true)
178            .title(Title::from("title").alignment(HorizontalAlignment::Center));
179        // Get value
180        assert_eq!(component.state(), State::None);
181    }
182
183    #[test]
184    fn various_text_types() {
185        // Vec of Lines
186        let _ = Paragraph::default().text(vec![Line::raw("hello")]);
187        // Direct text
188        let _ = Paragraph::default().text(Text::from("hello"));
189    }
190}