Compare commits

...

22 Commits

Author SHA1 Message Date
Florian Dehau
3c38abb203 Release 0.2.2 2018-05-06 12:34:26 +02:00
Florian Dehau
4816563452 Update CHANGELOG 2018-05-06 11:49:32 +02:00
Florian Dehau
24dc73912b [examples] Update table example
Modify example to use variables outside of the closure scope
2018-05-06 11:49:32 +02:00
Florian Dehau
f96db9c74f [layout] Replace FnMut with FnOnce in Group::render
As the function does not need to mutate state and be run multiple times.
2018-05-06 11:49:32 +02:00
Florian Dehau
ef2054a45b [lib] Derive Debug on Terminal 2018-04-15 22:09:36 +02:00
Xavier Bestel
f4052e0e71 fix colors use, add missing Blue 2018-04-04 08:45:58 +02:00
Florian Dehau
524845cc74 Publish v0.2.1 2018-04-01 19:49:10 +02:00
Florian Dehau
4c356c5077 Update CHANGELOG 2018-04-01 19:03:49 +02:00
Florian Dehau
36dea8373f [widgets][paragraph] Fix text wrapping 2018-04-01 19:03:49 +02:00
Florian Dehau
2cb823a15b [lib] Fix conversion from cassowary-rs results to internal layouts
The library used to compute the layout may returned negative results
given strange contraints. To avoid overflows on unsigned integers operations,
those results will be converted to 0 instead of being converted as is.
2018-04-01 18:28:17 +02:00
Florian Dehau
169dc43565 [examples] Add layout example 2018-04-01 18:28:17 +02:00
Florian Dehau
4b53acab14 [doc] Fix layout example in documentation 2018-04-01 18:28:17 +02:00
Florian Dehau
c3acac797a Update CHANGELOG 2018-04-01 12:50:03 +02:00
Florian Dehau
dd2bf0ad13 Update CHANGELOG 2018-04-01 12:36:11 +02:00
Florian Dehau
f620af1455 [examples][list] Change style of first list 2018-04-01 12:36:11 +02:00
Florian Dehau
fcd1b7b187 [widgets][list] Set the style of the underlying list 2018-04-01 12:36:11 +02:00
Magnus Bergmark
d0d2f88346 BUG: Buffer::pos_of panics on inside-bounds index
- Add tests for this behavior.
- Extend documentation of Buffer::pos_of and Buffer::index_of
  - Clarify that the coordinates should be in global coordinate-space
  (rather than local).
  - Document panics.
  - Add examples.
2018-04-01 11:13:35 +02:00
Rafael Escobar
c56173462a Export AlternateScreenBackend. 2018-01-30 22:16:41 +01:00
Florian Dehau
58074f23c5 Update CHANGELOG 2018-01-30 22:16:41 +01:00
Florian Dehau
f816e6bbc4 [backends] Improve termion backend
* Add AlternateScreenBackend
* Add a way to create a TermionBackend with a custom config
* Improve return values of several methods
2018-01-30 22:16:41 +01:00
Florian Dehau
d53ecaeade [examples] Clean user input example 2018-01-27 10:46:58 +01:00
Florian Dehau
299279dc2d [examples] Add user input example 2018-01-27 10:46:58 +01:00
15 changed files with 463 additions and 49 deletions

View File

