feature: support default selection of average CPU graph (#1353)

* feature: support default selection of average CPU graph

* test
This commit is contained in:
Clement Tsang 2023-12-10 15:21:35 -05:00 committed by GitHub
parent 300f66700e
commit b6f92c2f3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 152 additions and 34 deletions

View file

@ -3,15 +3,3 @@ rustflags = ["-C", "target-feature=+crt-static"]
[target.i686-pc-windows-msvc] [target.i686-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]
# [target.arm-unknown-linux-gnueabihf]
# linker = "arm-linux-gnueabihf-gcc"
# [target.armv7-unknown-linux-gnueabihf]
# linker = "arm-linux-gnueabihf-gcc"
# [target.aarch64-unknown-linux-gnu]
# linker = "aarch64-linux-gnu-gcc"
# [target.aarch64-unknown-linux-musl]
# linker = "aarch64-linux-musl-gcc"

View file

@ -0,0 +1,11 @@
# CPU
## Default CPU Graph Selection
You can configure which CPU graph is shown by default when starting up bottom by setting `cpu.default`.
```toml
[cpu]
# One of "all" (default), "average"/"avg"
default = "average"
```

View file

@ -85,6 +85,10 @@
#[processes] #[processes]
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"] #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
# [cpu]
# One of "all" (default), "average"/"avg"
# default = "average"
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.

View file

@ -49,7 +49,7 @@ pub struct AppConfigFields {
pub temperature_type: temperature::TemperatureType, pub temperature_type: temperature::TemperatureType,
pub use_dot: bool, pub use_dot: bool,
pub left_legend: bool, pub left_legend: bool,
pub show_average_cpu: bool, pub show_average_cpu: bool, // TODO: Unify this in CPU options
pub use_current_cpu_total: bool, pub use_current_cpu_total: bool,
pub unnormalized_cpu: bool, pub unnormalized_cpu: bool,
pub use_basic_mode: bool, pub use_basic_mode: bool,
@ -1910,7 +1910,7 @@ impl App {
.proc_state .proc_state
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
proc_widget_state.table.set_first(); proc_widget_state.table.to_first();
} }
} }
BottomWidgetType::ProcSort => { BottomWidgetType::ProcSort => {
@ -1919,7 +1919,7 @@ impl App {
.proc_state .proc_state
.get_mut_widget_state(self.current_widget.widget_id - 2) .get_mut_widget_state(self.current_widget.widget_id - 2)
{ {
proc_widget_state.sort_table.set_first(); proc_widget_state.sort_table.to_first();
} }
} }
BottomWidgetType::Temp => { BottomWidgetType::Temp => {
@ -1928,7 +1928,7 @@ impl App {
.temp_state .temp_state
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
temp_widget_state.table.set_first(); temp_widget_state.table.to_first();
} }
} }
BottomWidgetType::Disk => { BottomWidgetType::Disk => {
@ -1937,7 +1937,7 @@ impl App {
.disk_state .disk_state
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
disk_widget_state.table.set_first(); disk_widget_state.table.to_first();
} }
} }
BottomWidgetType::CpuLegend => { BottomWidgetType::CpuLegend => {
@ -1946,7 +1946,7 @@ 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.table.set_first(); cpu_widget_state.table.to_first();
} }
} }
@ -1969,7 +1969,7 @@ impl App {
.proc_state .proc_state
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
proc_widget_state.table.set_last(); proc_widget_state.table.to_last();
} }
} }
BottomWidgetType::ProcSort => { BottomWidgetType::ProcSort => {
@ -1978,7 +1978,7 @@ impl App {
.proc_state .proc_state
.get_mut_widget_state(self.current_widget.widget_id - 2) .get_mut_widget_state(self.current_widget.widget_id - 2)
{ {
proc_widget_state.sort_table.set_last(); proc_widget_state.sort_table.to_last();
} }
} }
BottomWidgetType::Temp => { BottomWidgetType::Temp => {
@ -1987,7 +1987,7 @@ impl App {
.temp_state .temp_state
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
temp_widget_state.table.set_last(); temp_widget_state.table.to_last();
} }
} }
BottomWidgetType::Disk => { BottomWidgetType::Disk => {
@ -1997,7 +1997,7 @@ impl App {
.get_mut_widget_state(self.current_widget.widget_id) .get_mut_widget_state(self.current_widget.widget_id)
{ {
if !self.converted_data.disk_data.is_empty() { if !self.converted_data.disk_data.is_empty() {
disk_widget_state.table.set_last(); disk_widget_state.table.to_last();
} }
} }
} }
@ -2007,7 +2007,7 @@ 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.table.set_last(); cpu_widget_state.table.to_last();
} }
} }
_ => {} _ => {}

