Files
bottom/src/widgets/disk_table.rs
T
Clement Tsang 28972a1e64 refactor: refactor styling options (#1499)
Introduce a new configuration system for styling.
2024-07-29 09:03:35 +00:00

260 lines
8.5 KiB
Rust

use std::{borrow::Cow, cmp::max, num::NonZeroU16};
use crate::{
app::AppConfigFields,
canvas::components::data_table::{
ColumnHeader, DataTableColumn, DataTableProps, DataTableStyling, DataToCell, SortColumn,
SortDataTable, SortDataTableProps, SortOrder, SortsRow,
},
options::config::style::ColourPalette,
utils::{data_prefixes::get_decimal_bytes, general::sort_partial_fn},
};
#[derive(Clone, Debug)]
pub struct DiskWidgetData {
pub name: Cow<'static, str>,
pub mount_point: Cow<'static, str>,
pub free_bytes: Option<u64>,
pub used_bytes: Option<u64>,
pub total_bytes: Option<u64>,
pub summed_total_bytes: Option<u64>,
pub io_read: Cow<'static, str>,
pub io_write: Cow<'static, str>,
}
impl DiskWidgetData {
fn total_space(&self) -> Cow<'static, str> {
if let Some(total_bytes) = self.total_bytes {
let converted_total_space = get_decimal_bytes(total_bytes);
format!(
"{:.*}{}",
0, converted_total_space.0, converted_total_space.1
)
.into()
} else {
"N/A".into()
}
}
fn free_space(&self) -> Cow<'static, str> {
if let Some(free_bytes) = self.free_bytes {
let converted_free_space = get_decimal_bytes(free_bytes);
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into()
} else {
"N/A".into()
}
}
fn used_space(&self) -> Cow<'static, str> {
if let Some(used_bytes) = self.used_bytes {
let converted_free_space = get_decimal_bytes(used_bytes);
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into()
} else {
"N/A".into()
}
}
fn free_percent(&self) -> Option<f64> {
if let (Some(free_bytes), Some(summed_total_bytes)) =
(self.free_bytes, self.summed_total_bytes)
{
Some(free_bytes as f64 / summed_total_bytes as f64 * 100_f64)
} else {
None
}
}
fn free_percent_string(&self) -> Cow<'static, str> {
match self.free_percent() {
Some(val) => format!("{val:.1}%").into(),
None => "N/A".into(),
}
}
fn used_percent(&self) -> Option<f64> {
if let (Some(used_bytes), Some(summed_total_bytes)) =
(self.used_bytes, self.summed_total_bytes)
{
if summed_total_bytes > 0 {
Some(used_bytes as f64 / summed_total_bytes as f64 * 100_f64)
} else {
None
}
} else {
None
}
}
fn used_percent_string(&self) -> Cow<'static, str> {
match self.used_percent() {
Some(val) => format!("{val:.1}%").into(),
None => "N/A".into(),
}
}
}
pub enum DiskWidgetColumn {
Disk,
Mount,
Used,
Free,
Total,
UsedPercent,
FreePercent,
IoRead,
IoWrite,
}
impl ColumnHeader for DiskWidgetColumn {
fn text(&self) -> Cow<'static, str> {
match self {
DiskWidgetColumn::Disk => "Disk(d)",
DiskWidgetColumn::Mount => "Mount(m)",
DiskWidgetColumn::Used => "Used(u)",
DiskWidgetColumn::Free => "Free(n)",
DiskWidgetColumn::UsedPercent => "Used%(p)",
DiskWidgetColumn::FreePercent => "Free%",
DiskWidgetColumn::Total => "Total(t)",
DiskWidgetColumn::IoRead => "R/s(r)",
DiskWidgetColumn::IoWrite => "W/s(w)",
}
.into()
}
}
impl DataToCell<DiskWidgetColumn> for DiskWidgetData {
fn to_cell(
&self, column: &DiskWidgetColumn, _calculated_width: NonZeroU16,
) -> Option<Cow<'static, str>> {
let text = match column {
DiskWidgetColumn::Disk => self.name.clone(),
DiskWidgetColumn::Mount => self.mount_point.clone(),
DiskWidgetColumn::Used => self.used_space(),
DiskWidgetColumn::Free => self.free_space(),
DiskWidgetColumn::UsedPercent => self.used_percent_string(),
DiskWidgetColumn::FreePercent => self.free_percent_string(),
DiskWidgetColumn::Total => self.total_space(),
DiskWidgetColumn::IoRead => self.io_read.clone(),
DiskWidgetColumn::IoWrite => self.io_write.clone(),
};
Some(text)
}
fn column_widths<C: DataTableColumn<DiskWidgetColumn>>(
data: &[Self], _columns: &[C],
) -> Vec<u16>
where
Self: Sized,
{
let mut widths = vec![0; 7];
data.iter().for_each(|row| {
widths[0] = max(widths[0], row.name.len() as u16);
widths[1] = max(widths[1], row.mount_point.len() as u16);
});
widths
}
}
pub struct DiskTableWidget {
pub table: SortDataTable<DiskWidgetData, DiskWidgetColumn>,
pub force_update_data: bool,
}
impl SortsRow for DiskWidgetColumn {
type DataType = DiskWidgetData;
fn sort_data(&self, data: &mut [Self::DataType], descending: bool) {
match self {
DiskWidgetColumn::Disk => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.name, &b.name));
}
DiskWidgetColumn::Mount => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.mount_point, &b.mount_point));
}
DiskWidgetColumn::Used => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.used_bytes, &b.used_bytes));
}
DiskWidgetColumn::UsedPercent => {
data.sort_by(|a, b| {
sort_partial_fn(descending)(&a.used_percent(), &b.used_percent())
});
}
DiskWidgetColumn::Free => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.free_bytes, &b.free_bytes));
}
DiskWidgetColumn::FreePercent => {
data.sort_by(|a, b| {
sort_partial_fn(descending)(&a.free_percent(), &b.free_percent())
});
}
DiskWidgetColumn::Total => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.total_bytes, &b.total_bytes));
}
DiskWidgetColumn::IoRead => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_read, &b.io_read));
}
DiskWidgetColumn::IoWrite => {
data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_write, &b.io_write));
}
}
}
}
impl DiskTableWidget {
pub fn new(config: &AppConfigFields, palette: &ColourPalette) -> Self {
let columns = [
SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)),
SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)),
SortColumn::hard(DiskWidgetColumn::Used, 8).default_descending(),
SortColumn::hard(DiskWidgetColumn::Free, 8).default_descending(),
SortColumn::hard(DiskWidgetColumn::Total, 9).default_descending(),
SortColumn::hard(DiskWidgetColumn::UsedPercent, 9).default_descending(),
SortColumn::hard(DiskWidgetColumn::IoRead, 10).default_descending(),
SortColumn::hard(DiskWidgetColumn::IoWrite, 11).default_descending(),
];
let props = SortDataTableProps {
inner: DataTableProps {
title: Some(" Disks ".into()),
table_gap: config.table_gap,
left_to_right: true,
is_basic: config.use_basic_mode,
show_table_scroll_position: config.show_table_scroll_position,
show_current_entry_when_unfocused: false,
},
sort_index: 0,
order: SortOrder::Ascending,
};
let styling = DataTableStyling::from_palette(palette);
Self {
table: SortDataTable::new_sortable(columns, props, styling),
force_update_data: false,
}
}
/// Forces an update of the data stored.
#[inline]
pub fn force_data_update(&mut self) {
self.force_update_data = true;
}
/// Update the current table data.
pub fn set_table_data(&mut self, data: &[DiskWidgetData]) {
let mut data = data.to_vec();
if let Some(column) = self.table.columns.get(self.table.sort_index()) {
column.sort_by(&mut data, self.table.order());
}
self.table.set_data(data);
}
pub fn set_index(&mut self, index: usize) {
self.table.set_sort_index(index);
self.force_data_update();
}
}