mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-23 05:34:57 +02:00
refactor: move CPU graph over to new system
This commit is contained in:
parent
9e63642e9c
commit
c97126df22
12
src/app.rs
12
src/app.rs
@ -2228,8 +2228,8 @@ impl App {
|
|||||||
.cpu_state
|
.cpu_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||||
{
|
{
|
||||||
cpu_widget_state.scroll_state.current_scroll_position = 0;
|
cpu_widget_state.table_state.current_scroll_position = 0;
|
||||||
cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
|
cpu_widget_state.table_state.scroll_direction = ScrollDirection::Up;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2306,8 +2306,8 @@ impl App {
|
|||||||
{
|
{
|
||||||
let cap = self.canvas_data.cpu_data.len();
|
let cap = self.canvas_data.cpu_data.len();
|
||||||
if cap > 0 {
|
if cap > 0 {
|
||||||
cpu_widget_state.scroll_state.current_scroll_position = cap - 1;
|
cpu_widget_state.table_state.current_scroll_position = cap - 1;
|
||||||
cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
cpu_widget_state.table_state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2380,7 +2380,7 @@ impl App {
|
|||||||
.get_mut(&(self.current_widget.widget_id - 1))
|
.get_mut(&(self.current_widget.widget_id - 1))
|
||||||
{
|
{
|
||||||
cpu_widget_state
|
cpu_widget_state
|
||||||
.scroll_state
|
.table_state
|
||||||
.update_position(num_to_change_by, self.canvas_data.cpu_data.len());
|
.update_position(num_to_change_by, self.canvas_data.cpu_data.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2957,7 +2957,7 @@ impl App {
|
|||||||
.get_widget_state(self.current_widget.widget_id - 1)
|
.get_widget_state(self.current_widget.widget_id - 1)
|
||||||
{
|
{
|
||||||
if let Some(visual_index) =
|
if let Some(visual_index) =
|
||||||
cpu_widget_state.scroll_state.table_state.selected()
|
cpu_widget_state.table_state.table_state.selected()
|
||||||
{
|
{
|
||||||
self.change_cpu_legend_position(
|
self.change_cpu_legend_position(
|
||||||
offset_clicked_entry as i64 - visual_index as i64,
|
offset_clicked_entry as i64 - visual_index as i64,
|
||||||
|
@ -61,8 +61,19 @@ pub enum WidthBounds {
|
|||||||
|
|
||||||
impl WidthBounds {
|
impl WidthBounds {
|
||||||
pub const fn soft_from_str(name: &'static str, max_percentage: Option<f32>) -> WidthBounds {
|
pub const fn soft_from_str(name: &'static str, max_percentage: Option<f32>) -> WidthBounds {
|
||||||
|
let len = name.len() as u16;
|
||||||
WidthBounds::Soft {
|
WidthBounds::Soft {
|
||||||
min_width: name.len() as u16,
|
min_width: len,
|
||||||
|
desired: len,
|
||||||
|
max_percentage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn soft_from_str_with_alt(
|
||||||
|
name: &'static str, alt: &'static str, max_percentage: Option<f32>,
|
||||||
|
) -> WidthBounds {
|
||||||
|
WidthBounds::Soft {
|
||||||
|
min_width: alt.len() as u16,
|
||||||
desired: name.len() as u16,
|
desired: name.len() as u16,
|
||||||
max_percentage,
|
max_percentage,
|
||||||
}
|
}
|
||||||
@ -75,6 +86,9 @@ pub struct TableComponentColumn {
|
|||||||
|
|
||||||
/// A restriction on this column's width, if desired.
|
/// A restriction on this column's width, if desired.
|
||||||
pub width_bounds: WidthBounds,
|
pub width_bounds: WidthBounds,
|
||||||
|
|
||||||
|
/// The calculated width of the column.
|
||||||
|
pub calculated_width: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableComponentColumn {
|
impl TableComponentColumn {
|
||||||
@ -92,8 +106,13 @@ impl TableComponentColumn {
|
|||||||
CellContent::Simple(name.into())
|
CellContent::Simple(name.into())
|
||||||
},
|
},
|
||||||
width_bounds,
|
width_bounds,
|
||||||
|
calculated_width: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn should_skip(&self) -> bool {
|
||||||
|
self.calculated_width == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`TableComponentState`] deals with fields for a scrollable's current state.
|
/// [`TableComponentState`] deals with fields for a scrollable's current state.
|
||||||
@ -104,7 +123,6 @@ pub struct TableComponentState {
|
|||||||
pub scroll_direction: ScrollDirection,
|
pub scroll_direction: ScrollDirection,
|
||||||
pub table_state: TableState,
|
pub table_state: TableState,
|
||||||
pub columns: Vec<TableComponentColumn>,
|
pub columns: Vec<TableComponentColumn>,
|
||||||
pub calculated_widths: Vec<u16>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableComponentState {
|
impl TableComponentState {
|
||||||
@ -115,7 +133,6 @@ impl TableComponentState {
|
|||||||
scroll_direction: ScrollDirection::Down,
|
scroll_direction: ScrollDirection::Down,
|
||||||
table_state: Default::default(),
|
table_state: Default::default(),
|
||||||
columns,
|
columns,
|
||||||
calculated_widths: Vec::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,16 +149,18 @@ impl TableComponentState {
|
|||||||
|
|
||||||
let mut total_width_left = total_width;
|
let mut total_width_left = total_width;
|
||||||
|
|
||||||
let column_widths = &mut self.calculated_widths;
|
for column in self.columns.iter_mut() {
|
||||||
*column_widths = vec![0; self.columns.len()];
|
column.calculated_width = 0;
|
||||||
|
}
|
||||||
|
|
||||||
let columns = if left_to_right {
|
let columns = if left_to_right {
|
||||||
Either::Left(self.columns.iter().enumerate())
|
Either::Left(self.columns.iter_mut())
|
||||||
} else {
|
} else {
|
||||||
Either::Right(self.columns.iter().enumerate().rev())
|
Either::Right(self.columns.iter_mut().rev())
|
||||||
};
|
};
|
||||||
|
|
||||||
for (itx, column) in columns {
|
let mut num_columns = 0;
|
||||||
|
for column in columns {
|
||||||
match &column.width_bounds {
|
match &column.width_bounds {
|
||||||
WidthBounds::Soft {
|
WidthBounds::Soft {
|
||||||
min_width,
|
min_width,
|
||||||
@ -161,9 +180,10 @@ impl TableComponentState {
|
|||||||
|
|
||||||
if *min_width > space_taken {
|
if *min_width > space_taken {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else if space_taken > 0 {
|
||||||
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
||||||
column_widths[itx] = space_taken;
|
column.calculated_width = space_taken;
|
||||||
|
num_columns += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WidthBounds::Hard(width) => {
|
WidthBounds::Hard(width) => {
|
||||||
@ -171,27 +191,34 @@ impl TableComponentState {
|
|||||||
|
|
||||||
if *width > space_taken {
|
if *width > space_taken {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else if space_taken > 0 {
|
||||||
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
total_width_left = total_width_left.saturating_sub(space_taken + 1);
|
||||||
column_widths[itx] = space_taken;
|
column.calculated_width = space_taken;
|
||||||
|
num_columns += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(0) = column_widths.last() {
|
if num_columns > 0 {
|
||||||
column_widths.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !column_widths.is_empty() {
|
|
||||||
// Redistribute remaining.
|
// Redistribute remaining.
|
||||||
let amount_per_slot = total_width_left / column_widths.len() as u16;
|
let mut num_dist = num_columns;
|
||||||
total_width_left %= column_widths.len() as u16;
|
let amount_per_slot = total_width_left / num_dist;
|
||||||
for (index, width) in column_widths.iter_mut().enumerate() {
|
total_width_left %= num_dist;
|
||||||
if index < total_width_left.into() {
|
for column in self.columns.iter_mut() {
|
||||||
*width += amount_per_slot + 1;
|
if num_dist == 0 {
|
||||||
} else {
|
break;
|
||||||
*width += amount_per_slot;
|
}
|
||||||
|
|
||||||
|
if column.calculated_width > 0 {
|
||||||
|
if total_width_left > 0 {
|
||||||
|
column.calculated_width += amount_per_slot + 1;
|
||||||
|
total_width_left -= 1;
|
||||||
|
} else {
|
||||||
|
column.calculated_width += amount_per_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_dist -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -871,29 +898,13 @@ impl ProcState {
|
|||||||
pub struct NetWidgetState {
|
pub struct NetWidgetState {
|
||||||
pub current_display_time: u64,
|
pub current_display_time: u64,
|
||||||
pub autohide_timer: Option<Instant>,
|
pub autohide_timer: Option<Instant>,
|
||||||
// pub draw_max_range_cache: f64,
|
|
||||||
// pub draw_labels_cache: Vec<String>,
|
|
||||||
// pub draw_time_start_cache: f64,
|
|
||||||
// TODO: Re-enable these when we move net details state-side!
|
|
||||||
// pub unit_type: DataUnitTypes,
|
|
||||||
// pub scale_type: AxisScaling,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetWidgetState {
|
impl NetWidgetState {
|
||||||
pub fn init(
|
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
|
||||||
current_display_time: u64,
|
|
||||||
autohide_timer: Option<Instant>,
|
|
||||||
// unit_type: DataUnitTypes,
|
|
||||||
// scale_type: AxisScaling,
|
|
||||||
) -> Self {
|
|
||||||
NetWidgetState {
|
NetWidgetState {
|
||||||
current_display_time,
|
current_display_time,
|
||||||
autohide_timer,
|
autohide_timer,
|
||||||
// draw_max_range_cache: 0.0,
|
|
||||||
// draw_labels_cache: vec![],
|
|
||||||
// draw_time_start_cache: 0.0,
|
|
||||||
// unit_type,
|
|
||||||
// scale_type,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -924,20 +935,33 @@ pub struct CpuWidgetState {
|
|||||||
pub current_display_time: u64,
|
pub current_display_time: u64,
|
||||||
pub is_legend_hidden: bool,
|
pub is_legend_hidden: bool,
|
||||||
pub autohide_timer: Option<Instant>,
|
pub autohide_timer: Option<Instant>,
|
||||||
pub scroll_state: TableComponentState,
|
pub table_state: TableComponentState,
|
||||||
pub is_multi_graph_mode: bool,
|
pub is_multi_graph_mode: bool,
|
||||||
pub table_width_state: CanvasTableWidthState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpuWidgetState {
|
impl CpuWidgetState {
|
||||||
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
|
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
|
||||||
|
const CPU_LEGEND_HEADER: [(Cow<'static, str>, Option<Cow<'static, str>>); 2] =
|
||||||
|
[(Cow::Borrowed("CPU"), None), (Cow::Borrowed("Use%"), None)];
|
||||||
|
const WIDTHS: [WidthBounds; CPU_LEGEND_HEADER.len()] = [
|
||||||
|
WidthBounds::soft_from_str("CPU", Some(0.5)),
|
||||||
|
WidthBounds::soft_from_str("Use%", Some(0.5)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let table_state = TableComponentState::new(
|
||||||
|
CPU_LEGEND_HEADER
|
||||||
|
.iter()
|
||||||
|
.zip(WIDTHS)
|
||||||
|
.map(|(c, width)| TableComponentColumn::new(c.0.clone(), c.1.clone(), width))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
CpuWidgetState {
|
CpuWidgetState {
|
||||||
current_display_time,
|
current_display_time,
|
||||||
is_legend_hidden: false,
|
is_legend_hidden: false,
|
||||||
autohide_timer,
|
autohide_timer,
|
||||||
scroll_state: TableComponentState::default(),
|
table_state,
|
||||||
is_multi_graph_mode: false,
|
is_multi_graph_mode: false,
|
||||||
table_width_state: CanvasTableWidthState::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1166,7 +1190,6 @@ mod test {
|
|||||||
scroll_direction: ScrollDirection::Down,
|
scroll_direction: ScrollDirection::Down,
|
||||||
table_state: Default::default(),
|
table_state: Default::default(),
|
||||||
columns: vec![],
|
columns: vec![],
|
||||||
calculated_widths: vec![],
|
|
||||||
};
|
};
|
||||||
let s = &mut scroll;
|
let s = &mut scroll;
|
||||||
|
|
||||||
|
@ -17,6 +17,11 @@ use crate::{
|
|||||||
data_conversion::{CellContent, TableData, TableRow},
|
data_conversion::{CellContent, TableData, TableRow},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct TextTableTitle<'a> {
|
||||||
|
pub title: Cow<'a, str>,
|
||||||
|
pub is_expanded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TextTable<'a> {
|
pub struct TextTable<'a> {
|
||||||
pub table_gap: u16,
|
pub table_gap: u16,
|
||||||
pub is_force_redraw: bool,
|
pub is_force_redraw: bool,
|
||||||
@ -31,11 +36,8 @@ pub struct TextTable<'a> {
|
|||||||
/// The highlighted text style.
|
/// The highlighted text style.
|
||||||
pub highlighted_text_style: Style,
|
pub highlighted_text_style: Style,
|
||||||
|
|
||||||
/// The graph title.
|
/// The graph title and whether it is expanded (if there is one).
|
||||||
pub title: Cow<'a, str>,
|
pub title: Option<TextTableTitle<'a>>,
|
||||||
|
|
||||||
/// Whether this graph is expanded.
|
|
||||||
pub is_expanded: bool,
|
|
||||||
|
|
||||||
/// Whether this widget is selected.
|
/// Whether this widget is selected.
|
||||||
pub is_on_widget: bool,
|
pub is_on_widget: bool,
|
||||||
@ -58,42 +60,46 @@ pub struct TextTable<'a> {
|
|||||||
|
|
||||||
impl<'a> TextTable<'a> {
|
impl<'a> TextTable<'a> {
|
||||||
/// Generates a title for the [`TextTable`] widget, given the available space.
|
/// Generates a title for the [`TextTable`] widget, given the available space.
|
||||||
fn generate_title(&self, draw_loc: Rect, pos: usize, total: usize) -> Spans<'_> {
|
fn generate_title(&self, draw_loc: Rect, pos: usize, total: usize) -> Option<Spans<'_>> {
|
||||||
let title = if self.show_table_scroll_position {
|
self.title
|
||||||
let title_string = concat_string!(
|
.as_ref()
|
||||||
self.title,
|
.map(|TextTableTitle { title, is_expanded }| {
|
||||||
"(",
|
let title = if self.show_table_scroll_position {
|
||||||
pos.to_string(),
|
let title_string = concat_string!(
|
||||||
" of ",
|
title,
|
||||||
total.to_string(),
|
"(",
|
||||||
") "
|
pos.to_string(),
|
||||||
);
|
" of ",
|
||||||
|
total.to_string(),
|
||||||
|
") "
|
||||||
|
);
|
||||||
|
|
||||||
if title_string.len() + 2 <= draw_loc.width.into() {
|
if title_string.len() + 2 <= draw_loc.width.into() {
|
||||||
title_string
|
title_string
|
||||||
} else {
|
} else {
|
||||||
self.title.to_string()
|
title.to_string()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.title.to_string()
|
title.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.is_expanded {
|
if *is_expanded {
|
||||||
let title_base = concat_string!(title, "── Esc to go back ");
|
let title_base = concat_string!(title, "── Esc to go back ");
|
||||||
let esc = concat_string!(
|
let esc = concat_string!(
|
||||||
"─",
|
"─",
|
||||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||||
UnicodeSegmentation::graphemes(title_base.as_str(), true).count() + 2
|
UnicodeSegmentation::graphemes(title_base.as_str(), true).count() + 2
|
||||||
)),
|
)),
|
||||||
"─ Esc to go back "
|
"─ Esc to go back "
|
||||||
);
|
);
|
||||||
Spans::from(vec![
|
Spans::from(vec![
|
||||||
Span::styled(title, self.title_style),
|
Span::styled(title, self.title_style),
|
||||||
Span::styled(esc, self.border_style),
|
Span::styled(esc, self.border_style),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
Spans::from(Span::styled(title, self.title_style))
|
Spans::from(Span::styled(title, self.title_style))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn draw_text_table<B: Backend>(
|
pub fn draw_text_table<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState,
|
&self, f: &mut Frame<'_, B>, draw_loc: Rect, state: &mut TableComponentState,
|
||||||
@ -108,16 +114,19 @@ impl<'a> TextTable<'a> {
|
|||||||
.split(draw_loc)[0];
|
.split(draw_loc)[0];
|
||||||
|
|
||||||
let disk_block = if self.draw_border {
|
let disk_block = if self.draw_border {
|
||||||
let title = self.generate_title(
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(self.border_style);
|
||||||
|
|
||||||
|
if let Some(title) = self.generate_title(
|
||||||
draw_loc,
|
draw_loc,
|
||||||
state.current_scroll_position.saturating_add(1),
|
state.current_scroll_position.saturating_add(1),
|
||||||
table_data.data.len(),
|
table_data.data.len(),
|
||||||
);
|
) {
|
||||||
|
block.title(title)
|
||||||
Block::default()
|
} else {
|
||||||
.title(title)
|
block
|
||||||
.borders(Borders::ALL)
|
}
|
||||||
.border_style(self.border_style)
|
|
||||||
} else if self.is_on_widget {
|
} else if self.is_on_widget {
|
||||||
Block::default()
|
Block::default()
|
||||||
.borders(SIDE_BORDERS)
|
.borders(SIDE_BORDERS)
|
||||||
@ -179,30 +188,32 @@ impl<'a> TextTable<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let columns = &state.columns;
|
let columns = &state.columns;
|
||||||
let widths = &state.calculated_widths;
|
let header = Row::new(columns.iter().filter_map(|c| {
|
||||||
let header = Row::new(
|
if c.calculated_width == 0 {
|
||||||
columns
|
None
|
||||||
.iter()
|
} else {
|
||||||
.zip(widths)
|
Some(truncate_text(&c.name, c.calculated_width.into(), None))
|
||||||
.map(|(c, width)| truncate_text(&c.name, (*width).into(), None)),
|
}
|
||||||
)
|
}))
|
||||||
.style(self.header_style)
|
.style(self.header_style)
|
||||||
.bottom_margin(table_gap);
|
.bottom_margin(table_gap);
|
||||||
let disk_rows = sliced_vec.iter().map(|row| {
|
let table_rows = sliced_vec.iter().map(|row| {
|
||||||
let (row, style) = match row {
|
let (row, style) = match row {
|
||||||
TableRow::Raw(row) => (row, None),
|
TableRow::Raw(row) => (row, None),
|
||||||
TableRow::Styled(row, style) => (row, Some(*style)),
|
TableRow::Styled(row, style) => (row, Some(*style)),
|
||||||
};
|
};
|
||||||
|
|
||||||
Row::new(
|
Row::new(row.iter().zip(columns).filter_map(|(cell, c)| {
|
||||||
row.iter()
|
if c.calculated_width == 0 {
|
||||||
.zip(widths)
|
None
|
||||||
.map(|(cell, width)| truncate_text(cell, (*width).into(), style)),
|
} else {
|
||||||
)
|
Some(truncate_text(cell, c.calculated_width.into(), style))
|
||||||
|
}
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
let widget = {
|
let widget = {
|
||||||
let mut table = Table::new(disk_rows)
|
let mut table = Table::new(table_rows)
|
||||||
.block(disk_block)
|
.block(disk_block)
|
||||||
.highlight_style(self.highlighted_text_style)
|
.highlight_style(self.highlighted_text_style)
|
||||||
.style(self.text_style);
|
.style(self.text_style);
|
||||||
@ -216,9 +227,15 @@ impl<'a> TextTable<'a> {
|
|||||||
|
|
||||||
f.render_stateful_widget(
|
f.render_stateful_widget(
|
||||||
widget.widths(
|
widget.widths(
|
||||||
&(widths
|
&(columns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|w| Constraint::Length(*w))
|
.filter_map(|c| {
|
||||||
|
if c.calculated_width == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Constraint::Length(c.calculated_width))
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
),
|
),
|
||||||
margined_draw_loc,
|
margined_draw_loc,
|
||||||
|
@ -1,38 +1,34 @@
|
|||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, iter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{layout_manager::WidgetDirection, App, CpuWidgetState},
|
app::{layout_manager::WidgetDirection, App, CpuWidgetState},
|
||||||
canvas::{
|
canvas::{
|
||||||
components::{GraphData, TimeGraph},
|
components::{GraphData, TextTable, TimeGraph},
|
||||||
drawing_utils::{get_column_widths, get_start_position, should_hide_x_label},
|
drawing_utils::should_hide_x_label,
|
||||||
Painter,
|
Painter,
|
||||||
},
|
},
|
||||||
constants::*,
|
data_conversion::{CellContent, ConvertedCpuData, TableData, TableRow},
|
||||||
data_conversion::ConvertedCpuData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use concat_string::concat_string;
|
use concat_string::concat_string;
|
||||||
|
|
||||||
|
use itertools::Either;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
terminal::Frame,
|
terminal::Frame,
|
||||||
text::Text,
|
|
||||||
widgets::{Block, Borders, Row, Table},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
|
|
||||||
const AVG_POSITION: usize = 1;
|
const AVG_POSITION: usize = 1;
|
||||||
const ALL_POSITION: usize = 0;
|
const ALL_POSITION: usize = 0;
|
||||||
|
|
||||||
static CPU_LEGEND_HEADER_LENS: [usize; 2] =
|
|
||||||
[CPU_LEGEND_HEADER[0].len(), CPU_LEGEND_HEADER[1].len()];
|
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
pub fn draw_cpu<B: Backend>(
|
pub fn draw_cpu<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
||||||
) {
|
) {
|
||||||
if draw_loc.width as f64 * 0.15 <= 6.0 {
|
let legend_width = (draw_loc.width as f64 * 0.15) as u16;
|
||||||
|
|
||||||
|
if legend_width < 6 {
|
||||||
// Skip drawing legend
|
// Skip drawing legend
|
||||||
if app_state.current_widget.widget_id == (widget_id + 1) {
|
if app_state.current_widget.widget_id == (widget_id + 1) {
|
||||||
if app_state.app_config_fields.left_legend {
|
if app_state.app_config_fields.left_legend {
|
||||||
@ -55,18 +51,25 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let graph_width = draw_loc.width - legend_width;
|
||||||
let (graph_index, legend_index, constraints) =
|
let (graph_index, legend_index, constraints) =
|
||||||
if app_state.app_config_fields.left_legend {
|
if app_state.app_config_fields.left_legend {
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
[Constraint::Percentage(15), Constraint::Percentage(85)],
|
[
|
||||||
|
Constraint::Length(legend_width),
|
||||||
|
Constraint::Length(graph_width),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
[Constraint::Percentage(85), Constraint::Percentage(15)],
|
[
|
||||||
|
Constraint::Length(graph_width),
|
||||||
|
Constraint::Length(legend_width),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -121,7 +124,7 @@ impl Painter {
|
|||||||
) -> Vec<GraphData<'a>> {
|
) -> Vec<GraphData<'a>> {
|
||||||
let show_avg_offset = if show_avg_cpu { AVG_POSITION } else { 0 };
|
let show_avg_offset = if show_avg_cpu { AVG_POSITION } else { 0 };
|
||||||
|
|
||||||
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
|
let current_scroll_position = cpu_widget_state.table_state.current_scroll_position;
|
||||||
if current_scroll_position == ALL_POSITION {
|
if current_scroll_position == ALL_POSITION {
|
||||||
// This case ensures the other cases cannot have the position be equal to 0.
|
// This case ensures the other cases cannot have the position be equal to 0.
|
||||||
cpu_data
|
cpu_data
|
||||||
@ -224,149 +227,81 @@ impl Painter {
|
|||||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
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))
|
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1))
|
||||||
{
|
{
|
||||||
cpu_widget_state.is_legend_hidden = false; // TODO: This line (and the one above, see caller) is pretty dumb.
|
// TODO: This line (and the one above, see caller) is pretty dumb but I guess needed.
|
||||||
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
|
cpu_widget_state.is_legend_hidden = false;
|
||||||
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.scroll_bar,
|
|
||||||
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;
|
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
||||||
|
let cpu_data = {
|
||||||
// Calculate widths
|
let row_widths = vec![1, 3]; // TODO: Should change this to take const generics (usize) and an array.
|
||||||
if recalculate_column_widths {
|
let colour_iter = if show_avg_cpu {
|
||||||
cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
|
Either::Left(
|
||||||
cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
|
iter::once(&self.colours.all_colour_style)
|
||||||
draw_loc.width,
|
.chain(iter::once(&self.colours.avg_colour_style))
|
||||||
&[None, None],
|
.chain(self.colours.cpu_colour_styles.iter().cycle()),
|
||||||
&(CPU_LEGEND_HEADER_LENS
|
)
|
||||||
.iter()
|
|
||||||
.map(|width| Some(*width as u16))
|
|
||||||
.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 {
|
} else {
|
||||||
false
|
Either::Right(
|
||||||
|
iter::once(&self.colours.all_colour_style)
|
||||||
|
.chain(self.colours.cpu_colour_styles.iter().cycle()),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let truncated_legend = if is_first_column_hidden && cpu.legend_value.is_empty() {
|
let data = {
|
||||||
// For the case where we only have room for one column, display "All" in the normally blank area.
|
let iter = app_state.canvas_data.cpu_data.iter().zip(colour_iter);
|
||||||
Text::raw("All")
|
const CPU_WIDTH_CHECK: u16 = 10; // This is hard-coded, it's terrible.
|
||||||
} else {
|
if draw_loc.width < CPU_WIDTH_CHECK {
|
||||||
Text::raw(&cpu.legend_value)
|
Either::Left(iter.map(|(cpu, style)| {
|
||||||
};
|
let row = vec![
|
||||||
|
CellContent::Simple("".into()),
|
||||||
if !is_first_column_hidden
|
CellContent::Simple(if cpu.legend_value.is_empty() {
|
||||||
&& itx == offset_scroll_index
|
cpu.cpu_name.clone().into()
|
||||||
&& itx + start_position == ALL_POSITION
|
} else {
|
||||||
{
|
cpu.legend_value.clone().into()
|
||||||
truncated_name.patch_style(self.colours.currently_selected_text_style);
|
}),
|
||||||
Row::new(vec![truncated_name, truncated_legend])
|
];
|
||||||
} else {
|
TableRow::Styled(row, *style)
|
||||||
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 {
|
} else {
|
||||||
self.colours.cpu_colour_styles[(itx + start_position - ALL_POSITION - 1)
|
Either::Right(iter.map(|(cpu, style)| {
|
||||||
% self.colours.cpu_colour_styles.len()]
|
let row = vec![
|
||||||
})
|
CellContent::HasAlt {
|
||||||
|
alt: cpu.short_cpu_name.clone().into(),
|
||||||
|
main: cpu.cpu_name.clone().into(),
|
||||||
|
},
|
||||||
|
CellContent::Simple(cpu.legend_value.clone().into()),
|
||||||
|
];
|
||||||
|
TableRow::Styled(row, *style)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
.collect();
|
||||||
|
|
||||||
// Note we don't set highlight_style, as it should always be shown for this widget.
|
TableData { data, row_widths }
|
||||||
let border_and_title_style = if is_on_widget {
|
};
|
||||||
|
|
||||||
|
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||||
|
let border_style = if is_on_widget {
|
||||||
self.colours.highlighted_border_style
|
self.colours.highlighted_border_style
|
||||||
} else {
|
} else {
|
||||||
self.colours.border_style
|
self.colours.border_style
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw
|
TextTable {
|
||||||
f.render_stateful_widget(
|
table_gap: app_state.app_config_fields.table_gap,
|
||||||
Table::new(cpu_rows)
|
is_force_redraw: app_state.is_force_redraw,
|
||||||
.block(
|
recalculate_column_widths,
|
||||||
Block::default()
|
header_style: self.colours.table_header_style,
|
||||||
.borders(Borders::ALL)
|
border_style,
|
||||||
.border_style(border_and_title_style),
|
highlighted_text_style: self.colours.currently_selected_text_style, // We always highlight the selected CPU entry... not sure if I like this though.
|
||||||
)
|
title: None,
|
||||||
.header(
|
is_on_widget,
|
||||||
Row::new(CPU_LEGEND_HEADER.to_vec())
|
draw_border: true,
|
||||||
.style(self.colours.table_header_style)
|
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
||||||
.bottom_margin(table_gap),
|
title_style: self.colours.widget_title_style,
|
||||||
)
|
text_style: self.colours.text_style,
|
||||||
.widths(
|
left_to_right: false,
|
||||||
&(cpu_widget_state
|
}
|
||||||
.table_width_state
|
.draw_text_table(f, draw_loc, &mut cpu_widget_state.table_state, &cpu_data);
|
||||||
.calculated_column_widths
|
|
||||||
.iter()
|
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
|
||||||
.collect::<Vec<_>>()),
|
|
||||||
),
|
|
||||||
draw_loc,
|
|
||||||
cpu_table_state,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@ use tui::{backend::Backend, layout::Rect, terminal::Frame};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app,
|
app,
|
||||||
canvas::{components::TextTable, Painter},
|
canvas::{
|
||||||
|
components::{TextTable, TextTableTitle},
|
||||||
|
Painter,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
@ -13,7 +16,6 @@ impl Painter {
|
|||||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
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) {
|
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
|
||||||
let is_on_widget = app_state.current_widget.widget_id == widget_id;
|
let is_on_widget = app_state.current_widget.widget_id == widget_id;
|
||||||
|
|
||||||
let (border_style, highlighted_text_style) = if is_on_widget {
|
let (border_style, highlighted_text_style) = if is_on_widget {
|
||||||
(
|
(
|
||||||
self.colours.highlighted_border_style,
|
self.colours.highlighted_border_style,
|
||||||
@ -29,8 +31,10 @@ impl Painter {
|
|||||||
header_style: self.colours.table_header_style,
|
header_style: self.colours.table_header_style,
|
||||||
border_style,
|
border_style,
|
||||||
highlighted_text_style,
|
highlighted_text_style,
|
||||||
title: " Disks ".into(),
|
title: Some(TextTableTitle {
|
||||||
is_expanded: app_state.is_expanded,
|
title: " Disks ".into(),
|
||||||
|
is_expanded: app_state.is_expanded,
|
||||||
|
}),
|
||||||
is_on_widget,
|
is_on_widget,
|
||||||
draw_border,
|
draw_border,
|
||||||
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
||||||
|
@ -2,7 +2,10 @@ use tui::{backend::Backend, layout::Rect, terminal::Frame};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app,
|
app,
|
||||||
canvas::{components::TextTable, Painter},
|
canvas::{
|
||||||
|
components::{TextTable, TextTableTitle},
|
||||||
|
Painter,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
@ -29,8 +32,10 @@ impl Painter {
|
|||||||
header_style: self.colours.table_header_style,
|
header_style: self.colours.table_header_style,
|
||||||
border_style,
|
border_style,
|
||||||
highlighted_text_style,
|
highlighted_text_style,
|
||||||
title: " Temperatures ".into(),
|
title: Some(TextTableTitle {
|
||||||
is_expanded: app_state.is_expanded,
|
title: " Temperatures ".into(),
|
||||||
|
is_expanded: app_state.is_expanded,
|
||||||
|
}),
|
||||||
is_on_widget,
|
is_on_widget,
|
||||||
draw_border,
|
draw_border,
|
||||||
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
show_table_scroll_position: app_state.app_config_fields.show_table_scroll_position,
|
||||||
|
@ -238,7 +238,7 @@ pub fn convert_cpu_data_points(
|
|||||||
if data.cpu_data.len() + 1 != existing_cpu_data.len() {
|
if data.cpu_data.len() + 1 != existing_cpu_data.len() {
|
||||||
*existing_cpu_data = vec![ConvertedCpuData {
|
*existing_cpu_data = vec![ConvertedCpuData {
|
||||||
cpu_name: "All".to_string(),
|
cpu_name: "All".to_string(),
|
||||||
short_cpu_name: "All".to_string(),
|
short_cpu_name: "".to_string(),
|
||||||
cpu_data: vec![],
|
cpu_data: vec![],
|
||||||
legend_value: String::new(),
|
legend_value: String::new(),
|
||||||
}];
|
}];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user