Skip to main content

tui_realm_stdlib/components/
span.rs

1use tuirealm::command::{Cmd, CmdResult};
2use tuirealm::component::Component;
3use tuirealm::props::{
4    AttrValue, Attribute, Color, HorizontalAlignment, PropPayload, PropValue, Props, QueryResult,
5    SpanStatic, Style, TextModifiers,
6};
7use tuirealm::ratatui::Frame;
8use tuirealm::ratatui::layout::Rect;
9use tuirealm::ratatui::text::{Line, Span as RSpan, Text};
10use tuirealm::ratatui::widgets::Paragraph;
11use tuirealm::state::State;
12
13use crate::prop_ext::CommonProps;
14use crate::utils;
15
16/// A Span represents single-line, multi-style text, without any container support.
17///
18/// If single-style text is wanted, use [`Label`](super::Label).
19/// If multi-style, mutli-line, with container support is wanted, use [`Paragraph`](super::Paragraph).
20#[derive(Default)]
21#[must_use]
22pub struct Span {
23    common: CommonProps,
24    props: Props,
25}
26
27impl Span {
28    /// Set the main foreground color. This may get overwritten by individual text styles.
29    pub fn foreground(mut self, fg: Color) -> Self {
30        self.attr(Attribute::Foreground, AttrValue::Color(fg));
31        self
32    }
33
34    /// Set the main background color. This may get overwritten by individual text styles.
35    pub fn background(mut self, bg: Color) -> Self {
36        self.attr(Attribute::Background, AttrValue::Color(bg));
37        self
38    }
39
40    /// Set the main text modifiers. This may get overwritten by individual text styles.
41    pub fn modifiers(mut self, m: TextModifiers) -> Self {
42        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
43        self
44    }
45
46    /// Set the main style. This may get overwritten by individual text styles.
47    ///
48    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background) and [`modifiers`](Self::modifiers)!
49    pub fn style(mut self, style: Style) -> Self {
50        self.attr(Attribute::Style, AttrValue::Style(style));
51        self
52    }
53
54    /// Set the horizontal text alignment.
55    pub fn alignment_horizontal(mut self, a: HorizontalAlignment) -> Self {
56        self.attr(
57            Attribute::AlignmentHorizontal,
58            AttrValue::AlignmentHorizontal(a),
59        );
60        self
61    }
62
63    /// Set the Text content.
64    pub fn spans<T>(mut self, s: impl IntoIterator<Item = T>) -> Self
65    where
66        T: Into<SpanStatic>,
67    {
68        self.attr(
69            Attribute::Text,
70            AttrValue::Payload(PropPayload::Vec(
71                s.into_iter()
72                    .map(Into::into)
73                    .map(PropValue::TextSpan)
74                    .collect(),
75            )),
76        );
77        self
78    }
79}
80
81impl Component for Span {
82    fn view(&mut self, render: &mut Frame, area: Rect) {
83        if !self.common.display {
84            return;
85        }
86
87        // Make text
88        // binding required as "spans" is a reference and otherwise would not live long enough
89        let payload = self.props.get(Attribute::Text).and_then(|x| x.as_payload());
90        let text = match payload {
91            Some(PropPayload::Vec(lines)) => {
92                let lines: Vec<RSpan> = lines
93                    .iter()
94                    // this will skip any "PropValue" that is not a "TextSpan", instead of panicing
95                    .filter_map(|x| x.as_textspan())
96                    .map(utils::borrow_clone_span)
97                    .collect();
98                Text::from(Line::from(lines))
99            }
100            _ => Text::default(),
101        };
102        // Text properties
103        let alignment: HorizontalAlignment = self
104            .props
105            .get(Attribute::AlignmentHorizontal)
106            .and_then(AttrValue::as_alignment_horizontal)
107            .unwrap_or(HorizontalAlignment::Left);
108        render.render_widget(
109            Paragraph::new(text)
110                .alignment(alignment)
111                .style(self.common.style),
112            area,
113        );
114    }
115
116    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
117        if let Some(value) = self.common.get_for_query(attr) {
118            return Some(value);
119        }
120
121        self.props.get_for_query(attr)
122    }
123
124    fn attr(&mut self, attr: Attribute, value: AttrValue) {
125        if let Some(value) = self.common.set(attr, value) {
126            self.props.set(attr, value);
127        }
128    }
129
130    fn state(&self) -> State {
131        State::None
132    }
133
134    fn perform(&mut self, cmd: Cmd) -> CmdResult {
135        CmdResult::Invalid(cmd)
136    }
137}
138
139#[cfg(test)]
140mod tests {
141
142    use pretty_assertions::assert_eq;
143    use tuirealm::props::SpanStatic;
144    use tuirealm::ratatui::style::Stylize;
145
146    use super::*;
147
148    #[test]
149    fn test_components_span() {
150        let component = Span::default()
151            .background(Color::Blue)
152            .foreground(Color::Red)
153            .modifiers(TextModifiers::BOLD)
154            .alignment_horizontal(HorizontalAlignment::Center)
155            .spans([
156                SpanStatic::from("Press "),
157                SpanStatic::from("<ESC>").fg(Color::Cyan).bold(),
158                SpanStatic::from(" to quit"),
159            ]);
160        // Get value
161        assert_eq!(component.state(), State::None);
162    }
163
164    #[test]
165    fn various_spans_types() {
166        // Vec
167        let _ = Span::default().spans(vec![SpanStatic::raw("hello")]);
168        // static array
169        let _ = Span::default().spans([SpanStatic::raw("hello")]);
170        // boxed array
171        let _ = Span::default().spans(vec![SpanStatic::raw("hello")].into_boxed_slice());
172        // already a iterator
173        let _ = Span::default().spans(["Hello"].map(SpanStatic::raw));
174    }
175}