mirror of
https://github.com/ratatui/ratatui.git
synced 2025-10-02 15:25:54 +00:00

Consensus is that explicit imports make it easier to understand the example code. This commit removes the prelude import from all examples and replaces it with the necessary imports, and expands other glob imports (widget::*, Constraint::*, KeyCode::*, etc.) everywhere else. Prelude glob imports not in examples are not covered by this PR. See https://github.com/ratatui-org/ratatui/issues/1150 for more details.
210 lines
7.6 KiB
Rust
210 lines
7.6 KiB
Rust
use itertools::Itertools;
|
|
use ratatui::{
|
|
buffer::Buffer,
|
|
layout::{Alignment, Constraint, Layout, Margin, Rect},
|
|
style::{Styled, Stylize},
|
|
symbols::Marker,
|
|
widgets::{
|
|
canvas::{self, Canvas, Map, MapResolution, Points},
|
|
Block, BorderType, Clear, Padding, Row, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
|
Sparkline, StatefulWidget, Table, TableState, Widget,
|
|
},
|
|
};
|
|
|
|
use crate::{RgbSwatch, THEME};
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
pub struct TracerouteTab {
|
|
row_index: usize,
|
|
}
|
|
|
|
impl TracerouteTab {
|
|
/// Select the previous row (with wrap around).
|
|
pub fn prev_row(&mut self) {
|
|
self.row_index = self.row_index.saturating_add(HOPS.len() - 1) % HOPS.len();
|
|
}
|
|
|
|
/// Select the next row (with wrap around).
|
|
pub fn next_row(&mut self) {
|
|
self.row_index = self.row_index.saturating_add(1) % HOPS.len();
|
|
}
|
|
}
|
|
|
|
impl Widget for TracerouteTab {
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
RgbSwatch.render(area, buf);
|
|
let area = area.inner(Margin {
|
|
vertical: 1,
|
|
horizontal: 2,
|
|
});
|
|
Clear.render(area, buf);
|
|
Block::new().style(THEME.content).render(area, buf);
|
|
let horizontal = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
|
let vertical = Layout::vertical([Constraint::Min(0), Constraint::Length(3)]);
|
|
let [left, map] = horizontal.areas(area);
|
|
let [hops, pings] = vertical.areas(left);
|
|
|
|
render_hops(self.row_index, hops, buf);
|
|
render_ping(self.row_index, pings, buf);
|
|
render_map(self.row_index, map, buf);
|
|
}
|
|
}
|
|
|
|
fn render_hops(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
|
let mut state = TableState::default().with_selected(Some(selected_row));
|
|
let rows = HOPS
|
|
.iter()
|
|
.map(|hop| Row::new(vec![hop.host, hop.address]))
|
|
.collect_vec();
|
|
let block = Block::new()
|
|
.padding(Padding::new(1, 1, 1, 1))
|
|
.title_alignment(Alignment::Center)
|
|
.title("Traceroute bad.horse".bold().white());
|
|
StatefulWidget::render(
|
|
Table::new(rows, [Constraint::Max(100), Constraint::Length(15)])
|
|
.header(Row::new(vec!["Host", "Address"]).set_style(THEME.traceroute.header))
|
|
.highlight_style(THEME.traceroute.selected)
|
|
.block(block),
|
|
area,
|
|
buf,
|
|
&mut state,
|
|
);
|
|
let mut scrollbar_state = ScrollbarState::default()
|
|
.content_length(HOPS.len())
|
|
.position(selected_row);
|
|
let area = Rect {
|
|
width: area.width + 1,
|
|
y: area.y + 3,
|
|
height: area.height - 4,
|
|
..area
|
|
};
|
|
Scrollbar::default()
|
|
.orientation(ScrollbarOrientation::VerticalLeft)
|
|
.begin_symbol(None)
|
|
.end_symbol(None)
|
|
.track_symbol(None)
|
|
.thumb_symbol("▌")
|
|
.render(area, buf, &mut scrollbar_state);
|
|
}
|
|
|
|
pub fn render_ping(progress: usize, area: Rect, buf: &mut Buffer) {
|
|
let mut data = [
|
|
8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 3, 3, 2, 2, 1, 1, 1, 2, 2, 3, 4, 5, 6, 7, 7, 8, 8, 8, 7,
|
|
7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 2, 4, 6, 7, 8, 8, 8, 8, 6, 4, 2, 1, 1, 1, 1, 2, 2, 2, 3,
|
|
3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7,
|
|
];
|
|
let mid = progress % data.len();
|
|
data.rotate_left(mid);
|
|
Sparkline::default()
|
|
.block(
|
|
Block::new()
|
|
.title("Ping")
|
|
.title_alignment(Alignment::Center)
|
|
.border_type(BorderType::Thick),
|
|
)
|
|
.data(&data)
|
|
.style(THEME.traceroute.ping)
|
|
.render(area, buf);
|
|
}
|
|
|
|
fn render_map(selected_row: usize, area: Rect, buf: &mut Buffer) {
|
|
let theme = THEME.traceroute.map;
|
|
let path: Option<(&Hop, &Hop)> = HOPS.iter().tuple_windows().nth(selected_row);
|
|
let map = Map {
|
|
resolution: MapResolution::High,
|
|
color: theme.color,
|
|
};
|
|
Canvas::default()
|
|
.background_color(theme.background_color)
|
|
.block(
|
|
Block::new()
|
|
.padding(Padding::new(1, 0, 1, 0))
|
|
.style(theme.style),
|
|
)
|
|
.marker(Marker::HalfBlock)
|
|
// picked to show Australia for the demo as it's the most interesting part of the map
|
|
// (and the only part with hops ;))
|
|
.x_bounds([112.0, 155.0])
|
|
.y_bounds([-46.0, -11.0])
|
|
.paint(|context| {
|
|
context.draw(&map);
|
|
if let Some(path) = path {
|
|
context.draw(&canvas::Line::new(
|
|
path.0.location.0,
|
|
path.0.location.1,
|
|
path.1.location.0,
|
|
path.1.location.1,
|
|
theme.path,
|
|
));
|
|
context.draw(&Points {
|
|
color: theme.source,
|
|
coords: &[path.0.location], // sydney
|
|
});
|
|
context.draw(&Points {
|
|
color: theme.destination,
|
|
coords: &[path.1.location], // perth
|
|
});
|
|
}
|
|
})
|
|
.render(area, buf);
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Hop {
|
|
host: &'static str,
|
|
address: &'static str,
|
|
location: (f64, f64),
|
|
}
|
|
|
|
impl Hop {
|
|
const fn new(name: &'static str, address: &'static str, location: (f64, f64)) -> Self {
|
|
Self {
|
|
host: name,
|
|
address,
|
|
location,
|
|
}
|
|
}
|
|
}
|
|
|
|
const CANBERRA: (f64, f64) = (149.1, -35.3);
|
|
const SYDNEY: (f64, f64) = (151.1, -33.9);
|
|
const MELBOURNE: (f64, f64) = (144.9, -37.8);
|
|
const PERTH: (f64, f64) = (115.9, -31.9);
|
|
const DARWIN: (f64, f64) = (130.8, -12.4);
|
|
const BRISBANE: (f64, f64) = (153.0, -27.5);
|
|
const ADELAIDE: (f64, f64) = (138.6, -34.9);
|
|
|
|
// Go traceroute bad.horse some time, it's fun. these locations are made up and don't correspond
|
|
// to the actual IP addresses (which are in Toronto, Canada).
|
|
const HOPS: &[Hop] = &[
|
|
Hop::new("home", "127.0.0.1", CANBERRA),
|
|
Hop::new("bad.horse", "162.252.205.130", SYDNEY),
|
|
Hop::new("bad.horse", "162.252.205.131", MELBOURNE),
|
|
Hop::new("bad.horse", "162.252.205.132", BRISBANE),
|
|
Hop::new("bad.horse", "162.252.205.133", SYDNEY),
|
|
Hop::new("he.rides.across.the.nation", "162.252.205.134", PERTH),
|
|
Hop::new("the.thoroughbred.of.sin", "162.252.205.135", DARWIN),
|
|
Hop::new("he.got.the.application", "162.252.205.136", BRISBANE),
|
|
Hop::new("that.you.just.sent.in", "162.252.205.137", ADELAIDE),
|
|
Hop::new("it.needs.evaluation", "162.252.205.138", DARWIN),
|
|
Hop::new("so.let.the.games.begin", "162.252.205.139", PERTH),
|
|
Hop::new("a.heinous.crime", "162.252.205.140", BRISBANE),
|
|
Hop::new("a.show.of.force", "162.252.205.141", CANBERRA),
|
|
Hop::new("a.murder.would.be.nice.of.course", "162.252.205.142", PERTH),
|
|
Hop::new("bad.horse", "162.252.205.143", MELBOURNE),
|
|
Hop::new("bad.horse", "162.252.205.144", DARWIN),
|
|
Hop::new("bad.horse", "162.252.205.145", MELBOURNE),
|
|
Hop::new("he-s.bad", "162.252.205.146", PERTH),
|
|
Hop::new("the.evil.league.of.evil", "162.252.205.147", BRISBANE),
|
|
Hop::new("is.watching.so.beware", "162.252.205.148", DARWIN),
|
|
Hop::new("the.grade.that.you.receive", "162.252.205.149", PERTH),
|
|
Hop::new("will.be.your.last.we.swear", "162.252.205.150", ADELAIDE),
|
|
Hop::new("so.make.the.bad.horse.gleeful", "162.252.205.151", SYDNEY),
|
|
Hop::new("or.he-ll.make.you.his.mare", "162.252.205.152", MELBOURNE),
|
|
Hop::new("o_o", "162.252.205.153", BRISBANE),
|
|
Hop::new("you-re.saddled.up", "162.252.205.154", DARWIN),
|
|
Hop::new("there-s.no.recourse", "162.252.205.155", PERTH),
|
|
Hop::new("it-s.hi-ho.silver", "162.252.205.156", SYDNEY),
|
|
Hop::new("signed.bad.horse", "162.252.205.157", CANBERRA),
|
|
];
|