use crate::console::RenderContext;
use crate::text::Span;
#[derive(Debug, Clone)]
pub struct Segment {
pub spans: Vec<Span>,
pub newline: bool,
}
impl Segment {
pub fn new(spans: Vec<Span>) -> Self {
Segment {
spans,
newline: false,
}
}
pub fn line(spans: Vec<Span>) -> Self {
Segment {
spans,
newline: true,
}
}
pub fn empty_line() -> Self {
Segment {
spans: Vec::new(),
newline: true,
}
}
pub fn from_span(span: Span) -> Self {
Segment {
spans: vec![span],
newline: false,
}
}
pub fn width(&self) -> usize {
self.spans.iter().map(|s| s.width()).sum()
}
pub fn plain_text(&self) -> String {
self.spans.iter().map(|s| s.text.as_ref()).collect()
}
}
pub trait Renderable {
fn render(&self, context: &RenderContext) -> Vec<Segment>;
fn min_width(&self) -> usize {
1
}
fn max_width(&self) -> usize {
usize::MAX
}
}
impl Renderable for String {
fn render(&self, _context: &RenderContext) -> Vec<Segment> {
vec![Segment::new(vec![Span::raw(self.clone())])]
}
fn max_width(&self) -> usize {
unicode_width::UnicodeWidthStr::width(self.as_str())
}
}
impl Renderable for &str {
fn render(&self, _context: &RenderContext) -> Vec<Segment> {
vec![Segment::new(vec![Span::raw(self.to_string())])]
}
fn max_width(&self) -> usize {
unicode_width::UnicodeWidthStr::width(*self)
}
}
impl Renderable for crate::text::Text {
fn render(&self, context: &RenderContext) -> Vec<Segment> {
let lines = self.wrap(context.width);
lines
.into_iter()
.map(|line| {
let aligned = self.align_line(line, context.width);
Segment::line(aligned)
})
.collect()
}
fn min_width(&self) -> usize {
self.spans
.iter()
.flat_map(|s| s.text.split_whitespace())
.map(unicode_width::UnicodeWidthStr::width)
.max()
.unwrap_or(1)
}
fn max_width(&self) -> usize {
self.width()
}
}
pub type BoxedRenderable = Box<dyn Renderable + Send + Sync>;
impl Renderable for BoxedRenderable {
fn render(&self, context: &RenderContext) -> Vec<Segment> {
(**self).render(context)
}
fn min_width(&self) -> usize {
(**self).min_width()
}
fn max_width(&self) -> usize {
(**self).max_width()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_segment_width() {
let segment = Segment::new(vec![Span::raw("hello")]);
assert_eq!(segment.width(), 5);
}
#[test]
fn test_segment_plain_text() {
let segment = Segment::new(vec![Span::raw("hello"), Span::raw(" world")]);
assert_eq!(segment.plain_text(), "hello world");
}
#[test]
fn test_string_renderable() {
let s = "Hello, World!".to_string();
let context = RenderContext {
width: 80,
height: None,
};
let segments = s.render(&context);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0].plain_text(), "Hello, World!");
}
}