feat: split layout from rendering

* remove layout logic from Terminal
* replace Group with Layout
* add Frame intermediate object
This commit is contained in:
Florian Dehau 2018-08-12 19:44:52 +02:00
parent cfc90ab7f6
commit 7181970a32
25 changed files with 1001 additions and 973 deletions

View File

@ -10,7 +10,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{BarChart, Block, Borders, Widget}; use tui::widgets::{BarChart, Block, Borders, Widget};
use tui::Terminal; use tui::Terminal;
@ -124,41 +124,43 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.margin(2) let chunks = Layout::default()
.sizes(&[Size::Percent(50), Size::Percent(50)]) .direction(Direction::Vertical)
.render(t, &app.size, |t, chunks| { .margin(2)
BarChart::default() .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.block(Block::default().title("Data1").borders(Borders::ALL)) .split(&app.size);
.data(&app.data) BarChart::default()
.bar_width(9) .block(Block::default().title("Data1").borders(Borders::ALL))
.style(Style::default().fg(Color::Yellow)) .data(&app.data)
.value_style(Style::default().fg(Color::Black).bg(Color::Yellow)) .bar_width(9)
.render(t, &chunks[0]); .style(Style::default().fg(Color::Yellow))
Group::default() .value_style(Style::default().fg(Color::Black).bg(Color::Yellow))
.render(&mut f, &chunks[0]);
{
let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)]) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.render(t, &chunks[1], |t, chunks| { .split(&chunks[1]);
BarChart::default() BarChart::default()
.block(Block::default().title("Data2").borders(Borders::ALL)) .block(Block::default().title("Data2").borders(Borders::ALL))
.data(&app.data) .data(&app.data)
.bar_width(5) .bar_width(5)
.bar_gap(3) .bar_gap(3)
.style(Style::default().fg(Color::Green)) .style(Style::default().fg(Color::Green))
.value_style(Style::default().bg(Color::Green).modifier(Modifier::Bold)) .value_style(Style::default().bg(Color::Green).modifier(Modifier::Bold))
.render(t, &chunks[0]); .render(&mut f, &chunks[0]);
BarChart::default() BarChart::default()
.block(Block::default().title("Data3").borders(Borders::ALL)) .block(Block::default().title("Data3").borders(Borders::ALL))
.data(&app.data) .data(&app.data)
.style(Style::default().fg(Color::Red)) .style(Style::default().fg(Color::Red))
.bar_width(7) .bar_width(7)
.bar_gap(0) .bar_gap(0)
.value_style(Style::default().bg(Color::Red)) .value_style(Style::default().bg(Color::Red))
.label_style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic)) .label_style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.render(t, &chunks[1]); .render(&mut f, &chunks[1]);
}) }
}); }
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -6,7 +6,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Widget}; use tui::widgets::{Block, Borders, Widget};
use tui::Terminal; use tui::Terminal;
@ -35,49 +35,52 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, size: &Rect) { fn draw(t: &mut Terminal<MouseBackend>, size: &Rect) {
// Wrapping block for a group {
// Just draw the block and the group on the same area and build the group let mut f = t.get_frame();
// with at least a margin of 1 // Wrapping block for a group
Block::default().borders(Borders::ALL).render(t, size); // Just draw the block and the group on the same area and build the group
Group::default() // with at least a margin of 1
.direction(Direction::Vertical) Block::default().borders(Borders::ALL).render(&mut f, size);
.margin(4) let chunks = Layout::default()
.sizes(&[Size::Percent(50), Size::Percent(50)]) .direction(Direction::Vertical)
.render(t, size, |t, chunks| { .margin(4)
Group::default() .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(size);
{
let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)]) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.render(t, &chunks[0], |t, chunks| { .split(&chunks[0]);
Block::default() Block::default()
.title("With background") .title("With background")
.title_style(Style::default().fg(Color::Yellow)) .title_style(Style::default().fg(Color::Yellow))
.style(Style::default().bg(Color::Green)) .style(Style::default().bg(Color::Green))
.render(t, &chunks[0]); .render(&mut f, &chunks[0]);
Block::default() Block::default()
.title("Styled title") .title("Styled title")
.title_style( .title_style(
Style::default() Style::default()
.fg(Color::White) .fg(Color::White)
.bg(Color::Red) .bg(Color::Red)
.modifier(Modifier::Bold), .modifier(Modifier::Bold),
) )
.render(t, &chunks[1]); .render(&mut f, &chunks[1]);
}); }
Group::default() {
let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(50), Size::Percent(50)]) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.render(t, &chunks[1], |t, chunks| { .split(&chunks[1]);
Block::default() Block::default()
.title("With borders") .title("With borders")
.borders(Borders::ALL) .borders(Borders::ALL)
.render(t, &chunks[0]); .render(&mut f, &chunks[0]);
Block::default() Block::default()
.title("With styled borders") .title("With styled borders")
.border_style(Style::default().fg(Color::Cyan)) .border_style(Style::default().fg(Color::Cyan))
.borders(Borders::LEFT | Borders::RIGHT) .borders(Borders::LEFT | Borders::RIGHT)
.render(t, &chunks[1]); .render(&mut f, &chunks[1]);
}); }
}); }
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -10,7 +10,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::Color; use tui::style::Color;
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution}; use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::widgets::{Block, Borders, Widget}; use tui::widgets::{Block, Borders, Widget};
@ -148,58 +148,60 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Horizontal) let mut f = t.get_frame();
.sizes(&[Size::Percent(50), Size::Percent(50)]) let chunks = Layout::default()
.render(t, &app.size, |t, chunks| { .direction(Direction::Horizontal)
Canvas::default() .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.block(Block::default().borders(Borders::ALL).title("World")) .split(&app.size);
.paint(|ctx| { Canvas::default()
ctx.draw(&Map { .block(Block::default().borders(Borders::ALL).title("World"))
color: Color::White, .paint(|ctx| {
resolution: MapResolution::High, ctx.draw(&Map {
}); color: Color::White,
ctx.print(app.x, -app.y, "You are here", Color::Yellow); resolution: MapResolution::High,
}) });
.x_bounds([-180.0, 180.0]) ctx.print(app.x, -app.y, "You are here", Color::Yellow);
.y_bounds([-90.0, 90.0]) })
.render(t, &chunks[0]); .x_bounds([-180.0, 180.0])
Canvas::default() .y_bounds([-90.0, 90.0])
.block(Block::default().borders(Borders::ALL).title("List")) .render(&mut f, &chunks[0]);
.paint(|ctx| { Canvas::default()
ctx.draw(&Line { .block(Block::default().borders(Borders::ALL).title("List"))
x1: f64::from(app.ball.left()), .paint(|ctx| {
y1: f64::from(app.ball.top()), ctx.draw(&Line {
x2: f64::from(app.ball.right()), x1: f64::from(app.ball.left()),
y2: f64::from(app.ball.top()), y1: f64::from(app.ball.top()),
color: Color::Yellow, x2: f64::from(app.ball.right()),
}); y2: f64::from(app.ball.top()),
ctx.draw(&Line { color: Color::Yellow,
x1: f64::from(app.ball.right()), });
y1: f64::from(app.ball.top()), ctx.draw(&Line {
x2: f64::from(app.ball.right()), x1: f64::from(app.ball.right()),
y2: f64::from(app.ball.bottom()), y1: f64::from(app.ball.top()),
color: Color::Yellow, x2: f64::from(app.ball.right()),
}); y2: f64::from(app.ball.bottom()),
ctx.draw(&Line { color: Color::Yellow,
x1: f64::from(app.ball.right()), });
y1: f64::from(app.ball.bottom()), ctx.draw(&Line {
x2: f64::from(app.ball.left()), x1: f64::from(app.ball.right()),
y2: f64::from(app.ball.bottom()), y1: f64::from(app.ball.bottom()),
color: Color::Yellow, x2: f64::from(app.ball.left()),
}); y2: f64::from(app.ball.bottom()),
ctx.draw(&Line { color: Color::Yellow,
x1: f64::from(app.ball.left()), });
y1: f64::from(app.ball.bottom()), ctx.draw(&Line {
x2: f64::from(app.ball.left()), x1: f64::from(app.ball.left()),
y2: f64::from(app.ball.top()), y1: f64::from(app.ball.bottom()),
color: Color::Yellow, x2: f64::from(app.ball.left()),
}); y2: f64::from(app.ball.top()),
}) color: Color::Yellow,
.x_bounds([10.0, 110.0]) });
.y_bounds([10.0, 110.0]) })
.render(t, &chunks[1]); .x_bounds([10.0, 110.0])
}); .y_bounds([10.0, 110.0])
.render(&mut f, &chunks[1]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -122,46 +122,49 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Chart::default() {
.block( let mut f = t.get_frame();
Block::default() Chart::default()
.block(
Block::default()
.title("Chart") .title("Chart")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold)) .title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
.x_axis( .x_axis(
Axis::default() Axis::default()
.title("X Axis") .title("X Axis")
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic)) .labels_style(Style::default().modifier(Modifier::Italic))
.bounds(app.window) .bounds(app.window)
.labels(&[ .labels(&[
&format!("{}", app.window[0]), &format!("{}", app.window[0]),
&format!("{}", (app.window[0] + app.window[1]) / 2.0), &format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[1]), &format!("{}", app.window[1]),
]), ]),
) )
.y_axis( .y_axis(
Axis::default() Axis::default()
.title("Y Axis") .title("Y Axis")
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::Italic)) .labels_style(Style::default().modifier(Modifier::Italic))
.bounds([-20.0, 20.0]) .bounds([-20.0, 20.0])
.labels(&["-20", "0", "20"]), .labels(&["-20", "0", "20"]),
) )
.datasets(&[ .datasets(&[
Dataset::default() Dataset::default()
.name("data2") .name("data2")
.marker(Marker::Dot) .marker(Marker::Dot)
.style(Style::default().fg(Color::Cyan)) .style(Style::default().fg(Color::Cyan))
.data(&app.data1), .data(&app.data1),
Dataset::default() Dataset::default()
.name("data3") .name("data3")
.marker(Marker::Braille) .marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow)) .style(Style::default().fg(Color::Yellow))
.data(&app.data2), .data(&app.data2),
]) ])
.render(t, &app.size); .render(&mut f, &app.size);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -34,6 +34,6 @@ fn main() {
let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap(); let mut terminal = Terminal::new(MouseBackend::new().unwrap()).unwrap();
let size = terminal.size().unwrap(); let size = terminal.size().unwrap();
terminal.clear().unwrap(); terminal.clear().unwrap();
Label::default().text("Test").render(&mut terminal, &size); Label::default().text("Test").render(&mut terminal.get_frame(), &size);
terminal.draw().unwrap(); terminal.draw().unwrap();
} }

