1use std::sync::Arc;
6
7use crate::Renderable;
8use crate::console::Console;
9use crate::console::ConsoleOptions;
10use crate::group::Group;
11use crate::loop_helpers::loop_last;
12use crate::segment::{Segment, Segments};
13use crate::style::Style;
14
15pub struct Screen {
30 renderable: Arc<dyn Renderable>,
32 style: Option<Style>,
34 application_mode: bool,
36}
37
38impl Screen {
39 pub fn new(renderable: impl Renderable + 'static) -> Self {
41 Self {
42 renderable: Arc::new(renderable),
43 style: None,
44 application_mode: false,
45 }
46 }
47
48 pub fn new_many<I, R>(renderables: I) -> Self
53 where
54 I: IntoIterator<Item = R>,
55 R: Renderable + 'static,
56 {
57 Self::new(Group::new(renderables))
58 }
59
60 pub fn from_arc(renderable: Arc<dyn Renderable>) -> Self {
64 Self {
65 renderable,
66 style: None,
67 application_mode: false,
68 }
69 }
70
71 pub fn with_style(mut self, style: impl Into<Style>) -> Self {
73 self.style = Some(style.into());
74 self
75 }
76
77 pub fn with_application_mode(mut self, mode: bool) -> Self {
82 self.application_mode = mode;
83 self
84 }
85}
86
87impl Renderable for Screen {
88 fn render(&self, console: &Console, options: &ConsoleOptions) -> Segments {
89 let (width, height) = options.size;
90
91 let mut render_options = options.clone();
93 render_options.size = (width, height);
94 render_options.min_width = width.max(1);
95 render_options.max_width = width.max(1);
96 render_options.max_height = height;
97 render_options.height = Some(height);
98
99 let lines = console.render_lines(
101 &*self.renderable,
102 Some(&render_options),
103 self.style,
104 true, false, );
107
108 let lines = Segment::set_shape(&lines, width, Some(height), self.style, false);
110
111 let new_line = if self.application_mode {
113 Segment::new("\n\r")
114 } else {
115 Segment::line()
116 };
117
118 let mut out = Segments::new();
119 for (is_last, line) in loop_last(lines.into_iter()) {
120 for seg in line {
121 out.push(seg);
122 }
123 if !is_last {
124 out.push(new_line.clone());
125 }
126 }
127 out
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::Text;
135
136 #[test]
137 fn test_screen_new_many() {
138 let console = Console::new();
139 let options = ConsoleOptions {
140 size: (10, 3),
141 max_width: 10,
142 max_height: 3,
143 ..Default::default()
144 };
145
146 let screen = Screen::new_many([Text::plain("A"), Text::plain("B")]);
147 let output: String = screen
148 .render(&console, &options)
149 .iter()
150 .map(|s| s.text.to_string())
151 .collect();
152
153 assert!(output.contains("A"));
155 assert!(output.contains("B"));
156 assert!(output.contains('\n'));
157 }
158
159 #[test]
160 fn test_screen_basic() {
161 let console = Console::new();
162 let options = ConsoleOptions {
163 size: (10, 3),
164 max_width: 10,
165 max_height: 3,
166 ..Default::default()
167 };
168
169 let text = Text::plain("Hello");
170 let screen = Screen::new(text);
171 let segments = screen.render(&console, &options);
172
173 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
175
176 let newline_count = output.matches('\n').count();
178 assert_eq!(newline_count, 2);
179
180 let lines: Vec<&str> = output.split('\n').collect();
182 assert_eq!(lines.len(), 3);
183 for line in &lines {
184 assert_eq!(crate::cell_len(line), 10);
186 }
187 }
188
189 #[test]
190 fn test_screen_with_style() {
191 use crate::SimpleColor;
192
193 let console = Console::new();
194 let options = ConsoleOptions {
195 size: (10, 2),
196 max_width: 10,
197 max_height: 2,
198 ..Default::default()
199 };
200
201 let style = Style::new().with_bgcolor(SimpleColor::Standard(1));
203 let text = Text::plain("Hi");
204 let screen = Screen::new(text).with_style(style);
205 let segments = screen.render(&console, &options);
206
207 let styled_segments: Vec<_> = segments
209 .iter()
210 .filter(|s| s.style.is_some() && !s.text.is_empty() && s.text.as_ref() != "\n")
211 .collect();
212
213 assert!(!styled_segments.is_empty());
214 }
215
216 #[test]
217 fn test_screen_application_mode() {
218 let console = Console::new();
219 let options = ConsoleOptions {
220 size: (5, 2),
221 max_width: 5,
222 max_height: 2,
223 ..Default::default()
224 };
225
226 let text = Text::plain("a\nb");
227 let screen = Screen::new(text).with_application_mode(true);
228 let segments = screen.render(&console, &options);
229
230 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
231
232 assert!(output.contains("\n\r"));
234 let plain_newlines = output
236 .chars()
237 .zip(output.chars().skip(1).chain(std::iter::once('\0')))
238 .filter(|&(c, next)| c == '\n' && next != '\r')
239 .count();
240 assert_eq!(plain_newlines, 0);
241 }
242
243 #[test]
244 fn test_screen_crops_excess() {
245 let console = Console::new();
246 let options = ConsoleOptions {
247 size: (5, 2),
248 max_width: 5,
249 max_height: 2,
250 ..Default::default()
251 };
252
253 let text = Text::plain("Line 1\nLine 2\nLine 3\nLine 4");
255 let screen = Screen::new(text);
256 let segments = screen.render(&console, &options);
257
258 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
259
260 let lines: Vec<&str> = output.split('\n').collect();
262 assert_eq!(lines.len(), 2);
263
264 for line in &lines {
266 assert_eq!(crate::cell_len(line), 5);
267 }
268 }
269
270 #[test]
271 fn test_screen_from_arc() {
272 let console = Console::new();
273 let options = ConsoleOptions {
274 size: (10, 2),
275 max_width: 10,
276 max_height: 2,
277 ..Default::default()
278 };
279
280 let text: Arc<dyn Renderable> = Arc::new(Text::plain("Hello"));
281 let screen = Screen::from_arc(text);
282 let segments = screen.render(&console, &options);
283
284 let output: String = segments.iter().map(|s| s.text.to_string()).collect();
285 assert!(output.contains("Hello"));
286 }
287}