| use crate::demo::App; |
| use tui::{ |
| backend::Backend, |
| layout::{Constraint, Direction, Layout, Rect}, |
| style::{Color, Modifier, Style}, |
| symbols, |
| text::{Span, Spans}, |
| widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, |
| widgets::{ |
| Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, List, ListItem, Paragraph, Row, |
| Sparkline, Table, Tabs, Wrap, |
| }, |
| Frame, |
| }; |
| |
| pub fn draw<B: Backend>(f: &mut Frame<B>, app: &mut App) { |
| let chunks = Layout::default() |
| .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) |
| .split(f.size()); |
| let titles = app |
| .tabs |
| .titles |
| .iter() |
| .map(|t| Spans::from(Span::styled(*t, Style::default().fg(Color::Green)))) |
| .collect(); |
| let tabs = Tabs::new(titles) |
| .block(Block::default().borders(Borders::ALL).title(app.title)) |
| .highlight_style(Style::default().fg(Color::Yellow)) |
| .select(app.tabs.index); |
| f.render_widget(tabs, chunks[0]); |
| match app.tabs.index { |
| 0 => draw_first_tab(f, app, chunks[1]), |
| 1 => draw_second_tab(f, app, chunks[1]), |
| _ => {} |
| }; |
| } |
| |
| fn draw_first_tab<B>(f: &mut Frame<B>, app: &mut 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: &mut App, area: Rect) |
| where |
| B: Backend, |
| { |
| let chunks = Layout::default() |
| .constraints([Constraint::Length(2), Constraint::Length(3)].as_ref()) |
| .margin(1) |
| .split(area); |
| let block = Block::default().borders(Borders::ALL).title("Graphs"); |
| f.render_widget(block, area); |
| |
| let label = format!("{:.2}%", app.progress * 100.0); |
| let gauge = Gauge::default() |
| .block(Block::default().title("Gauge:")) |
| .gauge_style( |
| Style::default() |
| .fg(Color::Magenta) |
| .bg(Color::Black) |
| .add_modifier(Modifier::ITALIC | Modifier::BOLD), |
| ) |
| .label(label) |
| .ratio(app.progress); |
| f.render_widget(gauge, chunks[0]); |
| |
| let sparkline = Sparkline::default() |
| .block(Block::default().title("Sparkline:")) |
| .style(Style::default().fg(Color::Green)) |
| .data(&app.sparkline.points) |
| .bar_set(if app.enhanced_graphics { |
| symbols::bar::NINE_LEVELS |
| } else { |
| symbols::bar::THREE_LEVELS |
| }); |
| f.render_widget(sparkline, chunks[1]); |
| } |
| |
| fn draw_charts<B>(f: &mut Frame<B>, app: &mut 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]); |
| |
| // Draw tasks |
| let tasks: Vec<ListItem> = app |
| .tasks |
| .items |
| .iter() |
| .map(|i| ListItem::new(vec![Spans::from(Span::raw(*i))])) |
| .collect(); |
| let tasks = List::new(tasks) |
| .block(Block::default().borders(Borders::ALL).title("List")) |
| .highlight_style(Style::default().add_modifier(Modifier::BOLD)) |
| .highlight_symbol("> "); |
| f.render_stateful_widget(tasks, chunks[0], &mut app.tasks.state); |
| |
| // Draw logs |
| let info_style = Style::default().fg(Color::Blue); |
| 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 logs: Vec<ListItem> = app |
| .logs |
| .items |
| .iter() |
| .map(|&(evt, level)| { |
| let s = match level { |
| "ERROR" => error_style, |
| "CRITICAL" => critical_style, |
| "WARNING" => warning_style, |
| _ => info_style, |
| }; |
| let content = vec![Spans::from(vec![ |
| Span::styled(format!("{:<9}", level), s), |
| Span::raw(evt), |
| ])]; |
| ListItem::new(content) |
| }) |
| .collect(); |
| let logs = List::new(logs).block(Block::default().borders(Borders::ALL).title("List")); |
| f.render_stateful_widget(logs, chunks[1], &mut app.logs.state); |
| } |
| |
| let barchart = BarChart::default() |
| .block(Block::default().borders(Borders::ALL).title("Bar chart")) |
| .data(&app.barchart) |
| .bar_width(3) |
| .bar_gap(2) |
| .bar_set(if app.enhanced_graphics { |
| symbols::bar::NINE_LEVELS |
| } else { |
| symbols::bar::THREE_LEVELS |
| }) |
| .value_style( |
| Style::default() |
| .fg(Color::Black) |
| .bg(Color::Green) |
| .add_modifier(Modifier::ITALIC), |
| ) |
| .label_style(Style::default().fg(Color::Yellow)) |
| .bar_style(Style::default().fg(Color::Green)); |
| f.render_widget(barchart, chunks[1]); |
| } |
| if app.show_chart { |
| let x_labels = vec![ |
| Span::styled( |
| format!("{}", app.signals.window[0]), |
| Style::default().add_modifier(Modifier::BOLD), |
| ), |
| Span::raw(format!( |
| "{}", |
| (app.signals.window[0] + app.signals.window[1]) / 2.0 |
| )), |
| Span::styled( |
| format!("{}", app.signals.window[1]), |
| Style::default().add_modifier(Modifier::BOLD), |
| ), |
| ]; |
| let datasets = vec![ |
| Dataset::default() |
| .name("data2") |
| .marker(symbols::Marker::Dot) |
| .style(Style::default().fg(Color::Cyan)) |
| .data(&app.signals.sin1.points), |
| Dataset::default() |
| .name("data3") |
| .marker(if app.enhanced_graphics { |
| symbols::Marker::Braille |
| } else { |
| symbols::Marker::Dot |
| }) |
| .style(Style::default().fg(Color::Yellow)) |
| .data(&app.signals.sin2.points), |
| ]; |
| let chart = Chart::new(datasets) |
| .block( |
| Block::default() |
| .title(Span::styled( |
| "Chart", |
| Style::default() |
| .fg(Color::Cyan) |
| .add_modifier(Modifier::BOLD), |
| )) |
| .borders(Borders::ALL), |
| ) |
| .x_axis( |
| Axis::default() |
| .title("X Axis") |
| .style(Style::default().fg(Color::Gray)) |
| .bounds(app.signals.window) |
| .labels(x_labels), |
| ) |
| .y_axis( |
| Axis::default() |
| .title("Y Axis") |
| .style(Style::default().fg(Color::Gray)) |
| .bounds([-20.0, 20.0]) |
| .labels(vec![ |
| Span::styled("-20", Style::default().add_modifier(Modifier::BOLD)), |
| Span::raw("0"), |
| Span::styled("20", Style::default().add_modifier(Modifier::BOLD)), |
| ]), |
| ); |
| f.render_widget(chart, chunks[1]); |
| } |
| } |
| |
| fn draw_text<B>(f: &mut Frame<B>, area: Rect) |
| where |
| B: Backend, |
| { |
| let text = vec![ |
| Spans::from("This is a paragraph with several lines. You can change style your text the way you want"), |
| Spans::from(""), |
| Spans::from(vec![ |
| Span::from("For example: "), |
| Span::styled("under", Style::default().fg(Color::Red)), |
| Span::raw(" "), |
| Span::styled("the", Style::default().fg(Color::Green)), |
| Span::raw(" "), |
| Span::styled("rainbow", Style::default().fg(Color::Blue)), |
| Span::raw("."), |
| ]), |
| Spans::from(vec![ |
| Span::raw("Oh and if you didn't "), |
| Span::styled("notice", Style::default().add_modifier(Modifier::ITALIC)), |
| Span::raw(" you can "), |
| Span::styled("automatically", Style::default().add_modifier(Modifier::BOLD)), |
| Span::raw(" "), |
| Span::styled("wrap", Style::default().add_modifier(Modifier::REVERSED)), |
| Span::raw(" your "), |
| Span::styled("text", Style::default().add_modifier(Modifier::UNDERLINED)), |
| Span::raw(".") |
| ]), |
| Spans::from( |
| "One more thing is that it should display unicode characters: 10€" |
| ), |
| ]; |
| let block = Block::default().borders(Borders::ALL).title(Span::styled( |
| "Footer", |
| Style::default() |
| .fg(Color::Magenta) |
| .add_modifier(Modifier::BOLD), |
| )); |
| let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); |
| f.render_widget(paragraph, area); |
| } |
| |
| fn draw_second_tab<B>(f: &mut Frame<B>, app: &mut 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) |
| .add_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) |
| }); |
| let table = Table::new(header.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), |
| ]); |
| f.render_widget(table, chunks[0]); |
| |
| let map = 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 { |
| x: 0.0, |
| y: 30.0, |
| width: 10.0, |
| height: 10.0, |
| 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); |
| } |
| }) |
| .marker(if app.enhanced_graphics { |
| symbols::Marker::Braille |
| } else { |
| symbols::Marker::Dot |
| }) |
| .x_bounds([-180.0, 180.0]) |
| .y_bounds([-90.0, 90.0]); |
| f.render_widget(map, chunks[1]); |
| } |