use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::Line;
use unicode_width::UnicodeWidthStr;
use crate::geometry::Size;
use crate::surface::Surface;
use crate::view::{RenderCtx, View};
pub fn line_width(line: &Line) -> u16 {
line.spans
.iter()
.map(|s| UnicodeWidthStr::width(s.content.as_ref()) as u16)
.fold(0, u16::saturating_add)
}
pub struct Text {
lines: Vec<Line<'static>>,
}
impl Text {
pub fn new(lines: Vec<Line<'static>>) -> Self {
Self { lines }
}
pub fn raw(text: impl Into<String>) -> Self {
Self::new(vec![Line::from(text.into())])
}
}
impl View for Text {
fn measure(&self, _available: Size) -> Size {
let width = self.lines.iter().map(line_width).max().unwrap_or(0);
Size::new(width, self.lines.len() as u16)
}
fn render(&self, area: Rect, surface: &mut Surface, _ctx: &RenderCtx) {
for (row, line) in self.lines.iter().enumerate() {
let y = area.y.saturating_add(row as u16);
if y >= area.bottom() {
break;
}
let mut x = area.x;
for span in &line.spans {
if x >= area.right() {
break;
}
x = surface.set_string(x, y, span.content.as_ref(), span.style);
}
}
}
}
pub struct Paragraph {
text: String,
style: Style,
}
impl Paragraph {
pub fn new(text: impl Into<String>, style: Style) -> Self {
Self {
text: text.into(),
style,
}
}
fn wrap(&self, width: u16) -> Vec<String> {
if width == 0 {
return Vec::new();
}
self.text
.split('\n')
.flat_map(|para| {
let wrapped = textwrap::wrap(para, width as usize);
if wrapped.is_empty() {
vec![String::new()]
} else {
wrapped.into_iter().map(|c| c.into_owned()).collect()
}
})
.collect()
}
}
impl View for Paragraph {
fn measure(&self, available: Size) -> Size {
let lines = self.wrap(available.width);
let width = lines
.iter()
.map(|l| UnicodeWidthStr::width(l.as_str()) as u16)
.max()
.unwrap_or(0);
Size::new(width, lines.len() as u16)
}
fn render(&self, area: Rect, surface: &mut Surface, _ctx: &RenderCtx) {
for (row, line) in self.wrap(area.width).into_iter().enumerate() {
let y = area.y.saturating_add(row as u16);
if y >= area.bottom() {
break;
}
surface.set_string(area.x, y, &line, self.style);
}
}
}