use crate::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Style,
text::{StyledGrapheme, Text},
widgets::{
reflow::{LineComposer, LineTruncator, WordWrapper},
Block, Widget,
},
};
use std::iter;
use unicode_width::UnicodeWidthStr;
fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
match alignment {
Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
Alignment::Right => text_area_width.saturating_sub(line_width),
Alignment::Left => 0,
}
}
#[derive(Debug, Clone)]
pub struct Paragraph<'a> {
block: Option<Block<'a>>,
style: Style,
wrap: Option<Wrap>,
text: Text<'a>,
scroll: (u16, u16),
alignment: Alignment,
}
#[derive(Debug, Clone, Copy)]
pub struct Wrap {
pub trim: bool,
}
impl<'a> Paragraph<'a> {
pub fn new<T>(text: T) -> Paragraph<'a>
where
T: Into<Text<'a>>,
{
Paragraph {
block: None,
style: Default::default(),
wrap: None,
text: text.into(),
scroll: (0, 0),
alignment: Alignment::Left,
}
}
pub fn block(mut self, block: Block<'a>) -> Paragraph<'a> {
self.block = Some(block);
self
}
pub fn style(mut self, style: Style) -> Paragraph<'a> {
self.style = style;
self
}
pub fn wrap(mut self, wrap: Wrap) -> Paragraph<'a> {
self.wrap = Some(wrap);
self
}
pub fn scroll(mut self, offset: (u16, u16)) -> Paragraph<'a> {
self.scroll = offset;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a> {
self.alignment = alignment;
self
}
}
impl<'a> Widget for Paragraph<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let text_area = match self.block.take() {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
inner_area
}
None => area,
};
if text_area.height < 1 {
return;
}
let style = self.style;
let mut styled = self.text.lines.iter().flat_map(|spans| {
spans
.0
.iter()
.flat_map(|span| span.styled_graphemes(style))
.chain(iter::once(StyledGrapheme {
symbol: "\n",
style: self.style,
}))
});
let mut line_composer: Box<dyn LineComposer> = if let Some(Wrap { trim }) = self.wrap {
Box::new(WordWrapper::new(&mut styled, text_area.width, trim))
} else {
let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
if let Alignment::Left = self.alignment {
line_composer.set_horizontal_offset(self.scroll.1);
}
line_composer
};
let mut y = 0;
while let Some((current_line, current_line_width)) = line_composer.next_line() {
if y >= self.scroll.0 {
let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
for StyledGrapheme { symbol, style } in current_line {
buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll.0)
.set_symbol(if symbol.is_empty() {
" "
} else {
symbol
})
.set_style(*style);
x += symbol.width() as u16;
}
}
y += 1;
if y >= text_area.height + self.scroll.0 {
break;
}
}
}
}