1use crate::console::RenderContext;
6use crate::text::Span;
7
8#[derive(Debug, Clone)]
10pub struct Segment {
11 pub spans: Vec<Span>,
13 pub newline: bool,
15}
16
17impl Segment {
18 pub fn new(spans: Vec<Span>) -> Self {
20 Segment {
21 spans,
22 newline: false,
23 }
24 }
25
26 pub fn line(spans: Vec<Span>) -> Self {
28 Segment {
29 spans,
30 newline: true,
31 }
32 }
33
34 pub fn empty_line() -> Self {
36 Segment {
37 spans: Vec::new(),
38 newline: true,
39 }
40 }
41
42 pub fn from_span(span: Span) -> Self {
44 Segment {
45 spans: vec![span],
46 newline: false,
47 }
48 }
49
50 pub fn width(&self) -> usize {
52 self.spans.iter().map(|s| s.width()).sum()
53 }
54
55 pub fn plain_text(&self) -> String {
57 self.spans.iter().map(|s| s.text.as_ref()).collect()
58 }
59}
60
61pub trait Renderable {
66 fn render(&self, context: &RenderContext) -> Vec<Segment>;
71
72 fn min_width(&self) -> usize {
74 1
75 }
76
77 fn max_width(&self) -> usize {
79 usize::MAX
80 }
81}
82
83impl Renderable for String {
85 fn render(&self, _context: &RenderContext) -> Vec<Segment> {
86 vec![Segment::new(vec![Span::raw(self.clone())])]
87 }
88
89 fn max_width(&self) -> usize {
90 unicode_width::UnicodeWidthStr::width(self.as_str())
91 }
92}
93
94impl Renderable for &str {
96 fn render(&self, _context: &RenderContext) -> Vec<Segment> {
97 vec![Segment::new(vec![Span::raw(self.to_string())])]
98 }
99
100 fn max_width(&self) -> usize {
101 unicode_width::UnicodeWidthStr::width(*self)
102 }
103}
104
105impl Renderable for crate::text::Text {
107 fn render(&self, context: &RenderContext) -> Vec<Segment> {
108 let lines = self.wrap(context.width);
109 lines
110 .into_iter()
111 .map(|line| {
112 let aligned = self.align_line(line, context.width);
113 Segment::line(aligned)
114 })
115 .collect()
116 }
117
118 fn min_width(&self) -> usize {
119 self.spans
121 .iter()
122 .flat_map(|s| s.text.split_whitespace())
123 .map(unicode_width::UnicodeWidthStr::width)
124 .max()
125 .unwrap_or(1)
126 }
127
128 fn max_width(&self) -> usize {
129 self.width()
130 }
131}
132
133pub type BoxedRenderable = Box<dyn Renderable + Send + Sync>;
135
136impl Renderable for BoxedRenderable {
137 fn render(&self, context: &RenderContext) -> Vec<Segment> {
138 (**self).render(context)
139 }
140
141 fn min_width(&self) -> usize {
142 (**self).min_width()
143 }
144
145 fn max_width(&self) -> usize {
146 (**self).max_width()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_segment_width() {
156 let segment = Segment::new(vec![Span::raw("hello")]);
157 assert_eq!(segment.width(), 5);
158 }
159
160 #[test]
161 fn test_segment_plain_text() {
162 let segment = Segment::new(vec![Span::raw("hello"), Span::raw(" world")]);
163 assert_eq!(segment.plain_text(), "hello world");
164 }
165
166 #[test]
167 fn test_string_renderable() {
168 let s = "Hello, World!".to_string();
169 let context = RenderContext {
170 width: 80,
171 height: None,
172 };
173 let segments = s.render(&context);
174 assert_eq!(segments.len(), 1);
175 assert_eq!(segments[0].plain_text(), "Hello, World!");
176 }
177}