Skip to main content

rpi_tui/
truncated_text.rs

1//! Truncated text component.
2//!
3//! Displays text that is truncated to fit within a given width.
4
5use std::any::Any;
6use std::sync::Mutex;
7
8use super::component::Component;
9use crate::utils::{truncate_to_width, visible_width};
10
11/// Truncated text component.
12pub struct TruncatedText {
13    text: Mutex<String>,
14    max_width: usize,
15    ellipsis: String,
16    style: Option<String>,
17}
18
19impl TruncatedText {
20    /// Create a new truncated text.
21    pub fn new(text: &str, max_width: usize) -> Self {
22        Self {
23            text: Mutex::new(text.to_string()),
24            max_width,
25            ellipsis: "...".to_string(),
26            style: None,
27        }
28    }
29
30    /// Create with custom ellipsis.
31    pub fn with_ellipsis(text: &str, max_width: usize, ellipsis: &str) -> Self {
32        Self {
33            text: Mutex::new(text.to_string()),
34            max_width,
35            ellipsis: ellipsis.to_string(),
36            style: None,
37        }
38    }
39
40    /// Set text.
41    pub fn set_text(&self, text: &str) {
42        if let Ok(mut t) = self.text.lock() {
43            *t = text.to_string();
44        }
45    }
46
47    /// Get text.
48    pub fn get_text(&self) -> String {
49        self.text.lock().map(|t| t.clone()).unwrap_or_default()
50    }
51
52    /// Set max width.
53    pub fn set_max_width(&mut self, width: usize) {
54        self.max_width = width;
55    }
56
57    /// Set style (ANSI escape codes).
58    pub fn set_style(&mut self, style: &str) {
59        self.style = Some(style.to_string());
60    }
61
62    /// Check if text is truncated.
63    pub fn is_truncated(&self) -> bool {
64        let text = self.get_text();
65        visible_width(&text) > self.max_width
66    }
67}
68
69impl Component for TruncatedText {
70    fn render(&self, width: usize) -> Vec<String> {
71        let text = self.get_text();
72        let effective_width = self.max_width.min(width);
73
74        let truncated = truncate_to_width(&text, effective_width, &self.ellipsis);
75
76        let styled = if let Some(ref style) = self.style {
77            format!("{}{}\x1b[0m", style, truncated)
78        } else {
79            truncated
80        };
81
82        vec![styled]
83    }
84
85    fn invalidate(&self) {
86        // No cached state
87    }
88
89    fn as_any(&self) -> &dyn Any {
90        self
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_truncated_text() {
100        let text = TruncatedText::new("Hello World", 5);
101        let lines = text.render(10);
102        assert!(lines[0].contains("H"));
103    }
104
105    #[test]
106    fn test_is_truncated() {
107        let text = TruncatedText::new("Hello World", 5);
108        assert!(text.is_truncated());
109    }
110
111    #[test]
112    fn test_not_truncated() {
113        let text = TruncatedText::new("Hi", 10);
114        assert!(!text.is_truncated());
115    }
116}