tui-rs/src/widgets/paragraph.rs

211 lines
5.8 KiB
Rust
Raw Normal View History

use std::borrow::Cow;
use either::Either;
use itertools::{multipeek, MultiPeek};
use unicode_segmentation::UnicodeSegmentation;
2016-11-04 16:54:57 +00:00
use unicode_width::UnicodeWidthStr;
2016-10-14 17:44:52 +00:00
use buffer::Buffer;
use layout::{Alignment, Rect};
use style::Style;
2018-05-06 10:59:24 +00:00
use widgets::{Block, Widget};
2016-10-14 17:44:52 +00:00
/// A widget to display some text.
2016-11-04 16:54:57 +00:00
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// # use tui::widgets::{Block, Borders, Paragraph, Text};
2016-11-06 20:41:32 +00:00
/// # use tui::style::{Style, Color};
/// # use tui::layout::{Alignment};
2016-11-04 16:54:57 +00:00
/// # fn main() {
/// let text = [
2018-09-06 00:12:28 +00:00
/// Text::Data("First line\n".into()),
/// Text::StyledData("Second line\n".into(), Style::default().fg(Color::Red))
/// ];
/// Paragraph::new(text.iter())
/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
2016-11-06 20:41:32 +00:00
/// .style(Style::default().fg(Color::White).bg(Color::Black))
/// .alignment(Alignment::Center)
/// .wrap(true);
2016-11-04 16:54:57 +00:00
/// # }
/// ```
pub struct Paragraph<'a, 't, T>
where
T: Iterator<Item = &'t Text<'t>>,
{
2016-11-04 16:54:57 +00:00
/// A block to wrap the widget in
2016-10-14 17:44:52 +00:00
block: Option<Block<'a>>,
2016-11-06 17:49:57 +00:00
/// Widget style
style: Style,
2016-11-04 16:54:57 +00:00
/// Wrap the text or not
wrapping: bool,
2016-11-04 16:54:57 +00:00
/// The text to display
text: T,
/// Should we parse the text for embedded commands
raw: bool,
/// Scroll
scroll: u16,
/// Aligenment of the text
alignment: Alignment,
2016-10-14 17:44:52 +00:00
}
pub enum Text<'b> {
Data(Cow<'b, str>),
StyledData(Cow<'b, str>, Style),
}
impl<'a, 't, T> Paragraph<'a, 't, T>
where
T: Iterator<Item = &'t Text<'t>>,
{
pub fn new(text: T) -> Paragraph<'a, 't, T> {
2016-11-06 17:49:57 +00:00
Paragraph {
2016-10-14 17:44:52 +00:00
block: None,
2016-11-06 17:49:57 +00:00
style: Default::default(),
wrapping: false,
raw: false,
text,
scroll: 0,
alignment: Alignment::Left,
2016-10-14 17:44:52 +00:00
}
}
pub fn block(mut self, block: Block<'a>) -> Paragraph<'a, 't, T> {
2016-10-14 17:44:52 +00:00
self.block = Some(block);
self
}
pub fn style(mut self, style: Style) -> Paragraph<'a, 't, T> {
2016-11-06 17:49:57 +00:00
self.style = style;
2016-10-14 17:44:52 +00:00
self
}
pub fn wrap(mut self, flag: bool) -> Paragraph<'a, 't, T> {
self.wrapping = flag;
self
}
pub fn raw(mut self, flag: bool) -> Paragraph<'a, 't, T> {
self.raw = flag;
self
}
pub fn scroll(mut self, offset: u16) -> Paragraph<'a, 't, T> {
self.scroll = offset;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a, 't, T> {
self.alignment = alignment;
self
}
}
impl<'a, 't, T> Widget for Paragraph<'a, 't, T>
2017-09-11 05:58:37 +00:00
where
T: Iterator<Item = &'t Text<'t>>,
{
2018-08-12 22:27:56 +00:00
fn draw(&mut self, area: Rect, buf: &mut Buffer) {
let text_area = match self.block {
Some(ref mut b) => {
2016-11-02 18:16:53 +00:00
b.draw(area, buf);
b.inner(area)
}
2018-08-12 22:27:56 +00:00
None => area,
2016-10-14 17:44:52 +00:00
};
if text_area.height < 1 {
return;
}
2016-11-06 17:49:57 +00:00
self.background(&text_area, buf, self.style.bg);
let style = self.style;
2018-08-12 22:27:56 +00:00
let styled = self.text.by_ref().flat_map(|t| match *t {
Text::Data(ref d) => {
let data: &'t str = d; // coerce to &str
Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| (g, style)))
}
Text::StyledData(ref d, s) => {
let data: &'t str = d; // coerce to &str
Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| (g, s)))
}
});
let mut styled = multipeek(styled);
fn get_cur_line_len<'a, I: Iterator<Item = (&'a str, Style)>>(
styled: &mut MultiPeek<I>,
) -> u16 {
let mut line_len = 0;
while match &styled.peek() {
Some(&(x, _)) => x != "\n",
None => false,
} {
line_len += 1;
}
line_len
};
let mut x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}
Alignment::Right => (text_area.width).saturating_sub(get_cur_line_len(&mut styled)),
Alignment::Left => 0,
};
let mut y = 0;
let mut remove_leading_whitespaces = false;
2018-08-12 22:27:56 +00:00
while let Some((string, style)) = styled.next() {
if string == "\n" {
x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}
2018-08-12 22:27:56 +00:00
Alignment::Right => {
(text_area.width).saturating_sub(get_cur_line_len(&mut styled))
}
Alignment::Left => 0,
};
y += 1;
continue;
}
if x >= text_area.width && self.wrapping {
x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}
Alignment::Right => {
(text_area.width).saturating_sub(get_cur_line_len(&mut styled) + 1)
}
Alignment::Left => 0,
};
y += 1;
remove_leading_whitespaces = true
}
if remove_leading_whitespaces && string == " " {
continue;
}
remove_leading_whitespaces = false;
if y > text_area.height + self.scroll - 1 {
break;
}
2018-08-12 22:27:56 +00:00
if y < self.scroll {
continue;
}
buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll)
.set_symbol(string)
.set_style(style);
x += string.width() as u16;
2016-10-14 17:44:52 +00:00
}
}
}