View File

@ -15,12 +15,14 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution}; use tui::widgets::canvas::{Canvas, Line, Map, MapResolution};
use tui::widgets::{Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, Item, List, Marker, use tui::widgets::{
Paragraph, Row, SelectableList, Sparkline, Table, Tabs, Widget}; Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, Item, List, Marker, Paragraph, Row,
use tui::Terminal; SelectableList, Sparkline, Table, Tabs, Widget,
};
use tui::{Frame, Terminal};
use util::*; use util::*;
@ -266,178 +268,178 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> { fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.sizes(&[Size::Fixed(3), Size::Min(0)]) let chunks = Layout::default()
.render(t, &app.size, |t, chunks| { .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
Tabs::default() .split(&app.size);
.block(Block::default().borders(Borders::ALL).title("Tabs")) Tabs::default()
.titles(&app.tabs.titles) .block(Block::default().borders(Borders::ALL).title("Tabs"))
.style(Style::default().fg(Color::Green)) .titles(&app.tabs.titles)
.highlight_style(Style::default().fg(Color::Yellow)) .style(Style::default().fg(Color::Green))
.select(app.tabs.selection) .highlight_style(Style::default().fg(Color::Yellow))
.render(t, &chunks[0]); .select(app.tabs.selection)
match app.tabs.selection { .render(&mut f, &chunks[0]);
0 => { match app.tabs.selection {
draw_first_tab(t, app, &chunks[1]); 0 => {
} draw_first_tab(&mut f, app, &chunks[1]);
1 => { }
draw_second_tab(t, app, &chunks[1]); 1 => {
} draw_second_tab(&mut f, app, &chunks[1]);
_ => {} }
}; _ => {}
}); };
try!(t.draw()); }
Ok(()) t.draw()
} }
fn draw_first_tab(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) { fn draw_first_tab(f: &mut Frame<MouseBackend>, app: &App, area: &Rect) {
Group::default() let chunks = Layout::default()
.direction(Direction::Vertical) .constraints(
.sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)]) [
.render(t, area, |t, chunks| { Constraint::Length(7),
draw_gauges(t, app, &chunks[0]); Constraint::Min(7),
draw_charts(t, app, &chunks[1]); Constraint::Length(7),
draw_text(t, &chunks[2]); ].as_ref(),
}); )
.split(area);
draw_gauges(f, app, &chunks[0]);
draw_charts(f, app, &chunks[1]);
draw_text(f, &chunks[2]);
} }
fn draw_gauges(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) { fn draw_gauges(f: &mut Frame<MouseBackend>, app: &App, area: &Rect) {
let chunks = Layout::default()
.constraints([Constraint::Length(2), Constraint::Length(3)].as_ref())
.margin(1)
.split(&area);
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.title("Graphs") .title("Graphs")
.render(t, area); .render(f, area);
Group::default() Gauge::default()
.direction(Direction::Vertical) .block(Block::default().title("Gauge:"))
.margin(1) .style(
.sizes(&[Size::Fixed(2), Size::Fixed(3)]) Style::default()
.render(t, area, |t, chunks| { .fg(Color::Magenta)
Gauge::default() .bg(Color::Black)
.block(Block::default().title("Gauge:")) .modifier(Modifier::Italic),
.style( )
Style::default() .label(&format!("{} / 100", app.progress))
.fg(Color::Magenta) .percent(app.progress)
.bg(Color::Black) .render(f, &chunks[0]);
.modifier(Modifier::Italic), Sparkline::default()
) .block(Block::default().title("Sparkline:"))
.label(&format!("{} / 100", app.progress)) .style(Style::default().fg(Color::Green))
.percent(app.progress) .data(&app.data)
.render(t, &chunks[0]); .render(f, &chunks[1]);
Sparkline::default()
.block(Block::default().title("Sparkline:"))
.style(Style::default().fg(Color::Green))
.data(&app.data)
.render(t, &chunks[1]);
});
} }
fn draw_charts(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) { fn draw_charts(f: &mut Frame<MouseBackend>, app: &App, area: &Rect) {
let sizes = if app.show_chart { let constraints = if app.show_chart {
vec![Size::Percent(50), Size::Percent(50)] vec![Constraint::Percentage(50), Constraint::Percentage(50)]
} else { } else {
vec![Size::Percent(100)] vec![Constraint::Percentage(100)]
}; };
Group::default() let chunks = Layout::default()
.constraints(constraints)
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&sizes) .split(&area);
.render(t, area, |t, chunks| { {
Group::default() let chunks = Layout::default()
.direction(Direction::Vertical) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.sizes(&[Size::Percent(50), Size::Percent(50)]) .split(&chunks[0]);
.render(t, &chunks[0], |t, chunks| { {
Group::default() let chunks = Layout::default()
.direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.sizes(&[Size::Percent(50), Size::Percent(50)]) .direction(Direction::Horizontal)
.render(t, &chunks[0], |t, chunks| { .split(&chunks[0]);
SelectableList::default() SelectableList::default()
.block(Block::default().borders(Borders::ALL).title("List")) .block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items) .items(&app.items)
.select(app.selected) .select(app.selected)
.highlight_style( .highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
Style::default().fg(Color::Yellow).modifier(Modifier::Bold), .highlight_symbol(">")
) .render(f, &chunks[0]);
.highlight_symbol(">") let info_style = Style::default().fg(Color::White);
.render(t, &chunks[0]); let warning_style = Style::default().fg(Color::Yellow);
let info_style = Style::default().fg(Color::White); let error_style = Style::default().fg(Color::Magenta);
let warning_style = Style::default().fg(Color::Yellow); let critical_style = Style::default().fg(Color::Red);
let error_style = Style::default().fg(Color::Magenta); let events = app.events.iter().map(|&(evt, level)| {
let critical_style = Style::default().fg(Color::Red); Item::StyledData(
let events = app.events.iter().map(|&(evt, level)| { format!("{}: {}", level, evt),
Item::StyledData( match level {
format!("{}: {}", level, evt), "ERROR" => &error_style,
match level { "CRITICAL" => &critical_style,
"ERROR" => &error_style, "WARNING" => &warning_style,
"CRITICAL" => &critical_style, _ => &info_style,
"WARNING" => &warning_style, },
_ => &info_style, )
}, });
) List::new(events)
}); .block(Block::default().borders(Borders::ALL).title("List"))
List::new(events) .render(f, &chunks[1]);
.block(Block::default().borders(Borders::ALL).title("List")) }
.render(t, &chunks[1]); BarChart::default()
}); .block(Block::default().borders(Borders::ALL).title("Bar chart"))
BarChart::default() .data(&app.data4)
.block(Block::default().borders(Borders::ALL).title("Bar chart")) .bar_width(3)
.data(&app.data4) .bar_gap(2)
.bar_width(3) .value_style(
.bar_gap(2) Style::default()
.value_style( .fg(Color::Black)
Style::default() .bg(Color::Green)
.fg(Color::Black) .modifier(Modifier::Italic),
.bg(Color::Green) )
.modifier(Modifier::Italic), .label_style(Style::default().fg(Color::Yellow))
) .style(Style::default().fg(Color::Green))
.label_style(Style::default().fg(Color::Yellow)) .render(f, &chunks[1]);
.style(Style::default().fg(Color::Green)) }
.render(t, &chunks[1]); if app.show_chart {
}); Chart::default()
if app.show_chart { .block(
Chart::default() Block::default()
.block( .title("Chart")
Block::default() .title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold))
.title("Chart") .borders(Borders::ALL),
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::Bold)) )
.borders(Borders::ALL), .x_axis(
) Axis::default()
.x_axis( .title("X Axis")
Axis::default() .style(Style::default().fg(Color::Gray))
.title("X Axis") .labels_style(Style::default().modifier(Modifier::Italic))
.style(Style::default().fg(Color::Gray)) .bounds(app.window)
.labels_style(Style::default().modifier(Modifier::Italic)) .labels(&[
.bounds(app.window) &format!("{}", app.window[0]),
.labels(&[ &format!("{}", (app.window[0] + app.window[1]) / 2.0),
&format!("{}", app.window[0]), &format!("{}", app.window[1]),
&format!("{}", (app.window[0] + app.window[1]) / 2.0), ]),
&format!("{}", app.window[1]), )
]), .y_axis(
) Axis::default()
.y_axis( .title("Y Axis")
Axis::default() .style(Style::default().fg(Color::Gray))
.title("Y Axis") .labels_style(Style::default().modifier(Modifier::Italic))
.style(Style::default().fg(Color::Gray)) .bounds([-20.0, 20.0])
.labels_style(Style::default().modifier(Modifier::Italic)) .labels(&["-20", "0", "20"]),
.bounds([-20.0, 20.0]) )
.labels(&["-20", "0", "20"]), .datasets(&[
) Dataset::default()
.datasets(&[ .name("data2")
Dataset::default() .marker(Marker::Dot)
.name("data2") .style(Style::default().fg(Color::Cyan))
.marker(Marker::Dot) .data(&app.data2),
.style(Style::default().fg(Color::Cyan)) Dataset::default()
.data(&app.data2), .name("data3")
Dataset::default() .marker(Marker::Braille)
.name("data3") .style(Style::default().fg(Color::Yellow))
.marker(Marker::Braille) .data(&app.data3),
.style(Style::default().fg(Color::Yellow)) ])
.data(&app.data3), .render(f, &chunks[1]);
]) }
.render(t, &chunks[1]);
}
});
} }
fn draw_text(t: &mut Terminal<MouseBackend>, area: &Rect) { fn draw_text(f: &mut Frame<MouseBackend>, area: &Rect) {
Paragraph::default() Paragraph::default()
.block( .block(
Block::default() Block::default()
@ -457,61 +459,60 @@ fn draw_text(t: &mut Terminal<MouseBackend>, area: &Rect) {
it should display unicode characters properly: , ٩(-̮̮̃-̃)۶ ٩(̮̮̃̃)۶ ٩(̯͡͡)۶ \ it should display unicode characters properly: , ٩(-̮̮̃-̃)۶ ٩(̮̮̃̃)۶ ٩(̯͡͡)۶ \
٩(-̮̮̃̃).", ٩(-̮̮̃̃).",
) )
.render(t, area); .render(f, area);
} }
fn draw_second_tab(t: &mut Terminal<MouseBackend>, app: &App, area: &Rect) { fn draw_second_tab(f: &mut Frame<MouseBackend>, app: &App, area: &Rect) {
Group::default() let chunks = Layout::default()
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.sizes(&[Size::Percent(30), Size::Percent(70)]) .split(area);
.render(t, area, |t, chunks| { let up_style = Style::default().fg(Color::Green);
let up_style = Style::default().fg(Color::Green); let failure_style = Style::default().fg(Color::Red);
let failure_style = Style::default().fg(Color::Red); Table::new(
Table::new( ["Server", "Location", "Status"].into_iter(),
["Server", "Location", "Status"].into_iter(), app.servers.iter().map(|s| {
app.servers.iter().map(|s| { let style = if s.status == "Up" {
let style = if s.status == "Up" { &up_style
&up_style } else {
} else { &failure_style
&failure_style };
}; Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style)
Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style) }),
}), ).block(Block::default().title("Servers").borders(Borders::ALL))
).block(Block::default().title("Servers").borders(Borders::ALL)) .header_style(Style::default().fg(Color::Yellow))
.header_style(Style::default().fg(Color::Yellow)) .widths(&[15, 15, 10])
.widths(&[15, 15, 10]) .render(f, &chunks[0]);
.render(t, &chunks[0]);
Canvas::default() Canvas::default()
.block(Block::default().title("World").borders(Borders::ALL)) .block(Block::default().title("World").borders(Borders::ALL))
.paint(|ctx| { .paint(|ctx| {
ctx.draw(&Map { ctx.draw(&Map {
color: Color::White, color: Color::White,
resolution: MapResolution::High, resolution: MapResolution::High,
});
ctx.layer();
for (i, s1) in app.servers.iter().enumerate() {
for s2 in &app.servers[i + 1..] {
ctx.draw(&Line {
x1: s1.coords.1,
y1: s1.coords.0,
y2: s2.coords.0,
x2: s2.coords.1,
color: Color::Yellow,
}); });
ctx.layer(); }
for (i, s1) in app.servers.iter().enumerate() { }
for s2 in &app.servers[i + 1..] { for server in &app.servers {
ctx.draw(&Line { let color = if server.status == "Up" {
x1: s1.coords.1, Color::Green
y1: s1.coords.0, } else {
y2: s2.coords.0, Color::Red
x2: s2.coords.1, };
color: Color::Yellow, ctx.print(server.coords.1, server.coords.0, "X", color);
}); }
}
}
for server in &app.servers {
let color = if server.status == "Up" {
Color::Green
} else {
Color::Red
};
ctx.print(server.coords.1, server.coords.0, "X", color);
}
})
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.render(t, &chunks[1]);
}) })
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.render(f, &chunks[1]);
} }