View file

@ -40,6 +40,7 @@ pub struct DataTable<DataType, Header, S = Unsortable, C = Column<Header>> {
data: Vec<DataType>, data: Vec<DataType>,
sort_type: S, sort_type: S,
first_draw: bool, first_draw: bool,
first_index: Option<usize>,
_pd: PhantomData<(DataType, S, Header)>, _pd: PhantomData<(DataType, S, Header)>,
} }
@ -55,6 +56,7 @@ impl<DataType: DataToCell<H>, H: ColumnHeader> DataTable<DataType, H, Unsortable
data: vec![], data: vec![],
sort_type: Unsortable, sort_type: Unsortable,
first_draw: true, first_draw: true,
first_index: None,
_pd: PhantomData, _pd: PhantomData,
} }
} }
@ -63,14 +65,20 @@ impl<DataType: DataToCell<H>, H: ColumnHeader> DataTable<DataType, H, Unsortable
impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H>> impl<DataType: DataToCell<H>, H: ColumnHeader, S: SortType, C: DataTableColumn<H>>
DataTable<DataType, H, S, C> DataTable<DataType, H, S, C>
{ {
/// Sets the default value selected on first initialization, if possible.
pub fn first_draw_index(mut self, first_index: usize) -> Self {
self.first_index = Some(first_index);
self
}
/// Sets the scroll position to the first value. /// Sets the scroll position to the first value.
pub fn set_first(&mut self) { pub fn to_first(&mut self) {
self.state.current_index = 0; self.state.current_index = 0;
self.state.scroll_direction = ScrollDirection::Up; self.state.scroll_direction = ScrollDirection::Up;
} }
/// Sets the scroll position to the last value. /// Sets the scroll position to the last value.
pub fn set_last(&mut self) { pub fn to_last(&mut self) {
self.state.current_index = self.data.len().saturating_sub(1); self.state.current_index = self.data.len().saturating_sub(1);
self.state.scroll_direction = ScrollDirection::Down; self.state.scroll_direction = ScrollDirection::Down;
} }
@ -189,11 +197,11 @@ mod test {
let mut table = DataTable::new(columns, props, styling); let mut table = DataTable::new(columns, props, styling);
table.set_data((0..=4).map(|index| TestType { index }).collect::<Vec<_>>()); table.set_data((0..=4).map(|index| TestType { index }).collect::<Vec<_>>());
table.set_last(); table.to_last();
assert_eq!(table.current_index(), 4); assert_eq!(table.current_index(), 4);
assert_eq!(table.state.scroll_direction, ScrollDirection::Down); assert_eq!(table.state.scroll_direction, ScrollDirection::Down);
table.set_first(); table.to_first();
assert_eq!(table.current_index(), 0); assert_eq!(table.current_index(), 0);
assert_eq!(table.state.scroll_direction, ScrollDirection::Up); assert_eq!(table.state.scroll_direction, ScrollDirection::Up);

View file

@ -200,10 +200,15 @@ where
self.props.table_gap self.props.table_gap
}; };
let columns = &self.columns;
if !self.data.is_empty() || !self.first_draw { if !self.data.is_empty() || !self.first_draw {
self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing custom no results/entries message) if self.first_draw {
self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing custom no results/entries message)
if let Some(first_index) = self.first_index {
self.set_position(first_index);
}
}
let columns = &self.columns;
let rows = { let rows = {
let num_rows = let num_rows =
usize::from(inner_height.saturating_sub(table_gap + header_height)); usize::from(inner_height.saturating_sub(table_gap + header_height));

View file

@ -260,6 +260,7 @@ where
order: props.order, order: props.order,
}, },
first_draw: true, first_draw: true,
first_index: None,
data: vec![], data: vec![],
_pd: PhantomData, _pd: PhantomData,
} }

View file

@ -598,6 +598,10 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU% # PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"] #columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMEM%", "GPU%"]
# [cpu]
# One of "all" (default), "average"/"avg"
# default = "average"
# These are all the components that support custom theming. Note that colour support # These are all the components that support custom theming. Note that colour support
# will depend on terminal support. # will depend on terminal support.
#[colors] # Uncomment if you want to use custom colors #[colors] # Uncomment if you want to use custom colors

