Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(layout): new layout #139

Merged
merged 6 commits into from
Jan 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions src/display/components/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use super::TotalBandwidth;

const FIRST_HEIGHT_BREAKPOINT: u16 = 30;
const FIRST_WIDTH_BREAKPOINT: u16 = 120;
const SECOND_WIDTH_BREAKPOINT: u16 = 150;

fn top_app_and_bottom_split(rect: Rect) -> (Rect, Rect, Rect) {
let parts = ::tui::layout::Layout::default()
Expand Down Expand Up @@ -51,8 +50,8 @@ impl<'a> Layout<'a> {
fn build_two_children_layout(&self, rect: Rect) -> Vec<Rect> {
// if there are two elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
//if the space is not enough, we drop one element
self.progressive_split(rect, vec![])
// if the space is not enough, we drop one element
vec![rect]
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
Expand All @@ -63,23 +62,30 @@ impl<'a> Layout<'a> {
}

fn build_three_children_layout(&self, rect: Rect) -> Vec<Rect> {
//if there are three elements
// if there are three elements
if rect.height < FIRST_HEIGHT_BREAKPOINT && rect.width < FIRST_WIDTH_BREAKPOINT {
//if the space is not enough, we drop two elements
self.progressive_split(rect, vec![])
vec![rect]
} else if rect.height < FIRST_HEIGHT_BREAKPOINT {
// if the vertical space is not enough, we drop one element and we split vertically
self.progressive_split(rect, vec![Direction::Horizontal])
} else if rect.width < FIRST_WIDTH_BREAKPOINT {
// if the horizontal space is not enough, we drop one element and we split horizontally
self.progressive_split(rect, vec![Direction::Vertical])
} else if rect.width < SECOND_WIDTH_BREAKPOINT {
// if the horizontal space is not enough for the default layout, we display one wide element
// on top and we split horizontally the bottom
self.progressive_split(rect, vec![Direction::Vertical, Direction::Horizontal])
} else {
// default layout
self.progressive_split(rect, vec![Direction::Horizontal, Direction::Vertical])
let halves = ::tui::layout::Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(rect);
let top_quarters = ::tui::layout::Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(halves[0]);

vec![top_quarters[0], top_quarters[1], halves[1]]
}
}

Expand Down
199 changes: 157 additions & 42 deletions src/display/components/table.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ::std::collections::HashMap;
use ::std::collections::{BTreeMap, HashMap};

use ::tui::backend::Backend;
use ::tui::layout::Rect;
Expand All @@ -12,17 +12,6 @@ use crate::network::{display_connection_string, display_ip_or_host};
use ::std::net::IpAddr;
use std::iter::FromIterator;

const FIRST_WIDTH_BREAKPOINT: u16 = 50;
const SECOND_WIDTH_BREAKPOINT: u16 = 71;
const THIRD_WIDTH_BREAKPOINT: u16 = 95;

const MAX_FIRST_COLUMN_WIDTH_PERCENTAGE: u16 = 53;
const MAX_SECOND_COLUMN_WIDTH_PERCENTAGE: u16 = 21;
const MAX_THIRD_COLUMN_WIDTH_PERCENTAGE: u16 = 22;

const FIRST_COLUMN_WIDTHS: [u16; 4] = [10, 30, 40, 50];
const THIRD_COLUMN_WIDTHS: [u16; 4] = [20, 20, 20, 20];

fn display_upload_and_download(bandwidth: &impl Bandwidth) -> String {
format!(
"{} / {}",
Expand Down Expand Up @@ -50,10 +39,40 @@ fn sort_by_bandwidth<'a, T>(
list
}

pub enum ColumnCount {
Two,
Three,
}

impl ColumnCount {
pub fn as_u16(&self) -> u16 {
match &self {
ColumnCount::Two => 2,
ColumnCount::Three => 3,
}
}
}

pub struct ColumnData {
column_count: ColumnCount,
column_widths: Vec<u16>,
}

pub struct Table<'a> {
title: &'a str,
column_names: &'a [&'a str],
rows: Vec<Vec<String>>,
breakpoints: BTreeMap<u16, ColumnData>,
}

fn truncate_middle(row: &str, max_length: u16) -> String {
if row.len() as u16 > max_length {
let first_slice = &row[0..(max_length as usize / 2) - 2];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very important as the inputs are literals, but we could enforce that

  • max_length / 2 >= 2
  • row.len() >= max_length / 2 + 2

To avoid invalid indexes

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point... I had this in mind, but couldn't think about what I'd like to happen if that's the case. Do you have any ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, good question. This should only happen if the max_length is so small that the [..] itself does not fit and/or the hostname is also too small. Not sure, but maybe just as many dots as it fits, or just a dash?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this can only be a programmer error, since these values are hard-coded. Maybe just an expect so at least at runtime they know what went wrong?

let second_slice = &row[(row.len() - (max_length / 2) as usize + 2)..row.len()];
format!("{}[..]{}", first_slice, second_slice)
} else {
row.to_string()
}
}

impl<'a> Table<'a> {
Expand All @@ -76,10 +95,40 @@ impl<'a> Table<'a> {
.collect();
let connections_title = "Utilization by connection";
let connections_column_names = &["Connection", "Process", "Rate Up / Down"];
let mut breakpoints = BTreeMap::new();
breakpoints.insert(
0,
ColumnData {
column_count: ColumnCount::Two,
column_widths: vec![20, 23],
},
);
breakpoints.insert(
70,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![30, 12, 23],
},
);
breakpoints.insert(
100,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![60, 12, 23],
},
);
breakpoints.insert(
140,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![100, 12, 23],
},
);
Table {
title: connections_title,
column_names: connections_column_names,
rows: connections_rows,
breakpoints,
}
}
pub fn create_processes_table(state: &UIState) -> Self {
Expand All @@ -96,11 +145,41 @@ impl<'a> Table<'a> {
})
.collect();
let processes_title = "Utilization by process name";
let processes_column_names = &["Process", "Connection count", "Rate Up / Down"];
let processes_column_names = &["Process", "Connections", "Rate Up / Down"];
let mut breakpoints = BTreeMap::new();
breakpoints.insert(
0,
ColumnData {
column_count: ColumnCount::Two,
column_widths: vec![12, 23],
},
);
breakpoints.insert(
50,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![12, 12, 23],
},
);
breakpoints.insert(
100,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![40, 12, 23],
},
);
breakpoints.insert(
140,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![40, 12, 23],
},
);
Table {
title: processes_title,
column_names: processes_column_names,
rows: processes_rows,
breakpoints,
}
}
pub fn create_remote_addresses_table(
Expand All @@ -121,47 +200,83 @@ impl<'a> Table<'a> {
})
.collect();
let remote_addresses_title = "Utilization by remote address";
let remote_addresses_column_names =
&["Remote Address", "Connection Count", "Rate Up / Down"];
let remote_addresses_column_names = &["Remote Address", "Connections", "Rate Up / Down"];
let mut breakpoints = BTreeMap::new();
breakpoints.insert(
0,
ColumnData {
column_count: ColumnCount::Two,
column_widths: vec![12, 23],
},
);
breakpoints.insert(
70,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![30, 12, 23],
},
);
breakpoints.insert(
100,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![60, 12, 23],
},
);
breakpoints.insert(
140,
ColumnData {
column_count: ColumnCount::Three,
column_widths: vec![100, 12, 23],
},
);
Table {
title: remote_addresses_title,
column_names: remote_addresses_column_names,
rows: remote_addresses_rows,
breakpoints,
}
}
pub fn render(&self, frame: &mut Frame<impl Backend>, rect: Rect) {
// the second column is only rendered if there is enough room for it
// (over third breakpoint)
let widths = if rect.width < FIRST_WIDTH_BREAKPOINT {
vec![FIRST_COLUMN_WIDTHS[0], THIRD_COLUMN_WIDTHS[0]]
} else if rect.width < SECOND_WIDTH_BREAKPOINT {
vec![FIRST_COLUMN_WIDTHS[1], THIRD_COLUMN_WIDTHS[1]]
} else if rect.width < THIRD_WIDTH_BREAKPOINT {
vec![FIRST_COLUMN_WIDTHS[2], THIRD_COLUMN_WIDTHS[2]]
} else {
vec![
rect.width * MAX_FIRST_COLUMN_WIDTH_PERCENTAGE / 100,
rect.width * MAX_SECOND_COLUMN_WIDTH_PERCENTAGE / 100,
rect.width * MAX_THIRD_COLUMN_WIDTH_PERCENTAGE / 100 - 1,
]
};
let mut column_spacing: u16 = 0;
let mut widths = &vec![];
let mut column_count: &ColumnCount = &ColumnCount::Three;

let column_names = if rect.width < THIRD_WIDTH_BREAKPOINT {
vec![self.column_names[0], self.column_names[2]]
} else {
vec![
for (width_breakpoint, column_data) in self.breakpoints.iter() {
if *width_breakpoint < rect.width {
widths = &column_data.column_widths;
column_count = &column_data.column_count;

let total_column_width: u16 = widths.iter().sum();
if rect.width < total_column_width - column_count.as_u16() {
column_spacing = 0;
} else {
column_spacing = (rect.width - total_column_width) / column_count.as_u16();
}
}
}

let column_names = match column_count {
ColumnCount::Two => {
vec![self.column_names[0], self.column_names[2]] // always lose the middle column when needed
}
ColumnCount::Three => vec![
self.column_names[0],
self.column_names[1],
self.column_names[2],
]
],
};

let rows = self.rows.iter().map(|row| {
if rect.width < THIRD_WIDTH_BREAKPOINT {
vec![&row[0], &row[2]]
} else {
vec![&row[0], &row[1], &row[2]]
}
let rows = self.rows.iter().map(|row| match column_count {
ColumnCount::Two => vec![
truncate_middle(&row[0], widths[0]),
truncate_middle(&row[2], widths[1]),
],
ColumnCount::Three => vec![
truncate_middle(&row[0], widths[0]),
truncate_middle(&row[1], widths[1]),
truncate_middle(&row[2], widths[2]),
],
});

let table_rows = rows.map(|row| Row::StyledData(row.into_iter(), Style::default()));
Expand All @@ -171,7 +286,7 @@ impl<'a> Table<'a> {
.header_style(Style::default().fg(Color::Yellow))
.widths(&widths[..])
.style(Style::default())
.column_spacing(2)
.column_spacing(column_spacing)
.render(frame, rect);
}
}
2 changes: 1 addition & 1 deletion src/display/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ where
if !(opts.processes || opts.addresses || opts.connections) {
children = vec![
Table::create_processes_table(&self.state),
Table::create_connections_table(&self.state, &self.ip_to_host),
Table::create_remote_addresses_table(&self.state, &self.ip_to_host),
Table::create_connections_table(&self.state, &self.ip_to_host),
];
}
children
Expand Down
2 changes: 1 addition & 1 deletion src/tests/cases/snapshots/ui__basic_only_addresses.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ expression: "&terminal_draw_events_mirror[0]"
---
Total Rate Up / Down: 0Bps / 0Bps
┌Utilization by remote address───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│Remote Address Connection Count Rate Up / Down
│Remote Address Connections Rate Up / Down
│ │
│ │
│ │
Expand Down
2 changes: 1 addition & 1 deletion src/tests/cases/snapshots/ui__basic_only_connections.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ expression: "&terminal_draw_events_mirror[0]"
---
Total Rate Up / Down: 0Bps / 0Bps
┌Utilization by connection───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│Connection Process Rate Up / Down
│Connection Process Rate Up / Down
│ │
│ │
│ │
Expand Down
2 changes: 1 addition & 1 deletion src/tests/cases/snapshots/ui__basic_only_processes.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ expression: "&terminal_draw_events_mirror[0]"
---
Total Rate Up / Down: 0Bps / 0Bps
┌Utilization by process name─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│Process Connection count Rate Up / Down
│Process Connections Rate Up / Down
│ │
│ │
│ │
Expand Down
Loading