use std::sync::Arc;
use crate::Renderable;
use crate::console::Console;
use crate::console::ConsoleOptions;
use crate::group::Group;
use crate::loop_helpers::loop_last;
use crate::segment::{Segment, Segments};
use crate::style::Style;
pub struct Screen {
renderable: Arc<dyn Renderable>,
style: Option<Style>,
application_mode: bool,
}
impl Screen {
pub fn new(renderable: impl Renderable + 'static) -> Self {
Self {
renderable: Arc::new(renderable),
style: None,
application_mode: false,
}
}
pub fn new_many<I, R>(renderables: I) -> Self
where
I: IntoIterator<Item = R>,
R: Renderable + 'static,
{
Self::new(Group::new(renderables))
}
pub fn from_arc(renderable: Arc<dyn Renderable>) -> Self {
Self {
renderable,
style: None,
application_mode: false,
}
}
pub fn with_style(mut self, style: impl Into<Style>) -> Self {
self.style = Some(style.into());
self
}
pub fn with_application_mode(mut self, mode: bool) -> Self {
self.application_mode = mode;
self
}
}
impl Renderable for Screen {
fn render(&self, console: &Console, options: &ConsoleOptions) -> Segments {
let (width, height) = options.size;
let mut render_options = options.clone();
render_options.size = (width, height);
render_options.min_width = width.max(1);
render_options.max_width = width.max(1);
render_options.max_height = height;
render_options.height = Some(height);
let lines = console.render_lines(
&*self.renderable,
Some(&render_options),
self.style,
true, false, );
let lines = Segment::set_shape(&lines, width, Some(height), self.style, false);
let new_line = if self.application_mode {
Segment::new("\n\r")
} else {
Segment::line()
};
let mut out = Segments::new();
for (is_last, line) in loop_last(lines.into_iter()) {
for seg in line {
out.push(seg);
}
if !is_last {
out.push(new_line.clone());
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Text;
#[test]
fn test_screen_new_many() {
let console = Console::new();
let options = ConsoleOptions {
size: (10, 3),
max_width: 10,
max_height: 3,
..Default::default()
};
let screen = Screen::new_many([Text::plain("A"), Text::plain("B")]);
let output: String = screen
.render(&console, &options)
.iter()
.map(|s| s.text.to_string())
.collect();
assert!(output.contains("A"));
assert!(output.contains("B"));
assert!(output.contains('\n'));
}
#[test]
fn test_screen_basic() {
let console = Console::new();
let options = ConsoleOptions {
size: (10, 3),
max_width: 10,
max_height: 3,
..Default::default()
};
let text = Text::plain("Hello");
let screen = Screen::new(text);
let segments = screen.render(&console, &options);
let output: String = segments.iter().map(|s| s.text.to_string()).collect();
let newline_count = output.matches('\n').count();
assert_eq!(newline_count, 2);
let lines: Vec<&str> = output.split('\n').collect();
assert_eq!(lines.len(), 3);
for line in &lines {
assert_eq!(crate::cell_len(line), 10);
}
}
#[test]
fn test_screen_with_style() {
use crate::SimpleColor;
let console = Console::new();
let options = ConsoleOptions {
size: (10, 2),
max_width: 10,
max_height: 2,
..Default::default()
};
let style = Style::new().with_bgcolor(SimpleColor::Standard(1));
let text = Text::plain("Hi");
let screen = Screen::new(text).with_style(style);
let segments = screen.render(&console, &options);
let styled_segments: Vec<_> = segments
.iter()
.filter(|s| s.style.is_some() && !s.text.is_empty() && s.text.as_ref() != "\n")
.collect();
assert!(!styled_segments.is_empty());
}
#[test]
fn test_screen_application_mode() {
let console = Console::new();
let options = ConsoleOptions {
size: (5, 2),
max_width: 5,
max_height: 2,
..Default::default()
};
let text = Text::plain("a\nb");
let screen = Screen::new(text).with_application_mode(true);
let segments = screen.render(&console, &options);
let output: String = segments.iter().map(|s| s.text.to_string()).collect();
assert!(output.contains("\n\r"));
let plain_newlines = output
.chars()
.zip(output.chars().skip(1).chain(std::iter::once('\0')))
.filter(|&(c, next)| c == '\n' && next != '\r')
.count();
assert_eq!(plain_newlines, 0);
}
#[test]
fn test_screen_crops_excess() {
let console = Console::new();
let options = ConsoleOptions {
size: (5, 2),
max_width: 5,
max_height: 2,
..Default::default()
};
let text = Text::plain("Line 1\nLine 2\nLine 3\nLine 4");
let screen = Screen::new(text);
let segments = screen.render(&console, &options);
let output: String = segments.iter().map(|s| s.text.to_string()).collect();
let lines: Vec<&str> = output.split('\n').collect();
assert_eq!(lines.len(), 2);
for line in &lines {
assert_eq!(crate::cell_len(line), 5);
}
}
#[test]
fn test_screen_from_arc() {
let console = Console::new();
let options = ConsoleOptions {
size: (10, 2),
max_width: 10,
max_height: 2,
..Default::default()
};
let text: Arc<dyn Renderable> = Arc::new(Text::plain("Hello"));
let screen = Screen::from_arc(text);
let segments = screen.render(&console, &options);
let output: String = segments.iter().map(|s| s.text.to_string()).collect();
assert!(output.contains("Hello"));
}
}