View file

@ -28,8 +28,11 @@ use crate::{
pub mod layout_options; pub mod layout_options;
pub mod process_columns; mod process_columns;
use self::process_columns::ProcessConfig; pub use process_columns::ProcessConfig;
mod cpu;
pub use cpu::{CpuConfig, CpuDefault};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -43,6 +46,7 @@ pub struct Config {
pub temp_filter: Option<IgnoreList>, pub temp_filter: Option<IgnoreList>,
pub net_filter: Option<IgnoreList>, pub net_filter: Option<IgnoreList>,
pub processes: Option<ProcessConfig>, pub processes: Option<ProcessConfig>,
pub cpu: Option<CpuConfig>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -340,6 +344,11 @@ pub fn build_app(
widget.widget_id, widget.widget_id,
CpuWidgetState::new( CpuWidgetState::new(
&app_config_fields, &app_config_fields,
config
.cpu
.as_ref()
.map(|cfg| cfg.default)
.unwrap_or_default(),
default_time_value, default_time_value,
autohide_timer, autohide_timer,
styling, styling,

79
src/options/cpu.rs Normal file
View file

@ -0,0 +1,79 @@
use serde::Deserialize;
/// The default selection of the CPU widget. If the given selection is invalid, we will fall back to all.
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CpuDefault {
#[default]
All,
#[serde(alias = "avg")]
Average,
}
/// Process column settings.
#[derive(Clone, Debug, Default, Deserialize)]
pub struct CpuConfig {
#[serde(default)]
pub default: CpuDefault,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn default_cpu_default() {
let config = "";
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
match generated.default {
CpuDefault::All => {}
CpuDefault::Average => {
panic!("the default should be all")
}
}
}
#[test]
fn all_cpu_default() {
let config = r#"
default = "all"
"#;
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
match generated.default {
CpuDefault::All => {}
CpuDefault::Average => {
panic!("the default should be all")
}
}
}
#[test]
fn avg_cpu_default() {
let config = r#"
default = "avg"
"#;
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
match generated.default {
CpuDefault::All => {
panic!("the avg should be set")
}
CpuDefault::Average => {}
}
}
#[test]
fn average_cpu_default() {
let config = r#"
default = "average"
"#;
let generated: CpuConfig = toml_edit::de::from_str(config).unwrap();
match generated.default {
CpuDefault::All => {
panic!("the avg should be set")
}
CpuDefault::Average => {}
}
}
}

View file

@ -11,6 +11,7 @@ use crate::{
DataToCell, DataToCell,
}, },
data_conversion::CpuWidgetData, data_conversion::CpuWidgetData,
options::CpuDefault,
utils::gen_util::truncate_to_text, utils::gen_util::truncate_to_text,
}; };
@ -165,8 +166,8 @@ pub struct CpuWidgetState {
impl CpuWidgetState { impl CpuWidgetState {
pub fn new( pub fn new(
config: &AppConfigFields, current_display_time: u64, autohide_timer: Option<Instant>, config: &AppConfigFields, default_selection: CpuDefault, current_display_time: u64,
colours: &CanvasStyling, autohide_timer: Option<Instant>, colours: &CanvasStyling,
) -> Self { ) -> Self {
const COLUMNS: [Column<CpuWidgetColumn>; 2] = [ const COLUMNS: [Column<CpuWidgetColumn>; 2] = [
Column::soft(CpuWidgetColumn::CPU, Some(0.5)), Column::soft(CpuWidgetColumn::CPU, Some(0.5)),
@ -183,13 +184,21 @@ impl CpuWidgetState {
}; };
let styling = DataTableStyling::from_colours(colours); let styling = DataTableStyling::from_colours(colours);
let mut table = DataTable::new(COLUMNS, props, styling);
match default_selection {
CpuDefault::All => {}
CpuDefault::Average if !config.show_average_cpu => {}
CpuDefault::Average => {
table = table.first_draw_index(1);
}
}
CpuWidgetState { CpuWidgetState {
current_display_time, current_display_time,
is_legend_hidden: false, is_legend_hidden: false,
show_avg: config.show_average_cpu, show_avg: config.show_average_cpu,
autohide_timer, autohide_timer,
table: DataTable::new(COLUMNS, props, styling), table,
styling: CpuWidgetStyling::from_colours(colours), styling: CpuWidgetStyling::from_colours(colours),
} }
} }