mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-23 13:45:12 +02:00
refactor: Add sort capabilities to processes
This commit is contained in:
parent
3fa50605b3
commit
27736b7fc0
@ -17,3 +17,6 @@ pub use text_input::TextInput;
|
||||
|
||||
pub mod carousel;
|
||||
pub use carousel::Carousel;
|
||||
|
||||
pub mod sort_menu;
|
||||
pub use sort_menu::SortMenu;
|
||||
|
@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEve
|
||||
use tui::{layout::Rect, widgets::TableState};
|
||||
|
||||
use crate::app::{
|
||||
event::{WidgetEventResult, MultiKey, MultiKeyResult},
|
||||
event::{MultiKey, MultiKeyResult, WidgetEventResult},
|
||||
Component,
|
||||
};
|
||||
|
||||
@ -110,7 +110,7 @@ impl Scrollable {
|
||||
}
|
||||
|
||||
/// Update the index with this! This will automatically update the scroll direction as well!
|
||||
fn update_index(&mut self, new_index: usize) {
|
||||
pub fn set_index(&mut self, new_index: usize) {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match new_index.cmp(&self.current_index) {
|
||||
@ -130,7 +130,7 @@ impl Scrollable {
|
||||
|
||||
fn skip_to_first(&mut self) -> WidgetEventResult {
|
||||
if self.current_index != 0 {
|
||||
self.update_index(0);
|
||||
self.set_index(0);
|
||||
|
||||
WidgetEventResult::Redraw
|
||||
} else {
|
||||
@ -141,7 +141,7 @@ impl Scrollable {
|
||||
fn skip_to_last(&mut self) -> WidgetEventResult {
|
||||
let last_index = self.num_items - 1;
|
||||
if self.current_index != last_index {
|
||||
self.update_index(last_index);
|
||||
self.set_index(last_index);
|
||||
|
||||
WidgetEventResult::Redraw
|
||||
} else {
|
||||
@ -161,7 +161,7 @@ impl Scrollable {
|
||||
} else if self.current_index == new_index {
|
||||
WidgetEventResult::NoRedraw
|
||||
} else {
|
||||
self.update_index(new_index);
|
||||
self.set_index(new_index);
|
||||
WidgetEventResult::Redraw
|
||||
}
|
||||
}
|
||||
@ -176,12 +176,12 @@ impl Scrollable {
|
||||
if self.current_index == new_index {
|
||||
WidgetEventResult::NoRedraw
|
||||
} else {
|
||||
self.update_index(new_index);
|
||||
self.set_index(new_index);
|
||||
WidgetEventResult::Redraw
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_num_items(&mut self, num_items: usize) {
|
||||
pub fn set_num_items(&mut self, num_items: usize) {
|
||||
self.num_items = num_items;
|
||||
|
||||
if num_items <= self.current_index {
|
||||
|
76
src/app/widgets/base/sort_menu.rs
Normal file
76
src/app/widgets/base/sort_menu.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tui::{backend::Backend, layout::Rect, widgets::Block, Frame};
|
||||
|
||||
use crate::{
|
||||
app::{event::WidgetEventResult, text_table::SimpleColumn, Component, TextTable},
|
||||
canvas::Painter,
|
||||
};
|
||||
|
||||
use super::sort_text_table::SortableColumn;
|
||||
|
||||
/// A sortable, scrollable table with columns.
|
||||
pub struct SortMenu {
|
||||
/// The underlying table.
|
||||
table: TextTable,
|
||||
|
||||
/// The bounds.
|
||||
bounds: Rect,
|
||||
}
|
||||
|
||||
impl SortMenu {
|
||||
/// Creates a new [`SortMenu`].
|
||||
pub fn new(num_columns: usize) -> Self {
|
||||
let sort_menu_columns = vec![SimpleColumn::new_hard("Sort By".into(), None)];
|
||||
let mut sort_menu = TextTable::new(sort_menu_columns);
|
||||
sort_menu.set_num_items(num_columns);
|
||||
|
||||
Self {
|
||||
table: sort_menu,
|
||||
bounds: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the index of the [`SortMenu`].
|
||||
pub fn set_index(&mut self, index: usize) {
|
||||
self.table.scrollable.set_index(index);
|
||||
}
|
||||
|
||||
/// Returns the current index of the [`SortMenu`].
|
||||
pub fn current_index(&mut self) -> usize {
|
||||
self.table.scrollable.current_index()
|
||||
}
|
||||
|
||||
/// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
|
||||
pub fn draw_sort_menu<B: Backend, C: SortableColumn>(
|
||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, columns: &[C], block: Block<'_>,
|
||||
block_area: Rect,
|
||||
) {
|
||||
self.set_bounds(block_area);
|
||||
|
||||
let data = columns
|
||||
.iter()
|
||||
.map(|c| vec![(c.original_name().clone().into(), None, None)])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.table
|
||||
.draw_tui_table(painter, f, &data, block, block_area, true);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SortMenu {
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
|
||||
self.table.handle_key_event(event)
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult {
|
||||
self.table.handle_mouse_event(event)
|
||||
}
|
||||
}
|
@ -257,9 +257,6 @@ where
|
||||
|
||||
/// The underlying [`TextTable`].
|
||||
pub table: TextTable<S>,
|
||||
|
||||
/// A corresponding "sort" menu.
|
||||
pub sort_menu: TextTable,
|
||||
}
|
||||
|
||||
impl<S> SortableTextTable<S>
|
||||
@ -268,15 +265,9 @@ where
|
||||
{
|
||||
/// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty.
|
||||
pub fn new(columns: Vec<S>) -> Self {
|
||||
let sort_menu_columns = columns
|
||||
.iter()
|
||||
.map(|column| SimpleColumn::new_hard(column.original_name().clone(), None))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut st = Self {
|
||||
sort_index: 0,
|
||||
table: TextTable::new(columns),
|
||||
sort_menu: TextTable::new(sort_menu_columns),
|
||||
};
|
||||
st.set_sort_index(0);
|
||||
st
|
||||
@ -296,11 +287,16 @@ where
|
||||
self.table.current_scroll_index()
|
||||
}
|
||||
|
||||
/// Returns the current column.
|
||||
pub fn current_column(&self) -> &S {
|
||||
/// Returns the current column the table is sorting by.
|
||||
pub fn current_sorting_column(&self) -> &S {
|
||||
&self.table.columns[self.sort_index]
|
||||
}
|
||||
|
||||
/// Returns the current column index the table is sorting by.
|
||||
pub fn current_sorting_column_index(&self) -> usize {
|
||||
self.sort_index
|
||||
}
|
||||
|
||||
pub fn columns(&self) -> &[S] {
|
||||
&self.table.columns
|
||||
}
|
||||
@ -309,7 +305,7 @@ where
|
||||
self.table.set_column(index, column)
|
||||
}
|
||||
|
||||
fn set_sort_index(&mut self, new_index: usize) {
|
||||
pub 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_status() {
|
||||
@ -356,12 +352,6 @@ where
|
||||
self.table
|
||||
.draw_tui_table(painter, f, data, block, block_area, show_selected_entry);
|
||||
}
|
||||
|
||||
/// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`].
|
||||
pub fn draw_sort_table<B: Backend>(
|
||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, block: Block<'_>, block_area: Rect,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Component for SortableTextTable<S>
|
||||
|
@ -173,6 +173,10 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn columns(&self) -> &[C] {
|
||||
&self.columns
|
||||
}
|
||||
|
||||
fn displayed_column_names(&self) -> Vec<Cow<'static, str>> {
|
||||
self.columns
|
||||
.iter()
|
||||
@ -181,7 +185,7 @@ where
|
||||
}
|
||||
|
||||
pub fn set_num_items(&mut self, num_items: usize) {
|
||||
self.scrollable.update_num_items(num_items);
|
||||
self.scrollable.set_num_items(num_items);
|
||||
}
|
||||
|
||||
pub fn set_column(&mut self, index: usize, column: C) {
|
||||
|
@ -174,16 +174,12 @@ impl Widget for CpuGraph {
|
||||
}
|
||||
};
|
||||
|
||||
// debug!("Area: {:?}", area);
|
||||
|
||||
let split_area = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(constraints)
|
||||
.split(area);
|
||||
|
||||
// debug!("Split area: {:?}", split_area);
|
||||
|
||||
const Y_BOUNDS: [f64; 2] = [0.0, 100.5];
|
||||
let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()];
|
||||
|
||||
|
@ -7,7 +7,7 @@ use unicode_segmentation::GraphemeCursor;
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::Rect,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
widgets::{Block, Borders, TableState},
|
||||
Frame,
|
||||
};
|
||||
@ -15,7 +15,7 @@ use tui::{
|
||||
use crate::{
|
||||
app::{
|
||||
data_harvester::processes::ProcessHarvest,
|
||||
event::{MultiKey, MultiKeyResult, WidgetEventResult},
|
||||
event::{MultiKey, MultiKeyResult, ReturnSignal, WidgetEventResult},
|
||||
query::*,
|
||||
DataCollection,
|
||||
},
|
||||
@ -30,7 +30,7 @@ use super::{
|
||||
sort_text_table::{SimpleSortableColumn, SortStatus, SortableColumn},
|
||||
text_table::TextTableData,
|
||||
AppScrollWidgetState, CanvasTableWidthState, Component, CursorDirection, ScrollDirection,
|
||||
SortableTextTable, TextInput, TextTable, Widget,
|
||||
SortMenu, SortableTextTable, TextInput, Widget,
|
||||
};
|
||||
|
||||
/// AppSearchState deals with generic searching (I might do this in the future).
|
||||
@ -836,11 +836,17 @@ impl SortableColumn for ProcessSortColumn {
|
||||
}
|
||||
}
|
||||
|
||||
enum ProcessSortState {
|
||||
Shown,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
/// A searchable, sortable table to manage processes.
|
||||
pub struct ProcessManager {
|
||||
bounds: Rect,
|
||||
process_table: SortableTextTable<ProcessSortColumn>,
|
||||
sort_table: TextTable,
|
||||
sort_menu: SortMenu,
|
||||
|
||||
search_input: TextInput,
|
||||
|
||||
dd_multi: MultiKey,
|
||||
@ -848,7 +854,7 @@ pub struct ProcessManager {
|
||||
selected: ProcessManagerSelection,
|
||||
|
||||
in_tree_mode: bool,
|
||||
show_sort: bool, // TODO: Add this for temp and disk???
|
||||
sort_status: ProcessSortState,
|
||||
show_search: bool,
|
||||
|
||||
search_modifiers: SearchModifiers,
|
||||
@ -873,17 +879,15 @@ impl ProcessManager {
|
||||
ProcessSortColumn::new(ProcessSortType::State),
|
||||
];
|
||||
|
||||
let process_table = SortableTextTable::new(process_table_columns).default_sort_index(2);
|
||||
|
||||
let mut manager = Self {
|
||||
bounds: Rect::default(),
|
||||
process_table,
|
||||
sort_table: TextTable::new(vec![]), // TODO: Do this too
|
||||
sort_menu: SortMenu::new(process_table_columns.len()),
|
||||
process_table: SortableTextTable::new(process_table_columns).default_sort_index(2),
|
||||
search_input: TextInput::new(),
|
||||
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
|
||||
selected: ProcessManagerSelection::Processes,
|
||||
in_tree_mode: false,
|
||||
show_sort: false,
|
||||
sort_status: ProcessSortState::Hidden,
|
||||
show_search: false,
|
||||
search_modifiers: SearchModifiers::default(),
|
||||
display_data: Default::default(),
|
||||
@ -911,7 +915,9 @@ impl ProcessManager {
|
||||
if let ProcessManagerSelection::Sort = self.selected {
|
||||
WidgetEventResult::NoRedraw
|
||||
} else {
|
||||
self.show_sort = true;
|
||||
self.sort_menu
|
||||
.set_index(self.process_table.current_sorting_column_index());
|
||||
self.sort_status = ProcessSortState::Shown;
|
||||
self.selected = ProcessManagerSelection::Sort;
|
||||
WidgetEventResult::Redraw
|
||||
}
|
||||
@ -945,6 +951,20 @@ impl Component for ProcessManager {
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
|
||||
// "Global" handling:
|
||||
match event.code {
|
||||
KeyCode::Esc => {
|
||||
if let ProcessSortState::Shown = self.sort_status {
|
||||
self.sort_status = ProcessSortState::Hidden;
|
||||
if let ProcessManagerSelection::Sort = self.selected {
|
||||
self.selected = ProcessManagerSelection::Processes;
|
||||
}
|
||||
return WidgetEventResult::Redraw;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.selected {
|
||||
ProcessManagerSelection::Processes => {
|
||||
// Try to catch some stuff first...
|
||||
@ -982,7 +1002,7 @@ impl Component for ProcessManager {
|
||||
self.in_tree_mode = !self.in_tree_mode;
|
||||
return WidgetEventResult::Redraw;
|
||||
}
|
||||
KeyCode::F(6) => {
|
||||
KeyCode::Char('s') | KeyCode::F(6) => {
|
||||
return self.open_sort();
|
||||
}
|
||||
KeyCode::F(9) => {
|
||||
@ -1003,6 +1023,18 @@ impl Component for ProcessManager {
|
||||
self.process_table.handle_key_event(event)
|
||||
}
|
||||
ProcessManagerSelection::Sort => {
|
||||
match event.code {
|
||||
KeyCode::Enter if event.modifiers.is_empty() => {
|
||||
self.process_table
|
||||
.set_sort_index(self.sort_menu.current_index());
|
||||
return WidgetEventResult::Signal(ReturnSignal::Update);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.sort_menu.handle_key_event(event)
|
||||
}
|
||||
ProcessManagerSelection::Search => {
|
||||
if event.modifiers.is_empty() {
|
||||
match event.code {
|
||||
KeyCode::F(1) => {}
|
||||
@ -1019,9 +1051,8 @@ impl Component for ProcessManager {
|
||||
}
|
||||
}
|
||||
|
||||
self.sort_table.handle_key_event(event)
|
||||
self.search_input.handle_key_event(event)
|
||||
}
|
||||
ProcessManagerSelection::Search => self.search_input.handle_key_event(event),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1041,12 +1072,12 @@ impl Component for ProcessManager {
|
||||
WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s),
|
||||
}
|
||||
}
|
||||
} else if self.sort_table.does_border_intersect_mouse(&event) {
|
||||
} else if self.sort_menu.does_border_intersect_mouse(&event) {
|
||||
if let ProcessManagerSelection::Sort = self.selected {
|
||||
self.sort_table.handle_mouse_event(event)
|
||||
self.sort_menu.handle_mouse_event(event)
|
||||
} else {
|
||||
self.selected = ProcessManagerSelection::Sort;
|
||||
self.sort_table.handle_mouse_event(event);
|
||||
self.sort_menu.handle_mouse_event(event);
|
||||
WidgetEventResult::Redraw
|
||||
}
|
||||
} else if self.search_input.does_border_intersect_mouse(&event) {
|
||||
@ -1063,7 +1094,7 @@ impl Component for ProcessManager {
|
||||
}
|
||||
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected {
|
||||
ProcessManagerSelection::Processes => self.process_table.handle_mouse_event(event),
|
||||
ProcessManagerSelection::Sort => self.sort_table.handle_mouse_event(event),
|
||||
ProcessManagerSelection::Sort => self.sort_menu.handle_mouse_event(event),
|
||||
ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event),
|
||||
},
|
||||
_ => WidgetEventResult::NoRedraw,
|
||||
@ -1079,16 +1110,76 @@ impl Widget for ProcessManager {
|
||||
fn draw<B: Backend>(
|
||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
|
||||
) {
|
||||
let block = Block::default()
|
||||
.border_style(if selected {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
})
|
||||
.borders(Borders::ALL);
|
||||
match self.sort_status {
|
||||
ProcessSortState::Shown => {
|
||||
const SORT_CONSTRAINTS: [Constraint; 2] =
|
||||
[Constraint::Length(10), Constraint::Min(0)];
|
||||
|
||||
self.process_table
|
||||
.draw_tui_table(painter, f, &self.display_data, block, area, selected);
|
||||
let split_area = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(SORT_CONSTRAINTS)
|
||||
.split(area);
|
||||
|
||||
let sort_block = Block::default()
|
||||
.border_style(if selected {
|
||||
if let ProcessManagerSelection::Sort = self.selected {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
}
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
})
|
||||
.borders(Borders::ALL);
|
||||
self.sort_menu.draw_sort_menu(
|
||||
painter,
|
||||
f,
|
||||
self.process_table.columns(),
|
||||
sort_block,
|
||||
split_area[0],
|
||||
);
|
||||
|
||||
let process_block = Block::default()
|
||||
.border_style(if selected {
|
||||
if let ProcessManagerSelection::Processes = self.selected {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
}
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
})
|
||||
.borders(Borders::ALL);
|
||||
|
||||
self.process_table.draw_tui_table(
|
||||
painter,
|
||||
f,
|
||||
&self.display_data,
|
||||
process_block,
|
||||
split_area[1],
|
||||
selected,
|
||||
);
|
||||
}
|
||||
ProcessSortState::Hidden => {
|
||||
let block = Block::default()
|
||||
.border_style(if selected {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
})
|
||||
.borders(Borders::ALL);
|
||||
|
||||
self.process_table.draw_tui_table(
|
||||
painter,
|
||||
f,
|
||||
&self.display_data,
|
||||
block,
|
||||
area,
|
||||
selected,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||
@ -1099,58 +1190,60 @@ impl Widget for ProcessManager {
|
||||
// TODO: Filtering
|
||||
true
|
||||
})
|
||||
.sorted_by(match self.process_table.current_column().sort_type {
|
||||
ProcessSortType::Pid => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
|
||||
}
|
||||
ProcessSortType::Count => {
|
||||
todo!()
|
||||
}
|
||||
ProcessSortType::Name => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
|
||||
}
|
||||
ProcessSortType::Command => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
|
||||
}
|
||||
ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
|
||||
},
|
||||
ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
|
||||
},
|
||||
ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
|
||||
},
|
||||
ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
|
||||
},
|
||||
ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
|
||||
},
|
||||
ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.total_read_bytes.cmp(&b.total_read_bytes)
|
||||
},
|
||||
ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.total_write_bytes.cmp(&b.total_write_bytes)
|
||||
},
|
||||
ProcessSortType::User => {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
|
||||
.sorted_by(
|
||||
match self.process_table.current_sorting_column().sort_type {
|
||||
ProcessSortType::Pid => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.pid.cmp(&b.pid)
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
|_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
|
||||
ProcessSortType::Count => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
ProcessSortType::State => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.process_state.cmp(&b.process_state)
|
||||
}
|
||||
});
|
||||
ProcessSortType::Name => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.name.cmp(&b.name)
|
||||
}
|
||||
ProcessSortType::Command => {
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.command.cmp(&b.command)
|
||||
}
|
||||
ProcessSortType::Cpu => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
FloatOrd(a.cpu_usage_percent).cmp(&FloatOrd(b.cpu_usage_percent))
|
||||
},
|
||||
ProcessSortType::Mem => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.mem_usage_bytes.cmp(&b.mem_usage_bytes)
|
||||
},
|
||||
ProcessSortType::MemPercent => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
FloatOrd(a.mem_usage_percent).cmp(&FloatOrd(b.mem_usage_percent))
|
||||
},
|
||||
ProcessSortType::Rps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.read_bytes_per_sec.cmp(&b.read_bytes_per_sec)
|
||||
},
|
||||
ProcessSortType::Wps => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.write_bytes_per_sec.cmp(&b.write_bytes_per_sec)
|
||||
},
|
||||
ProcessSortType::TotalRead => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.total_read_bytes.cmp(&b.total_read_bytes)
|
||||
},
|
||||
ProcessSortType::TotalWrite => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.total_write_bytes.cmp(&b.total_write_bytes)
|
||||
},
|
||||
ProcessSortType::User => {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
|a: &&ProcessHarvest, b: &&ProcessHarvest| a.user.cmp(&b.user)
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
|_a: &&ProcessHarvest, _b: &&ProcessHarvest| Ord::Eq
|
||||
}
|
||||
}
|
||||
ProcessSortType::State => |a: &&ProcessHarvest, b: &&ProcessHarvest| {
|
||||
a.process_state.cmp(&b.process_state)
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
self.display_data = if let SortStatus::SortDescending = self
|
||||
.process_table
|
||||
.current_column()
|
||||
.current_sorting_column()
|
||||
.sortable_column
|
||||
.sorting_status()
|
||||
{
|
||||
|
@ -4,7 +4,7 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use bottom::{app::event::EventResult, canvas, constants::*, options::*, *};
|
||||
use bottom::{app::event::EventResult, canvas, options::*, *};
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
|
@ -359,16 +359,41 @@ impl Painter {
|
||||
if let Some(layout_node) = arena.get(node).map(|n| n.get()) {
|
||||
match layout_node {
|
||||
LayoutNode::Row(row) => {
|
||||
let split_area = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(row.constraints.clone())
|
||||
.split(area);
|
||||
let split_area = {
|
||||
let initial_split = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(row.constraints.clone())
|
||||
.split(area);
|
||||
|
||||
// debug!(
|
||||
// "Row - constraints: {:#?}, split_area: {:#?}",
|
||||
// row.constraints, split_area
|
||||
// );
|
||||
if initial_split.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
let mut checked_split =
|
||||
Vec::with_capacity(initial_split.len());
|
||||
let mut right_value = initial_split[0].right();
|
||||
checked_split.push(initial_split[0]);
|
||||
|
||||
for rect in initial_split[1..].iter() {
|
||||
if right_value == rect.left() {
|
||||
right_value = rect.right();
|
||||
checked_split.push(*rect);
|
||||
} else {
|
||||
let diff = rect.x.saturating_sub(right_value);
|
||||
let new_rect = Rect::new(
|
||||
right_value,
|
||||
rect.y,
|
||||
rect.width + diff,
|
||||
rect.height,
|
||||
);
|
||||
right_value = new_rect.right();
|
||||
checked_split.push(new_rect);
|
||||
}
|
||||
}
|
||||
|
||||
checked_split
|
||||
}
|
||||
};
|
||||
|
||||
for (child, child_area) in node.children(arena).zip(split_area) {
|
||||
traverse_and_draw_tree(
|
||||
@ -384,16 +409,41 @@ impl Painter {
|
||||
}
|
||||
}
|
||||
LayoutNode::Col(col) => {
|
||||
let split_area = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(col.constraints.clone())
|
||||
.split(area);
|
||||
let split_area = {
|
||||
let initial_split = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(col.constraints.clone())
|
||||
.split(area);
|
||||
|
||||
// debug!(
|
||||
// "Col - constraints: {:#?}, split_area: {:#?}",
|
||||
// col.constraints, split_area
|
||||
// );
|
||||
if initial_split.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
let mut checked_split =
|
||||
Vec::with_capacity(initial_split.len());
|
||||
let mut bottom_value = initial_split[0].bottom();
|
||||
checked_split.push(initial_split[0]);
|
||||
|
||||
for rect in initial_split[1..].iter() {
|
||||
if bottom_value == rect.top() {
|
||||
bottom_value = rect.bottom();
|
||||
checked_split.push(*rect);
|
||||
} else {
|
||||
let diff = rect.y.saturating_sub(bottom_value);
|
||||
let new_rect = Rect::new(
|
||||
rect.x,
|
||||
bottom_value,
|
||||
rect.width,
|
||||
rect.height + diff,
|
||||
);
|
||||
bottom_value = new_rect.bottom();
|
||||
checked_split.push(new_rect);
|
||||
}
|
||||
}
|
||||
|
||||
checked_split
|
||||
}
|
||||
};
|
||||
|
||||
for (child, child_area) in node.children(arena).zip(split_area) {
|
||||
traverse_and_draw_tree(
|
||||
@ -409,8 +459,6 @@ impl Painter {
|
||||
}
|
||||
}
|
||||
LayoutNode::Widget => {
|
||||
// debug!("Widget - area: {:#?}", area);
|
||||
|
||||
if let Some(widget) = lookup_map.get_mut(&node) {
|
||||
widget.set_bounds(area);
|
||||
widget.draw(painter, f, area, selected_id == node);
|
||||
|
Loading…
x
Reference in New Issue
Block a user