Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c38abb203 | ||
|
|
4816563452 | ||
|
|
24dc73912b | ||
|
|
f96db9c74f | ||
|
|
ef2054a45b | ||
|
|
f4052e0e71 | ||
|
|
524845cc74 | ||
|
|
4c356c5077 | ||
|
|
36dea8373f | ||
|
|
2cb823a15b | ||
|
|
169dc43565 | ||
|
|
4b53acab14 | ||
|
|
c3acac797a | ||
|
|
dd2bf0ad13 | ||
|
|
f620af1455 | ||
|
|
fcd1b7b187 | ||
|
|
d0d2f88346 | ||
|
|
c56173462a | ||
|
|
58074f23c5 | ||
|
|
f816e6bbc4 | ||
|
|
d53ecaeade | ||
|
|
299279dc2d |
29
CHANGELOG.md
29
CHANGELOG.md
@@ -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
|
||||
|
||||
18
Cargo.toml
18
Cargo.toml
@@ -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
104
examples/layout.rs
Normal 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();
|
||||
}
|
||||
@@ -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]);
|
||||
{
|
||||
|
||||
@@ -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
131
examples/user_input.rs
Normal 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();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct LayoutEntry {
|
||||
}
|
||||
|
||||
/// Interface to the terminal backed by Termion
|
||||
#[derive(Debug)]
|
||||
pub struct Terminal<B>
|
||||
where
|
||||
B: Backend,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user