@@ -1,5 +1,34 @@
# Changelog
## To be released
## v0.2.2 - 2018-05-06
### Added
* `Terminal` implements `Debug`
### Changed
* Use `FnOnce` instead of `FnMut` in Group::render
## v0.2.1 - 2018-04-01
### Added
* Add `AlternateScreenBackend` in `termion` backend
* Add `TermionBackend::with_stdout` in order to let an user of the library
provides its own termion struct
* Add tests and documentation for `Buffer::pos_of`
* Remove leading whitespaces when wrapping text
### Fixed
* Fix `debug_assert` in `Buffer::pos_of`
* Pass the style of `SelectableList` to the underlying `List`
* Fix missing character when wrapping text
* Fix panic when specifying layout constraints
## v0.2.0 - 2017-12-26
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "tui"
version = "0.2.0"
version = "0.2.2"
authors = ["Florian Dehau <work@fdehau.com>"]
description = """
A library to build rich terminal user interfaces or dashboards
@@ -19,15 +19,15 @@ default = ["termion"]
[dependencies]
bitflags = "1.0.1"
cassowary = "0.3.0"
log = "0.4.0"
log = "0.4.1"
unicode-segmentation = "1.2.0"
unicode-width = "0.1.4"
termion = { version = "1.5.1", optional = true }
rustbox = { version = "0.9.0", optional = true }
rustbox = { version = "0.11.0", optional = true }
[dev-dependencies]
stderrlog = "0.2.3"
rand = "0.4.1"
stderrlog = "0.3.0"
rand = "0.4.2"
[[example]]
name = "barchart"
@@ -81,3 +81,11 @@ path = "examples/table.rs"
[[example]]
name = "tabs"
path = "examples/tabs.rs"
[[example]]
name = "user_input"
path = "examples/user_input.rs"
[[example]]
name = "layout"
path = "examples/layout.rs"

104
examples/layout.rs Normal file
View File

@@ -0,0 +1,104 @@
extern crate log;
extern crate stderrlog;
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Widget};
use tui::layout::{Direction, Group, Rect, Size};
struct App {
size: Rect,
}
impl App {
fn new() -> App {
App {
size: Rect::default(),
}
}
}
enum Event {
Input(event::Key),
}
fn main() {
stderrlog::new().verbosity(4).init().unwrap();
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if size != app.size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
_ => {}
},
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)])
.render(t, &app.size, |t, chunks| {
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(t, &chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(t, &chunks[2]);
});
t.draw().unwrap();
}

View File

@@ -160,11 +160,13 @@ fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &app.size, |t, chunks| {
let style = Style::default().fg(Color::Black).bg(Color::White);
SelectableList::default()
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.style(style)
.highlight_style(style.clone().fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(t, &chunks[0]);
{

View File

@@ -84,23 +84,23 @@ fn main() {
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let normal_style = Style::default().fg(Color::White);
let header = ["Header1", "Header2", "Header3"];
let rows = app.items.iter().enumerate().map(|(i, item)| {
if i == app.selected {
Row::StyledData(item.into_iter(), &selected_style)
} else {
Row::StyledData(item.into_iter(), &normal_style)
}
});
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(100)])
.margin(5)
.render(t, &app.size, |t, chunks| {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let normal_style = Style::default().fg(Color::White);
Table::new(
["Header1", "Header2", "Header3"].into_iter(),
app.items.iter().enumerate().map(|(i, item)| {
if i == app.selected {
Row::StyledData(item.into_iter(), &selected_style)
} else {
Row::StyledData(item.into_iter(), &normal_style)
}
}),
).block(Block::default().borders(Borders::ALL).title("Table"))
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.render(t, &chunks[0]);
});

131
examples/user_input.rs Normal file
View File

@@ -0,0 +1,131 @@
/// A simple example demonstrating how to handle user input. This is
/// a bit out of the scope of the library as it does not provide any
/// input handling out of the box. However, it may helps some to get
/// started.
///
/// This is a very simple example:
/// * A input box always focused. Every character you type is registered
/// here
/// * Pressing Backspace erases a character
/// * Pressing Enter pushes the current input in the history of previous
/// messages
extern crate termion;
extern crate tui;
use std::io;
use std::thread;
use std::sync::mpsc;
use termion::event;
use termion::input::TermRead;
use tui::Terminal;
use tui::backend::MouseBackend;
use tui::widgets::{Block, Borders, Item, List, Paragraph, Widget};
use tui::layout::{Direction, Group, Rect, Size};
use tui::style::{Color, Style};
struct App {
size: Rect,
input: String,
messages: Vec<String>,
}
impl App {
fn new() -> App {
App {
size: Rect::default(),
input: String::new(),
messages: Vec::new(),
}
}
}
enum Event {
Input(event::Key),
}
fn main() {
// Terminal initialization
let backend = MouseBackend::new().unwrap();
let mut terminal = Terminal::new(backend).unwrap();
// Channels
let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
// Input
thread::spawn(move || {
let stdin = io::stdin();
for c in stdin.keys() {
let evt = c.unwrap();
input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') {
break;
}
}
});
// App
let mut app = App::new();
// First draw call
terminal.clear().unwrap();
terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap();
draw(&mut terminal, &app);
loop {
let size = terminal.size().unwrap();
if app.size != size {
terminal.resize(size).unwrap();
app.size = size;
}
let evt = rx.recv().unwrap();
match evt {
Event::Input(input) => match input {
event::Key::Char('q') => {
break;
}
event::Key::Char('\n') => {
app.messages.push(app.input.drain(..).collect());
}
event::Key::Char(c) => {
app.input.push(c);
}
event::Key::Backspace => {
app.input.pop();
}
_ => {}
},
}
draw(&mut terminal, &app);
}
terminal.show_cursor().unwrap();
terminal.clear().unwrap();
}
fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default()
.direction(Direction::Vertical)
.margin(2)
.sizes(&[Size::Fixed(3), Size::Min(1)])
.render(t, &app.size, |t, chunks| {
Paragraph::default()
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Input"))
.text(&app.input)
.render(t, &chunks[0]);
List::new(
app.messages
.iter()
.enumerate()
.map(|(i, m)| Item::Data(format!("{}: {}", i, m))),
).block(Block::default().borders(Borders::ALL).title("Messages"))
.render(t, &chunks[1]);
});
t.draw().unwrap();
}

View File

@@ -11,7 +11,7 @@ pub use self::rustbox::RustboxBackend;
#[cfg(feature = "termion")]
mod termion;
#[cfg(feature = "termion")]
pub use self::termion::{MouseBackend, RawBackend, TermionBackend};
pub use self::termion::{MouseBackend, RawBackend, TermionBackend, AlternateScreenBackend};
pub trait Backend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>

View File

@@ -20,11 +20,13 @@ where
pub type RawBackend = TermionBackend<termion::raw::RawTerminal<io::Stdout>>;
pub type MouseBackend =
TermionBackend<termion::input::MouseTerminal<termion::raw::RawTerminal<io::Stdout>>>;
pub type AlternateScreenBackend =
TermionBackend<termion::screen::AlternateScreen<termion::raw::RawTerminal<io::Stdout>>>;
impl RawBackend {
pub fn new() -> Result<RawBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
Ok(TermionBackend { stdout: raw })
Ok(TermionBackend::with_stdout(raw))
}
}
@@ -32,7 +34,37 @@ impl MouseBackend {
pub fn new() -> Result<MouseBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
let mouse = termion::input::MouseTerminal::from(raw);
Ok(TermionBackend { stdout: mouse })
Ok(TermionBackend::with_stdout(mouse))
}
}
impl AlternateScreenBackend {
pub fn new() -> Result<AlternateScreenBackend, io::Error> {
let raw = io::stdout().into_raw_mode()?;
let screen = termion::screen::AlternateScreen::from(raw);
Ok(TermionBackend::with_stdout(screen))
}
}
impl<W> TermionBackend<W>
where
W: Write,
{
pub fn with_stdout(stdout: W) -> TermionBackend<W> {
TermionBackend { stdout }
}
}
impl<W> Write for TermionBackend<W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stdout.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
}
@@ -41,28 +73,25 @@ where
W: Write,
{
/// Clears the entire screen and move the cursor to the top left of the screen
fn clear(&mut self) -> Result<(), io::Error> {
fn clear(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::clear::All)?;
write!(self.stdout, "{}", termion::cursor::Goto(1, 1))?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
/// Hides cursor
fn hide_cursor(&mut self) -> Result<(), io::Error> {
fn hide_cursor(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::cursor::Hide)?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
/// Shows cursor
fn show_cursor(&mut self) -> Result<(), io::Error> {
fn show_cursor(&mut self) -> io::Result<()> {
write!(self.stdout, "{}", termion::cursor::Show)?;
self.stdout.flush()?;
Ok(())
self.stdout.flush()
}
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
@@ -108,8 +137,7 @@ where
Color::Reset.termion_fg(),
Color::Reset.termion_bg(),
Modifier::Reset.termion_modifier()
)?;
Ok(())
)
}
/// Return the size of the terminal
@@ -123,9 +151,8 @@ where
})
}
fn flush(&mut self) -> Result<(), io::Error> {
try!(self.stdout.flush());
Ok(())
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
}
@@ -167,16 +194,18 @@ impl Color {
Color::Red => termion_fg!(Red),
Color::Green => termion_fg!(Green),
Color::Yellow => termion_fg!(Yellow),
Color::Blue => termion_fg!(Blue),
Color::Magenta => termion_fg!(Magenta),
Color::Cyan => termion_fg!(Cyan),
Color::Gray => termion_fg_rgb!(146, 131, 116),
Color::DarkGray => termion_fg_rgb!(80, 73, 69),
Color::Gray => termion_fg!(White),
Color::DarkGray => termion_fg!(LightBlack),
Color::LightRed => termion_fg!(LightRed),
Color::LightGreen => termion_fg!(LightGreen),
Color::LightBlue => termion_fg!(LightBlue),
Color::LightYellow => termion_fg!(LightYellow),
Color::LightMagenta => termion_fg!(LightMagenta),
Color::LightCyan => termion_fg!(LightCyan),
Color::White => termion_fg!(White),
Color::White => termion_fg!(LightWhite),
Color::Rgb(r, g, b) => termion_fg_rgb!(r, g, b),
}
}
@@ -187,16 +216,18 @@ impl Color {
Color::Red => termion_bg!(Red),
Color::Green => termion_bg!(Green),
Color::Yellow => termion_bg!(Yellow),
Color::Blue => termion_bg!(Blue),
Color::Magenta => termion_bg!(Magenta),
Color::Cyan => termion_bg!(Cyan),
Color::Gray => termion_bg_rgb!(146, 131, 116),
Color::DarkGray => termion_bg_rgb!(80, 73, 69),
Color::Gray => termion_bg!(White),
Color::DarkGray => termion_bg!(LightBlack),
Color::LightRed => termion_bg!(LightRed),
Color::LightGreen => termion_bg!(LightGreen),
Color::LightBlue => termion_bg!(LightBlue),
Color::LightYellow => termion_bg!(LightYellow),
Color::LightMagenta => termion_bg!(LightMagenta),
Color::LightCyan => termion_bg!(LightCyan),
Color::White => termion_bg!(White),
Color::White => termion_bg!(LightWhite),
Color::Rgb(r, g, b) => termion_bg_rgb!(r, g, b),
}
}

View File

@@ -153,7 +153,34 @@ impl Buffer {
&mut self.content[i]
}
/// Returns the index in the Vec<Cell> for the given (x, y)
/// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
///
/// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
///
/// # Examples
///
/// ```
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// // Global coordinates to the top corner of this buffer's area
/// assert_eq!(buffer.index_of(200, 100), 0);
/// ```
///
/// # Panics
///
/// Panics when given an coordinate that is outside of this Buffer's area.
///
/// ```should_panic
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
/// // starts at (200, 100).
/// buffer.index_of(0, 0); // Panics
/// ```
pub fn index_of(&self, x: u16, y: u16) -> usize {
debug_assert!(
x >= self.area.left() && x < self.area.right() && y >= self.area.top()
@@ -166,10 +193,36 @@ impl Buffer {
((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
}
/// Returns the coordinates of a cell given its index
/// Returns the (global) coordinates of a cell given its index
///
/// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
///
/// # Examples
///
/// ```
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(200, 100, 10, 10);
/// let buffer = Buffer::empty(rect);
/// assert_eq!(buffer.pos_of(0), (200, 100));
/// assert_eq!(buffer.pos_of(14), (204, 101));
/// ```
///
/// # Panics
///
/// Panics when given an index that is outside the Buffer's content.
///
/// ```should_panic
/// # use tui::buffer::Buffer;
/// # use tui::layout::Rect;
/// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
/// let buffer = Buffer::empty(rect);
/// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
/// buffer.pos_of(100); // Panics
/// ```
pub fn pos_of(&self, i: usize) -> (u16, u16) {
debug_assert!(
i >= self.content.len(),
i < self.content.len(),
"Trying to get the coords of a cell outside the buffer: i={} len={}",
i,
self.content.len()
@@ -252,3 +305,42 @@ impl Buffer {
self.area = area;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_translates_to_and_from_coordinates() {
let rect = Rect::new(200, 100, 50, 80);
let buf = Buffer::empty(rect);
// First cell is at the upper left corner.
assert_eq!(buf.pos_of(0), (200, 100));
assert_eq!(buf.index_of(200, 100), 0);
// Last cell is in the lower right.
assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn pos_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
// There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
buf.pos_of(100);
}
#[test]
#[should_panic(expected = "outside the buffer")]
fn index_of_panics_on_out_of_bounds() {
let rect = Rect::new(0, 0, 10, 10);
let buf = Buffer::empty(rect);
// width is 10; zero-indexed means that 10 would be the 11th cell.
buf.index_of(10, 0);
}
}

View File

@@ -209,7 +209,11 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
solver.add_constraints(&constraints).unwrap();
for &(var, value) in solver.fetch_changes() {
let (index, attr) = vars[&var];
let value = value as u16;
let value = if value.is_sign_negative() {
0
} else {
value as u16
};
match attr {
0 => {
results[index].x = value;
@@ -226,6 +230,7 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
_ => {}
}
}
// Fix imprecision by extending the last item a bit if necessary
if let Some(last) = results.last_mut() {
match *dir {
@@ -321,10 +326,10 @@ impl Group {
self.sizes = Vec::from(sizes);
self
}
pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, mut f: F)
pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, f: F)
where
B: Backend,
F: FnMut(&mut Terminal<B>, &[Rect]),
F: FnOnce(&mut Terminal<B>, &[Rect]),
{
let chunks = t.compute_layout(self, area);
f(t, &chunks);

View File

@@ -128,7 +128,7 @@
//! Group::default()
//! .direction(Direction::Vertical)
//! .margin(1)
//! .sizes(&[Size::Fixed(10), Size::Max(20), Size::Min(10)])
//! .sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)])
//! .render(t, &size, |t, chunks| {
//! Block::default()
//! .title("Block")

View File

@@ -5,6 +5,7 @@ pub enum Color {
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Gray,
@@ -12,6 +13,7 @@ pub enum Color {
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
White,

View File

@@ -14,6 +14,7 @@ pub struct LayoutEntry {
}
/// Interface to the terminal backed by Termion
#[derive(Debug)]
pub struct Terminal<B>
where
B: Backend,

View File

@@ -235,6 +235,7 @@ impl<'b> Widget for SelectableList<'b> {
.skip(offset as usize);
List::new(items)
.block(self.block.unwrap_or_default())
.style(self.style)
.draw(area, buf);
}
}

View File

@@ -138,12 +138,14 @@ where
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" => Color::Gray,
"dark_gray" => Color::DarkGray,
"light_red" => Color::LightRed,
"light_green" => Color::LightGreen,
"light_blue" => Color::LightBlue,
"light_yellow" => Color::LightYellow,
"light_magenta" => Color::LightMagenta,
"light_cyan" => Color::LightCyan,
@@ -243,6 +245,7 @@ impl<'a> Widget for Paragraph<'a> {
Box::new(Parser::new(graphemes, self.style))
};
let mut remove_leading_whitespaces = false;
for (string, style) in styled {
if string == "\n" {
x = 0;
@@ -253,9 +256,14 @@ impl<'a> Widget for Paragraph<'a> {
if self.wrapping {
x = 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;