mirror of
https://github.com/ratatui/ratatui.git
synced 2025-10-02 15:25:54 +00:00
feat: split layout from rendering
* remove layout logic from Terminal * replace Group with Layout * add Frame intermediate object
This commit is contained in:
parent
cfc90ab7f6
commit
7181970a32
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
427
examples/demo.rs
427
examples/demo.rs
@ -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]);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
469
src/layout.rs
469
src/layout.rs
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
56
src/lib.rs
56
src/lib.rs
@ -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};
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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])
|
||||||
}) {
|
}) {
|
||||||
|
@ -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)| {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user