View File

@ -10,7 +10,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Gauge, Widget}; use tui::widgets::{Block, Borders, Gauge, Widget};
use tui::Terminal; use tui::Terminal;
@ -119,39 +119,43 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.margin(2) let chunks = Layout::default()
.sizes(&[ .direction(Direction::Vertical)
Size::Percent(25), .margin(2)
Size::Percent(25), .constraints(
Size::Percent(25), [
Size::Percent(25), Constraint::Percentage(25),
]) Constraint::Percentage(25),
.render(t, &app.size, |t, chunks| { Constraint::Percentage(25),
Gauge::default() Constraint::Percentage(25),
.block(Block::default().title("Gauge1").borders(Borders::ALL)) ].as_ref(),
.style(Style::default().fg(Color::Yellow)) )
.percent(app.progress1) .split(&app.size);
.render(t, &chunks[0]); Gauge::default()
Gauge::default() .block(Block::default().title("Gauge1").borders(Borders::ALL))
.block(Block::default().title("Gauge2").borders(Borders::ALL)) .style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Magenta).bg(Color::Green)) .percent(app.progress1)
.percent(app.progress2) .render(&mut f, &chunks[0]);
.label(&format!("{}/100", app.progress2)) Gauge::default()
.render(t, &chunks[1]); .block(Block::default().title("Gauge2").borders(Borders::ALL))
Gauge::default() .style(Style::default().fg(Color::Magenta).bg(Color::Green))
.block(Block::default().title("Gauge2").borders(Borders::ALL)) .percent(app.progress2)
.style(Style::default().fg(Color::Yellow)) .label(&format!("{}/100", app.progress2))
.percent(app.progress3) .render(&mut f, &chunks[1]);
.render(t, &chunks[2]); Gauge::default()
Gauge::default() .block(Block::default().title("Gauge2").borders(Borders::ALL))
.block(Block::default().title("Gauge3").borders(Borders::ALL)) .style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic)) .percent(app.progress3)
.percent(app.progress4) .render(&mut f, &chunks[2]);
.label(&format!("{}/100", app.progress2)) Gauge::default()
.render(t, &chunks[3]); .block(Block::default().title("Gauge3").borders(Borders::ALL))
}); .style(Style::default().fg(Color::Cyan).modifier(Modifier::Italic))
.percent(app.progress4)
.label(&format!("{}/100", app.progress2))
.render(&mut f, &chunks[3]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -11,7 +11,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::widgets::{Block, Borders, Widget}; use tui::widgets::{Block, Borders, Widget};
use tui::Terminal; use tui::Terminal;
@ -86,19 +86,28 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)]) let chunks = Layout::default()
.render(t, &app.size, |t, chunks| { .direction(Direction::Vertical)
Block::default() .constraints(
.title("Block") [
.borders(Borders::ALL) Constraint::Percentage(10),
.render(t, &chunks[0]); Constraint::Percentage(80),
Block::default() Constraint::Percentage(10),
.title("Block 2") ].as_ref(),
.borders(Borders::ALL) )
.render(t, &chunks[2]); .split(&app.size);
});
Block::default()
.title("Block")
.borders(Borders::ALL)
.render(&mut f, &chunks[0]);
Block::default()
.title("Block 2")
.borders(Borders::ALL)
.render(&mut f, &chunks[2]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -10,7 +10,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Corner, Direction, Group, Rect, Size}; use tui::layout::{Constraint, Corner, Direction, Rect, Layout};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Item, List, SelectableList, Widget}; use tui::widgets::{Block, Borders, Item, List, SelectableList, Widget};
use tui::Terminal; use tui::Terminal;
@ -156,37 +156,39 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Horizontal) let mut f = t.get_frame();
.sizes(&[Size::Percent(50), Size::Percent(50)]) let chunks = Layout::default()
.render(t, &app.size, |t, chunks| { .direction(Direction::Horizontal)
let style = Style::default().fg(Color::Black).bg(Color::White); .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
SelectableList::default() .split(&app.size);
.block(Block::default().borders(Borders::ALL).title("List"))
.items(&app.items)
.select(app.selected)
.style(style)
.highlight_style(style.clone().fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(t, &chunks[0]);
{
let events = app.events.iter().map(|&(evt, level)| {
Item::StyledData(
format!("{}: {}", level, evt),
match level {
"ERROR" => &app.error_style,
"CRITICAL" => &app.critical_style,
"WARNING" => &app.warning_style,
_ => &app.info_style,
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.start_corner(Corner::BottomLeft)
.render(t, &chunks[1]);
}
});
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)
.style(style)
.highlight_style(style.clone().fg(Color::LightGreen).modifier(Modifier::Bold))
.highlight_symbol(">")
.render(&mut f, &chunks[0]);
{
let events = app.events.iter().map(|&(evt, level)| {
Item::StyledData(
format!("{}: {}", level, evt),
match level {
"ERROR" => &app.error_style,
"CRITICAL" => &app.critical_style,
"WARNING" => &app.warning_style,
_ => &app.info_style,
},
)
});
List::new(events)
.block(Block::default().borders(Borders::ALL).title("List"))
.start_corner(Corner::BottomLeft)
.render(&mut f, &chunks[1]);
}
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -6,7 +6,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Alignment, Color, Style}; use tui::style::{Alignment, Color, Style};
use tui::widgets::{Block, Paragraph, Widget}; use tui::widgets::{Block, Paragraph, Widget};
use tui::Terminal; use tui::Terminal;
@ -37,63 +37,57 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, size: &Rect) { fn draw(t: &mut Terminal<MouseBackend>, size: &Rect) {
Block::default() {
.style(Style::default().bg(Color::White)) let mut f = t.get_frame();
.render(t, size); Block::default()
.style(Style::default().bg(Color::White))
.render(&mut f, size);
Group::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(5) .margin(5)
.sizes(&[Size::Percent(30), Size::Percent(30), Size::Percent(30)]) .constraints(
.render(t, size, |t, chunks| { [
Group::default() Constraint::Percentage(30),
.direction(Direction::Horizontal) Constraint::Percentage(30),
.sizes(&[Size::Percent(100)]) Constraint::Percentage(30),
.render(t, &chunks[0], |t, chunks| { ].as_ref(),
Paragraph::default() )
.alignment(Alignment::Left) .split(size);
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(t, &chunks[0]);
});
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(100)])
.render(t, &chunks[1], |t, chunks| {
Paragraph::default()
.alignment(Alignment::Center)
.wrap(true)
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(t, &chunks[0]);
});
Group::default()
.direction(Direction::Horizontal)
.sizes(&[Size::Percent(100)])
.render(t, &chunks[2], |t, chunks| {
Paragraph::default()
.alignment(Alignment::Right)
.wrap(true)
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(t, &chunks[0]);
});
});
Paragraph::default()
.alignment(Alignment::Left)
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(&mut f, &chunks[0]);
Paragraph::default()
.alignment(Alignment::Center)
.wrap(true)
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(&mut f, &chunks[1]);
Paragraph::default()
.alignment(Alignment::Right)
.wrap(true)
.text(
"This is a line\n{fg=red This is a line}\n{bg=red This is a \
line}\n{mod=italic This is a line}\n{mod=bold This is a \
line}\n{mod=crossed_out This is a line}\n{mod=invert This is a \
line}\n{mod=underline This is a \
line}\n{bg=green;fg=yellow;mod=italic This is a line}\n",
)
.render(&mut f, &chunks[2]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -5,7 +5,7 @@ use rustbox::Key;
use std::error::Error; use std::error::Error;
use tui::backend::RustboxBackend; use tui::backend::RustboxBackend;
use tui::layout::{Direction, Group, Size}; use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Paragraph, Widget}; use tui::widgets::{Block, Borders, Paragraph, Widget};
use tui::Terminal; use tui::Terminal;
@ -30,22 +30,19 @@ fn main() {
fn draw(t: &mut Terminal<RustboxBackend>) { fn draw(t: &mut Terminal<RustboxBackend>) {
let size = t.size().unwrap(); let size = t.size().unwrap();
{
Group::default() let mut f = t.get_frame();
.direction(Direction::Vertical) Paragraph::default()
.sizes(&[Size::Percent(100)]) .block(
.render(t, &size, |t, chunks| { Block::default()
Paragraph::default() .title("Rustbox backend")
.block( .title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
Block::default() .borders(Borders::ALL)
.title("Rustbox backend") .border_style(Style::default().fg(Color::Magenta)),
.title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Magenta)),
) )
.text("It {yellow works}!") .text("It {yellow works}!")
.render(t, &chunks[0]); .render(&mut f, &size);
}); }
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -13,7 +13,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Sparkline, Widget}; use tui::widgets::{Block, Borders, Sparkline, Widget};
use tui::Terminal; use tui::Terminal;
@ -119,40 +119,48 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.margin(2) let chunks = Layout::default()
.sizes(&[Size::Fixed(3), Size::Fixed(3), Size::Fixed(7), Size::Min(0)]) .direction(Direction::Vertical)
.render(t, &app.size, |t, chunks| { .margin(2)
Sparkline::default() .constraints(
.block( [
Block::default() Constraint::Length(3),
.title("Data1") Constraint::Length(3),
.borders(Borders::LEFT | Borders::RIGHT), Constraint::Length(7),
) Constraint::Min(0),
.data(&app.data1) ].as_ref(),
.style(Style::default().fg(Color::Yellow)) )
.render(t, &chunks[0]); .split(&app.size);
Sparkline::default() Sparkline::default()
.block( .block(
Block::default() Block::default()
.title("Data2") .title("Data1")
.borders(Borders::LEFT | Borders::RIGHT), .borders(Borders::LEFT | Borders::RIGHT),
) )
.data(&app.data2) .data(&app.data1)
.style(Style::default().bg(Color::Green)) .style(Style::default().fg(Color::Yellow))
.render(t, &chunks[1]); .render(&mut f, &chunks[0]);
// Multiline Sparkline::default()
Sparkline::default() .block(
.block( Block::default()
Block::default() .title("Data2")
.title("Data3") .borders(Borders::LEFT | Borders::RIGHT),
.borders(Borders::LEFT | Borders::RIGHT), )
) .data(&app.data2)
.data(&app.data3) .style(Style::default().bg(Color::Green))
.style(Style::default().fg(Color::Red)) .render(&mut f, &chunks[1]);
.render(t, &chunks[2]); // Multiline
}); Sparkline::default()
.block(
Block::default()
.title("Data3")
.borders(Borders::LEFT | Borders::RIGHT),
)
.data(&app.data3)
.style(Style::default().fg(Color::Red))
.render(&mut f, &chunks[2]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -7,7 +7,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Row, Table, Widget}; use tui::widgets::{Block, Borders, Row, Table, Widget};
use tui::Terminal; use tui::Terminal;
@ -47,7 +47,7 @@ fn main() {
terminal.clear().unwrap(); terminal.clear().unwrap();
terminal.hide_cursor().unwrap(); terminal.hide_cursor().unwrap();
app.size = terminal.size().unwrap(); app.size = terminal.size().unwrap();
draw(&mut terminal, &app); draw(&mut terminal, &app).unwrap();
// Input // Input
let stdin = io::stdin(); let stdin = io::stdin();
@ -76,34 +76,35 @@ fn main() {
}, },
_ => {} _ => {}
}; };
draw(&mut terminal, &app); draw(&mut terminal, &app).unwrap();
} }
terminal.show_cursor().unwrap(); terminal.show_cursor().unwrap();
terminal.clear().unwrap(); terminal.clear().unwrap();
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) -> Result<(), io::Error> {
let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold); {
let normal_style = Style::default().fg(Color::White); let mut frame = t.get_frame();
let header = ["Header1", "Header2", "Header3"]; let selected_style = Style::default().fg(Color::Yellow).modifier(Modifier::Bold);
let rows = app.items.iter().enumerate().map(|(i, item)| { let normal_style = Style::default().fg(Color::White);
if i == app.selected { let header = ["Header1", "Header2", "Header3"];
Row::StyledData(item.into_iter(), &selected_style) let rows = app.items.iter().enumerate().map(|(i, item)| {
} else { if i == app.selected {
Row::StyledData(item.into_iter(), &normal_style) 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| {
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.render(t, &chunks[0]);
}); });
t.draw().unwrap(); let rects = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.margin(5)
.split(&app.size);
Table::new(header.into_iter(), rows)
.block(Block::default().borders(Borders::ALL).title("Table"))
.widths(&[10, 10, 10])
.render(&mut frame, &rects[0]);
}
t.draw()
} }

View File

@ -9,7 +9,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Tabs, Widget}; use tui::widgets::{Block, Borders, Tabs, Widget};
use tui::Terminal; use tui::Terminal;
@ -64,50 +64,51 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &mut App) { fn draw(t: &mut Terminal<MouseBackend>, app: &mut App) {
Block::default() {
.style(Style::default().bg(Color::White)) let mut f = t.get_frame();
.render(t, &app.size); let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(&app.size);
Group::default() Block::default()
.direction(Direction::Vertical) .style(Style::default().bg(Color::White))
.margin(5) .render(&mut f, &app.size);
.sizes(&[Size::Fixed(3), Size::Min(0)]) Tabs::default()
.render(t, &app.size, |t, chunks| { .block(Block::default().borders(Borders::ALL).title("Tabs"))
Tabs::default() .titles(&app.tabs.titles)
.block(Block::default().borders(Borders::ALL).title("Tabs")) .select(app.tabs.selection)
.titles(&app.tabs.titles) .style(Style::default().fg(Color::Cyan))
.select(app.tabs.selection) .highlight_style(Style::default().fg(Color::Yellow))
.style(Style::default().fg(Color::Cyan)) .render(&mut f, &chunks[0]);
.highlight_style(Style::default().fg(Color::Yellow)) match app.tabs.selection {
.render(t, &chunks[0]); 0 => {
match app.tabs.selection { Block::default()
0 => { .title("Inner 0")
Block::default() .borders(Borders::ALL)
.title("Inner 0") .render(&mut f, &chunks[1]);
.borders(Borders::ALL)
.render(t, &chunks[1]);
}
1 => {
Block::default()
.title("Inner 1")
.borders(Borders::ALL)
.render(t, &chunks[1]);
}
2 => {
Block::default()
.title("Inner 2")
.borders(Borders::ALL)
.render(t, &chunks[1]);
}
3 => {
Block::default()
.title("Inner 3")
.borders(Borders::ALL)
.render(t, &chunks[1]);
}
_ => {}
} }
}); 1 => {
Block::default()
.title("Inner 1")
.borders(Borders::ALL)
.render(&mut f, &chunks[1]);
}
2 => {
Block::default()
.title("Inner 2")
.borders(Borders::ALL)
.render(&mut f, &chunks[1]);
}
3 => {
Block::default()
.title("Inner 3")
.borders(Borders::ALL)
.render(&mut f, &chunks[1]);
}
_ => {}
}
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -20,7 +20,7 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::backend::MouseBackend; use tui::backend::MouseBackend;
use tui::layout::{Direction, Group, Rect, Size}; use tui::layout::{Constraint, Direction, Layout, Rect};
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, Item, List, Paragraph, Widget}; use tui::widgets::{Block, Borders, Item, List, Paragraph, Widget};
use tui::Terminal; use tui::Terminal;
@ -108,24 +108,26 @@ fn main() {
} }
fn draw(t: &mut Terminal<MouseBackend>, app: &App) { fn draw(t: &mut Terminal<MouseBackend>, app: &App) {
Group::default() {
.direction(Direction::Vertical) let mut f = t.get_frame();
.margin(2) let chunks = Layout::default()
.sizes(&[Size::Fixed(3), Size::Min(1)]) .direction(Direction::Vertical)
.render(t, &app.size, |t, chunks| { .margin(2)
Paragraph::default() .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.style(Style::default().fg(Color::Yellow)) .split(&app.size);
.block(Block::default().borders(Borders::ALL).title("Input")) Paragraph::default()
.text(&app.input) .style(Style::default().fg(Color::Yellow))
.render(t, &chunks[0]); .block(Block::default().borders(Borders::ALL).title("Input"))
List::new( .text(&app.input)
app.messages .render(&mut f, &chunks[0]);
.iter() List::new(
.enumerate() app.messages
.map(|(i, m)| Item::Data(format!("{}: {}", i, m))), .iter()
).block(Block::default().borders(Borders::ALL).title("Messages")) .enumerate()
.render(t, &chunks[1]); .map(|(i, m)| Item::Data(format!("{}: {}", i, m))),
}); ).block(Block::default().borders(Borders::ALL).title("Messages"))
.render(&mut f, &chunks[1]);
}
t.draw().unwrap(); t.draw().unwrap();
} }

View File

@ -183,7 +183,9 @@ impl Buffer {
/// ``` /// ```
pub fn index_of(&self, x: u16, y: u16) -> usize { pub fn index_of(&self, x: u16, y: u16) -> usize {
debug_assert!( debug_assert!(
x >= self.area.left() && x < self.area.right() && y >= self.area.top() x >= self.area.left()
&& x < self.area.right()
&& y >= self.area.top()
&& y < self.area.bottom(), && y < self.area.bottom(),
"Trying to access position outside the buffer: x={}, y={}, area={:?}", "Trying to access position outside the buffer: x={}, y={}, area={:?}",
x, x,

View File

@ -1,12 +1,10 @@
use std::cell::RefCell;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashMap; use std::collections::HashMap;
use cassowary::strength::{REQUIRED, WEAK}; use cassowary::strength::{REQUIRED, WEAK};
use cassowary::WeightedRelation::*; use cassowary::WeightedRelation::*;
use cassowary::{Constraint, Expression, Solver, Variable}; use cassowary::{Constraint as CassowaryConstraint, Expression, Solver, Variable};
use backend::Backend;
use terminal::Terminal;
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
pub enum Corner { pub enum Corner {
@ -22,6 +20,237 @@ pub enum Direction {
Vertical, Vertical,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Constraint {
// TODO: enforce range 0 - 100
Percentage(u16),
Length(u16),
Max(u16),
Min(u16),
}
// TODO: enforce constraints size once const generics has landed
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Layout {
direction: Direction,
margin: u16,
constraints: Vec<Constraint>,
}
thread_local! {
static LAYOUT_CACHE: RefCell<HashMap<(Rect, Layout), Vec<Rect>>> = RefCell::new(HashMap::new());
}
impl Default for Layout {
fn default() -> Layout {
Layout {
direction: Direction::Vertical,
margin: 0,
constraints: Vec::new(),
}
}
}
impl Layout {
pub fn constraints<C>(mut self, constraints: C) -> Layout
where
C: Into<Vec<Constraint>>,
{
self.constraints = constraints.into();
self
}
pub fn margin(mut self, margin: u16) -> Layout {
self.margin = margin;
self
}
pub fn direction(mut self, direction: Direction) -> Layout {
self.direction = direction;
self
}
/// Wrapper function around the cassowary-rs solver to be able to split a given
/// area into smaller ones based on the preferred widths or heights and the direction.
///
/// # Examples
/// ```
/// # extern crate tui;
/// # use tui::layout::{Rect, Constraint, Direction, Layout};
///
/// # fn main() {
/// let chunks = Layout::default()
/// .direction(Direction::Vertical)
/// .constraints([Constraint::Length(5), Constraint::Min(0)].as_ref())
/// .split(&Rect{x: 2, y: 2, width: 10, height: 10});
/// assert_eq!(chunks, vec![Rect{x:2, y: 2, width: 10, height: 5},
/// Rect{x: 2, y: 7, width: 10, height: 5}])
/// # }
///
/// ```
pub fn split(self, area: &Rect) -> Vec<Rect> {
// TODO: Maybe use a fixed size cache ?
LAYOUT_CACHE.with(|c| {
return c
.borrow_mut()
.entry((*area, self.clone()))
.or_insert_with(|| split(area, self))
.clone();
})
}
}
fn split(area: &Rect, layout: Layout) -> Vec<Rect> {
let mut solver = Solver::new();
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
let elements = layout
.constraints
.iter()
.map(|_| Element::new())
.collect::<Vec<Element>>();
let mut results = layout
.constraints
.iter()
.map(|_| Rect::default())
.collect::<Vec<Rect>>();
let dest_area = area.inner(layout.margin);
for (i, e) in elements.iter().enumerate() {
vars.insert(e.x, (i, 0));
vars.insert(e.y, (i, 1));
vars.insert(e.width, (i, 2));
vars.insert(e.height, (i, 3));
}
let mut ccs: Vec<CassowaryConstraint> =
Vec::with_capacity(elements.len() * 4 + layout.constraints.len() * 6);
for elt in &elements {
ccs.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
ccs.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
ccs.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
ccs.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
}
if let Some(first) = elements.first() {
ccs.push(match layout.direction {
Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
});
}
if let Some(last) = elements.last() {
ccs.push(match layout.direction {
Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
});
}
match layout.direction {
Direction::Horizontal => {
for pair in elements.windows(2) {
ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
}
for (i, size) in layout.constraints.iter().enumerate() {
ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
ccs.push(match *size {
Constraint::Length(v) => elements[i].width | EQ(WEAK) | f64::from(v),
Constraint::Percentage(v) => {
elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
}
Constraint::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
Constraint::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
});
}
}
Direction::Vertical => {
for pair in elements.windows(2) {
ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
}
for (i, size) in layout.constraints.iter().enumerate() {
ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
ccs.push(match *size {
Constraint::Length(v) => elements[i].height | EQ(WEAK) | f64::from(v),
Constraint::Percentage(v) => {
elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
}
Constraint::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
Constraint::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
});
}
}
}
solver.add_constraints(&ccs).unwrap();
for &(var, value) in solver.fetch_changes() {
let (index, attr) = vars[&var];
let value = if value.is_sign_negative() {
0
} else {
value as u16
};
match attr {
0 => {
results[index].x = value;
}
1 => {
results[index].y = value;
}
2 => {
results[index].width = value;
}
3 => {
results[index].height = value;
}
_ => {}
}
}
// Fix imprecision by extending the last item a bit if necessary
if let Some(last) = results.last_mut() {
match layout.direction {
Direction::Vertical => {
last.height = dest_area.bottom() - last.y;
}
Direction::Horizontal => {
last.width = dest_area.right() - last.x;
}
}
}
results
}
/// A container used by the solver inside split
struct Element {
x: Variable,
y: Variable,
width: Variable,
height: Variable,
}
impl Element {
fn new() -> Element {
Element {
x: Variable::new(),
y: Variable::new(),
width: Variable::new(),
height: Variable::new(),
}
}
fn left(&self) -> Variable {
self.x
}
fn top(&self) -> Variable {
self.y
}
fn right(&self) -> Expression {
self.x + self.width
}
fn bottom(&self) -> Expression {
self.y + self.height
}
}
/// A simple rectangle used in the computation of the layout and to give widgets an hint about the /// A simple rectangle used in the computation of the layout and to give widgets an hint about the
/// area they are supposed to render to. /// area they are supposed to render to.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
@ -113,233 +342,9 @@ impl Rect {
} }
pub fn intersects(&self, other: &Rect) -> bool { pub fn intersects(&self, other: &Rect) -> bool {
self.x < other.x + other.width && self.x + self.width > other.x self.x < other.x + other.width
&& self.y < other.y + other.height && self.y + self.height > other.y && self.x + self.width > other.x
} && self.y < other.y + other.height
} && self.y + self.height > other.y
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Size {
Fixed(u16),
Percent(u16),
Max(u16),
Min(u16),
}
/// Wrapper function around the cassowary-rs solver to be able to split a given
/// area into smaller ones based on the preferred widths or heights and the direction.
///
/// # Examples
/// ```
/// # extern crate tui;
/// # use tui::layout::{Rect, Size, Direction, split};
///
/// # fn main() {
/// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
/// &Direction::Vertical,
/// 0,
/// &[Size::Fixed(5), Size::Min(0)]);
/// assert_eq!(chunks, vec![Rect{x:2, y: 2, width: 10, height: 5},
/// Rect{x: 2, y: 7, width: 10, height: 5}])
/// # }
///
/// ```
pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
let mut solver = Solver::new();
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
let elements = sizes
.iter()
.map(|_| Element::new())
.collect::<Vec<Element>>();
let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
let dest_area = area.inner(margin);
for (i, e) in elements.iter().enumerate() {
vars.insert(e.x, (i, 0));
vars.insert(e.y, (i, 1));
vars.insert(e.width, (i, 2));
vars.insert(e.height, (i, 3));
}
let mut constraints: Vec<Constraint> = Vec::with_capacity(elements.len() * 4 + sizes.len() * 6);
for elt in &elements {
constraints.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
constraints.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
constraints.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
constraints.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
}
if let Some(first) = elements.first() {
constraints.push(match *dir {
Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
});
}
if let Some(last) = elements.last() {
constraints.push(match *dir {
Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
});
}
match *dir {
Direction::Horizontal => {
for pair in elements.windows(2) {
constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
}
for (i, size) in sizes.iter().enumerate() {
constraints.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
constraints.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
constraints.push(match *size {
Size::Fixed(v) => elements[i].width | EQ(WEAK) | f64::from(v),
Size::Percent(v) => {
elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
}
Size::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
Size::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
});
}
}
Direction::Vertical => {
for pair in elements.windows(2) {
constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
}
for (i, size) in sizes.iter().enumerate() {
constraints.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
constraints.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
constraints.push(match *size {
Size::Fixed(v) => elements[i].height | EQ(WEAK) | f64::from(v),
Size::Percent(v) => {
elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
}
Size::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
Size::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
});
}
}
}
solver.add_constraints(&constraints).unwrap();
for &(var, value) in solver.fetch_changes() {
let (index, attr) = vars[&var];
let value = if value.is_sign_negative() {
0
} else {
value as u16
};
match attr {
0 => {
results[index].x = value;
}
1 => {
results[index].y = value;
}
2 => {
results[index].width = value;
}
3 => {
results[index].height = value;
}
_ => {}
}
}
// Fix imprecision by extending the last item a bit if necessary
if let Some(last) = results.last_mut() {
match *dir {
Direction::Vertical => {
last.height = dest_area.bottom() - last.y;
}
Direction::Horizontal => {
last.width = dest_area.right() - last.x;
}
}
}
results
}
/// A container used by the solver inside split
struct Element {
x: Variable,
y: Variable,
width: Variable,
height: Variable,
}
impl Element {
fn new() -> Element {
Element {
x: Variable::new(),
y: Variable::new(),
width: Variable::new(),
height: Variable::new(),
}
}
fn left(&self) -> Variable {
self.x
}
fn top(&self) -> Variable {
self.y
}
fn right(&self) -> Expression {
self.x + self.width
}
fn bottom(&self) -> Expression {
self.y + self.height
}
}
/// Describes a layout and may be used to group widgets in a specific area of the terminal
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// use tui::layout::{Group, Direction, Size};
/// # fn main() {
/// Group::default()
/// .direction(Direction::Vertical)
/// .margin(0)
/// .sizes(&[Size::Percent(50), Size::Percent(50)]);
/// # }
/// ```
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct Group {
pub direction: Direction,
pub margin: u16,
pub sizes: Vec<Size>,
}
impl Default for Group {
fn default() -> Group {
Group {
direction: Direction::Horizontal,
margin: 0,
sizes: Vec::new(),
}
}
}
impl Group {
pub fn direction(&mut self, direction: Direction) -> &mut Group {
self.direction = direction;
self
}
pub fn margin(&mut self, margin: u16) -> &mut Group {
self.margin = margin;
self
}
pub fn sizes(&mut self, sizes: &[Size]) -> &mut Group {
self.sizes = Vec::from(sizes);
self
}
pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, f: F)
where
B: Backend,
F: FnOnce(&mut Terminal<B>, &[Rect]),
{
let chunks = t.compute_layout(self, area);
f(t, &chunks);
} }
} }

View File

@ -69,7 +69,7 @@
//! use tui::Terminal; //! use tui::Terminal;
//! use tui::backend::RawBackend; //! use tui::backend::RawBackend;
//! use tui::widgets::{Widget, Block, Borders}; //! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Group, Size, Direction}; //! use tui::layout::{Layout, Constraint, Direction};
//! //!
//! fn main() { //! fn main() {
//! let mut terminal = init().expect("Failed initialization"); //! let mut terminal = init().expect("Failed initialization");
@ -85,10 +85,13 @@
//! //!
//! let size = t.size()?; //! let size = t.size()?;
//! //!
//! Block::default() //! {
//! .title("Block") //! let mut f = t.get_frame();
//! .borders(Borders::ALL) //! Block::default()
//! .render(t, &size); //! .title("Block")
//! .borders(Borders::ALL)
//! .render(&mut f, &size);
//! }
//! //!
//! t.draw() //! t.draw()
//! } //! }
@ -96,10 +99,9 @@
//! //!
//! ## Layout //! ## Layout
//! //!
//! The library comes with a basic yet useful layout management object called //! The library comes with a basic yet useful layout management object called `Layout`. As you may
//! `Group`. As you may see below and in the examples, the library makes heavy //! see below and in the examples, the library makes heavy use of the builder pattern to provide
//! use of the builder pattern to provide full customization. And the `Group` //! full customization. And `Layout` is no exception:
//! object is no exception:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! extern crate tui; //! extern crate tui;
@ -109,7 +111,7 @@
//! use tui::Terminal; //! use tui::Terminal;
//! use tui::backend::RawBackend; //! use tui::backend::RawBackend;
//! use tui::widgets::{Widget, Block, Borders}; //! use tui::widgets::{Widget, Block, Borders};
//! use tui::layout::{Group, Size, Direction}; //! use tui::layout::{Layout, Constraint, Direction};
//! //!
//! fn main() { //! fn main() {
//! let mut terminal = init().expect("Failed initialization"); //! let mut terminal = init().expect("Failed initialization");
@ -125,20 +127,28 @@
//! //!
//! let size = t.size()?; //! let size = t.size()?;
//! //!
//! Group::default() //! {
//! let mut f = t.get_frame();
//! let chunks = Layout::default()
//! .direction(Direction::Vertical) //! .direction(Direction::Vertical)
//! .margin(1) //! .margin(1)
//! .sizes(&[Size::Percent(10), Size::Percent(80), Size::Percent(10)]) //! .constraints(
//! .render(t, &size, |t, chunks| { //! [
//! Block::default() //! Constraint::Percentage(10),
//! .title("Block") //! Constraint::Percentage(80),
//! .borders(Borders::ALL) //! Constraint::Percentage(10)
//! .render(t, &chunks[0]); //! ].as_ref()
//! Block::default() //! )
//! .title("Block 2") //! .split(&size);
//! .borders(Borders::ALL) //! Block::default()
//! .render(t, &chunks[2]); //! .title("Block")
//! }); //! .borders(Borders::ALL)
//! .render(&mut f, &chunks[0]);
//! Block::default()
//! .title("Block 2")
//! .borders(Borders::ALL)
//! .render(&mut f, &chunks[2]);
//! }
//! //!
//! t.draw() //! t.draw()
//! } //! }
@ -170,4 +180,4 @@ pub mod symbols;
pub mod terminal; pub mod terminal;
pub mod widgets; pub mod widgets;
pub use self::terminal::Terminal; pub use self::terminal::{Frame, Terminal};

View File

@ -1,18 +1,10 @@
use std::collections::HashMap;
use std::io; use std::io;
use backend::Backend; use backend::Backend;
use buffer::Buffer; use buffer::Buffer;
use layout::{split, Group, Rect}; use layout::Rect;
use widgets::Widget; use widgets::Widget;
/// Holds a computed layout and keeps track of its use between successive draw calls
#[derive(Debug)]
pub struct LayoutEntry {
chunks: Vec<Rect>,
hot: bool,
}
/// Interface to the terminal backed by Termion /// Interface to the terminal backed by Termion
#[derive(Debug)] #[derive(Debug)]
pub struct Terminal<B> pub struct Terminal<B>
@ -20,8 +12,6 @@ where
B: Backend, B: Backend,
{ {
backend: B, backend: B,
/// Cache to prevent the layout to be computed at each draw call
layout_cache: HashMap<(Group, Rect), LayoutEntry>,
/// Holds the results of the current and previous draw calls. The two are compared at the end /// Holds the results of the current and previous draw calls. The two are compared at the end
/// of each draw pass to output the necessary updates to the terminal /// of each draw pass to output the necessary updates to the terminal
buffers: [Buffer; 2], buffers: [Buffer; 2],
@ -29,6 +19,26 @@ where
current: usize, current: usize,
} }
pub struct Frame<'a, B: 'a>
where
B: Backend,
{
terminal: &'a mut Terminal<B>,
}
impl<'a, B> Frame<'a, B>
where
B: Backend,
{
/// Calls the draw method of a given widget on the current buffer
pub fn render<W>(&mut self, widget: &mut W, area: &Rect)
where
W: Widget,
{
widget.draw(area, self.terminal.current_buffer_mut());
}
}
impl<B> Terminal<B> impl<B> Terminal<B>
where where
B: Backend, B: Backend,
@ -36,15 +46,22 @@ where
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and /// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
/// default colors for the foreground and the background /// default colors for the foreground and the background
pub fn new(backend: B) -> Result<Terminal<B>, io::Error> { pub fn new(backend: B) -> Result<Terminal<B>, io::Error> {
let size = try!(backend.size()); let size = backend.size()?;
Ok(Terminal { Ok(Terminal {
backend: backend, backend: backend,
layout_cache: HashMap::new(),
buffers: [Buffer::empty(size), Buffer::empty(size)], buffers: [Buffer::empty(size), Buffer::empty(size)],
current: 0, current: 0,
}) })
} }
pub fn get_frame<'a>(&'a mut self) -> Frame<'a, B> {
Frame { terminal: self }
}
pub fn current_buffer_mut<'a>(&'a mut self) -> &'a mut Buffer {
&mut self.buffers[self.current]
}
pub fn backend(&self) -> &B { pub fn backend(&self) -> &B {
&self.backend &self.backend
} }
@ -53,27 +70,6 @@ where
&mut self.backend &mut self.backend
} }
/// Check if we have already computed a layout for a given group, otherwise it creates one and
/// add it to the layout cache. Moreover the function marks the queried entries so that we can
/// clean outdated ones at the end of the draw call.
pub fn compute_layout(&mut self, group: &Group, area: &Rect) -> Vec<Rect> {
let entry = self.layout_cache
.entry((group.clone(), *area))
.or_insert_with(|| {
let chunks = split(area, &group.direction, group.margin, &group.sizes);
debug!(
"New layout computed:\n* Group = {:?}\n* Chunks = {:?}",
group, chunks
);
LayoutEntry {
chunks: chunks,
hot: true,
}
});
entry.hot = true;
entry.chunks.clone()
}
/// Builds a string representing the minimal escape sequences and characters set necessary to /// Builds a string representing the minimal escape sequences and characters set necessary to
/// update the UI and writes it to stdout. /// update the UI and writes it to stdout.
pub fn flush(&mut self) -> Result<(), io::Error> { pub fn flush(&mut self) -> Result<(), io::Error> {
@ -96,21 +92,12 @@ where
self.backend.draw(content) self.backend.draw(content)
} }
/// Calls the draw method of a given widget on the current buffer
pub fn render<W>(&mut self, widget: &mut W, area: &Rect)
where
W: Widget,
{
widget.draw(area, &mut self.buffers[self.current]);
}
/// Updates the interface so that internal buffers matches the current size of the terminal. /// Updates the interface so that internal buffers matches the current size of the terminal.
/// This leads to a full redraw of the screen. /// This leads to a full redraw of the screen.
pub fn resize(&mut self, area: Rect) -> Result<(), io::Error> { pub fn resize(&mut self, area: Rect) -> Result<(), io::Error> {
self.buffers[self.current].resize(area); self.buffers[self.current].resize(area);
self.buffers[1 - self.current].resize(area);
self.buffers[1 - self.current].reset(); self.buffers[1 - self.current].reset();
self.layout_cache.clear(); self.buffers[1 - self.current].resize(area);
self.backend.clear() self.backend.clear()
} }
@ -119,20 +106,6 @@ where
// Draw to stdout // Draw to stdout
self.flush()?; self.flush()?;
// Clean layout cache
let hot = self.layout_cache
.drain()
.filter(|&(_, ref v)| v.hot)
.collect::<Vec<((Group, Rect), LayoutEntry)>>();
for (key, value) in hot {
self.layout_cache.insert(key, value);
}
for e in self.layout_cache.values_mut() {
e.hot = false;
}
// Swap buffers // Swap buffers
self.buffers[1 - self.current].reset(); self.buffers[1 - self.current].reset();
self.current = 1 - self.current; self.current = 1 - self.current;

