use std::sync::{Arc, Mutex};
use crate::control::Control;
use crate::live::VerticalOverflowMethod;
use crate::loop_helpers::loop_last;
use crate::segment::{ControlType, Segment, Segments};
use crate::style::Style;
use crate::text::Text;
use crate::{Console, ConsoleOptions, Measurement, Renderable};
pub struct LiveRender {
renderable: Arc<Mutex<Arc<dyn Renderable>>>,
style: Option<Style>,
vertical_overflow: VerticalOverflowMethod,
shape: Arc<Mutex<Option<(usize, usize)>>>,
}
impl LiveRender {
pub fn new(renderable: impl Renderable + 'static) -> Self {
Self {
renderable: Arc::new(Mutex::new(Arc::new(renderable))),
style: None,
vertical_overflow: VerticalOverflowMethod::Ellipsis,
shape: Arc::new(Mutex::new(None)),
}
}
pub fn with_style(mut self, style: impl Into<Style>) -> Self {
self.style = Some(style.into());
self
}
pub fn with_vertical_overflow(mut self, method: VerticalOverflowMethod) -> Self {
self.vertical_overflow = method;
self
}
pub fn last_render_height(&self) -> usize {
self.shape.lock().unwrap().map(|(_, h)| h).unwrap_or(0)
}
pub fn set_renderable(&self, renderable: impl Renderable + 'static) {
*self.renderable.lock().unwrap() = Arc::new(renderable);
}
pub fn position_cursor(&self) -> Control {
if let Some((_, height)) = *self.shape.lock().unwrap() {
let mut control = Control::new();
control.push(ControlType::CarriageReturn);
control.push(ControlType::EraseInLine(2));
for _ in 1..height {
control.push(ControlType::CursorUp(1));
control.push(ControlType::EraseInLine(2));
}
control
} else {
Control::new()
}
}
pub fn restore_cursor(&self) -> Control {
if let Some((_, height)) = *self.shape.lock().unwrap() {
let mut control = Control::new();
control.push(ControlType::CarriageReturn);
for _ in 0..height {
control.push(ControlType::CursorUp(1));
control.push(ControlType::EraseInLine(2));
}
control
} else {
Control::new()
}
}
}
impl Renderable for LiveRender {
fn render(&self, console: &Console, options: &ConsoleOptions) -> Segments {
let renderable = self.renderable.lock().unwrap().clone();
let style = self.style;
let mut lines = console.render_lines(&*renderable, Some(options), style, false, false);
let mut shape = Segment::get_shape(&lines);
let (_, height) = shape;
let max_height = options.size.1;
if height > max_height {
match self.vertical_overflow {
VerticalOverflowMethod::Crop => {
lines.truncate(max_height);
shape = Segment::get_shape(&lines);
}
VerticalOverflowMethod::Ellipsis => {
lines.truncate(max_height.saturating_sub(1));
let ellipsis_style = options.get_style("live.ellipsis").unwrap_or_default();
let ellipsis = Text::styled("...", ellipsis_style).center(options.max_width);
let ellipsis_lines =
console.render_lines(&ellipsis, Some(options), None, false, false);
if let Some(first) = ellipsis_lines.into_iter().next() {
lines.push(first);
}
shape = Segment::get_shape(&lines);
}
VerticalOverflowMethod::Visible => {
}
}
}
*self.shape.lock().unwrap() = Some(shape);
let new_line = 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
}
fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
let renderable = self.renderable.lock().unwrap().clone();
renderable.measure(console, options)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Console;
#[test]
fn test_live_render_basic() {
let text = Text::plain("Hello World");
let live_render = LiveRender::new(text);
let console = Console::new();
let options = console.options();
let segments = live_render.render(&console, options);
assert!(!segments.is_empty());
assert!(live_render.last_render_height() > 0);
}
#[test]
fn test_live_render_last_render_height() {
let text = Text::plain("Line 1\nLine 2\nLine 3");
let live_render = LiveRender::new(text);
assert_eq!(live_render.last_render_height(), 0);
let console = Console::new();
let options = console.options();
let _ = live_render.render(&console, options);
assert_eq!(live_render.last_render_height(), 3);
}
#[test]
fn test_live_render_position_cursor_no_render() {
let text = Text::plain("Hello");
let live_render = LiveRender::new(text);
let control = live_render.position_cursor();
assert!(control.is_empty());
}
#[test]
fn test_live_render_position_cursor_after_render() {
let text = Text::plain("Line 1\nLine 2\nLine 3");
let live_render = LiveRender::new(text);
let console = Console::new();
let options = console.options();
let _ = live_render.render(&console, options);
let control = live_render.position_cursor();
assert!(!control.is_empty());
let segments = control.render(&console, options);
assert_eq!(segments.len(), 6);
}
#[test]
fn test_live_render_restore_cursor_no_render() {
let text = Text::plain("Hello");
let live_render = LiveRender::new(text);
let control = live_render.restore_cursor();
assert!(control.is_empty());
}
#[test]
fn test_live_render_restore_cursor_after_render() {
let text = Text::plain("Line 1\nLine 2\nLine 3");
let live_render = LiveRender::new(text);
let console = Console::new();
let options = console.options();
let _ = live_render.render(&console, options);
let control = live_render.restore_cursor();
assert!(!control.is_empty());
let segments = control.render(&console, options);
assert_eq!(segments.len(), 7);
}
#[test]
fn test_live_render_set_renderable() {
let text1 = Text::plain("Hello");
let live_render = LiveRender::new(text1);
let console = Console::new();
let options = console.options();
let _ = live_render.render(&console, options);
assert_eq!(live_render.last_render_height(), 1);
let text2 = Text::plain("Line 1\nLine 2");
live_render.set_renderable(text2);
let _ = live_render.render(&console, options);
assert_eq!(live_render.last_render_height(), 2);
}
#[test]
fn test_live_render_vertical_overflow_crop() {
let lines: Vec<&str> = (0..100).map(|_| "Line").collect();
let text = Text::plain(lines.join("\n"));
let live_render =
LiveRender::new(text).with_vertical_overflow(VerticalOverflowMethod::Crop);
let console = Console::new();
let mut options = console.options().clone();
options.size = (80, 10);
let _ = live_render.render(&console, &options);
assert_eq!(live_render.last_render_height(), 10);
}
#[test]
fn test_live_render_vertical_overflow_ellipsis() {
let lines: Vec<&str> = (0..100).map(|_| "Line").collect();
let text = Text::plain(lines.join("\n"));
let live_render =
LiveRender::new(text).with_vertical_overflow(VerticalOverflowMethod::Ellipsis);
let console = Console::new();
let mut options = console.options().clone();
options.size = (80, 10);
let _ = live_render.render(&console, &options);
assert_eq!(live_render.last_render_height(), 10);
}
#[test]
fn test_live_render_vertical_overflow_visible() {
let lines: Vec<&str> = (0..100).map(|_| "Line").collect();
let text = Text::plain(lines.join("\n"));
let live_render =
LiveRender::new(text).with_vertical_overflow(VerticalOverflowMethod::Visible);
let console = Console::new();
let mut options = console.options().clone();
options.size = (80, 10);
let _ = live_render.render(&console, &options);
assert_eq!(live_render.last_render_height(), 100);
}
#[test]
fn test_live_render_with_style() {
let text = Text::plain("Hello");
let style = Style::new().with_bold(true);
let live_render = LiveRender::new(text).with_style(style);
let console = Console::new();
let options = console.options();
let segments = live_render.render(&console, options);
assert!(!segments.is_empty());
}
#[test]
fn test_live_render_is_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<LiveRender>();
assert_sync::<LiveRender>();
}
}