mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-26 14:40:30 +00:00
94877f4e7e
This allows table column widths to be adapted more and scale with the UI. The constraints are solved using the Cassowary solver. An added constraint for fitting them all in the width is added.
294 lines
10 KiB
Rust
294 lines
10 KiB
Rust
use std::io;
|
|
|
|
use tui::backend::Backend;
|
|
use tui::layout::{Constraint, Direction, Layout, Rect};
|
|
use tui::style::{Color, Modifier, Style};
|
|
use tui::widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle};
|
|
use tui::widgets::{
|
|
Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, List, Marker, Paragraph, Row,
|
|
SelectableList, Sparkline, Table, Tabs, Text, Widget,
|
|
};
|
|
use tui::{Frame, Terminal};
|
|
|
|
use crate::demo::App;
|
|
|
|
pub fn draw<B: Backend>(terminal: &mut Terminal<B>, app: &App) -> Result<(), io::Error> {
|
|
terminal.draw(|mut f| {
|
|
let chunks = Layout::default()
|
|
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
|
|
.split(f.size());
|
|
Tabs::default()
|
|
.block(Block::default().borders(Borders::ALL).title(app.title))
|
|
.titles(&app.tabs.titles)
|
|
.style(Style::default().fg(Color::Green))
|
|
.highlight_style(Style::default().fg(Color::Yellow))
|
|
.select(app.tabs.index)
|
|
.render(&mut f, chunks[0]);
|
|
match app.tabs.index {
|
|
0 => draw_first_tab(&mut f, &app, chunks[1]),
|
|
1 => draw_second_tab(&mut f, &app, chunks[1]),
|
|
_ => {}
|
|
};
|
|
})
|
|
}
|
|
|
|
fn draw_first_tab<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
|
where
|
|
B: Backend,
|
|
{
|
|
let chunks = Layout::default()
|
|
.constraints(
|
|
[
|
|
Constraint::Length(7),
|
|
Constraint::Min(7),
|
|
Constraint::Length(7),
|
|
]
|
|
.as_ref(),
|
|
)
|
|
.split(area);
|
|
draw_gauges(f, app, chunks[0]);
|
|
draw_charts(f, app, chunks[1]);
|
|
draw_text(f, chunks[2]);
|
|
}
|
|
|
|
fn draw_gauges<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
|
where
|
|
B: Backend,
|
|
{
|
|
let chunks = Layout::default()
|
|
.constraints([Constraint::Length(2), Constraint::Length(3)].as_ref())
|
|
.margin(1)
|
|
.split(area);
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.title("Graphs")
|
|
.render(f, area);
|
|
Gauge::default()
|
|
.block(Block::default().title("Gauge:"))
|
|
.style(
|
|
Style::default()
|
|
.fg(Color::Magenta)
|
|
.bg(Color::Black)
|
|
.modifier(Modifier::ITALIC | Modifier::BOLD),
|
|
)
|
|
.label(&format!("{} / 100", app.progress))
|
|
.percent(app.progress)
|
|
.render(f, chunks[0]);
|
|
Sparkline::default()
|
|
.block(Block::default().title("Sparkline:"))
|
|
.style(Style::default().fg(Color::Green))
|
|
.data(&app.sparkline.points)
|
|
.render(f, chunks[1]);
|
|
}
|
|
|
|
fn draw_charts<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
|
where
|
|
B: Backend,
|
|
{
|
|
let constraints = if app.show_chart {
|
|
vec![Constraint::Percentage(50), Constraint::Percentage(50)]
|
|
} else {
|
|
vec![Constraint::Percentage(100)]
|
|
};
|
|
let chunks = Layout::default()
|
|
.constraints(constraints)
|
|
.direction(Direction::Horizontal)
|
|
.split(area);
|
|
{
|
|
let chunks = Layout::default()
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
.split(chunks[0]);
|
|
{
|
|
let chunks = Layout::default()
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
.direction(Direction::Horizontal)
|
|
.split(chunks[0]);
|
|
SelectableList::default()
|
|
.block(Block::default().borders(Borders::ALL).title("List"))
|
|
.items(&app.tasks.items)
|
|
.select(Some(app.tasks.selected))
|
|
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::BOLD))
|
|
.highlight_symbol(">")
|
|
.render(f, chunks[0]);
|
|
let info_style = Style::default().fg(Color::White);
|
|
let warning_style = Style::default().fg(Color::Yellow);
|
|
let error_style = Style::default().fg(Color::Magenta);
|
|
let critical_style = Style::default().fg(Color::Red);
|
|
let events = app.logs.items.iter().map(|&(evt, level)| {
|
|
Text::styled(
|
|
format!("{}: {}", level, evt),
|
|
match level {
|
|
"ERROR" => error_style,
|
|
"CRITICAL" => critical_style,
|
|
"WARNING" => warning_style,
|
|
_ => info_style,
|
|
},
|
|
)
|
|
});
|
|
List::new(events)
|
|
.block(Block::default().borders(Borders::ALL).title("List"))
|
|
.render(f, chunks[1]);
|
|
}
|
|
BarChart::default()
|
|
.block(Block::default().borders(Borders::ALL).title("Bar chart"))
|
|
.data(&app.barchart)
|
|
.bar_width(3)
|
|
.bar_gap(2)
|
|
.value_style(
|
|
Style::default()
|
|
.fg(Color::Black)
|
|
.bg(Color::Green)
|
|
.modifier(Modifier::ITALIC),
|
|
)
|
|
.label_style(Style::default().fg(Color::Yellow))
|
|
.style(Style::default().fg(Color::Green))
|
|
.render(f, chunks[1]);
|
|
}
|
|
if app.show_chart {
|
|
Chart::default()
|
|
.block(
|
|
Block::default()
|
|
.title("Chart")
|
|
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
|
|
.borders(Borders::ALL),
|
|
)
|
|
.x_axis(
|
|
Axis::default()
|
|
.title("X Axis")
|
|
.style(Style::default().fg(Color::Gray))
|
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
|
.bounds(app.signals.window)
|
|
.labels(&[
|
|
&format!("{}", app.signals.window[0]),
|
|
&format!("{}", (app.signals.window[0] + app.signals.window[1]) / 2.0),
|
|
&format!("{}", app.signals.window[1]),
|
|
]),
|
|
)
|
|
.y_axis(
|
|
Axis::default()
|
|
.title("Y Axis")
|
|
.style(Style::default().fg(Color::Gray))
|
|
.labels_style(Style::default().modifier(Modifier::ITALIC))
|
|
.bounds([-20.0, 20.0])
|
|
.labels(&["-20", "0", "20"]),
|
|
)
|
|
.datasets(&[
|
|
Dataset::default()
|
|
.name("data2")
|
|
.marker(Marker::Dot)
|
|
.style(Style::default().fg(Color::Cyan))
|
|
.data(&app.signals.sin1.points),
|
|
Dataset::default()
|
|
.name("data3")
|
|
.marker(Marker::Braille)
|
|
.style(Style::default().fg(Color::Yellow))
|
|
.data(&app.signals.sin2.points),
|
|
])
|
|
.render(f, chunks[1]);
|
|
}
|
|
}
|
|
|
|
fn draw_text<B>(f: &mut Frame<B>, area: Rect)
|
|
where
|
|
B: Backend,
|
|
{
|
|
let text = [
|
|
Text::raw("This is a paragraph with several lines. You can change style your text the way you want.\n\nFox example: "),
|
|
Text::styled("under", Style::default().fg(Color::Red)),
|
|
Text::raw(" "),
|
|
Text::styled("the", Style::default().fg(Color::Green)),
|
|
Text::raw(" "),
|
|
Text::styled("rainbow", Style::default().fg(Color::Blue)),
|
|
Text::raw(".\nOh and if you didn't "),
|
|
Text::styled("notice", Style::default().modifier(Modifier::ITALIC)),
|
|
Text::raw(" you can "),
|
|
Text::styled("automatically", Style::default().modifier(Modifier::BOLD)),
|
|
Text::raw(" "),
|
|
Text::styled("wrap", Style::default().modifier(Modifier::REVERSED)),
|
|
Text::raw(" your "),
|
|
Text::styled("text", Style::default().modifier(Modifier::UNDERLINED)),
|
|
Text::raw(".\nOne more thing is that it should display unicode characters: 10€")
|
|
];
|
|
Paragraph::new(text.iter())
|
|
.block(
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.title("Footer")
|
|
.title_style(Style::default().fg(Color::Magenta).modifier(Modifier::BOLD)),
|
|
)
|
|
.wrap(true)
|
|
.render(f, area);
|
|
}
|
|
|
|
fn draw_second_tab<B>(f: &mut Frame<B>, app: &App, area: Rect)
|
|
where
|
|
B: Backend,
|
|
{
|
|
let chunks = Layout::default()
|
|
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref())
|
|
.direction(Direction::Horizontal)
|
|
.split(area);
|
|
let up_style = Style::default().fg(Color::Green);
|
|
let failure_style = Style::default()
|
|
.fg(Color::Red)
|
|
.modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT);
|
|
let header = ["Server", "Location", "Status"];
|
|
let rows = app.servers.iter().map(|s| {
|
|
let style = if s.status == "Up" {
|
|
up_style
|
|
} else {
|
|
failure_style
|
|
};
|
|
Row::StyledData(vec![s.name, s.location, s.status].into_iter(), style)
|
|
});
|
|
Table::new(header.into_iter(), rows)
|
|
.block(Block::default().title("Servers").borders(Borders::ALL))
|
|
.header_style(Style::default().fg(Color::Yellow))
|
|
.widths(&[
|
|
Constraint::Length(15),
|
|
Constraint::Length(15),
|
|
Constraint::Length(10),
|
|
])
|
|
.render(f, chunks[0]);
|
|
|
|
Canvas::default()
|
|
.block(Block::default().title("World").borders(Borders::ALL))
|
|
.paint(|ctx| {
|
|
ctx.draw(&Map {
|
|
color: Color::White,
|
|
resolution: MapResolution::High,
|
|
});
|
|
ctx.layer();
|
|
ctx.draw(&Rectangle {
|
|
rect: Rect {
|
|
x: 0,
|
|
y: 30,
|
|
width: 10,
|
|
height: 10,
|
|
},
|
|
color: Color::Yellow,
|
|
});
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
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(f, chunks[1]);
|
|
}
|