mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-25 22:55:06 +02:00
refactor: separate out sorted and non-sorted text tables
This commit is contained in:
parent
6b69e373de
commit
b72e76aa71
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
app::{
|
||||
text_table::Column, DiskTable, MemGraph, NetGraph, OldNetGraph, ProcessManager, TempTable,
|
||||
sort_text_table::SortableColumn, DiskTable, MemGraph, NetGraph, OldNetGraph,
|
||||
ProcessManager, TempTable,
|
||||
},
|
||||
error::{BottomError, Result},
|
||||
options::layout_options::{Row, RowChildren},
|
||||
@ -14,7 +15,9 @@ use typed_builder::*;
|
||||
use crate::app::widgets::Widget;
|
||||
use crate::constants::DEFAULT_WIDGET_ID;
|
||||
|
||||
use super::{event::SelectionAction, CpuGraph, TextTable, TimeGraph, TmpBottomWidget, UsedWidgets};
|
||||
use super::{
|
||||
event::SelectionAction, CpuGraph, SortableTextTable, TimeGraph, TmpBottomWidget, UsedWidgets,
|
||||
};
|
||||
|
||||
/// Represents a more usable representation of the layout, derived from the
|
||||
/// config.
|
||||
@ -1058,9 +1061,9 @@ pub fn create_layout_tree(
|
||||
match widget_type {
|
||||
BottomWidgetType::Cpu => {
|
||||
let graph = TimeGraph::from_config(app_config_fields);
|
||||
let legend = TextTable::new(vec![
|
||||
Column::new_flex("CPU", None, false, 0.5),
|
||||
Column::new_flex("Use%", None, false, 0.5),
|
||||
let legend = SortableTextTable::new(vec![
|
||||
SortableColumn::new_flex("CPU".into(), None, false, 0.5),
|
||||
SortableColumn::new_flex("Use%".into(), None, false, 0.5),
|
||||
]);
|
||||
let legend_position = super::CpuGraphLegendPosition::Right;
|
||||
|
||||
|
@ -3,6 +3,9 @@
|
||||
pub mod text_table;
|
||||
pub use text_table::TextTable;
|
||||
|
||||
pub mod sort_text_table;
|
||||
pub use sort_text_table::SortableTextTable;
|
||||
|
||||
pub mod time_graph;
|
||||
pub use time_graph::TimeGraph;
|
||||
|
||||
|
275
src/app/widgets/base/sort_text_table.rs
Normal file
275
src/app/widgets/base/sort_text_table.rs
Normal file
@ -0,0 +1,275 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
||||
use tui::{
|
||||
layout::Rect,
|
||||
widgets::{Table, TableState},
|
||||
};
|
||||
|
||||
use crate::app::{event::EventResult, Component, TextTable};
|
||||
|
||||
use super::text_table::{DesiredColumnWidth, SimpleColumn, TableColumn};
|
||||
|
||||
fn get_shortcut_name(e: &KeyEvent) -> String {
|
||||
let modifier = if e.modifiers.is_empty() {
|
||||
""
|
||||
} else if let KeyModifiers::ALT = e.modifiers {
|
||||
"Alt+"
|
||||
} else if let KeyModifiers::SHIFT = e.modifiers {
|
||||
"Shift+"
|
||||
} else if let KeyModifiers::CONTROL = e.modifiers {
|
||||
"Ctrl+"
|
||||
} else {
|
||||
// For now, that's all we support, though combos/more could be added.
|
||||
""
|
||||
};
|
||||
|
||||
let key: Cow<'static, str> = match e.code {
|
||||
KeyCode::Backspace => "Backspace".into(),
|
||||
KeyCode::Enter => "Enter".into(),
|
||||
KeyCode::Left => "Left".into(),
|
||||
KeyCode::Right => "Right".into(),
|
||||
KeyCode::Up => "Up".into(),
|
||||
KeyCode::Down => "Down".into(),
|
||||
KeyCode::Home => "Home".into(),
|
||||
KeyCode::End => "End".into(),
|
||||
KeyCode::PageUp => "PgUp".into(),
|
||||
KeyCode::PageDown => "PgDown".into(),
|
||||
KeyCode::Tab => "Tab".into(),
|
||||
KeyCode::BackTab => "BackTab".into(),
|
||||
KeyCode::Delete => "Del".into(),
|
||||
KeyCode::Insert => "Insert".into(),
|
||||
KeyCode::F(num) => format!("F{}", num).into(),
|
||||
KeyCode::Char(c) => format!("{}", c).into(),
|
||||
KeyCode::Null => "Null".into(),
|
||||
KeyCode::Esc => "Esc".into(),
|
||||
};
|
||||
|
||||
format!("({}{})", modifier, key).into()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SortStatus {
|
||||
NotSorting,
|
||||
SortAscending,
|
||||
SortDescending,
|
||||
}
|
||||
|
||||
/// A [`SortableColumn`] represents some column in a [`SortableTextTable`].
|
||||
#[derive(Debug)]
|
||||
pub struct SortableColumn {
|
||||
pub shortcut: Option<(KeyEvent, String)>,
|
||||
pub default_descending: bool,
|
||||
pub internal: SimpleColumn,
|
||||
sorting: SortStatus,
|
||||
}
|
||||
|
||||
impl SortableColumn {
|
||||
/// Creates a new [`SortableColumn`].
|
||||
fn new(
|
||||
shortcut_name: Cow<'static, str>, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
desired_width: DesiredColumnWidth,
|
||||
) -> Self {
|
||||
let shortcut = shortcut.map(|e| (e, get_shortcut_name(&e)));
|
||||
Self {
|
||||
shortcut,
|
||||
default_descending,
|
||||
internal: SimpleColumn::new(shortcut_name, desired_width),
|
||||
sorting: SortStatus::NotSorting,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`SortableColumn`] with a hard desired width. If none is specified,
|
||||
/// it will instead use the name's length + 1.
|
||||
pub fn new_hard(
|
||||
name: Cow<'static, str>, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
hard_length: Option<u16>,
|
||||
) -> Self {
|
||||
let shortcut_name = if let Some(shortcut) = shortcut {
|
||||
get_shortcut_name(&shortcut).into()
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let shortcut_name_len = shortcut_name.len();
|
||||
|
||||
SortableColumn::new(
|
||||
shortcut_name,
|
||||
shortcut,
|
||||
default_descending,
|
||||
DesiredColumnWidth::Hard(hard_length.unwrap_or(shortcut_name_len as u16 + 1)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new [`SortableColumn`] with a flexible desired width.
|
||||
pub fn new_flex(
|
||||
name: Cow<'static, str>, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
max_percentage: f64,
|
||||
) -> Self {
|
||||
let shortcut_name = if let Some(shortcut) = shortcut {
|
||||
get_shortcut_name(&shortcut).into()
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let shortcut_name_len = shortcut_name.len();
|
||||
|
||||
SortableColumn::new(
|
||||
shortcut_name,
|
||||
shortcut,
|
||||
default_descending,
|
||||
DesiredColumnWidth::Flex {
|
||||
desired: shortcut_name_len as u16,
|
||||
max_percentage,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableColumn for SortableColumn {
|
||||
fn display_name(&self) -> Cow<'static, str> {
|
||||
const UP_ARROW: &'static str = "▲";
|
||||
const DOWN_ARROW: &'static str = "▼";
|
||||
format!(
|
||||
"{}{}",
|
||||
self.internal.display_name(),
|
||||
match &self.sorting {
|
||||
SortStatus::NotSorting => "",
|
||||
SortStatus::SortAscending => UP_ARROW,
|
||||
SortStatus::SortDescending => DOWN_ARROW,
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn get_desired_width(&self) -> &DesiredColumnWidth {
|
||||
self.internal.get_desired_width()
|
||||
}
|
||||
|
||||
fn get_x_bounds(&self) -> Option<(u16, u16)> {
|
||||
self.internal.get_x_bounds()
|
||||
}
|
||||
|
||||
fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>) {
|
||||
self.internal.set_x_bounds(x_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
/// A sortable, scrollable table with columns.
|
||||
pub struct SortableTextTable {
|
||||
/// Which index we're sorting by.
|
||||
sort_index: usize,
|
||||
|
||||
/// The underlying [`TextTable`].
|
||||
pub table: TextTable<SortableColumn>,
|
||||
}
|
||||
|
||||
impl SortableTextTable {
|
||||
pub fn new(columns: Vec<SortableColumn>) -> Self {
|
||||
let mut st = Self {
|
||||
sort_index: 0,
|
||||
table: TextTable::new(columns),
|
||||
};
|
||||
st.set_sort_index(0);
|
||||
st
|
||||
}
|
||||
|
||||
pub fn default_ltr(mut self, ltr: bool) -> Self {
|
||||
self.table = self.table.default_ltr(ltr);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_sort_index(mut self, index: usize) -> Self {
|
||||
self.set_sort_index(index);
|
||||
self
|
||||
}
|
||||
|
||||
fn set_sort_index(&mut self, new_index: usize) {
|
||||
if new_index == self.sort_index {
|
||||
if let Some(column) = self.table.columns.get_mut(self.sort_index) {
|
||||
match column.sorting {
|
||||
SortStatus::NotSorting => {
|
||||
if column.default_descending {
|
||||
column.sorting = SortStatus::SortDescending;
|
||||
} else {
|
||||
column.sorting = SortStatus::SortAscending;
|
||||
}
|
||||
}
|
||||
SortStatus::SortAscending => {
|
||||
column.sorting = SortStatus::SortDescending;
|
||||
}
|
||||
SortStatus::SortDescending => {
|
||||
column.sorting = SortStatus::SortAscending;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(column) = self.table.columns.get_mut(self.sort_index) {
|
||||
column.sorting = SortStatus::NotSorting;
|
||||
}
|
||||
|
||||
if let Some(column) = self.table.columns.get_mut(new_index) {
|
||||
if column.default_descending {
|
||||
column.sorting = SortStatus::SortDescending;
|
||||
} else {
|
||||
column.sorting = SortStatus::SortAscending;
|
||||
}
|
||||
}
|
||||
|
||||
self.sort_index = new_index;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Table`] representing the sort list.
|
||||
pub fn create_sort_list(&mut self) -> (Table<'_>, TableState) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SortableTextTable {
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
for (index, column) in self.table.columns.iter().enumerate() {
|
||||
if let Some((shortcut, _)) = column.shortcut {
|
||||
if shortcut == event {
|
||||
self.set_sort_index(index);
|
||||
return EventResult::Redraw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.table.scrollable.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
|
||||
if !self.does_intersect_mouse(&event) {
|
||||
return EventResult::NoRedraw;
|
||||
}
|
||||
|
||||
// Note these are representing RELATIVE coordinates! They *need* the above intersection check for validity!
|
||||
let x = event.column - self.table.bounds.left();
|
||||
let y = event.row - self.table.bounds.top();
|
||||
|
||||
if y == 0 {
|
||||
for (index, column) in self.table.columns.iter().enumerate() {
|
||||
if let Some((start, end)) = column.internal.get_x_bounds() {
|
||||
if x >= start && x <= end {
|
||||
self.set_sort_index(index);
|
||||
return EventResult::Redraw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.table.scrollable.handle_mouse_event(event)
|
||||
} else {
|
||||
self.table.scrollable.handle_mouse_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
self.table.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.table.bounds = new_bounds;
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{max, min, Ordering},
|
||||
cmp::{max, min},
|
||||
};
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::{
|
||||
layout::{Constraint, Rect},
|
||||
text::Text,
|
||||
@ -24,110 +24,79 @@ pub enum DesiredColumnWidth {
|
||||
Flex { desired: u16, max_percentage: f64 },
|
||||
}
|
||||
|
||||
/// A [`ColumnType`] is a
|
||||
pub trait ColumnType {
|
||||
type DataType;
|
||||
/// A trait that must be implemented for anything using a [`TextTable`].
|
||||
#[allow(unused_variables)]
|
||||
pub trait TableColumn {
|
||||
fn display_name(&self) -> Cow<'static, str>;
|
||||
|
||||
fn sort_function(a: Self::DataType, b: Self::DataType) -> Ordering;
|
||||
fn get_desired_width(&self) -> &DesiredColumnWidth;
|
||||
|
||||
fn get_x_bounds(&self) -> Option<(u16, u16)>;
|
||||
|
||||
fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>);
|
||||
}
|
||||
|
||||
/// A [`Column`] represents some column in a [`TextTable`].
|
||||
/// A [`SimpleColumn`] represents some column in a [`TextTable`].
|
||||
#[derive(Debug)]
|
||||
pub struct Column {
|
||||
pub name: &'static str,
|
||||
pub shortcut: Option<(KeyEvent, String)>,
|
||||
pub default_descending: bool,
|
||||
pub struct SimpleColumn {
|
||||
name: Cow<'static, str>,
|
||||
|
||||
// TODO: I would remove these in the future, storing them here feels weird...
|
||||
pub desired_width: DesiredColumnWidth,
|
||||
pub x_bounds: Option<(u16, u16)>,
|
||||
desired_width: DesiredColumnWidth,
|
||||
x_bounds: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
/// Creates a new [`Column`].
|
||||
pub fn new(
|
||||
name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
desired_width: DesiredColumnWidth,
|
||||
) -> Self {
|
||||
impl SimpleColumn {
|
||||
/// Creates a new [`SimpleColumn`].
|
||||
pub fn new(name: Cow<'static, str>, desired_width: DesiredColumnWidth) -> Self {
|
||||
Self {
|
||||
name,
|
||||
x_bounds: None,
|
||||
shortcut: shortcut.map(|e| {
|
||||
let modifier = if e.modifiers.is_empty() {
|
||||
""
|
||||
} else if let KeyModifiers::ALT = e.modifiers {
|
||||
"Alt+"
|
||||
} else if let KeyModifiers::SHIFT = e.modifiers {
|
||||
"Shift+"
|
||||
} else if let KeyModifiers::CONTROL = e.modifiers {
|
||||
"Ctrl+"
|
||||
} else {
|
||||
// For now, that's all we support, though combos/more could be added.
|
||||
""
|
||||
};
|
||||
|
||||
let key: Cow<'static, str> = match e.code {
|
||||
KeyCode::Backspace => "Backspace".into(),
|
||||
KeyCode::Enter => "Enter".into(),
|
||||
KeyCode::Left => "Left".into(),
|
||||
KeyCode::Right => "Right".into(),
|
||||
KeyCode::Up => "Up".into(),
|
||||
KeyCode::Down => "Down".into(),
|
||||
KeyCode::Home => "Home".into(),
|
||||
KeyCode::End => "End".into(),
|
||||
KeyCode::PageUp => "PgUp".into(),
|
||||
KeyCode::PageDown => "PgDown".into(),
|
||||
KeyCode::Tab => "Tab".into(),
|
||||
KeyCode::BackTab => "BackTab".into(),
|
||||
KeyCode::Delete => "Del".into(),
|
||||
KeyCode::Insert => "Insert".into(),
|
||||
KeyCode::F(num) => format!("F{}", num).into(),
|
||||
KeyCode::Char(c) => format!("{}", c).into(),
|
||||
KeyCode::Null => "Null".into(),
|
||||
KeyCode::Esc => "Esc".into(),
|
||||
};
|
||||
|
||||
let shortcut_name = format!("({}{})", modifier, key);
|
||||
|
||||
(e, shortcut_name)
|
||||
}),
|
||||
default_descending,
|
||||
desired_width,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Column`] with a hard desired width. If none is specified,
|
||||
/// Creates a new [`SimpleColumn`] with a hard desired width. If none is specified,
|
||||
/// it will instead use the name's length + 1.
|
||||
pub fn new_hard(
|
||||
name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
hard_length: Option<u16>,
|
||||
) -> Self {
|
||||
// TODO: It should really be based on the shortcut name...
|
||||
Column::new(
|
||||
pub fn new_hard(name: Cow<'static, str>, hard_length: Option<u16>) -> Self {
|
||||
let name_len = name.len();
|
||||
SimpleColumn::new(
|
||||
name,
|
||||
shortcut,
|
||||
default_descending,
|
||||
DesiredColumnWidth::Hard(hard_length.unwrap_or(name.len() as u16 + 1)),
|
||||
DesiredColumnWidth::Hard(hard_length.unwrap_or(name_len as u16 + 1)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new [`Column`] with a flexible desired width.
|
||||
pub fn new_flex(
|
||||
name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||
max_percentage: f64,
|
||||
) -> Self {
|
||||
Column::new(
|
||||
/// Creates a new [`SimpleColumn`] with a flexible desired width.
|
||||
pub fn new_flex(name: Cow<'static, str>, max_percentage: f64) -> Self {
|
||||
let name_len = name.len();
|
||||
SimpleColumn::new(
|
||||
name,
|
||||
shortcut,
|
||||
default_descending,
|
||||
DesiredColumnWidth::Flex {
|
||||
desired: name.len() as u16,
|
||||
desired: name_len as u16,
|
||||
max_percentage,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableColumn for SimpleColumn {
|
||||
fn display_name(&self) -> Cow<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn get_desired_width(&self) -> &DesiredColumnWidth {
|
||||
&self.desired_width
|
||||
}
|
||||
|
||||
fn get_x_bounds(&self) -> Option<(u16, u16)> {
|
||||
self.x_bounds
|
||||
}
|
||||
|
||||
fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>) {
|
||||
self.x_bounds = x_bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum CachedColumnWidths {
|
||||
Uncached,
|
||||
@ -138,47 +107,45 @@ enum CachedColumnWidths {
|
||||
}
|
||||
|
||||
/// A sortable, scrollable table with columns.
|
||||
pub struct TextTable {
|
||||
pub struct TextTable<C = SimpleColumn>
|
||||
where
|
||||
C: TableColumn,
|
||||
{
|
||||
/// Controls the scrollable state.
|
||||
scrollable: Scrollable,
|
||||
pub scrollable: Scrollable,
|
||||
|
||||
/// The columns themselves.
|
||||
columns: Vec<Column>,
|
||||
pub columns: Vec<C>,
|
||||
|
||||
/// Cached column width data.
|
||||
cached_column_widths: CachedColumnWidths,
|
||||
|
||||
/// Whether to show a gap between the column headers and the columns.
|
||||
show_gap: bool,
|
||||
pub show_gap: bool,
|
||||
|
||||
/// The bounding box of the [`TextTable`].
|
||||
bounds: Rect, // TODO: Consider moving bounds to something else???
|
||||
|
||||
/// Which index we're sorting by.
|
||||
sort_index: usize,
|
||||
|
||||
/// Whether we're sorting by ascending order.
|
||||
sort_ascending: bool,
|
||||
pub bounds: Rect, // TODO: Consider moving bounds to something else???
|
||||
|
||||
/// Whether we draw columns from left-to-right.
|
||||
left_to_right: bool,
|
||||
pub left_to_right: bool,
|
||||
}
|
||||
|
||||
impl TextTable {
|
||||
pub fn new(columns: Vec<Column>) -> Self {
|
||||
impl<C> TextTable<C>
|
||||
where
|
||||
C: TableColumn,
|
||||
{
|
||||
pub fn new(columns: Vec<C>) -> Self {
|
||||
Self {
|
||||
scrollable: Scrollable::new(0),
|
||||
columns,
|
||||
cached_column_widths: CachedColumnWidths::Uncached,
|
||||
show_gap: true,
|
||||
bounds: Rect::default(),
|
||||
sort_index: 0,
|
||||
sort_ascending: true,
|
||||
left_to_right: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn left_to_right(mut self, ltr: bool) -> Self {
|
||||
pub fn default_ltr(mut self, ltr: bool) -> Self {
|
||||
self.left_to_right = ltr;
|
||||
self
|
||||
}
|
||||
@ -188,50 +155,10 @@ impl TextTable {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn sort_index(mut self, sort_index: usize) -> Self {
|
||||
self.sort_index = sort_index;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn column_names(&self) -> Vec<&'static str> {
|
||||
self.columns.iter().map(|column| column.name).collect()
|
||||
}
|
||||
|
||||
pub fn sorted_column_names(&self) -> Vec<String> {
|
||||
const UP_ARROW: char = '▲';
|
||||
const DOWN_ARROW: char = '▼';
|
||||
|
||||
pub fn displayed_column_names(&self) -> Vec<Cow<'static, str>> {
|
||||
self.columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, column)| {
|
||||
if index == self.sort_index {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
column.name,
|
||||
if let Some(shortcut) = &column.shortcut {
|
||||
shortcut.1.as_str()
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if self.sort_ascending {
|
||||
UP_ARROW
|
||||
} else {
|
||||
DOWN_ARROW
|
||||
}
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
column.name,
|
||||
if let Some(shortcut) = &column.shortcut {
|
||||
shortcut.1.as_str()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
.map(|column| column.display_name())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -239,19 +166,19 @@ impl TextTable {
|
||||
self.scrollable.update_num_items(num_items);
|
||||
}
|
||||
|
||||
pub fn update_a_column(&mut self, index: usize, column: Column) {
|
||||
pub fn update_single_column(&mut self, index: usize, column: C) {
|
||||
if let Some(c) = self.columns.get_mut(index) {
|
||||
*c = column;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_desired_column_widths(
|
||||
columns: &[Column], data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
|
||||
columns: &[C], data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
|
||||
) -> Vec<DesiredColumnWidth> {
|
||||
columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(column_index, c)| match c.desired_width {
|
||||
.map(|(column_index, c)| match c.get_desired_width() {
|
||||
DesiredColumnWidth::Hard(width) => {
|
||||
let max_len = data
|
||||
.iter()
|
||||
@ -274,12 +201,12 @@ impl TextTable {
|
||||
.map(|(s, _)| s.len())
|
||||
.unwrap_or(0) as u16;
|
||||
|
||||
DesiredColumnWidth::Hard(max(max_len, width))
|
||||
DesiredColumnWidth::Hard(max(max_len, *width))
|
||||
}
|
||||
DesiredColumnWidth::Flex {
|
||||
desired: _,
|
||||
max_percentage: _,
|
||||
} => c.desired_width.clone(),
|
||||
} => c.get_desired_width().clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@ -390,7 +317,7 @@ impl TextTable {
|
||||
let mut column_start = 0;
|
||||
for (column, width) in self.columns.iter_mut().zip(&column_widths) {
|
||||
let column_end = column_start + *width;
|
||||
column.x_bounds = Some((column_start, column_end));
|
||||
column.set_x_bounds(Some((column_start, column_end)));
|
||||
column_start = column_end + 1;
|
||||
}
|
||||
}
|
||||
@ -468,7 +395,7 @@ impl TextTable {
|
||||
});
|
||||
|
||||
// Now build up our headers...
|
||||
let header = Row::new(self.sorted_column_names())
|
||||
let header = Row::new(self.displayed_column_names())
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap);
|
||||
|
||||
@ -483,59 +410,23 @@ impl TextTable {
|
||||
tui_state,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`Table`] representing the sort list.
|
||||
pub fn create_sort_list(&mut self) -> (Table<'_>, TableState) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TextTable {
|
||||
impl<C> Component for TextTable<C>
|
||||
where
|
||||
C: TableColumn,
|
||||
{
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
|
||||
for (index, column) in self.columns.iter().enumerate() {
|
||||
if let Some((shortcut, _)) = column.shortcut {
|
||||
if shortcut == event {
|
||||
if self.sort_index == index {
|
||||
// Just flip the sort if we're already sorting by this.
|
||||
self.sort_ascending = !self.sort_ascending;
|
||||
} else {
|
||||
self.sort_index = index;
|
||||
self.sort_ascending = !column.default_descending;
|
||||
}
|
||||
return EventResult::Redraw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollable.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
|
||||
if !self.does_intersect_mouse(&event) {
|
||||
return EventResult::NoRedraw;
|
||||
}
|
||||
|
||||
// Note these are representing RELATIVE coordinates! They *need* the above intersection check for validity!
|
||||
let x = event.column - self.bounds.left();
|
||||
let y = event.row - self.bounds.top();
|
||||
|
||||
if y == 0 {
|
||||
for (index, column) in self.columns.iter().enumerate() {
|
||||
if let Some((start, end)) = column.x_bounds {
|
||||
if x >= start && x <= end {
|
||||
if self.sort_index == index {
|
||||
// Just flip the sort if we're already sorting by this.
|
||||
self.sort_ascending = !self.sort_ascending;
|
||||
} else {
|
||||
self.sort_index = index;
|
||||
self.sort_ascending = !column.default_descending;
|
||||
}
|
||||
return EventResult::Redraw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollable.handle_mouse_event(event)
|
||||
} else {
|
||||
self.scrollable.handle_mouse_event(event)
|
||||
}
|
||||
self.scrollable.handle_mouse_event(event)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect {
|
||||
|
@ -5,7 +5,9 @@ use tui::layout::Rect;
|
||||
|
||||
use crate::app::event::EventResult;
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, TimeGraph, Widget};
|
||||
use super::{
|
||||
AppScrollWidgetState, CanvasTableWidthState, Component, SortableTextTable, TimeGraph, Widget,
|
||||
};
|
||||
|
||||
pub struct CpuWidgetState {
|
||||
pub current_display_time: u64,
|
||||
@ -67,7 +69,7 @@ pub enum CpuGraphLegendPosition {
|
||||
/// A widget designed to show CPU usage via a graph, along with a side legend implemented as a [`TextTable`].
|
||||
pub struct CpuGraph {
|
||||
graph: TimeGraph,
|
||||
legend: TextTable,
|
||||
legend: SortableTextTable,
|
||||
pub legend_position: CpuGraphLegendPosition,
|
||||
|
||||
bounds: Rect,
|
||||
@ -77,7 +79,7 @@ pub struct CpuGraph {
|
||||
impl CpuGraph {
|
||||
/// Creates a new [`CpuGraph`].
|
||||
pub fn new(
|
||||
graph: TimeGraph, legend: TextTable, legend_position: CpuGraphLegendPosition,
|
||||
graph: TimeGraph, legend: SortableTextTable, legend_position: CpuGraphLegendPosition,
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
|
@ -9,11 +9,11 @@ use tui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{event::EventResult, text_table::Column},
|
||||
app::{event::EventResult, sort_text_table::SortableColumn},
|
||||
canvas::{DisplayableData, Painter},
|
||||
};
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget};
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, SortableTextTable, Widget};
|
||||
|
||||
pub struct DiskWidgetState {
|
||||
pub scroll_state: AppScrollWidgetState,
|
||||
@ -50,20 +50,20 @@ impl DiskState {
|
||||
|
||||
/// A table displaying disk data. Essentially a wrapper around a [`TextTable`].
|
||||
pub struct DiskTable {
|
||||
table: TextTable,
|
||||
table: SortableTextTable,
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl Default for DiskTable {
|
||||
fn default() -> Self {
|
||||
let table = TextTable::new(vec![
|
||||
Column::new_flex("Disk", None, false, 0.2),
|
||||
Column::new_flex("Mount", None, false, 0.2),
|
||||
Column::new_hard("Used", None, false, Some(4)),
|
||||
Column::new_hard("Free", None, false, Some(6)),
|
||||
Column::new_hard("Total", None, false, Some(6)),
|
||||
Column::new_hard("R/s", None, false, Some(7)),
|
||||
Column::new_hard("W/s", None, false, Some(7)),
|
||||
let table = SortableTextTable::new(vec![
|
||||
SortableColumn::new_flex("Disk".into(), None, false, 0.2),
|
||||
SortableColumn::new_flex("Mount".into(), None, false, 0.2),
|
||||
SortableColumn::new_hard("Used".into(), None, false, Some(5)),
|
||||
SortableColumn::new_hard("Free".into(), None, false, Some(6)),
|
||||
SortableColumn::new_hard("Total".into(), None, false, Some(6)),
|
||||
SortableColumn::new_hard("R/s".into(), None, false, Some(7)),
|
||||
SortableColumn::new_hard("W/s".into(), None, false, Some(7)),
|
||||
]);
|
||||
|
||||
Self {
|
||||
@ -112,6 +112,7 @@ impl Widget for DiskTable {
|
||||
let draw_area = block.inner(area);
|
||||
let (table, widths, mut tui_state) =
|
||||
self.table
|
||||
.table
|
||||
.create_draw_table(painter, &data.disk_data, draw_area);
|
||||
|
||||
let table = table.highlight_style(if selected {
|
||||
|
@ -23,7 +23,7 @@ use ProcessSorting::*;
|
||||
|
||||
use super::{
|
||||
AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection,
|
||||
TextInput, TextTable, Widget,
|
||||
SortableTextTable, TextInput, TextTable, Widget,
|
||||
};
|
||||
|
||||
/// AppSearchState deals with generic searching (I might do this in the future).
|
||||
@ -640,7 +640,7 @@ struct SearchModifiers {
|
||||
/// A searchable, sortable table to manage processes.
|
||||
pub struct ProcessManager {
|
||||
bounds: Rect,
|
||||
process_table: TextTable,
|
||||
process_table: SortableTextTable,
|
||||
sort_table: TextTable,
|
||||
search_input: TextInput,
|
||||
|
||||
@ -662,8 +662,8 @@ impl ProcessManager {
|
||||
|
||||
let mut manager = Self {
|
||||
bounds: Rect::default(),
|
||||
process_table: TextTable::new(process_table_columns), // TODO: Do this
|
||||
sort_table: TextTable::new(vec![]), // TODO: Do this too
|
||||
process_table: SortableTextTable::new(process_table_columns), // TODO: Do this
|
||||
sort_table: TextTable::new(vec![]), // TODO: Do this too
|
||||
search_input: TextInput::new(),
|
||||
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
|
||||
selected: ProcessManagerSelection::Processes,
|
||||
@ -854,7 +854,7 @@ impl Widget for ProcessManager {
|
||||
|
||||
self.set_bounds(area);
|
||||
let draw_area = block.inner(area);
|
||||
let (process_table, widths, mut tui_state) = self.process_table.create_draw_table(
|
||||
let (process_table, widths, mut tui_state) = self.process_table.table.create_draw_table(
|
||||
painter,
|
||||
&vec![], // TODO: Fix this
|
||||
draw_area,
|
||||
|
@ -9,11 +9,11 @@ use tui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{event::EventResult, text_table::Column},
|
||||
app::{event::EventResult, sort_text_table::SortableColumn},
|
||||
canvas::{DisplayableData, Painter},
|
||||
};
|
||||
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget};
|
||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, SortableTextTable, Widget};
|
||||
|
||||
pub struct TempWidgetState {
|
||||
pub scroll_state: AppScrollWidgetState,
|
||||
@ -50,17 +50,17 @@ impl TempState {
|
||||
|
||||
/// A table displaying disk data. Essentially a wrapper around a [`TextTable`].
|
||||
pub struct TempTable {
|
||||
table: TextTable,
|
||||
table: SortableTextTable,
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl Default for TempTable {
|
||||
fn default() -> Self {
|
||||
let table = TextTable::new(vec![
|
||||
Column::new_flex("Sensor", None, false, 0.8),
|
||||
Column::new_hard("Temp", None, false, Some(4)),
|
||||
let table = SortableTextTable::new(vec![
|
||||
SortableColumn::new_flex("Sensor".into(), None, false, 0.8),
|
||||
SortableColumn::new_hard("Temp".into(), None, false, Some(5)),
|
||||
])
|
||||
.left_to_right(false);
|
||||
.default_ltr(false);
|
||||
|
||||
Self {
|
||||
table,
|
||||
@ -108,6 +108,7 @@ impl Widget for TempTable {
|
||||
let draw_area = block.inner(area);
|
||||
let (table, widths, mut tui_state) =
|
||||
self.table
|
||||
.table
|
||||
.create_draw_table(painter, &data.temp_sensor_data, draw_area);
|
||||
|
||||
let table = table.highlight_style(if selected {
|
||||
|
Loading…
x
Reference in New Issue
Block a user