View File

@ -123,13 +123,15 @@ impl<'a> Widget for BarChart<'a> {
self.background(&chart_area, buf, self.style.bg); self.background(&chart_area, buf, self.style.bg);
let max = self.max let max = self
.max
.unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc))); .unwrap_or_else(|| self.data.iter().fold(0, |acc, &(_, v)| max(v, acc)));
let max_index = min( let max_index = min(
(chart_area.width / (self.bar_width + self.bar_gap)) as usize, (chart_area.width / (self.bar_width + self.bar_gap)) as usize,
self.data.len(), self.data.len(),
); );
let mut data = self.data let mut data = self
.data
.iter() .iter()
.take(max_index) .take(max_index)
.map(|&(l, v)| (l, v * u64::from(chart_area.height) * 8 / max)) .map(|&(l, v)| (l, v * u64::from(chart_area.height) * 8 / max))
@ -170,7 +172,8 @@ impl<'a> Widget for BarChart<'a> {
let width = value_label.width() as u16; let width = value_label.width() as u16;
if width < self.bar_width { if width < self.bar_width {
buf.set_string( buf.set_string(
chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) chart_area.left()
+ i as u16 * (self.bar_width + self.bar_gap)
+ (self.bar_width - width) / 2, + (self.bar_width - width) / 2,
chart_area.bottom() - 2, chart_area.bottom() - 2,
value_label, value_label,

View File

@ -277,7 +277,9 @@ where
// Finally draw the labels // Finally draw the labels
let style = Style::default().bg(self.background_color); let style = Style::default().bg(self.background_color);
for label in ctx.labels.iter().filter(|l| { for label in ctx.labels.iter().filter(|l| {
!(l.x < self.x_bounds[0] || l.x > self.x_bounds[1] || l.y < self.y_bounds[0] !(l.x < self.x_bounds[0]
|| l.x > self.x_bounds[1]
|| l.y < self.y_bounds[0]
|| l.y > self.y_bounds[1]) || l.y > self.y_bounds[1])
}) { }) {
let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1) let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1)

View File

@ -428,7 +428,8 @@ where
for dataset in self.datasets { for dataset in self.datasets {
match dataset.marker { match dataset.marker {
Marker::Dot => for &(x, y) in dataset.data.iter().filter(|&&(x, y)| { Marker::Dot => for &(x, y) in dataset.data.iter().filter(|&&(x, y)| {
!(x < self.x_axis.bounds[0] || x > self.x_axis.bounds[1] !(x < self.x_axis.bounds[0]
|| x > self.x_axis.bounds[1]
|| y < self.y_axis.bounds[0] || y < self.y_axis.bounds[0]
|| y > self.y_axis.bounds[1]) || y > self.y_axis.bounds[1])
}) { }) {

View File

@ -95,7 +95,8 @@ where
self.background(&list_area, buf, self.style.bg); self.background(&list_area, buf, self.style.bg);
for (i, item) in self.items for (i, item) in self
.items
.by_ref() .by_ref()
.enumerate() .enumerate()
.take(list_area.height as usize) .take(list_area.height as usize)
@ -230,7 +231,8 @@ impl<'b> Widget for SelectableList<'b> {
}; };
// Render items // Render items
let items = self.items let items = self
.items
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, item)| { .map(|(i, item)| {

View File

@ -23,7 +23,7 @@ use backend::Backend;
use buffer::Buffer; use buffer::Buffer;
use layout::Rect; use layout::Rect;
use style::Color; use style::Color;
use terminal::Terminal; use terminal::Frame;
/// Bitflags that can be composed to set the visible borders essentially on the block widget. /// Bitflags that can be composed to set the visible borders essentially on the block widget.
bitflags! { bitflags! {
@ -57,11 +57,11 @@ pub trait Widget {
} }
} }
/// Helper method that can be chained with a widget's builder methods to render it. /// Helper method that can be chained with a widget's builder methods to render it.
fn render<B>(&mut self, t: &mut Terminal<B>, area: &Rect) fn render<B>(&mut self, f: &mut Frame<B>, area: &Rect)
where where
Self: Sized, Self: Sized,
B: Backend, B: Backend,
{ {
t.render(self, area); f.render(self, area);
} }
} }

View File

@ -86,7 +86,8 @@ impl<'a> Widget for Sparkline<'a> {
None => *self.data.iter().max().unwrap_or(&1u64), None => *self.data.iter().max().unwrap_or(&1u64),
}; };
let max_index = min(spark_area.width as usize, self.data.len()); let max_index = min(spark_area.width as usize, self.data.len());
let mut data = self.data let mut data = self
.data
.iter() .iter()
.take(max_index) .take(max_index)
.map(|e| e * u64::from(spark_area.height) * 8 / max) .map(|e| e * u64::from(spark_area.height) * 8 / max)