Skip to main content

tui_lipan/widgets/text/
mod.rs

1//! Text widgets.
2
3mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_text_constrained;
8pub(crate) use layout::split_spans_on_newlines;
9pub use node::TextNode;
10pub use reconcile::reconcile_text;
11
12use std::sync::Arc;
13
14use crate::core::element::{Element, ElementKind};
15use crate::style::{Span, Style};
16
17/// Overflow behavior when content doesn't fit.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
19pub enum Overflow {
20    /// Choose behavior based on available space.
21    #[default]
22    Auto,
23    /// Clip overflowing content.
24    Clip,
25    /// Clip from the start, keeping the tail visible.
26    ClipStart,
27    /// Truncate overflowing lines with `…`.
28    Ellipsis,
29    /// Soft-wrap lines to fit available width.
30    Wrap,
31}
32
33/// A text element.
34#[derive(Clone, Debug)]
35pub struct Text {
36    /// Text segments.
37    pub spans: Vec<Span>,
38    /// Base style for all spans.
39    pub style: Style,
40    /// Overflow strategy.
41    pub overflow: Overflow,
42    /// Requested width.
43    pub width: crate::style::Length,
44    /// Requested height.
45    pub height: crate::style::Length,
46}
47
48impl Text {
49    /// Create a new text element.
50    pub fn new(content: impl Into<Arc<str>>) -> Self {
51        Self {
52            spans: vec![Span::new(content)],
53            style: Style::default(),
54            overflow: Overflow::Auto,
55            width: crate::style::Length::Auto,
56            height: crate::style::Length::Auto,
57        }
58    }
59
60    /// Create text from multiple spans.
61    pub fn from_spans(spans: impl IntoIterator<Item = Span>) -> Self {
62        Self {
63            spans: spans.into_iter().collect(),
64            style: Style::default(),
65            overflow: Overflow::Auto,
66            width: crate::style::Length::Auto,
67            height: crate::style::Length::Auto,
68        }
69    }
70
71    /// Create text from an ANSI-escaped string.
72    ///
73    /// SGR escape sequences (colors, bold, italic, etc.) are converted to
74    /// styled spans. Non-SGR sequences are silently stripped.
75    pub fn from_ansi(input: &str) -> Self {
76        Self::from_spans(crate::style::ansi::parse_ansi(input))
77    }
78
79    /// Add a span.
80    pub fn span(mut self, span: impl Into<Span>) -> Self {
81        self.spans.push(span.into());
82        self
83    }
84
85    /// Set base style.
86    pub fn style(mut self, style: Style) -> Self {
87        self.style = style;
88        self
89    }
90
91    /// Set overflow behavior.
92    pub fn overflow(mut self, overflow: Overflow) -> Self {
93        self.overflow = overflow;
94        self
95    }
96
97    /// Set width.
98    pub fn width(mut self, width: crate::style::Length) -> Self {
99        self.width = width;
100        self
101    }
102
103    /// Set height.
104    pub fn height(mut self, height: crate::style::Length) -> Self {
105        self.height = height;
106        self
107    }
108
109    /// Returns the concatenated plain text content.
110    pub fn plain_content(&self) -> String {
111        let mut s = String::new();
112        for span in &self.spans {
113            s.push_str(&span.content);
114        }
115        s
116    }
117}
118
119// Implement From<TextNode> back to Text to facilitate shared render code or debugging if needed.
120impl From<TextNode> for Text {
121    fn from(node: TextNode) -> Self {
122        Self {
123            spans: node.spans,
124            style: node.style,
125            overflow: node.overflow,
126            width: node.widget_key.width,
127            height: node.widget_key.height,
128        }
129    }
130}
131
132impl From<Text> for Element {
133    fn from(value: Text) -> Self {
134        Element::new(ElementKind::Text(value))
135    }
136}
137
138impl Default for Text {
139    fn default() -> Self {
140        Self {
141            spans: Vec::new(),
142            style: Style::default(),
143            overflow: Overflow::Auto,
144            width: crate::style::Length::Auto,
145            height: crate::style::Length::Auto,
146        }
147    }
148}
149
150impl crate::layout::hash::LayoutHash for Text {
151    fn layout_hash(
152        &self,
153        hasher: &mut impl std::hash::Hasher,
154        _recurse: &dyn Fn(&Element) -> Option<u64>,
155    ) -> Option<()> {
156        use std::hash::Hash;
157        self.width.hash(hasher);
158        self.height.hash(hasher);
159        self.overflow.hash(hasher);
160        crate::layout::hash::hash_spans_content(&self.spans, hasher);
161        Some(())
162    }
163}