refactor: rip out trait system for drawing widgets

This rips out this weird trait system I previously used for drawing
widgets, where I implemented a trait onto the Painter struct that did
the drawing.  I have no idea what I was thinking back then.
This commit is contained in:
ClementTsang 2021-08-24 22:34:25 -04:00
parent 189be96622
commit dd7e183ec8
16 changed files with 3070 additions and 3221 deletions

View File

@ -270,6 +270,10 @@ impl AppState {
EventResult::NoRedraw
}
}
BottomEvent::Resize {
width: _,
height: _,
} => EventResult::Redraw,
BottomEvent::Clean => {
self.data_collection
.clean_data(constants::STALE_MAX_MILLISECONDS);

View File

@ -233,6 +233,12 @@ fn main() -> Result<()> {
try_drawing(&mut terminal, &mut app, &mut painter)?;
}
}
BottomEvent::Resize {
width: _,
height: _,
} => {
try_drawing(&mut terminal, &mut app, &mut painter)?;
}
BottomEvent::Clean => {
app.data_collection
.clean_data(constants::STALE_MAX_MILLISECONDS);

View File

@ -95,8 +95,6 @@ impl FromStr for ColourScheme {
/// Handles the canvas' state. TODO: [OPT] implement this.
pub struct Painter {
pub colours: CanvasColours,
height: u16,
width: u16,
styled_help_text: Vec<Spans<'static>>,
is_mac_os: bool, // FIXME: This feels out of place...
row_constraints: Vec<Constraint>,
@ -182,8 +180,6 @@ impl Painter {
let mut painter = Painter {
colours: CanvasColours::default(),
height: 0,
width: 0,
styled_help_text: Vec::default(),
is_mac_os: cfg!(target_os = "macos"),
row_constraints,
@ -313,36 +309,6 @@ impl Painter {
let terminal_height = terminal_size.height;
let terminal_width = terminal_size.width;
if (self.height == 0 && self.width == 0)
|| (self.height != terminal_height || self.width != terminal_width)
{
app_state.is_force_redraw = true;
self.height = terminal_height;
self.width = terminal_width;
}
if app_state.should_get_widget_bounds() {
// If we're force drawing, reset ALL mouse boundaries.
for widget in app_state.widget_map.values_mut() {
widget.top_left_corner = None;
widget.bottom_right_corner = None;
}
// Reset dd_dialog...
app_state.delete_dialog_state.button_positions = vec![];
// Reset battery dialog...
for battery_widget in app_state.battery_state.widget_states.values_mut() {
battery_widget.tab_click_locs = None;
}
// Reset column headers for sorting in process widget...
for proc_widget in app_state.proc_state.widget_states.values_mut() {
proc_widget.columns.column_header_y_loc = None;
proc_widget.columns.column_header_x_locs = None;
}
}
if app_state.help_dialog_state.is_showing_help {
let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3;
let border_len = terminal_height.saturating_sub(gen_help_len) / 2;
@ -461,39 +427,45 @@ impl Painter {
.constraints([Constraint::Percentage(100)])
.split(terminal_size);
match &app_state.current_widget.widget_type {
Cpu => self.draw_cpu(
Cpu => draw_cpu(
self,
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
CpuLegend => self.draw_cpu(
CpuLegend => draw_cpu(
self,
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id - 1,
),
Mem | BasicMem => self.draw_memory_graph(
Mem | BasicMem => draw_memory_graph(
self,
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
Disk => self.draw_disk_table(
Disk => draw_disk_table(
self,
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
Temp => self.draw_temp_table(
Temp => draw_temp_table(
self,
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
Net => self.draw_network_graph(
Net => draw_network_graph(
self,
&mut f,
app_state,
rect[0],
@ -508,9 +480,10 @@ impl Painter {
_ => 0,
};
self.draw_process_features(&mut f, app_state, rect[0], true, widget_id);
draw_process_features(self, &mut f, app_state, rect[0], true, widget_id);
}
Battery => self.draw_battery_display(
Battery => draw_battery_display(
self,
&mut f,
app_state,
rect[0],
@ -555,16 +528,17 @@ impl Painter {
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(vertical_chunks[1]);
self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0], 1);
self.draw_basic_memory(&mut f, app_state, middle_chunks[0], 2);
self.draw_basic_network(&mut f, app_state, middle_chunks[1], 3);
draw_basic_cpu(self, &mut f, app_state, vertical_chunks[0], 1);
draw_basic_memory(self, &mut f, app_state, middle_chunks[0], 2);
draw_basic_network(self, &mut f, app_state, middle_chunks[1], 3);
let mut later_widget_id: Option<u64> = None;
if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
let widget_id = basic_table_widget_state.currently_displayed_widget_id;
later_widget_id = Some(widget_id);
match basic_table_widget_state.currently_displayed_widget_type {
Disk => self.draw_disk_table(
Disk => draw_disk_table(
self,
&mut f,
app_state,
vertical_chunks[3],
@ -578,7 +552,8 @@ impl Painter {
ProcSort => 2,
_ => 0,
};
self.draw_process_features(
draw_process_features(
self,
&mut f,
app_state,
vertical_chunks[3],
@ -586,14 +561,16 @@ impl Painter {
wid,
);
}
Temp => self.draw_temp_table(
Temp => draw_temp_table(
self,
&mut f,
app_state,
vertical_chunks[3],
false,
widget_id,
),
Battery => self.draw_battery_display(
Battery => draw_battery_display(
self,
&mut f,
app_state,
vertical_chunks[3],
@ -605,7 +582,7 @@ impl Painter {
}
if let Some(widget_id) = later_widget_id {
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[2], widget_id);
draw_basic_table_arrows(self, &mut f, app_state, vertical_chunks[2], widget_id);
}
} else {
// Draws using the passed in (or default) layout.
@ -713,23 +690,25 @@ impl Painter {
for (widget, widget_draw_loc) in widgets.children.iter().zip(widget_draw_locs) {
match &widget.widget_type {
Empty => {}
Cpu => self.draw_cpu(f, app_state, *widget_draw_loc, widget.widget_id),
Mem => self.draw_memory_graph(f, app_state, *widget_draw_loc, widget.widget_id),
Net => self.draw_network(f, app_state, *widget_draw_loc, widget.widget_id),
Cpu => draw_cpu(self, f, app_state, *widget_draw_loc, widget.widget_id),
Mem => draw_memory_graph(self, f, app_state, *widget_draw_loc, widget.widget_id),
Net => draw_network(self, f, app_state, *widget_draw_loc, widget.widget_id),
Temp => {
self.draw_temp_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
draw_temp_table(self, f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Disk => {
self.draw_disk_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
draw_disk_table(self, f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Proc => self.draw_process_features(
Proc => draw_process_features(
self,
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
Battery => self.draw_battery_display(
Battery => draw_battery_display(
self,
f,
app_state,
*widget_draw_loc,

View File

@ -10,14 +10,14 @@ pub mod network_graph;
pub mod process_table;
pub mod temp_table;
pub use basic_table_arrows::BasicTableArrows;
pub use battery_display::BatteryDisplayWidget;
pub use cpu_basic::CpuBasicWidget;
pub use cpu_graph::CpuGraphWidget;
pub use disk_table::DiskTableWidget;
pub use mem_basic::MemBasicWidget;
pub use mem_graph::MemGraphWidget;
pub use network_basic::NetworkBasicWidget;
pub use network_graph::NetworkGraphWidget;
pub use process_table::ProcessTableWidget;
pub use temp_table::TempTableWidget;
pub use basic_table_arrows::*;
pub use battery_display::*;
pub use cpu_basic::*;
pub use cpu_graph::*;
pub use disk_table::*;
pub use mem_basic::*;
pub use mem_graph::*;
pub use network_basic::*;
pub use network_graph::*;
pub use process_table::*;
pub use temp_table::*;

View File

@ -12,154 +12,142 @@ use tui::{
widgets::{Block, Paragraph},
};
pub trait BasicTableArrows {
fn draw_basic_table_arrows<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_basic_table_arrows<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(current_table) = app_state.widget_map.get(&widget_id) {
let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type {
current_table
.right_neighbour
.map(|id| app_state.widget_map.get(&id).unwrap())
.unwrap()
} else {
current_table
};
impl BasicTableArrows for Painter {
fn draw_basic_table_arrows<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
if let Some(current_table) = app_state.widget_map.get(&widget_id) {
let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type {
let (left_table, right_table) = (
{
current_table
.left_neighbour
.map(|left_widget_id| {
app_state
.widget_map
.get(&left_widget_id)
.map(|left_widget| {
if left_widget.widget_type == BottomWidgetType::ProcSort {
left_widget
.left_neighbour
.map(|left_left_widget_id| {
app_state.widget_map.get(&left_left_widget_id).map(
|left_left_widget| &left_left_widget.widget_type,
)
})
.unwrap_or(Some(&BottomWidgetType::Temp))
.unwrap_or(&BottomWidgetType::Temp)
} else {
&left_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Temp)
})
.unwrap_or(&BottomWidgetType::Temp)
},
{
current_table
.right_neighbour
.map(|id| app_state.widget_map.get(&id).unwrap())
.unwrap()
} else {
current_table
};
.map(|right_widget_id| {
app_state
.widget_map
.get(&right_widget_id)
.map(|right_widget| {
if right_widget.widget_type == BottomWidgetType::ProcSort {
right_widget
.right_neighbour
.map(|right_right_widget_id| {
app_state.widget_map.get(&right_right_widget_id).map(
|right_right_widget| {
&right_right_widget.widget_type
},
)
})
.unwrap_or(Some(&BottomWidgetType::Disk))
.unwrap_or(&BottomWidgetType::Disk)
} else {
&right_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Disk)
})
.unwrap_or(&BottomWidgetType::Disk)
},
);
let (left_table, right_table) = (
{
current_table
.left_neighbour
.map(|left_widget_id| {
app_state
.widget_map
.get(&left_widget_id)
.map(|left_widget| {
if left_widget.widget_type == BottomWidgetType::ProcSort {
left_widget
.left_neighbour
.map(|left_left_widget_id| {
app_state.widget_map.get(&left_left_widget_id).map(
|left_left_widget| {
&left_left_widget.widget_type
},
)
})
.unwrap_or(Some(&BottomWidgetType::Temp))
.unwrap_or(&BottomWidgetType::Temp)
} else {
&left_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Temp)
})
.unwrap_or(&BottomWidgetType::Temp)
},
{
current_table
.right_neighbour
.map(|right_widget_id| {
app_state
.widget_map
.get(&right_widget_id)
.map(|right_widget| {
if right_widget.widget_type == BottomWidgetType::ProcSort {
right_widget
.right_neighbour
.map(|right_right_widget_id| {
app_state
.widget_map
.get(&right_right_widget_id)
.map(|right_right_widget| {
&right_right_widget.widget_type
})
})
.unwrap_or(Some(&BottomWidgetType::Disk))
.unwrap_or(&BottomWidgetType::Disk)
} else {
&right_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Disk)
})
.unwrap_or(&BottomWidgetType::Disk)
},
);
let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name();
let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name();
let num_spaces =
usize::from(draw_loc.width).saturating_sub(6 + left_name.len() + right_name.len());
let num_spaces =
usize::from(draw_loc.width).saturating_sub(6 + left_name.len() + right_name.len());
let left_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", left_name),
painter.colours.text_style,
)),
];
let left_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", left_name),
self.colours.text_style,
)),
];
let right_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", right_name),
painter.colours.text_style,
)),
];
let right_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", right_name),
self.colours.text_style,
)),
];
let margined_draw_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(2 + left_name.len() as u16),
Constraint::Length(num_spaces as u16),
Constraint::Length(2 + right_name.len() as u16),
])
.horizontal_margin(1)
.split(draw_loc);
let margined_draw_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(2 + left_name.len() as u16),
Constraint::Length(num_spaces as u16),
Constraint::Length(2 + right_name.len() as u16),
])
.horizontal_margin(1)
.split(draw_loc);
f.render_widget(
Paragraph::new(left_arrow_text).block(Block::default()),
margined_draw_loc[0],
);
f.render_widget(
Paragraph::new(right_arrow_text)
.block(Block::default())
.alignment(Alignment::Right),
margined_draw_loc[2],
);
f.render_widget(
Paragraph::new(left_arrow_text).block(Block::default()),
margined_draw_loc[0],
);
f.render_widget(
Paragraph::new(right_arrow_text)
.block(Block::default())
.alignment(Alignment::Right),
margined_draw_loc[2],
);
if app_state.should_get_widget_bounds() {
// Some explanations for future readers:
// - The "height" as of writing of this entire widget is 2. If it's 1, it occasionally doesn't draw.
// - As such, the buttons are only on the lower part of this 2-high widget.
// - So, we want to only check at one location, the `draw_loc.y + 1`, and that's it.
// - But why is it "+2" then? Well, it's because I have a REALLY ugly hack
// for mouse button checking, since most button checks are of the form `(draw_loc.y + draw_loc.height)`,
// and the same for the x and width. Unfortunately, if you check using >= and <=, the outer bound is
// actually too large - so, we assume all of them are one too big and check via < (see
// https://github.com/ClementTsang/bottom/pull/459 for details).
// - So in other words, to make it simple, we keep this to a standard and overshoot by one here.
if let Some(basic_table) = &mut app_state.basic_table_widget_state {
basic_table.left_tlc =
Some((margined_draw_loc[0].x, margined_draw_loc[0].y + 1));
basic_table.left_brc = Some((
margined_draw_loc[0].x + margined_draw_loc[0].width,
margined_draw_loc[0].y + 2,
));
basic_table.right_tlc =
Some((margined_draw_loc[2].x, margined_draw_loc[2].y + 1));
basic_table.right_brc = Some((
margined_draw_loc[2].x + margined_draw_loc[2].width,
margined_draw_loc[2].y + 2,
));
}
if app_state.should_get_widget_bounds() {
// Some explanations for future readers:
// - The "height" as of writing of this entire widget is 2. If it's 1, it occasionally doesn't draw.
// - As such, the buttons are only on the lower part of this 2-high widget.
// - So, we want to only check at one location, the `draw_loc.y + 1`, and that's it.
// - But why is it "+2" then? Well, it's because I have a REALLY ugly hack
// for mouse button checking, since most button checks are of the form `(draw_loc.y + draw_loc.height)`,
// and the same for the x and width. Unfortunately, if you check using >= and <=, the outer bound is
// actually too large - so, we assume all of them are one too big and check via < (see
// https://github.com/ClementTsang/bottom/pull/459 for details).
// - So in other words, to make it simple, we keep this to a standard and overshoot by one here.
if let Some(basic_table) = &mut app_state.basic_table_widget_state {
basic_table.left_tlc = Some((margined_draw_loc[0].x, margined_draw_loc[0].y + 1));
basic_table.left_brc = Some((
margined_draw_loc[0].x + margined_draw_loc[0].width,
margined_draw_loc[0].y + 2,
));
basic_table.right_tlc = Some((margined_draw_loc[2].x, margined_draw_loc[2].y + 1));
basic_table.right_brc = Some((
margined_draw_loc[2].x + margined_draw_loc[2].width,
margined_draw_loc[2].y + 2,
));
}
}
}

View File

@ -13,198 +13,184 @@ use tui::{
};
use unicode_segmentation::UnicodeSegmentation;
pub trait BatteryDisplayWidget {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
pub fn draw_battery_display<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let should_get_widget_bounds = app_state.should_get_widget_bounds();
if let Some(battery_widget_state) = app_state.battery_state.widget_states.get_mut(&widget_id) {
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
impl BatteryDisplayWidget for Painter {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
let should_get_widget_bounds = app_state.should_get_widget_bounds();
if let Some(battery_widget_state) =
app_state.battery_state.widget_states.get_mut(&widget_id)
{
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Battery ── Esc to go back ";
Spans::from(vec![
Span::styled(" Battery ".to_string(), self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Battery ── Esc to go back ";
Spans::from(vec![
Span::styled(" Battery ".to_string(), painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
])
} else {
Spans::from(Span::styled(
" Battery ".to_string(),
self.colours.widget_title_style,
))
};
border_style,
),
])
} else {
Spans::from(Span::styled(
" Battery ".to_string(),
painter.colours.widget_title_style,
))
};
let battery_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let battery_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let battery_names = app_state
.canvas_data
.battery_data
.iter()
.map(|battery| &battery.battery_name)
.collect::<Vec<_>>();
let battery_names = app_state
.canvas_data
.battery_data
.iter()
.map(|battery| &battery.battery_name)
.collect::<Vec<_>>();
let tab_draw_loc = Layout::default()
.constraints([
Constraint::Length(1),
Constraint::Length(2),
Constraint::Min(0),
])
.direction(Direction::Vertical)
.split(draw_loc)[1];
let tab_draw_loc = Layout::default()
.constraints([
Constraint::Length(1),
Constraint::Length(2),
Constraint::Min(0),
])
.direction(Direction::Vertical)
.split(draw_loc)[1];
f.render_widget(
Tabs::new(
battery_names
.iter()
.map(|name| Spans::from((*name).clone()))
.collect::<Vec<_>>(),
)
.block(Block::default())
.divider(tui::symbols::line::VERTICAL)
.style(self.colours.text_style)
.highlight_style(self.colours.currently_selected_text_style)
.select(battery_widget_state.currently_selected_battery_index),
tab_draw_loc,
f.render_widget(
Tabs::new(
battery_names
.iter()
.map(|name| Spans::from((*name).clone()))
.collect::<Vec<_>>(),
)
.block(Block::default())
.divider(tui::symbols::line::VERTICAL)
.style(painter.colours.text_style)
.highlight_style(painter.colours.currently_selected_text_style)
.select(battery_widget_state.currently_selected_battery_index),
tab_draw_loc,
);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
if let Some(battery_details) = app_state
.canvas_data
.battery_data
.get(battery_widget_state.currently_selected_battery_index)
{
// Assuming a 50/50 split in width
let bar_length = usize::from((draw_loc.width.saturating_sub(2) / 2).saturating_sub(8));
let charge_percentage = battery_details.charge_percentage;
let num_bars = calculate_basic_use_bars(charge_percentage, bar_length);
let bars = format!(
"[{}{}{:3.0}%]",
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
charge_percentage,
);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
if let Some(battery_details) = app_state
.canvas_data
.battery_data
.get(battery_widget_state.currently_selected_battery_index)
{
// Assuming a 50/50 split in width
let bar_length =
usize::from((draw_loc.width.saturating_sub(2) / 2).saturating_sub(8));
let charge_percentage = battery_details.charge_percentage;
let num_bars = calculate_basic_use_bars(charge_percentage, bar_length);
let bars = format!(
"[{}{}{:3.0}%]",
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
charge_percentage,
);
let battery_rows = vec![
Row::new(vec![
Cell::from("Charge %").style(self.colours.text_style),
Cell::from(bars).style(if charge_percentage < 10.0 {
self.colours.low_battery_colour
} else if charge_percentage < 50.0 {
self.colours.medium_battery_colour
} else {
self.colours.high_battery_colour
}),
]),
Row::new(vec!["Consumption", &battery_details.watt_consumption])
.style(self.colours.text_style),
if let Some(duration_until_full) = &battery_details.duration_until_full {
Row::new(vec!["Time to full", duration_until_full])
.style(self.colours.text_style)
} else if let Some(duration_until_empty) = &battery_details.duration_until_empty
{
Row::new(vec!["Time to empty", duration_until_empty])
.style(self.colours.text_style)
let battery_rows = vec![
Row::new(vec![
Cell::from("Charge %").style(painter.colours.text_style),
Cell::from(bars).style(if charge_percentage < 10.0 {
painter.colours.low_battery_colour
} else if charge_percentage < 50.0 {
painter.colours.medium_battery_colour
} else {
Row::new(vec!["Time to full/empty", "N/A"]).style(self.colours.text_style)
},
Row::new(vec!["Health %", &battery_details.health])
.style(self.colours.text_style),
];
painter.colours.high_battery_colour
}),
]),
Row::new(vec!["Consumption", &battery_details.watt_consumption])
.style(painter.colours.text_style),
if let Some(duration_until_full) = &battery_details.duration_until_full {
Row::new(vec!["Time to full", duration_until_full])
.style(painter.colours.text_style)
} else if let Some(duration_until_empty) = &battery_details.duration_until_empty {
Row::new(vec!["Time to empty", duration_until_empty])
.style(painter.colours.text_style)
} else {
Row::new(vec!["Time to full/empty", "N/A"]).style(painter.colours.text_style)
},
Row::new(vec!["Health %", &battery_details.health])
.style(painter.colours.text_style),
];
// Draw
f.render_widget(
Table::new(battery_rows)
.block(battery_block)
.header(Row::new(vec![""]).bottom_margin(table_gap))
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)]),
margined_draw_loc,
);
} else {
let mut contents = vec![Spans::default(); table_gap as usize];
// Draw
f.render_widget(
Table::new(battery_rows)
.block(battery_block)
.header(Row::new(vec![""]).bottom_margin(table_gap))
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)]),
margined_draw_loc,
);
} else {
let mut contents = vec![Spans::default(); table_gap as usize];
contents.push(Spans::from(Span::styled(
"No data found for this battery",
self.colours.text_style,
)));
contents.push(Spans::from(Span::styled(
"No data found for this battery",
painter.colours.text_style,
)));
f.render_widget(
Paragraph::new(contents).block(battery_block),
margined_draw_loc,
);
f.render_widget(
Paragraph::new(contents).block(battery_block),
margined_draw_loc,
);
}
if should_get_widget_bounds {
// Tab wizardry
if !battery_names.is_empty() {
let mut current_x = tab_draw_loc.x;
let current_y = tab_draw_loc.y;
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
for battery in battery_names {
// +1 because there's a space after the tab label.
let width = unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16;
tab_click_locs.push(((current_x, current_y), (current_x + width, current_y)));
// +4 because we want to go one space, then one space past to get to the '|', then 2 more
// to start at the blank space before the tab label.
current_x += width + 4;
}
battery_widget_state.tab_click_locs = Some(tab_click_locs);
}
if should_get_widget_bounds {
// Tab wizardry
if !battery_names.is_empty() {
let mut current_x = tab_draw_loc.x;
let current_y = tab_draw_loc.y;
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
for battery in battery_names {
// +1 because there's a space after the tab label.
let width = unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16;
tab_click_locs
.push(((current_x, current_y), (current_x + width, current_y)));
// +4 because we want to go one space, then one space past to get to the '|', then 2 more
// to start at the blank space before the tab label.
current_x += width + 4;
}
battery_widget_state.tab_click_locs = Some(tab_click_locs);
}
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}

View File

@ -15,192 +15,185 @@ use tui::{
widgets::{Block, Paragraph},
};
pub trait CpuBasicWidget {
fn draw_basic_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_basic_cpu<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
// Skip the first element, it's the "all" element
if app_state.canvas_data.cpu_data.len() > 1 {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data[1..];
impl CpuBasicWidget for Painter {
fn draw_basic_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
// Skip the first element, it's the "all" element
if app_state.canvas_data.cpu_data.len() > 1 {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data[1..];
// This is a bit complicated, but basically, we want to draw SOME number
// of columns to draw all CPUs. Ideally, as well, we want to not have
// to ever scroll.
// **General logic** - count number of elements in cpu_data. Then see how
// many rows and columns we have in draw_loc (-2 on both sides for border?).
// I think what we can do is try to fit in as many in one column as possible.
// If not, then add a new column.
// Then, from this, split the row space across ALL columns. From there, generate
// the desired lengths.
// This is a bit complicated, but basically, we want to draw SOME number
// of columns to draw all CPUs. Ideally, as well, we want to not have
// to ever scroll.
// **General logic** - count number of elements in cpu_data. Then see how
// many rows and columns we have in draw_loc (-2 on both sides for border?).
// I think what we can do is try to fit in as many in one column as possible.
// If not, then add a new column.
// Then, from this, split the row space across ALL columns. From there, generate
// the desired lengths.
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style),
draw_loc,
);
}
let num_cpus = cpu_data.len();
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let num_cpus = cpu_data.len();
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
if draw_loc.height > 0 {
let remaining_height = usize::from(draw_loc.height);
const REQUIRED_COLUMNS: usize = 4;
if draw_loc.height > 0 {
let remaining_height = usize::from(draw_loc.height);
const REQUIRED_COLUMNS: usize = 4;
let chunk_vec =
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
let chunks = Layout::default()
.constraints(chunk_vec)
.direction(Direction::Horizontal)
.split(draw_loc);
let chunk_vec =
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
let chunks = Layout::default()
.constraints(chunk_vec)
.direction(Direction::Horizontal)
.split(draw_loc);
const CPU_NAME_SPACE: usize = 3;
const BAR_BOUND_SPACE: usize = 2;
const PERCENTAGE_SPACE: usize = 4;
const MARGIN_SPACE: usize = 2;
const CPU_NAME_SPACE: usize = 3;
const BAR_BOUND_SPACE: usize = 2;
const PERCENTAGE_SPACE: usize = 4;
const MARGIN_SPACE: usize = 2;
const COMBINED_SPACING: usize =
CPU_NAME_SPACE + BAR_BOUND_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
const REDUCED_SPACING: usize = CPU_NAME_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
let chunk_width = chunks[0].width as usize;
const COMBINED_SPACING: usize =
CPU_NAME_SPACE + BAR_BOUND_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
const REDUCED_SPACING: usize = CPU_NAME_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
let chunk_width = chunks[0].width as usize;
// Inspired by htop.
// We do +4 as if it's too few bars in the bar length, it's kinda pointless.
let cpu_bars = if chunk_width >= COMBINED_SPACING + 4 {
let bar_length = chunk_width - COMBINED_SPACING;
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
// Inspired by htop.
// We do +4 as if it's too few bars in the bar length, it's kinda pointless.
let cpu_bars = if chunk_width >= COMBINED_SPACING + 4 {
let bar_length = chunk_width - COMBINED_SPACING;
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
let num_bars = calculate_basic_use_bars(use_percentage, bar_length);
format!(
"{:3}[{}{}{:3.0}%]",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
} else {
0.0
};
(cpu_index - 1).to_string()
}
} else {
cpu_index.to_string()
},
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else if chunk_width >= REDUCED_SPACING {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
let num_bars = calculate_basic_use_bars(use_percentage, bar_length);
format!(
"{:3}[{}{}{:3.0}%]",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
format!(
"{:3} {:3.0}%",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
} else {
(cpu_index - 1).to_string()
}
} else {
cpu_index.to_string()
},
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
format!("{:3.0}%", use_percentage.round(),)
})
.collect::<Vec<_>>()
};
let mut row_counter = num_cpus;
let mut start_index = 0;
for (itx, chunk) in chunks.iter().enumerate() {
// Explicitly check... don't want an accidental DBZ or underflow, this ensures
// to_divide is > 0
if REQUIRED_COLUMNS > itx {
let to_divide = REQUIRED_COLUMNS - itx;
let how_many_cpus = min(
remaining_height,
(row_counter / to_divide)
+ (if row_counter % to_divide == 0 { 0 } else { 1 }),
);
row_counter -= how_many_cpus;
let end_index = min(start_index + how_many_cpus, num_cpus);
let cpu_column = (start_index..end_index)
.map(|itx| {
Spans::from(Span {
content: (&cpu_bars[itx]).into(),
style: if show_avg_cpu {
if itx == 0 {
painter.colours.avg_colour_style
} else {
(cpu_index - 1).to_string()
painter.colours.cpu_colour_styles
[(itx - 1) % painter.colours.cpu_colour_styles.len()]
}
} else {
cpu_index.to_string()
painter.colours.cpu_colour_styles
[itx % painter.colours.cpu_colour_styles.len()]
},
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else if chunk_width >= REDUCED_SPACING {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
format!(
"{:3} {:3.0}%",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
} else {
(cpu_index - 1).to_string()
}
} else {
cpu_index.to_string()
},
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
format!("{:3.0}%", use_percentage.round(),)
})
.collect::<Vec<_>>()
};
let mut row_counter = num_cpus;
let mut start_index = 0;
for (itx, chunk) in chunks.iter().enumerate() {
// Explicitly check... don't want an accidental DBZ or underflow, this ensures
// to_divide is > 0
if REQUIRED_COLUMNS > itx {
let to_divide = REQUIRED_COLUMNS - itx;
let how_many_cpus = min(
remaining_height,
(row_counter / to_divide)
+ (if row_counter % to_divide == 0 { 0 } else { 1 }),
);
row_counter -= how_many_cpus;
let end_index = min(start_index + how_many_cpus, num_cpus);
let cpu_column = (start_index..end_index)
.map(|itx| {
Spans::from(Span {
content: (&cpu_bars[itx]).into(),
style: if show_avg_cpu {
if itx == 0 {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles
[(itx - 1) % self.colours.cpu_colour_styles.len()]
}
} else {
self.colours.cpu_colour_styles
[itx % self.colours.cpu_colour_styles.len()]
},
})
})
.collect::<Vec<_>>();
})
.collect::<Vec<_>>();
start_index += how_many_cpus;
start_index += how_many_cpus;
let margined_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(*chunk)[0];
let margined_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(*chunk)[0];
f.render_widget(
Paragraph::new(cpu_column).block(Block::default()),
margined_loc,
);
}
f.render_widget(
Paragraph::new(cpu_column).block(Block::default()),
margined_loc,
);
}
}
}
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -32,253 +32,245 @@ static CPU_LEGEND_HEADER_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
.collect::<Vec<_>>()
});
pub trait CpuGraphWidget {
fn draw_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
fn draw_cpu_legend<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_cpu<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if draw_loc.width as f64 * 0.15 <= 6.0 {
// Skip drawing legend
if app_state.current_widget.widget_id == (widget_id + 1) {
if app_state.app_config_fields.left_legend {
app_state.move_widget_selection(&WidgetDirection::Right);
} else {
app_state.move_widget_selection(&WidgetDirection::Left);
}
}
draw_cpu_graph(painter, f, app_state, draw_loc, widget_id);
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
cpu_widget_state.is_legend_hidden = true;
}
impl CpuGraphWidget for Painter {
fn draw_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
if draw_loc.width as f64 * 0.15 <= 6.0 {
// Skip drawing legend
if app_state.current_widget.widget_id == (widget_id + 1) {
if app_state.app_config_fields.left_legend {
app_state.move_widget_selection(&WidgetDirection::Right);
} else {
app_state.move_widget_selection(&WidgetDirection::Left);
}
}
self.draw_cpu_graph(f, app_state, draw_loc, widget_id);
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
cpu_widget_state.is_legend_hidden = true;
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(bottom_widget) = app_state.widget_map.get_mut(&widget_id) {
bottom_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
bottom_widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(bottom_widget) = app_state.widget_map.get_mut(&widget_id) {
bottom_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
bottom_widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
} else {
let (graph_index, legend_index, constraints) = if app_state.app_config_fields.left_legend {
(
1,
0,
[Constraint::Percentage(15), Constraint::Percentage(85)],
)
} else {
let (graph_index, legend_index, constraints) =
if app_state.app_config_fields.left_legend {
(
1,
0,
[Constraint::Percentage(15), Constraint::Percentage(85)],
)
} else {
(
0,
1,
[Constraint::Percentage(85), Constraint::Percentage(15)],
)
};
(
0,
1,
[Constraint::Percentage(85), Constraint::Percentage(15)],
)
};
let partitioned_draw_loc = Layout::default()
.margin(0)
.direction(Direction::Horizontal)
.constraints(constraints)
.split(draw_loc);
let partitioned_draw_loc = Layout::default()
.margin(0)
.direction(Direction::Horizontal)
.constraints(constraints)
.split(draw_loc);
self.draw_cpu_graph(f, app_state, partitioned_draw_loc[graph_index], widget_id);
self.draw_cpu_legend(
f,
app_state,
partitioned_draw_loc[legend_index],
widget_id + 1,
);
draw_cpu_graph(
painter,
f,
app_state,
partitioned_draw_loc[graph_index],
widget_id,
);
draw_cpu_legend(
painter,
f,
app_state,
partitioned_draw_loc[legend_index],
widget_id + 1,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(cpu_widget) = app_state.widget_map.get_mut(&widget_id) {
cpu_widget.top_left_corner = Some((
partitioned_draw_loc[graph_index].x,
partitioned_draw_loc[graph_index].y,
));
cpu_widget.bottom_right_corner = Some((
partitioned_draw_loc[graph_index].x
+ partitioned_draw_loc[graph_index].width,
partitioned_draw_loc[graph_index].y
+ partitioned_draw_loc[graph_index].height,
));
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(cpu_widget) = app_state.widget_map.get_mut(&widget_id) {
cpu_widget.top_left_corner = Some((
partitioned_draw_loc[graph_index].x,
partitioned_draw_loc[graph_index].y,
));
cpu_widget.bottom_right_corner = Some((
partitioned_draw_loc[graph_index].x + partitioned_draw_loc[graph_index].width,
partitioned_draw_loc[graph_index].y + partitioned_draw_loc[graph_index].height,
));
}
if let Some(legend_widget) = app_state.widget_map.get_mut(&(widget_id + 1)) {
legend_widget.top_left_corner = Some((
partitioned_draw_loc[legend_index].x,
partitioned_draw_loc[legend_index].y,
));
legend_widget.bottom_right_corner = Some((
partitioned_draw_loc[legend_index].x
+ partitioned_draw_loc[legend_index].width,
partitioned_draw_loc[legend_index].y
+ partitioned_draw_loc[legend_index].height,
));
}
if let Some(legend_widget) = app_state.widget_map.get_mut(&(widget_id + 1)) {
legend_widget.top_left_corner = Some((
partitioned_draw_loc[legend_index].x,
partitioned_draw_loc[legend_index].y,
));
legend_widget.bottom_right_corner = Some((
partitioned_draw_loc[legend_index].x + partitioned_draw_loc[legend_index].width,
partitioned_draw_loc[legend_index].y
+ partitioned_draw_loc[legend_index].height,
));
}
}
}
}
fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
fn draw_cpu_graph<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let display_time_labels = vec![
Span::styled(
format!("{}s", cpu_widget_state.current_display_time / 1000),
self.colours.graph_style,
),
Span::styled("0s".to_string(), self.colours.graph_style),
];
let display_time_labels = vec![
Span::styled(
format!("{}s", cpu_widget_state.current_display_time / 1000),
painter.colours.graph_style,
),
Span::styled("0s".to_string(), painter.colours.graph_style),
];
let y_axis_labels = vec![
Span::styled(" 0%", self.colours.graph_style),
Span::styled("100%", self.colours.graph_style),
];
let y_axis_labels = vec![
Span::styled(" 0%", painter.colours.graph_style),
Span::styled("100%", painter.colours.graph_style),
];
let time_start = -(cpu_widget_state.current_display_time as f64);
let time_start = -(cpu_widget_state.current_display_time as f64);
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& cpu_widget_state.autohide_timer.is_none())
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& cpu_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = cpu_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = cpu_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
} else {
cpu_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(self.colours.graph_style)
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
} else {
cpu_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
let y_axis = Axis::default()
.style(self.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_labels);
let y_axis = Axis::default()
.style(painter.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_labels);
let use_dot = app_state.app_config_fields.use_dot;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
let use_dot = app_state.app_config_fields.use_dot;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
let interpolated_cpu_points = cpu_data
.iter_mut()
.enumerate()
.map(|(itx, cpu)| {
let to_show = if current_scroll_position == ALL_POSITION {
true
} else {
itx == current_scroll_position
};
let interpolated_cpu_points = cpu_data
.iter_mut()
.enumerate()
.map(|(itx, cpu)| {
let to_show = if current_scroll_position == ALL_POSITION {
true
} else {
itx == current_scroll_position
};
if to_show {
if let Some(end_pos) = cpu
.cpu_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = cpu.cpu_data.get(start_pos);
let inside_point = cpu.cpu_data.get(end_pos);
if to_show {
if let Some(end_pos) = cpu
.cpu_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = cpu.cpu_data.get(start_pos);
let inside_point = cpu.cpu_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) =
(outside_point, inside_point)
{
let old = *outside_point;
if let (Some(outside_point), Some(inside_point)) =
(outside_point, inside_point)
{
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = cpu.cpu_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
if let Some(to_replace) = cpu.cpu_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Point somehow doesn't exist in our data
None // Failed to get mutable reference.
}
} else {
None // Point is already "leftmost", no need to interpolate.
None // Point somehow doesn't exist in our data
}
} else {
None // There is no point.
None // Point is already "leftmost", no need to interpolate.
}
} else {
None
None // There is no point.
}
})
.collect::<Vec<_>>();
} else {
None
}
})
.collect::<Vec<_>>();
let dataset_vector: Vec<Dataset<'_>> = if current_scroll_position == ALL_POSITION {
cpu_data
.iter()
.enumerate()
.rev()
.map(|(itx, cpu)| {
Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && itx == AVG_POSITION {
self.colours.avg_colour_style
} else if itx == ALL_POSITION {
self.colours.all_colour_style
} else {
self.colours.cpu_colour_styles[(itx - 1 // Because of the all position
let dataset_vector: Vec<Dataset<'_>> = if current_scroll_position == ALL_POSITION {
cpu_data
.iter()
.enumerate()
.rev()
.map(|(itx, cpu)| {
Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && itx == AVG_POSITION {
painter.colours.avg_colour_style
} else if itx == ALL_POSITION {
painter.colours.all_colour_style
} else {
painter.colours.cpu_colour_styles[(itx - 1 // Because of the all position
- (if show_avg_cpu {
AVG_POSITION
} else {
0
}))
% self.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)
})
.collect()
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
vec![Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && current_scroll_position == AVG_POSITION {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles[(cpu_widget_state
% painter.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)
})
.collect()
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
vec![Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && current_scroll_position == AVG_POSITION {
painter.colours.avg_colour_style
} else {
painter.colours.cpu_colour_styles[(cpu_widget_state
.scroll_state
.current_scroll_position
- 1 // Because of the all position
@ -287,249 +279,249 @@ impl CpuGraphWidget for Painter {
} else {
0
}))
% self.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)]
} else {
vec![]
};
% painter.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)]
} else {
vec![]
};
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = if cfg!(target_family = "unix") {
let load_avg = app_state.canvas_data.load_avg_data;
let load_avg_str = format!(
"─ {:.2} {:.2} {:.2} ",
load_avg[0], load_avg[1], load_avg[2]
);
let load_avg_str_size =
UnicodeSegmentation::graphemes(load_avg_str.as_str(), true).count();
let title = if cfg!(target_family = "unix") {
let load_avg = app_state.canvas_data.load_avg_data;
let load_avg_str = format!(
"─ {:.2} {:.2} {:.2} ",
load_avg[0], load_avg[1], load_avg[2]
);
let load_avg_str_size =
UnicodeSegmentation::graphemes(load_avg_str.as_str(), true).count();
if app_state.is_expanded {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
Spans::from(vec![
Span::styled(" CPU ", self.colours.widget_title_style),
Span::styled(load_avg_str, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
load_avg_str_size
+ UnicodeSegmentation::graphemes(TITLE_BASE, true).count()
+ 2
))
),
border_style,
),
])
} else {
Spans::from(vec![
Span::styled(" CPU ", self.colours.widget_title_style),
Span::styled(load_avg_str, self.colours.widget_title_style),
])
}
} else if app_state.is_expanded {
if app_state.is_expanded {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
Spans::from(vec![
Span::styled(" CPU ", self.colours.widget_title_style),
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(load_avg_str, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
load_avg_str_size
+ UnicodeSegmentation::graphemes(TITLE_BASE, true).count()
+ 2
))
),
border_style,
),
])
} else {
Spans::from(vec![Span::styled(" CPU ", self.colours.widget_title_style)])
};
f.render_widget(
Chart::new(dataset_vector)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style),
)
.x_axis(x_axis)
.y_axis(y_axis),
draw_loc,
);
// Reset interpolated points
cpu_data
.iter_mut()
.zip(interpolated_cpu_points)
.for_each(|(cpu, interpolation)| {
if let Some((index, old_value)) = interpolation {
if let Some(to_replace) = cpu.cpu_data.get_mut(index) {
*to_replace = old_value;
}
}
});
}
}
fn draw_cpu_legend<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1))
{
cpu_widget_state.is_legend_hidden = false;
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state;
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
),
&cpu_widget_state.scroll_state.scroll_direction,
&mut cpu_widget_state.scroll_state.previous_scroll_position,
cpu_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
cpu_table_state.select(Some(
cpu_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_cpu_data = &cpu_data[start_position..];
let offset_scroll_index = cpu_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position);
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
// Calculate widths
if recalculate_column_widths {
cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&[None, None],
&(CPU_LEGEND_HEADER_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.5), Some(0.5)],
&(cpu_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
false,
);
Spans::from(vec![
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(load_avg_str, painter.colours.widget_title_style),
])
}
} else if app_state.is_expanded {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
let dcw = &cpu_widget_state.table_width_state.desired_column_widths;
let ccw = &cpu_widget_state.table_width_state.calculated_column_widths;
let cpu_rows = sliced_cpu_data.iter().enumerate().map(|(itx, cpu)| {
let mut truncated_name =
if let (Some(desired_column_width), Some(calculated_column_width)) =
(dcw.get(0), ccw.get(0))
{
if *desired_column_width > *calculated_column_width {
Text::raw(&cpu.short_cpu_name)
} else {
Text::raw(&cpu.cpu_name)
}
} else {
Text::raw(&cpu.cpu_name)
};
Spans::from(vec![
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(vec![Span::styled(
" CPU ",
painter.colours.widget_title_style,
)])
};
let is_first_column_hidden = if let Some(calculated_column_width) = ccw.get(0) {
*calculated_column_width == 0
} else {
false
};
f.render_widget(
Chart::new(dataset_vector)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style),
)
.x_axis(x_axis)
.y_axis(y_axis),
draw_loc,
);
let truncated_legend = if is_first_column_hidden && cpu.legend_value.is_empty() {
// For the case where we only have room for one column, display "All" in the normally blank area.
Text::raw("All")
} else {
Text::raw(&cpu.legend_value)
};
if !is_first_column_hidden
&& itx == offset_scroll_index
&& itx + start_position == ALL_POSITION
{
truncated_name.patch_style(self.colours.currently_selected_text_style);
Row::new(vec![truncated_name, truncated_legend])
} else {
let cpu_string_row = vec![truncated_name, truncated_legend];
Row::new(cpu_string_row).style(if itx == offset_scroll_index {
self.colours.currently_selected_text_style
} else if itx + start_position == ALL_POSITION {
self.colours.all_colour_style
} else if show_avg_cpu {
if itx + start_position == AVG_POSITION {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles[(itx + start_position
- AVG_POSITION
- 1)
% self.colours.cpu_colour_styles.len()]
}
} else {
self.colours.cpu_colour_styles[(itx + start_position - ALL_POSITION - 1)
% self.colours.cpu_colour_styles.len()]
})
// Reset interpolated points
cpu_data
.iter_mut()
.zip(interpolated_cpu_points)
.for_each(|(cpu, interpolation)| {
if let Some((index, old_value)) = interpolation {
if let Some(to_replace) = cpu.cpu_data.get_mut(index) {
*to_replace = old_value;
}
}
});
// Note we don't set highlight_style, as it should always be shown for this widget.
let border_and_title_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
// Draw
f.render_stateful_widget(
Table::new(cpu_rows)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(border_and_title_style),
)
.header(
Row::new(CPU_LEGEND_HEADER.to_vec())
.style(self.colours.table_header_style)
.bottom_margin(table_gap),
)
.widths(
&(cpu_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
draw_loc,
cpu_table_state,
);
}
}
}
fn draw_cpu_legend<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1)) {
cpu_widget_state.is_legend_hidden = false;
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state;
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&cpu_widget_state.scroll_state.scroll_direction,
&mut cpu_widget_state.scroll_state.previous_scroll_position,
cpu_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
cpu_table_state.select(Some(
cpu_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_cpu_data = &cpu_data[start_position..];
let offset_scroll_index = cpu_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position);
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
// Calculate widths
if recalculate_column_widths {
cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&[None, None],
&(CPU_LEGEND_HEADER_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.5), Some(0.5)],
&(cpu_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
false,
);
}
let dcw = &cpu_widget_state.table_width_state.desired_column_widths;
let ccw = &cpu_widget_state.table_width_state.calculated_column_widths;
let cpu_rows = sliced_cpu_data.iter().enumerate().map(|(itx, cpu)| {
let mut truncated_name =
if let (Some(desired_column_width), Some(calculated_column_width)) =
(dcw.get(0), ccw.get(0))
{
if *desired_column_width > *calculated_column_width {
Text::raw(&cpu.short_cpu_name)
} else {
Text::raw(&cpu.cpu_name)
}
} else {
Text::raw(&cpu.cpu_name)
};
let is_first_column_hidden = if let Some(calculated_column_width) = ccw.get(0) {
*calculated_column_width == 0
} else {
false
};
let truncated_legend = if is_first_column_hidden && cpu.legend_value.is_empty() {
// For the case where we only have room for one column, display "All" in the normally blank area.
Text::raw("All")
} else {
Text::raw(&cpu.legend_value)
};
if !is_first_column_hidden
&& itx == offset_scroll_index
&& itx + start_position == ALL_POSITION
{
truncated_name.patch_style(painter.colours.currently_selected_text_style);
Row::new(vec![truncated_name, truncated_legend])
} else {
let cpu_string_row = vec![truncated_name, truncated_legend];
Row::new(cpu_string_row).style(if itx == offset_scroll_index {
painter.colours.currently_selected_text_style
} else if itx + start_position == ALL_POSITION {
painter.colours.all_colour_style
} else if show_avg_cpu {
if itx + start_position == AVG_POSITION {
painter.colours.avg_colour_style
} else {
painter.colours.cpu_colour_styles[(itx + start_position - AVG_POSITION - 1)
% painter.colours.cpu_colour_styles.len()]
}
} else {
painter.colours.cpu_colour_styles[(itx + start_position - ALL_POSITION - 1)
% painter.colours.cpu_colour_styles.len()]
})
}
});
// Note we don't set highlight_style, as it should always be shown for this widget.
let border_and_title_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
// Draw
f.render_stateful_widget(
Table::new(cpu_rows)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(border_and_title_style),
)
.header(
Row::new(CPU_LEGEND_HEADER.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.widths(
&(cpu_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
draw_loc,
cpu_table_state,
);
}
}

View File

@ -27,249 +27,239 @@ static DISK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
.collect::<Vec<_>>()
});
pub trait DiskTableWidget {
fn draw_disk_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
);
}
pub fn draw_disk_table<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&disk_widget_state.scroll_state.scroll_direction,
&mut disk_widget_state.scroll_state.previous_scroll_position,
disk_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = app_state.current_widget.widget_id == widget_id;
let disk_table_state = &mut disk_widget_state.scroll_state.table_state;
disk_table_state.select(Some(
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
impl DiskTableWidget for Painter {
fn draw_disk_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
),
&disk_widget_state.scroll_state.scroll_direction,
&mut disk_widget_state.scroll_state.previous_scroll_position,
disk_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = app_state.current_widget.widget_id == widget_id;
let disk_table_state = &mut disk_widget_state.scroll_state.table_state;
disk_table_state.select(Some(
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
// Calculate widths
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
if recalculate_column_widths {
disk_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = DISK_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
// Calculate widths
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
if recalculate_column_widths {
disk_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = DISK_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
}
column_widths
};
disk_widget_state.table_width_state.desired_column_widths = disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.zip(&hard_widths)
.map(|(current, hard)| {
if let Some(hard) = hard {
if *hard > *current {
*hard
} else {
*current
}
}
column_widths
};
disk_widget_state.table_width_state.desired_column_widths = disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.zip(&hard_widths)
.map(|(current, hard)| {
if let Some(hard) = hard {
if *hard > *current {
*hard
} else {
*current
}
})
.collect::<Vec<_>>();
} else {
*current
}
})
.collect::<Vec<_>>();
disk_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(DISK_HEADERS_LENS
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
&[Some(0.2), Some(0.2), None, None, None, None, None],
&(disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
true,
);
}
disk_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(DISK_HEADERS_LENS
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
&[Some(0.2), Some(0.2), None, None, None, None, None],
&(disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
true,
);
}
let dcw = &disk_widget_state.table_width_state.desired_column_widths;
let ccw = &disk_widget_state.table_width_state.calculated_column_widths;
let disk_rows =
sliced_vec.iter().map(|disk_row| {
let truncated_data = disk_row.iter().zip(&hard_widths).enumerate().map(
|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
let dcw = &disk_widget_state.table_width_state.desired_column_widths;
let ccw = &disk_widget_state.table_width_state.calculated_column_widths;
let disk_rows = sliced_vec.iter().map(|disk_row| {
let truncated_data =
disk_row
.iter()
.zip(&hard_widths)
.enumerate()
.map(|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
return Text::raw(format!("{}", first_n));
}
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
return Text::raw(format!("{}", first_n));
}
}
}
}
Text::raw(entry)
},
);
Text::raw(entry)
});
Row::new(truncated_data)
});
Row::new(truncated_data)
});
let (border_style, highlight_style) = if is_on_widget {
(
self.colours.highlighted_border_style,
self.colours.currently_selected_text_style,
)
} else {
(self.colours.border_style, self.colours.text_style)
};
let (border_style, highlight_style) = if is_on_widget {
(
painter.colours.highlighted_border_style,
painter.colours.currently_selected_text_style,
)
} else {
(painter.colours.border_style, painter.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Disk ({} of {}) ",
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.disk_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Disk ".to_string()
}
} else {
" Disk ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Disk ".to_string(),
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(
expanded_title_base.as_str(),
true
)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
};
let disk_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw!
f.render_stateful_widget(
Table::new(disk_rows)
.block(disk_block)
.header(
Row::new(DISK_HEADERS.to_vec())
.style(self.colours.table_header_style)
.bottom_margin(table_gap),
)
.highlight_style(highlight_style)
.style(self.colours.text_style)
.widths(
&(disk_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
disk_table_state,
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Disk ({} of {}) ",
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.disk_data.len()
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Disk ".to_string()
}
} else {
" Disk ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Disk ".to_string(),
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
};
let disk_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw!
f.render_stateful_widget(
Table::new(disk_rows)
.block(disk_block)
.header(
Row::new(DISK_HEADERS.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(
&(disk_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
disk_table_state,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}

View File

@ -13,118 +13,111 @@ use tui::{
widgets::{Block, Paragraph},
};
pub trait MemBasicWidget {
fn draw_basic_memory<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_basic_memory<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
impl MemBasicWidget for Painter {
fn draw_basic_memory<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
let margined_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(draw_loc);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style),
draw_loc,
);
}
let ram_use_percentage = if let Some(mem) = mem_data.last() {
mem.1
} else {
0.0
};
let swap_use_percentage = if let Some(swap) = swap_data.last() {
swap.1
} else {
0.0
};
const EMPTY_MEMORY_FRAC_STRING: &str = "0.0B/0.0B";
let trimmed_memory_frac =
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
let trimmed_swap_frac =
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
// +7 due to 3 + 2 + 2 columns for the name & space + bar bounds + margin spacing
// Then + length of fraction
let ram_bar_length =
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_memory_frac.len());
let swap_bar_length =
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_swap_frac.len());
let num_bars_ram = calculate_basic_use_bars(ram_use_percentage, ram_bar_length);
let num_bars_swap = calculate_basic_use_bars(swap_use_percentage, swap_bar_length);
// TODO: Use different styling for the frac.
let mem_label = if app_state.basic_mode_use_percent {
format!(
"RAM[{}{}{:3.0}%]\n",
"|".repeat(num_bars_ram),
" ".repeat(ram_bar_length - num_bars_ram + trimmed_memory_frac.len() - 4),
ram_use_percentage.round()
)
} else {
format!(
"RAM[{}{}{}]\n",
"|".repeat(num_bars_ram),
" ".repeat(ram_bar_length - num_bars_ram),
trimmed_memory_frac
)
};
let swap_label = if app_state.basic_mode_use_percent {
format!(
"SWP[{}{}{:3.0}%]",
"|".repeat(num_bars_swap),
" ".repeat(swap_bar_length - num_bars_swap + trimmed_swap_frac.len() - 4),
swap_use_percentage.round()
)
} else {
format!(
"SWP[{}{}{}]",
"|".repeat(num_bars_swap),
" ".repeat(swap_bar_length - num_bars_swap),
trimmed_swap_frac
)
};
let mem_text = vec![
Spans::from(Span::styled(mem_label, self.colours.ram_style)),
Spans::from(Span::styled(swap_label, self.colours.swap_style)),
];
let margined_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(draw_loc);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Paragraph::new(mem_text).block(Block::default()),
margined_loc[0],
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
let ram_use_percentage = if let Some(mem) = mem_data.last() {
mem.1
} else {
0.0
};
let swap_use_percentage = if let Some(swap) = swap_data.last() {
swap.1
} else {
0.0
};
const EMPTY_MEMORY_FRAC_STRING: &str = "0.0B/0.0B";
let trimmed_memory_frac =
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
let trimmed_swap_frac =
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
// +7 due to 3 + 2 + 2 columns for the name & space + bar bounds + margin spacing
// Then + length of fraction
let ram_bar_length =
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_memory_frac.len());
let swap_bar_length =
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_swap_frac.len());
let num_bars_ram = calculate_basic_use_bars(ram_use_percentage, ram_bar_length);
let num_bars_swap = calculate_basic_use_bars(swap_use_percentage, swap_bar_length);
// TODO: Use different styling for the frac.
let mem_label = if app_state.basic_mode_use_percent {
format!(
"RAM[{}{}{:3.0}%]\n",
"|".repeat(num_bars_ram),
" ".repeat(ram_bar_length - num_bars_ram + trimmed_memory_frac.len() - 4),
ram_use_percentage.round()
)
} else {
format!(
"RAM[{}{}{}]\n",
"|".repeat(num_bars_ram),
" ".repeat(ram_bar_length - num_bars_ram),
trimmed_memory_frac
)
};
let swap_label = if app_state.basic_mode_use_percent {
format!(
"SWP[{}{}{:3.0}%]",
"|".repeat(num_bars_swap),
" ".repeat(swap_bar_length - num_bars_swap + trimmed_swap_frac.len() - 4),
swap_use_percentage.round()
)
} else {
format!(
"SWP[{}{}{}]",
"|".repeat(num_bars_swap),
" ".repeat(swap_bar_length - num_bars_swap),
trimmed_swap_frac
)
};
let mem_text = vec![
Spans::from(Span::styled(mem_label, painter.colours.ram_style)),
Spans::from(Span::styled(swap_label, painter.colours.swap_style)),
];
f.render_widget(
Paragraph::new(mem_text).block(Block::default()),
margined_loc[0],
);
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -15,235 +15,226 @@ use tui::{
};
use unicode_segmentation::UnicodeSegmentation;
pub trait MemGraphWidget {
fn draw_memory_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_memory_graph<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data;
let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data;
impl MemGraphWidget for Painter {
fn draw_memory_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data;
let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data;
let time_start = -(mem_widget_state.current_display_time as f64);
let time_start = -(mem_widget_state.current_display_time as f64);
let display_time_labels = vec![
Span::styled(
format!("{}s", mem_widget_state.current_display_time / 1000),
painter.colours.graph_style,
),
Span::styled("0s".to_string(), painter.colours.graph_style),
];
let y_axis_label = vec![
Span::styled(" 0%", painter.colours.graph_style),
Span::styled("100%", painter.colours.graph_style),
];
let display_time_labels = vec![
Span::styled(
format!("{}s", mem_widget_state.current_display_time / 1000),
self.colours.graph_style,
),
Span::styled("0s".to_string(), self.colours.graph_style),
];
let y_axis_label = vec![
Span::styled(" 0%", self.colours.graph_style),
Span::styled("100%", self.colours.graph_style),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& mem_widget_state.autohide_timer.is_none())
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& mem_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = mem_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = mem_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
} else {
mem_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(self.colours.graph_style)
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
} else {
mem_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
let y_axis = Axis::default()
.style(self.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_label);
let y_axis = Axis::default()
.style(painter.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_label);
// Interpolate values to avoid ugly gaps
let interpolated_mem_point = if let Some(end_pos) = mem_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = mem_data.get(start_pos);
let inside_point = mem_data.get(end_pos);
// Interpolate values to avoid ugly gaps
let interpolated_mem_point = if let Some(end_pos) = mem_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = mem_data.get(start_pos);
let inside_point = mem_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point)
{
let old = *outside_point;
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = mem_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
if let Some(to_replace) = mem_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Point somehow doesn't exist in our data
None // Failed to get mutable reference.
}
} else {
None // Point is already "leftmost", no need to interpolate.
None // Point somehow doesn't exist in our data
}
} else {
None // There is no point.
};
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
let interpolated_swap_point = if let Some(end_pos) = swap_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = swap_data.get(start_pos);
let inside_point = swap_data.get(end_pos);
let interpolated_swap_point = if let Some(end_pos) = swap_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = swap_data.get(start_pos);
let inside_point = swap_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point)
{
let old = *outside_point;
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = swap_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
if let Some(to_replace) = swap_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Point somehow doesn't exist in our data
None // Failed to get mutable reference.
}
} else {
None // Point is already "leftmost", no need to interpolate.
None // Point somehow doesn't exist in our data
}
} else {
None // There is no point.
};
let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(mem_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.ram_style)
.data(mem_data)
.graph_type(tui::widgets::GraphType::Line),
);
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
let swap_label = format!("SWP:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.swap_style)
.data(swap_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back ";
Spans::from(vec![
Span::styled(" Memory ", self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Memory ".to_string(),
self.colours.widget_title_style,
))
};
f.render_widget(
Chart::new(mem_canvas_vec)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
draw_loc,
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(mem_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.ram_style)
.data(mem_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
// Now if you're done, reset any interpolated points!
if let Some((index, old_value)) = interpolated_mem_point {
if let Some(to_replace) = mem_data.get_mut(index) {
*to_replace = old_value;
}
}
if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
let swap_label = format!("SWP:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.swap_style)
.data(swap_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
if let Some((index, old_value)) = interpolated_swap_point {
if let Some(to_replace) = swap_data.get_mut(index) {
*to_replace = old_value;
}
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back ";
Spans::from(vec![
Span::styled(" Memory ", painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Memory ".to_string(),
painter.colours.widget_title_style,
))
};
f.render_widget(
Chart::new(mem_canvas_vec)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
draw_loc,
);
// Now if you're done, reset any interpolated points!
if let Some((index, old_value)) = interpolated_mem_point {
if let Some(to_replace) = mem_data.get_mut(index) {
*to_replace = old_value;
}
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
if let Some((index, old_value)) = interpolated_swap_point {
if let Some(to_replace) = swap_data.get_mut(index) {
*to_replace = old_value;
}
}
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -8,71 +8,64 @@ use tui::{
widgets::{Block, Paragraph},
};
pub trait NetworkBasicWidget {
fn draw_basic_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
);
}
pub fn draw_basic_network<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(draw_loc);
impl NetworkBasicWidget for Painter {
fn draw_basic_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect, widget_id: u64,
) {
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(draw_loc);
let net_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let net_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let total_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style),
draw_loc,
);
}
let rx_label = format!("RX: {}", &app_state.canvas_data.rx_display);
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
let total_rx_label = format!("Total RX: {}", &app_state.canvas_data.total_rx_display);
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
let net_text = vec![
Spans::from(Span::styled(rx_label, self.colours.rx_style)),
Spans::from(Span::styled(tx_label, self.colours.tx_style)),
];
let total_net_text = vec![
Spans::from(Span::styled(total_rx_label, self.colours.total_rx_style)),
Spans::from(Span::styled(total_tx_label, self.colours.total_tx_style)),
];
f.render_widget(Paragraph::new(net_text).block(Block::default()), net_loc[0]);
let total_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Paragraph::new(total_net_text).block(Block::default()),
total_loc[0],
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
let rx_label = format!("RX: {}", &app_state.canvas_data.rx_display);
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
let total_rx_label = format!("Total RX: {}", &app_state.canvas_data.total_rx_display);
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
let net_text = vec![
Spans::from(Span::styled(rx_label, painter.colours.rx_style)),
Spans::from(Span::styled(tx_label, painter.colours.tx_style)),
];
let total_net_text = vec![
Spans::from(Span::styled(total_rx_label, painter.colours.total_rx_style)),
Spans::from(Span::styled(total_tx_label, painter.colours.total_tx_style)),
];
f.render_widget(Paragraph::new(net_text).block(Block::default()), net_loc[0]);
f.render_widget(
Paragraph::new(total_net_text).block(Block::default()),
total_loc[0],
);
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -27,105 +27,97 @@ static TEMP_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
.collect::<Vec<_>>()
});
pub trait TempTableWidget {
fn draw_temp_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
);
}
pub fn draw_temp_table<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&temp_widget_state.scroll_state.scroll_direction,
&mut temp_widget_state.scroll_state.previous_scroll_position,
temp_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let temp_table_state = &mut temp_widget_state.scroll_state.table_state;
temp_table_state.select(Some(
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
impl TempTableWidget for Painter {
fn draw_temp_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(self.table_height_offset),
),
&temp_widget_state.scroll_state.scroll_direction,
&mut temp_widget_state.scroll_state.previous_scroll_position,
temp_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let temp_table_state = &mut temp_widget_state.scroll_state.table_state;
temp_table_state.select(Some(
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
// Calculate widths
let hard_widths = [None, None];
if recalculate_column_widths {
temp_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = TEMP_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
// Calculate widths
let hard_widths = [None, None];
if recalculate_column_widths {
temp_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = TEMP_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
}
}
column_widths
};
temp_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(TEMP_HEADERS_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.80), Some(-1.0)],
&temp_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>(),
false,
);
}
column_widths
};
temp_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(TEMP_HEADERS_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.80), Some(-1.0)],
&temp_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>(),
false,
);
}
let dcw = &temp_widget_state.table_width_state.desired_column_widths;
let ccw = &temp_widget_state.table_width_state.calculated_column_widths;
let temperature_rows =
sliced_vec.iter().map(|temp_row| {
let truncated_data = temp_row.iter().zip(&hard_widths).enumerate().map(
|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
let dcw = &temp_widget_state.table_width_state.desired_column_widths;
let ccw = &temp_widget_state.table_width_state.calculated_column_widths;
let temperature_rows = sliced_vec.iter().map(|temp_row| {
let truncated_data =
temp_row
.iter()
.zip(&hard_widths)
.enumerate()
.map(|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
Text::raw(format!("{}", first_n))
} else {
Text::raw(entry)
}
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
Text::raw(format!("{}", first_n))
} else {
Text::raw(entry)
}
@ -135,131 +127,129 @@ impl TempTableWidget for Painter {
} else {
Text::raw(entry)
}
},
);
} else {
Text::raw(entry)
}
});
Row::new(truncated_data)
});
Row::new(truncated_data)
});
let (border_style, highlight_style) = if is_on_widget {
(
self.colours.highlighted_border_style,
self.colours.currently_selected_text_style,
)
} else {
(self.colours.border_style, self.colours.text_style)
};
let (border_style, highlight_style) = if is_on_widget {
(
painter.colours.highlighted_border_style,
painter.colours.currently_selected_text_style,
)
} else {
(painter.colours.border_style, painter.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Temperatures ({} of {}) ",
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.temp_sensor_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Temperatures ".to_string()
}
} else {
" Temperatures ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Temperatures ".to_string(),
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(
expanded_title_base.as_str(),
true
)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
};
let temp_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw
f.render_stateful_widget(
Table::new(temperature_rows)
.header(
Row::new(TEMP_HEADERS.to_vec())
.style(self.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(temp_block)
.highlight_style(highlight_style)
.style(self.colours.text_style)
.widths(
&(temp_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
temp_table_state,
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Temperatures ({} of {}) ",
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.temp_sensor_data.len()
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
// Note there is no difference between this and using draw_loc, but I'm too lazy to fix it.
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Temperatures ".to_string()
}
} else {
" Temperatures ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Temperatures ".to_string(),
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
};
let temp_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw
f.render_stateful_widget(
Table::new(temperature_rows)
.header(
Row::new(TEMP_HEADERS.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(temp_block)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(
&(temp_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
temp_table_state,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
// Note there is no difference between this and using draw_loc, but I'm too lazy to fix it.
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}

View File

@ -64,6 +64,7 @@ pub enum BottomEvent {
KeyInput(KeyEvent),
MouseInput(MouseEvent),
Update(Box<data_harvester::Data>),
Resize { width: u16, height: u16 },
Clean,
}
@ -635,7 +636,11 @@ pub fn create_input_thread(
}
}
},
Event::Resize(_, _) => {}
Event::Resize(width, height) => {
if sender.send(BottomEvent::Resize { width, height }).is_err() {
break;
}
}
}
}
}