mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-25 22:55:06 +02:00
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:
parent
189be96622
commit
dd7e183ec8
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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::*;
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user