mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-28 16:14:16 +02:00
refactor: start moving over the event system
This commit is contained in:
parent
0afc371eaa
commit
6b69e373de
508
src/app.rs
508
src/app.rs
@ -13,7 +13,7 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::event::{KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind};
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, NodeId};
|
||||||
use unicode_segmentation::GraphemeCursor;
|
use unicode_segmentation::GraphemeCursor;
|
||||||
@ -28,12 +28,14 @@ pub use widgets::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
canvas,
|
canvas,
|
||||||
constants::{self, MAX_SIGNAL},
|
constants::{self, MAX_SIGNAL},
|
||||||
|
data_conversion::*,
|
||||||
units::data_units::DataUnit,
|
units::data_units::DataUnit,
|
||||||
|
update_final_process_list,
|
||||||
utils::error::{BottomError, Result},
|
utils::error::{BottomError, Result},
|
||||||
BottomEvent, Pid,
|
BottomEvent, Pid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::event::{does_point_intersect_rect, EventResult, ReturnSignal};
|
use self::event::{EventResult, ReturnSignal, ReturnSignalResult};
|
||||||
|
|
||||||
const MAX_SEARCH_LENGTH: usize = 200;
|
const MAX_SEARCH_LENGTH: usize = 200;
|
||||||
|
|
||||||
@ -269,9 +271,38 @@ impl AppState {
|
|||||||
// TODO: Write this.
|
// TODO: Write this.
|
||||||
|
|
||||||
if event.modifiers.is_empty() {
|
if event.modifiers.is_empty() {
|
||||||
todo!()
|
match event.code {
|
||||||
} else if let KeyModifiers::ALT = event.modifiers {
|
KeyCode::Esc => {
|
||||||
todo!()
|
if self.is_expanded {
|
||||||
|
self.is_expanded = false;
|
||||||
|
Some(EventResult::Redraw)
|
||||||
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
|
self.help_dialog_state.is_showing_help = false;
|
||||||
|
self.help_dialog_state.scroll_state.current_scroll_index = 0;
|
||||||
|
Some(EventResult::Redraw)
|
||||||
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
|
self.close_dd();
|
||||||
|
Some(EventResult::Redraw)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('q') => Some(EventResult::Quit),
|
||||||
|
KeyCode::Char('e') => {
|
||||||
|
self.is_expanded = !self.is_expanded;
|
||||||
|
Some(EventResult::Redraw)
|
||||||
|
}
|
||||||
|
KeyCode::Char('?') => {
|
||||||
|
self.help_dialog_state.is_showing_help = true;
|
||||||
|
Some(EventResult::Redraw)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else if let KeyModifiers::CONTROL = event.modifiers {
|
||||||
|
match event.code {
|
||||||
|
KeyCode::Char('c') => Some(EventResult::Quit),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -296,30 +327,49 @@ impl AppState {
|
|||||||
// Not great, but basically a blind lookup through the table for anything that clips the click location.
|
// Not great, but basically a blind lookup through the table for anything that clips the click location.
|
||||||
// TODO: Would be cool to use a kd-tree or something like that in the future.
|
// TODO: Would be cool to use a kd-tree or something like that in the future.
|
||||||
|
|
||||||
let x = event.column;
|
match &event.kind {
|
||||||
let y = event.row;
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
|
if self.is_expanded {
|
||||||
for (id, widget) in self.widget_lookup_map.iter_mut() {
|
if let Some(widget) =
|
||||||
if does_point_intersect_rect(x, y, widget.bounds()) {
|
self.widget_lookup_map.get_mut(&self.selected_widget)
|
||||||
let is_id_selected = self.selected_widget == *id;
|
{
|
||||||
self.selected_widget = *id;
|
return widget.handle_mouse_event(event);
|
||||||
|
}
|
||||||
if is_id_selected {
|
|
||||||
return widget.handle_mouse_event(event);
|
|
||||||
} else {
|
} else {
|
||||||
// If the aren't equal, *force* a redraw.
|
for (id, widget) in self.widget_lookup_map.iter_mut() {
|
||||||
widget.handle_mouse_event(event);
|
if widget.does_intersect_mouse(&event) {
|
||||||
return EventResult::Redraw;
|
let is_id_selected = self.selected_widget == *id;
|
||||||
|
self.selected_widget = *id;
|
||||||
|
|
||||||
|
if is_id_selected {
|
||||||
|
return widget.handle_mouse_event(event);
|
||||||
|
} else {
|
||||||
|
// If the aren't equal, *force* a redraw.
|
||||||
|
widget.handle_mouse_event(event);
|
||||||
|
return EventResult::Redraw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
|
||||||
|
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget)
|
||||||
|
{
|
||||||
|
return widget.handle_mouse_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult::NoRedraw
|
EventResult::NoRedraw
|
||||||
}
|
}
|
||||||
BottomEvent::Update(_new_data) => {
|
BottomEvent::Update(new_data) => {
|
||||||
|
self.data_collection.eat_data(new_data);
|
||||||
|
|
||||||
if !self.is_frozen {
|
if !self.is_frozen {
|
||||||
// TODO: Update all data, and redraw.
|
self.convert_data();
|
||||||
todo!()
|
|
||||||
|
EventResult::Redraw
|
||||||
} else {
|
} else {
|
||||||
EventResult::NoRedraw
|
EventResult::NoRedraw
|
||||||
}
|
}
|
||||||
@ -336,16 +386,98 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles a [`ReturnSignal`], and returns an [`EventResult`].
|
/// Handles a [`ReturnSignal`], and returns an [`ReturnSignalResult`].
|
||||||
pub fn handle_return_signal(&mut self, return_signal: ReturnSignal) -> EventResult {
|
pub fn handle_return_signal(&mut self, return_signal: ReturnSignal) -> ReturnSignalResult {
|
||||||
match return_signal {
|
match return_signal {
|
||||||
ReturnSignal::Nothing => EventResult::NoRedraw,
|
|
||||||
ReturnSignal::KillProcess => {
|
ReturnSignal::KillProcess => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_data(&mut self) {
|
||||||
|
// TODO: Probably refactor this.
|
||||||
|
|
||||||
|
// Network
|
||||||
|
if self.used_widgets.use_net {
|
||||||
|
let network_data = convert_network_data_points(
|
||||||
|
&self.data_collection,
|
||||||
|
false,
|
||||||
|
self.app_config_fields.use_basic_mode
|
||||||
|
|| self.app_config_fields.use_old_network_legend,
|
||||||
|
&self.app_config_fields.network_scale_type,
|
||||||
|
&self.app_config_fields.network_unit_type,
|
||||||
|
self.app_config_fields.network_use_binary_prefix,
|
||||||
|
);
|
||||||
|
self.canvas_data.network_data_rx = network_data.rx;
|
||||||
|
self.canvas_data.network_data_tx = network_data.tx;
|
||||||
|
self.canvas_data.rx_display = network_data.rx_display;
|
||||||
|
self.canvas_data.tx_display = network_data.tx_display;
|
||||||
|
if let Some(total_rx_display) = network_data.total_rx_display {
|
||||||
|
self.canvas_data.total_rx_display = total_rx_display;
|
||||||
|
}
|
||||||
|
if let Some(total_tx_display) = network_data.total_tx_display {
|
||||||
|
self.canvas_data.total_tx_display = total_tx_display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disk
|
||||||
|
if self.used_widgets.use_disk {
|
||||||
|
self.canvas_data.disk_data = convert_disk_row(&self.data_collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperatures
|
||||||
|
if self.used_widgets.use_temp {
|
||||||
|
self.canvas_data.temp_sensor_data = convert_temp_row(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory
|
||||||
|
if self.used_widgets.use_mem {
|
||||||
|
self.canvas_data.mem_data = convert_mem_data_points(&self.data_collection, false);
|
||||||
|
self.canvas_data.swap_data = convert_swap_data_points(&self.data_collection, false);
|
||||||
|
let (memory_labels, swap_labels) = convert_mem_labels(&self.data_collection);
|
||||||
|
|
||||||
|
self.canvas_data.mem_labels = memory_labels;
|
||||||
|
self.canvas_data.swap_labels = swap_labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.used_widgets.use_cpu {
|
||||||
|
// CPU
|
||||||
|
convert_cpu_data_points(&self.data_collection, &mut self.canvas_data.cpu_data, false);
|
||||||
|
self.canvas_data.load_avg_data = self.data_collection.load_avg_harvest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processes
|
||||||
|
if self.used_widgets.use_proc {
|
||||||
|
self.update_all_process_lists();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery
|
||||||
|
if self.used_widgets.use_battery {
|
||||||
|
self.canvas_data.battery_data = convert_battery_harvest(&self.data_collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_collect)]
|
||||||
|
fn update_all_process_lists(&mut self) {
|
||||||
|
// TODO: Probably refactor this.
|
||||||
|
|
||||||
|
// According to clippy, I can avoid a collect... but if I follow it,
|
||||||
|
// I end up conflicting with the borrow checker since app is used within the closure... hm.
|
||||||
|
if !self.is_frozen {
|
||||||
|
let widget_ids = self
|
||||||
|
.proc_state
|
||||||
|
.widget_states
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
widget_ids.into_iter().for_each(|widget_id| {
|
||||||
|
update_final_process_list(self, widget_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_esc(&mut self) {
|
pub fn on_esc(&mut self) {
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
if self.is_in_dialog() {
|
if self.is_in_dialog() {
|
||||||
@ -1316,41 +1448,41 @@ impl AppState {
|
|||||||
pub fn start_killing_process(&mut self) {
|
pub fn start_killing_process(&mut self) {
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
|
|
||||||
if let Some(proc_widget_state) = self
|
// if let Some(proc_widget_state) = self
|
||||||
.proc_state
|
// .proc_state
|
||||||
.widget_states
|
// .widget_states
|
||||||
.get(&self.current_widget.widget_id)
|
// .get(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if let Some(corresponding_filtered_process_list) = self
|
// if let Some(corresponding_filtered_process_list) = self
|
||||||
.canvas_data
|
// .canvas_data
|
||||||
.finalized_process_data_map
|
// .finalized_process_data_map
|
||||||
.get(&self.current_widget.widget_id)
|
// .get(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if proc_widget_state.scroll_state.current_scroll_position
|
// if proc_widget_state.scroll_state.current_scroll_position
|
||||||
< corresponding_filtered_process_list.len()
|
// < corresponding_filtered_process_list.len()
|
||||||
{
|
// {
|
||||||
let current_process: (String, Vec<Pid>);
|
// let current_process: (String, Vec<Pid>);
|
||||||
if self.is_grouped(self.current_widget.widget_id) {
|
// if self.is_grouped(self.current_widget.widget_id) {
|
||||||
if let Some(process) = &corresponding_filtered_process_list
|
// if let Some(process) = &corresponding_filtered_process_list
|
||||||
.get(proc_widget_state.scroll_state.current_scroll_position)
|
// .get(proc_widget_state.scroll_state.current_scroll_position)
|
||||||
{
|
// {
|
||||||
current_process = (process.name.to_string(), process.group_pids.clone())
|
// current_process = (process.name.to_string(), process.group_pids.clone())
|
||||||
} else {
|
// } else {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
let process = corresponding_filtered_process_list
|
// let process = corresponding_filtered_process_list
|
||||||
[proc_widget_state.scroll_state.current_scroll_position]
|
// [proc_widget_state.scroll_state.current_scroll_position]
|
||||||
.clone();
|
// .clone();
|
||||||
current_process = (process.name.clone(), vec![process.pid])
|
// current_process = (process.name.clone(), vec![process.pid])
|
||||||
};
|
// };
|
||||||
|
|
||||||
self.to_delete_process_list = Some(current_process);
|
// self.to_delete_process_list = Some(current_process);
|
||||||
self.delete_dialog_state.is_showing_dd = true;
|
// self.delete_dialog_state.is_showing_dd = true;
|
||||||
self.is_determining_widget_boundary = true;
|
// self.is_determining_widget_boundary = true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_char_key(&mut self, caught_char: char) {
|
pub fn on_char_key(&mut self, caught_char: char) {
|
||||||
@ -2222,85 +2354,85 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_to_last(&mut self) {
|
pub fn skip_to_last(&mut self) {
|
||||||
if !self.ignore_normal_keybinds() {
|
// if !self.ignore_normal_keybinds() {
|
||||||
match self.current_widget.widget_type {
|
// match self.current_widget.widget_type {
|
||||||
BottomWidgetType::Proc => {
|
// BottomWidgetType::Proc => {
|
||||||
if let Some(proc_widget_state) = self
|
// if let Some(proc_widget_state) = self
|
||||||
.proc_state
|
// .proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if let Some(finalized_process_data) = self
|
// if let Some(finalized_process_data) = self
|
||||||
.canvas_data
|
// .canvas_data
|
||||||
.finalized_process_data_map
|
// .finalized_process_data_map
|
||||||
.get(&self.current_widget.widget_id)
|
// .get(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if !self.canvas_data.finalized_process_data_map.is_empty() {
|
// if !self.canvas_data.finalized_process_data_map.is_empty() {
|
||||||
proc_widget_state.scroll_state.current_scroll_position =
|
// proc_widget_state.scroll_state.current_scroll_position =
|
||||||
finalized_process_data.len() - 1;
|
// finalized_process_data.len() - 1;
|
||||||
proc_widget_state.scroll_state.scroll_direction =
|
// proc_widget_state.scroll_state.scroll_direction =
|
||||||
ScrollDirection::Down;
|
// ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
BottomWidgetType::ProcSort => {
|
// BottomWidgetType::ProcSort => {
|
||||||
if let Some(proc_widget_state) = self
|
// if let Some(proc_widget_state) = self
|
||||||
.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.columns.current_scroll_position =
|
// proc_widget_state.columns.current_scroll_position =
|
||||||
proc_widget_state.columns.get_enabled_columns_len() - 1;
|
// proc_widget_state.columns.get_enabled_columns_len() - 1;
|
||||||
proc_widget_state.columns.scroll_direction = ScrollDirection::Down;
|
// proc_widget_state.columns.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
BottomWidgetType::Temp => {
|
// BottomWidgetType::Temp => {
|
||||||
if let Some(temp_widget_state) = self
|
// if let Some(temp_widget_state) = self
|
||||||
.temp_state
|
// .temp_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if !self.canvas_data.temp_sensor_data.is_empty() {
|
// if !self.canvas_data.temp_sensor_data.is_empty() {
|
||||||
temp_widget_state.scroll_state.current_scroll_position =
|
// temp_widget_state.scroll_state.current_scroll_position =
|
||||||
self.canvas_data.temp_sensor_data.len() - 1;
|
// self.canvas_data.temp_sensor_data.len() - 1;
|
||||||
temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
// temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
BottomWidgetType::Disk => {
|
// BottomWidgetType::Disk => {
|
||||||
if let Some(disk_widget_state) = self
|
// if let Some(disk_widget_state) = self
|
||||||
.disk_state
|
// .disk_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if !self.canvas_data.disk_data.is_empty() {
|
// if !self.canvas_data.disk_data.is_empty() {
|
||||||
disk_widget_state.scroll_state.current_scroll_position =
|
// disk_widget_state.scroll_state.current_scroll_position =
|
||||||
self.canvas_data.disk_data.len() - 1;
|
// self.canvas_data.disk_data.len() - 1;
|
||||||
disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
// disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
BottomWidgetType::CpuLegend => {
|
// BottomWidgetType::CpuLegend => {
|
||||||
if let Some(cpu_widget_state) = self
|
// if let Some(cpu_widget_state) = self
|
||||||
.cpu_state
|
// .cpu_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id - 1)
|
// .get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||||
{
|
// {
|
||||||
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.scroll_state.current_scroll_position = cap - 1;
|
||||||
cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
// cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
_ => {}
|
// _ => {}
|
||||||
}
|
// }
|
||||||
self.reset_multi_tap_keys();
|
// self.reset_multi_tap_keys();
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
// } else if self.help_dialog_state.is_showing_help {
|
||||||
self.help_dialog_state.scroll_state.current_scroll_index = self
|
// self.help_dialog_state.scroll_state.current_scroll_index = self
|
||||||
.help_dialog_state
|
// .help_dialog_state
|
||||||
.scroll_state
|
// .scroll_state
|
||||||
.max_scroll_index
|
// .max_scroll_index
|
||||||
.saturating_sub(1);
|
// .saturating_sub(1);
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
// } else if self.delete_dialog_state.is_showing_dd {
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(MAX_SIGNAL);
|
// self.delete_dialog_state.selected_signal = KillSignal::Kill(MAX_SIGNAL);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrement_position_count(&mut self) {
|
pub fn decrement_position_count(&mut self) {
|
||||||
@ -2381,35 +2513,35 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the new position.
|
/// Returns the new position.
|
||||||
fn increment_process_position(&mut self, num_to_change_by: i64) -> Option<usize> {
|
fn increment_process_position(&mut self, _num_to_change_by: i64) -> Option<usize> {
|
||||||
if let Some(proc_widget_state) = self
|
// if let Some(proc_widget_state) = self
|
||||||
.proc_state
|
// .proc_state
|
||||||
.get_mut_widget_state(self.current_widget.widget_id)
|
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
||||||
if let Some(finalized_process_data) = self
|
// if let Some(finalized_process_data) = self
|
||||||
.canvas_data
|
// .canvas_data
|
||||||
.finalized_process_data_map
|
// .finalized_process_data_map
|
||||||
.get(&self.current_widget.widget_id)
|
// .get(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if current_posn as i64 + num_to_change_by >= 0
|
// if current_posn as i64 + num_to_change_by >= 0
|
||||||
&& current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64
|
// && current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64
|
||||||
{
|
// {
|
||||||
proc_widget_state.scroll_state.current_scroll_position =
|
// proc_widget_state.scroll_state.current_scroll_position =
|
||||||
(current_posn as i64 + num_to_change_by) as usize;
|
// (current_posn as i64 + num_to_change_by) as usize;
|
||||||
} else {
|
// } else {
|
||||||
return None;
|
// return None;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if num_to_change_by < 0 {
|
// if num_to_change_by < 0 {
|
||||||
proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
|
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
|
||||||
} else {
|
// } else {
|
||||||
proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return Some(proc_widget_state.scroll_state.current_scroll_position);
|
// return Some(proc_widget_state.scroll_state.current_scroll_position);
|
||||||
}
|
// }
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -2537,32 +2669,32 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_collapsing_process_branch(&mut self) {
|
fn toggle_collapsing_process_branch(&mut self) {
|
||||||
if let Some(proc_widget_state) = self
|
// if let Some(proc_widget_state) = self
|
||||||
.proc_state
|
// .proc_state
|
||||||
.widget_states
|
// .widget_states
|
||||||
.get_mut(&self.current_widget.widget_id)
|
// .get_mut(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
||||||
|
|
||||||
if let Some(displayed_process_list) = self
|
// if let Some(displayed_process_list) = self
|
||||||
.canvas_data
|
// .canvas_data
|
||||||
.finalized_process_data_map
|
// .finalized_process_data_map
|
||||||
.get(&self.current_widget.widget_id)
|
// .get(&self.current_widget.widget_id)
|
||||||
{
|
// {
|
||||||
if let Some(corresponding_process) = displayed_process_list.get(current_posn) {
|
// if let Some(corresponding_process) = displayed_process_list.get(current_posn) {
|
||||||
let corresponding_pid = corresponding_process.pid;
|
// let corresponding_pid = corresponding_process.pid;
|
||||||
|
|
||||||
if let Some(process_data) = self
|
// if let Some(process_data) = self
|
||||||
.canvas_data
|
// .canvas_data
|
||||||
.single_process_data
|
// .single_process_data
|
||||||
.get_mut(&corresponding_pid)
|
// .get_mut(&corresponding_pid)
|
||||||
{
|
// {
|
||||||
process_data.is_collapsed_entry = !process_data.is_collapsed_entry;
|
// process_data.is_collapsed_entry = !process_data.is_collapsed_entry;
|
||||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
// self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoom_out(&mut self) {
|
fn zoom_out(&mut self) {
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use tui::layout::Rect;
|
|
||||||
|
|
||||||
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
|
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
|
||||||
|
|
||||||
/// These are "signals" that are sent along with an [`EventResult`] to signify a potential additional action
|
/// These are "signals" that are sent along with an [`EventResult`] to signify a potential additional action
|
||||||
/// that the caller must do, along with the "core" result of either drawing or redrawing.
|
/// that the caller must do, along with the "core" result of either drawing or redrawing.
|
||||||
pub enum ReturnSignal {
|
pub enum ReturnSignal {
|
||||||
/// Do nothing.
|
|
||||||
Nothing,
|
|
||||||
/// A signal returned when some process widget was told to try to kill a process (or group of processes).
|
/// A signal returned when some process widget was told to try to kill a process (or group of processes).
|
||||||
KillProcess,
|
KillProcess,
|
||||||
}
|
}
|
||||||
@ -141,8 +137,3 @@ impl MultiKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether points `(x, y)` intersect a given [`Rect`].
|
|
||||||
pub fn does_point_intersect_rect(x: u16, y: u16, rect: Rect) -> bool {
|
|
||||||
x >= rect.left() && x <= rect.right() && y >= rect.top() && y <= rect.bottom()
|
|
||||||
}
|
|
||||||
|
@ -1082,10 +1082,7 @@ pub fn create_layout_tree(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Proc => {
|
BottomWidgetType::Proc => {
|
||||||
widget_lookup_map.insert(
|
widget_lookup_map.insert(widget_id, ProcessManager::new(process_defaults).into());
|
||||||
widget_id,
|
|
||||||
ProcessManager::new(process_defaults.is_tree).into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
BottomWidgetType::Temp => {
|
BottomWidgetType::Temp => {
|
||||||
widget_lookup_map.insert(widget_id, TempTable::default().into());
|
widget_lookup_map.insert(widget_id, TempTable::default().into());
|
||||||
|
@ -2,12 +2,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use tui::{
|
use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame};
|
||||||
backend::Backend,
|
|
||||||
layout::Rect,
|
|
||||||
widgets::{Block, TableState},
|
|
||||||
Frame,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
@ -66,6 +61,14 @@ pub trait Component {
|
|||||||
|
|
||||||
/// Updates a [`Component`]s bounding box to `new_bounds`.
|
/// Updates a [`Component`]s bounding box to `new_bounds`.
|
||||||
fn set_bounds(&mut self, new_bounds: Rect);
|
fn set_bounds(&mut self, new_bounds: Rect);
|
||||||
|
|
||||||
|
/// Returns whether a [`MouseEvent`] intersects a [`Component`].
|
||||||
|
fn does_intersect_mouse(&self, event: &MouseEvent) -> bool {
|
||||||
|
let x = event.column;
|
||||||
|
let y = event.row;
|
||||||
|
let rect = self.bounds();
|
||||||
|
x >= rect.left() && x <= rect.right() && y >= rect.top() && y <= rect.bottom()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for actual fully-fledged widgets to be displayed in bottom.
|
/// A trait for actual fully-fledged widgets to be displayed in bottom.
|
||||||
@ -104,8 +107,8 @@ pub trait Widget {
|
|||||||
|
|
||||||
/// Draws a [`Widget`]. Defaults to doing nothing.
|
/// Draws a [`Widget`]. Defaults to doing nothing.
|
||||||
fn draw<B: Backend>(
|
fn draw<B: Backend>(
|
||||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, block: Block<'_>,
|
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, data: &DisplayableData,
|
||||||
data: &DisplayableData,
|
selected: bool,
|
||||||
) {
|
) {
|
||||||
// TODO: Remove the default implementation in the future!
|
// TODO: Remove the default implementation in the future!
|
||||||
// TODO: Do another pass on ALL of the draw code - currently it's just glue, it should eventually be done properly!
|
// TODO: Do another pass on ALL of the draw code - currently it's just glue, it should eventually be done properly!
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent};
|
use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
||||||
use tui::{layout::Rect, widgets::TableState};
|
use tui::{layout::Rect, widgets::TableState};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
@ -6,68 +6,125 @@ use crate::app::{
|
|||||||
Component,
|
Component,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ScrollDirection {
|
pub enum ScrollDirection {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A "scrollable" [`Widget`] component. Intended for use as part of another [`Widget`] - as such, it does
|
/// We save the previous window index for future reference, but we must invalidate if the area changes.
|
||||||
/// not have any bounds or the like.
|
#[derive(Default)]
|
||||||
|
struct WindowIndex {
|
||||||
|
index: usize,
|
||||||
|
cached_area: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A "scrollable" [`Component`]. Intended for use as part of another [`Component`] to help manage scrolled state.
|
||||||
pub struct Scrollable {
|
pub struct Scrollable {
|
||||||
|
/// The currently selected index. Do *NOT* directly update this, use the helper functions!
|
||||||
current_index: usize,
|
current_index: usize,
|
||||||
previous_index: usize,
|
|
||||||
|
/// The "window index" is the "start" of the displayed data range, used for drawing purposes. See
|
||||||
|
/// [`Scrollable::get_list_start`] for more details.
|
||||||
|
window_index: WindowIndex,
|
||||||
|
|
||||||
|
/// The direction we're scrolling in.
|
||||||
scroll_direction: ScrollDirection,
|
scroll_direction: ScrollDirection,
|
||||||
|
|
||||||
|
/// How many items to keep track of.
|
||||||
num_items: usize,
|
num_items: usize,
|
||||||
|
|
||||||
|
/// tui-rs' internal table state; used to keep track of the *visually* selected index.
|
||||||
tui_state: TableState,
|
tui_state: TableState,
|
||||||
|
|
||||||
|
/// Manages the `gg` double-tap shortcut.
|
||||||
gg_manager: MultiKey,
|
gg_manager: MultiKey,
|
||||||
|
|
||||||
|
/// The bounds of the [`Scrollable`] component.
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scrollable {
|
impl Scrollable {
|
||||||
/// Creates a new [`Scrollable`].
|
/// Creates a new [`Scrollable`].
|
||||||
pub fn new(num_items: usize) -> Self {
|
pub fn new(num_items: usize) -> Self {
|
||||||
|
let mut tui_state = TableState::default();
|
||||||
|
tui_state.select(Some(0));
|
||||||
Self {
|
Self {
|
||||||
current_index: 0,
|
current_index: 0,
|
||||||
previous_index: 0,
|
window_index: WindowIndex::default(),
|
||||||
scroll_direction: ScrollDirection::Down,
|
scroll_direction: ScrollDirection::Down,
|
||||||
num_items,
|
num_items,
|
||||||
tui_state: TableState::default(),
|
tui_state,
|
||||||
gg_manager: MultiKey::register(vec!['g', 'g']), // TODO: Use a static arrayvec
|
gg_manager: MultiKey::register(vec!['g', 'g']), // TODO: Use a static arrayvec
|
||||||
bounds: Rect::default(),
|
bounds: Rect::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Scrollable`]. Note this will set the associated [`TableState`] to select the first entry.
|
/// Returns the currently selected index of the [`Scrollable`].
|
||||||
pub fn new_selected(num_items: usize) -> Self {
|
|
||||||
let mut scrollable = Scrollable::new(num_items);
|
|
||||||
scrollable.tui_state.select(Some(0));
|
|
||||||
|
|
||||||
scrollable
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn index(&self) -> usize {
|
pub fn index(&self) -> usize {
|
||||||
self.current_index
|
self.current_index
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the index with this! This will automatically update the previous index and scroll direction!
|
/// Returns the start of the [`Scrollable`] when displayed.
|
||||||
|
pub fn get_list_start(&mut self, num_visible_rows: usize) -> usize {
|
||||||
|
// So it's probably confusing - what is the "window index"?
|
||||||
|
// The idea is that we display a "window" of data in tables that *contains* the currently selected index.
|
||||||
|
if self.window_index.cached_area != self.bounds {
|
||||||
|
self.window_index.index = 0;
|
||||||
|
self.window_index.cached_area = self.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list_start = match self.scroll_direction {
|
||||||
|
ScrollDirection::Down => {
|
||||||
|
if self.current_index < self.window_index.index + num_visible_rows {
|
||||||
|
// If, using the current window index, we can see the element
|
||||||
|
// (so within that and + num_visible_rows) just reuse the current previously scrolled position
|
||||||
|
self.window_index.index
|
||||||
|
} else if self.current_index >= num_visible_rows {
|
||||||
|
// Else if the current position past the last element visible in the list, omit
|
||||||
|
// until we can see that element. The +1 is of how indexes start at 0.
|
||||||
|
|
||||||
|
self.window_index.index = self.current_index - num_visible_rows + 1;
|
||||||
|
self.window_index.index
|
||||||
|
} else {
|
||||||
|
// Else, if it is not past the last element visible, do not omit anything
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollDirection::Up => {
|
||||||
|
if self.current_index <= self.window_index.index {
|
||||||
|
// If it's past the first element, then show from that element downwards
|
||||||
|
self.window_index.index = self.current_index;
|
||||||
|
} else if self.current_index >= self.window_index.index + num_visible_rows {
|
||||||
|
self.window_index.index = self.current_index - num_visible_rows + 1;
|
||||||
|
}
|
||||||
|
// Else, don't change what our start position is from whatever it is set to!
|
||||||
|
self.window_index.index
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tui_state
|
||||||
|
.select(Some(self.current_index.saturating_sub(list_start)));
|
||||||
|
|
||||||
|
list_start
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the index with this! This will automatically update the scroll direction as well!
|
||||||
fn update_index(&mut self, new_index: usize) {
|
fn update_index(&mut self, new_index: usize) {
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
match new_index.cmp(&self.current_index) {
|
match new_index.cmp(&self.current_index) {
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
self.previous_index = self.current_index;
|
|
||||||
self.current_index = new_index;
|
self.current_index = new_index;
|
||||||
self.scroll_direction = ScrollDirection::Down;
|
self.scroll_direction = ScrollDirection::Down;
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
self.previous_index = self.current_index;
|
|
||||||
self.current_index = new_index;
|
self.current_index = new_index;
|
||||||
self.scroll_direction = ScrollDirection::Up;
|
self.scroll_direction = ScrollDirection::Up;
|
||||||
}
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
Ordering::Equal => {}
|
// Do nothing.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,33 +151,32 @@ impl Scrollable {
|
|||||||
|
|
||||||
/// Moves *downward* by *incrementing* the current index.
|
/// Moves *downward* by *incrementing* the current index.
|
||||||
fn move_down(&mut self, change_by: usize) -> EventResult {
|
fn move_down(&mut self, change_by: usize) -> EventResult {
|
||||||
|
if self.num_items == 0 {
|
||||||
|
return EventResult::NoRedraw;
|
||||||
|
}
|
||||||
|
|
||||||
let new_index = self.current_index + change_by;
|
let new_index = self.current_index + change_by;
|
||||||
if new_index >= self.num_items {
|
if new_index >= self.num_items {
|
||||||
let last_index = self.num_items - 1;
|
EventResult::NoRedraw
|
||||||
if self.current_index != last_index {
|
|
||||||
self.update_index(last_index);
|
|
||||||
|
|
||||||
EventResult::Redraw
|
|
||||||
} else {
|
|
||||||
EventResult::NoRedraw
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.update_index(new_index);
|
if self.current_index == new_index {
|
||||||
EventResult::Redraw
|
EventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
self.update_index(new_index);
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves *upward* by *decrementing* the current index.
|
/// Moves *upward* by *decrementing* the current index.
|
||||||
fn move_up(&mut self, change_by: usize) -> EventResult {
|
fn move_up(&mut self, change_by: usize) -> EventResult {
|
||||||
let new_index = self.current_index.saturating_sub(change_by);
|
if self.num_items == 0 {
|
||||||
if new_index == 0 {
|
return EventResult::NoRedraw;
|
||||||
if self.current_index != 0 {
|
}
|
||||||
self.update_index(0);
|
|
||||||
|
|
||||||
EventResult::Redraw
|
let new_index = self.current_index.saturating_sub(change_by);
|
||||||
} else {
|
if self.current_index == new_index {
|
||||||
EventResult::NoRedraw
|
EventResult::NoRedraw
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.update_index(new_index);
|
self.update_index(new_index);
|
||||||
EventResult::Redraw
|
EventResult::Redraw
|
||||||
@ -133,15 +189,15 @@ impl Scrollable {
|
|||||||
if num_items <= self.current_index {
|
if num_items <= self.current_index {
|
||||||
self.current_index = num_items.saturating_sub(1);
|
self.current_index = num_items.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if num_items <= self.previous_index {
|
|
||||||
self.previous_index = num_items.saturating_sub(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_items(&self) -> usize {
|
pub fn num_items(&self) -> usize {
|
||||||
self.num_items
|
self.num_items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tui_state(&self) -> TableState {
|
||||||
|
self.tui_state.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Scrollable {
|
impl Component for Scrollable {
|
||||||
@ -169,29 +225,31 @@ impl Component for Scrollable {
|
|||||||
|
|
||||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||||
match event.kind {
|
match event.kind {
|
||||||
crossterm::event::MouseEventKind::Down(MouseButton::Left) => {
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
// This requires a bit of fancy calculation. The main trick is remembering that
|
if self.does_intersect_mouse(&event) {
|
||||||
// we are using a *visual* index here - not what is the actual index! Luckily, we keep track of that
|
// This requires a bit of fancy calculation. The main trick is remembering that
|
||||||
// inside our linked copy of TableState!
|
// we are using a *visual* index here - not what is the actual index! Luckily, we keep track of that
|
||||||
|
// inside our linked copy of TableState!
|
||||||
|
|
||||||
// Note that y is assumed to be *relative*;
|
// Note that y is assumed to be *relative*;
|
||||||
// we assume that y starts at where the list starts (and there are no gaps or whatever).
|
// we assume that y starts at where the list starts (and there are no gaps or whatever).
|
||||||
let y = usize::from(event.row - self.bounds.top());
|
let y = usize::from(event.row - self.bounds.top());
|
||||||
|
|
||||||
if let Some(selected) = self.tui_state.selected() {
|
if let Some(selected) = self.tui_state.selected() {
|
||||||
if y > selected {
|
if y > selected {
|
||||||
let offset = y - selected;
|
let offset = y - selected;
|
||||||
return self.move_down(offset);
|
return self.move_down(offset);
|
||||||
} else {
|
} else if y < selected {
|
||||||
let offset = selected - y;
|
let offset = selected - y;
|
||||||
return self.move_up(offset);
|
return self.move_up(offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult::NoRedraw
|
EventResult::NoRedraw
|
||||||
}
|
}
|
||||||
crossterm::event::MouseEventKind::ScrollDown => self.move_down(1),
|
MouseEventKind::ScrollDown => self.move_down(1),
|
||||||
crossterm::event::MouseEventKind::ScrollUp => self.move_up(1),
|
MouseEventKind::ScrollUp => self.move_up(1),
|
||||||
_ => EventResult::NoRedraw,
|
_ => EventResult::NoRedraw,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{max, min},
|
cmp::{max, min, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
||||||
use tui::{
|
use tui::{
|
||||||
layout::{Constraint, Rect},
|
layout::{Constraint, Rect},
|
||||||
text::Text,
|
text::Text,
|
||||||
@ -24,6 +24,13 @@ pub enum DesiredColumnWidth {
|
|||||||
Flex { desired: u16, max_percentage: f64 },
|
Flex { desired: u16, max_percentage: f64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`ColumnType`] is a
|
||||||
|
pub trait ColumnType {
|
||||||
|
type DataType;
|
||||||
|
|
||||||
|
fn sort_function(a: Self::DataType, b: Self::DataType) -> Ordering;
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Column`] represents some column in a [`TextTable`].
|
/// A [`Column`] represents some column in a [`TextTable`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Column {
|
pub struct Column {
|
||||||
@ -33,7 +40,7 @@ pub struct Column {
|
|||||||
|
|
||||||
// TODO: I would remove these in the future, storing them here feels weird...
|
// TODO: I would remove these in the future, storing them here feels weird...
|
||||||
pub desired_width: DesiredColumnWidth,
|
pub desired_width: DesiredColumnWidth,
|
||||||
pub x_bounds: (u16, u16),
|
pub x_bounds: Option<(u16, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Column {
|
impl Column {
|
||||||
@ -44,7 +51,7 @@ impl Column {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
x_bounds: (0, 0),
|
x_bounds: None,
|
||||||
shortcut: shortcut.map(|e| {
|
shortcut: shortcut.map(|e| {
|
||||||
let modifier = if e.modifiers.is_empty() {
|
let modifier = if e.modifiers.is_empty() {
|
||||||
""
|
""
|
||||||
@ -90,16 +97,17 @@ impl Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new [`Column`] with a hard desired width. If none is specified,
|
/// Creates a new [`Column`] with a hard desired width. If none is specified,
|
||||||
/// it will instead use the name's length.
|
/// it will instead use the name's length + 1.
|
||||||
pub fn new_hard(
|
pub fn new_hard(
|
||||||
name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool,
|
name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool,
|
||||||
hard_length: Option<u16>,
|
hard_length: Option<u16>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// TODO: It should really be based on the shortcut name...
|
||||||
Column::new(
|
Column::new(
|
||||||
name,
|
name,
|
||||||
shortcut,
|
shortcut,
|
||||||
default_descending,
|
default_descending,
|
||||||
DesiredColumnWidth::Hard(hard_length.unwrap_or(name.len() as u16)),
|
DesiredColumnWidth::Hard(hard_length.unwrap_or(name.len() as u16 + 1)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +246,7 @@ impl TextTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_desired_column_widths(
|
pub fn get_desired_column_widths(
|
||||||
columns: &[Column], data: &[Vec<String>],
|
columns: &[Column], data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
|
||||||
) -> Vec<DesiredColumnWidth> {
|
) -> Vec<DesiredColumnWidth> {
|
||||||
columns
|
columns
|
||||||
.iter()
|
.iter()
|
||||||
@ -248,8 +256,22 @@ impl TextTable {
|
|||||||
let max_len = data
|
let max_len = data
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|c| c.get(column_index))
|
.filter_map(|c| c.get(column_index))
|
||||||
.max_by(|x, y| x.len().cmp(&y.len()))
|
.max_by(|(x, short_x), (y, short_y)| {
|
||||||
.map(|s| s.len())
|
let x = if let Some(short_x) = short_x {
|
||||||
|
short_x
|
||||||
|
} else {
|
||||||
|
x
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = if let Some(short_y) = short_y {
|
||||||
|
short_y
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
};
|
||||||
|
|
||||||
|
x.len().cmp(&y.len())
|
||||||
|
})
|
||||||
|
.map(|(s, _)| s.len())
|
||||||
.unwrap_or(0) as u16;
|
.unwrap_or(0) as u16;
|
||||||
|
|
||||||
DesiredColumnWidth::Hard(max(max_len, width))
|
DesiredColumnWidth::Hard(max(max_len, width))
|
||||||
@ -262,16 +284,16 @@ impl TextTable {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cache(&mut self, area: Rect, data: &[Vec<String>]) -> Vec<u16> {
|
fn get_cache(
|
||||||
|
&mut self, area: Rect, data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
|
||||||
|
) -> Vec<u16> {
|
||||||
fn calculate_column_widths(
|
fn calculate_column_widths(
|
||||||
left_to_right: bool, mut desired_widths: Vec<DesiredColumnWidth>, total_width: u16,
|
left_to_right: bool, mut desired_widths: Vec<DesiredColumnWidth>, total_width: u16,
|
||||||
) -> Vec<u16> {
|
) -> Vec<u16> {
|
||||||
debug!("OG desired widths: {:?}", desired_widths);
|
|
||||||
let mut total_width_left = total_width;
|
let mut total_width_left = total_width;
|
||||||
if !left_to_right {
|
if !left_to_right {
|
||||||
desired_widths.reverse();
|
desired_widths.reverse();
|
||||||
}
|
}
|
||||||
debug!("Desired widths: {:?}", desired_widths);
|
|
||||||
|
|
||||||
let mut column_widths: Vec<u16> = Vec::with_capacity(desired_widths.len());
|
let mut column_widths: Vec<u16> = Vec::with_capacity(desired_widths.len());
|
||||||
for width in desired_widths {
|
for width in desired_widths {
|
||||||
@ -303,7 +325,6 @@ impl TextTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("Initial column widths: {:?}", column_widths);
|
|
||||||
|
|
||||||
if !column_widths.is_empty() {
|
if !column_widths.is_empty() {
|
||||||
let amount_per_slot = total_width_left / column_widths.len() as u16;
|
let amount_per_slot = total_width_left / column_widths.len() as u16;
|
||||||
@ -321,8 +342,6 @@ impl TextTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Column widths: {:?}", column_widths);
|
|
||||||
|
|
||||||
column_widths
|
column_widths
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,9 +349,11 @@ impl TextTable {
|
|||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
vec![0; self.columns.len()]
|
vec![0; self.columns.len()]
|
||||||
} else {
|
} else {
|
||||||
match &mut self.cached_column_widths {
|
let was_cached: bool;
|
||||||
|
let column_widths = match &mut self.cached_column_widths {
|
||||||
CachedColumnWidths::Uncached => {
|
CachedColumnWidths::Uncached => {
|
||||||
// Always recalculate.
|
// Always recalculate.
|
||||||
|
was_cached = false;
|
||||||
let desired_widths = TextTable::get_desired_column_widths(&self.columns, data);
|
let desired_widths = TextTable::get_desired_column_widths(&self.columns, data);
|
||||||
let calculated_widths =
|
let calculated_widths =
|
||||||
calculate_column_widths(self.left_to_right, desired_widths, area.width);
|
calculate_column_widths(self.left_to_right, desired_widths, area.width);
|
||||||
@ -349,6 +370,7 @@ impl TextTable {
|
|||||||
} => {
|
} => {
|
||||||
if *cached_area != area {
|
if *cached_area != area {
|
||||||
// Recalculate!
|
// Recalculate!
|
||||||
|
was_cached = false;
|
||||||
let desired_widths =
|
let desired_widths =
|
||||||
TextTable::get_desired_column_widths(&self.columns, data);
|
TextTable::get_desired_column_widths(&self.columns, data);
|
||||||
let calculated_widths =
|
let calculated_widths =
|
||||||
@ -358,10 +380,22 @@ impl TextTable {
|
|||||||
|
|
||||||
calculated_widths
|
calculated_widths
|
||||||
} else {
|
} else {
|
||||||
|
was_cached = true;
|
||||||
cached_data.clone()
|
cached_data.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !was_cached {
|
||||||
|
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_start = column_end + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
column_widths
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,9 +406,9 @@ impl TextTable {
|
|||||||
/// Note if the number of columns don't match in the [`TextTable`] and data,
|
/// Note if the number of columns don't match in the [`TextTable`] and data,
|
||||||
/// it will only create as many columns as it can grab data from both sources from.
|
/// it will only create as many columns as it can grab data from both sources from.
|
||||||
pub fn create_draw_table(
|
pub fn create_draw_table(
|
||||||
&mut self, painter: &Painter, data: &[Vec<String>], area: Rect,
|
&mut self, painter: &Painter, data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
|
||||||
|
area: Rect,
|
||||||
) -> (Table<'_>, Vec<Constraint>, TableState) {
|
) -> (Table<'_>, Vec<Constraint>, TableState) {
|
||||||
// TODO: Change data: &[Vec<String>] to &[Vec<Cow<'static, str>>]
|
|
||||||
use tui::widgets::Row;
|
use tui::widgets::Row;
|
||||||
|
|
||||||
let table_gap = if !self.show_gap || area.height < TABLE_GAP_HEIGHT_LIMIT {
|
let table_gap = if !self.show_gap || area.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||||
@ -383,15 +417,16 @@ impl TextTable {
|
|||||||
1
|
1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.update_num_items(data.len());
|
||||||
self.set_bounds(area);
|
self.set_bounds(area);
|
||||||
let scrollable_height = area.height.saturating_sub(1 + table_gap);
|
let table_extras = 1 + table_gap;
|
||||||
|
let scrollable_height = area.height.saturating_sub(table_extras);
|
||||||
self.scrollable.set_bounds(Rect::new(
|
self.scrollable.set_bounds(Rect::new(
|
||||||
area.x,
|
area.x,
|
||||||
area.y + 1 + table_gap,
|
area.y + table_extras,
|
||||||
area.width,
|
area.width,
|
||||||
scrollable_height,
|
scrollable_height,
|
||||||
));
|
));
|
||||||
self.update_num_items(data.len());
|
|
||||||
|
|
||||||
// Calculate widths first, since we need them later.
|
// Calculate widths first, since we need them later.
|
||||||
let calculated_widths = self.get_cache(area, data);
|
let calculated_widths = self.get_cache(area, data);
|
||||||
@ -403,7 +438,7 @@ impl TextTable {
|
|||||||
// Then calculate rows. We truncate the amount of data read based on height,
|
// Then calculate rows. We truncate the amount of data read based on height,
|
||||||
// as well as truncating some entries based on available width.
|
// as well as truncating some entries based on available width.
|
||||||
let data_slice = {
|
let data_slice = {
|
||||||
let start = self.scrollable.index();
|
let start = self.scrollable.get_list_start(scrollable_height as usize);
|
||||||
let end = std::cmp::min(
|
let end = std::cmp::min(
|
||||||
self.scrollable.num_items(),
|
self.scrollable.num_items(),
|
||||||
start + scrollable_height as usize,
|
start + scrollable_height as usize,
|
||||||
@ -411,17 +446,25 @@ impl TextTable {
|
|||||||
&data[start..end]
|
&data[start..end]
|
||||||
};
|
};
|
||||||
let rows = data_slice.iter().map(|row| {
|
let rows = data_slice.iter().map(|row| {
|
||||||
Row::new(row.iter().zip(&calculated_widths).map(|(cell, width)| {
|
Row::new(
|
||||||
let width = *width as usize;
|
row.iter()
|
||||||
let graphemes =
|
.zip(&calculated_widths)
|
||||||
UnicodeSegmentation::graphemes(cell.as_str(), true).collect::<Vec<&str>>();
|
.map(|((text, shrunk_text), width)| {
|
||||||
let grapheme_width = graphemes.len();
|
let width = *width as usize;
|
||||||
if width < grapheme_width && width > 1 {
|
let graphemes = UnicodeSegmentation::graphemes(text.as_ref(), true)
|
||||||
Text::raw(format!("{}…", graphemes[..(width - 1)].concat()))
|
.collect::<Vec<&str>>();
|
||||||
} else {
|
let grapheme_width = graphemes.len();
|
||||||
Text::raw(cell.to_owned())
|
if width < grapheme_width && width > 1 {
|
||||||
}
|
if let Some(shrunk_text) = shrunk_text {
|
||||||
}))
|
Text::raw(shrunk_text.clone())
|
||||||
|
} else {
|
||||||
|
Text::raw(format!("{}…", graphemes[..(width - 1)].concat()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text::raw(text.to_owned())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now build up our headers...
|
// Now build up our headers...
|
||||||
@ -430,8 +473,7 @@ impl TextTable {
|
|||||||
.bottom_margin(table_gap);
|
.bottom_margin(table_gap);
|
||||||
|
|
||||||
// And return tui-rs's [`TableState`].
|
// And return tui-rs's [`TableState`].
|
||||||
let mut tui_state = TableState::default();
|
let tui_state = self.scrollable.tui_state();
|
||||||
tui_state.select(Some(self.scrollable.index()));
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Table::new(rows)
|
Table::new(rows)
|
||||||
@ -464,25 +506,33 @@ impl Component for TextTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||||
// Note these are representing RELATIVE coordinates!
|
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
|
||||||
let x = event.column - self.bounds.left();
|
if !self.does_intersect_mouse(&event) {
|
||||||
let y = event.row - self.bounds.top();
|
return EventResult::NoRedraw;
|
||||||
|
}
|
||||||
|
|
||||||
if y == 0 {
|
// Note these are representing RELATIVE coordinates! They *need* the above intersection check for validity!
|
||||||
for (index, column) in self.columns.iter().enumerate() {
|
let x = event.column - self.bounds.left();
|
||||||
let (start, end) = column.x_bounds;
|
let y = event.row - self.bounds.top();
|
||||||
if start >= x && end <= y {
|
|
||||||
if self.sort_index == index {
|
if y == 0 {
|
||||||
// Just flip the sort if we're already sorting by this.
|
for (index, column) in self.columns.iter().enumerate() {
|
||||||
self.sort_ascending = !self.sort_ascending;
|
if let Some((start, end)) = column.x_bounds {
|
||||||
} else {
|
if x >= start && x <= end {
|
||||||
self.sort_index = index;
|
if self.sort_index == index {
|
||||||
self.sort_ascending = !column.default_descending;
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult::NoRedraw
|
self.scrollable.handle_mouse_event(event)
|
||||||
} else {
|
} else {
|
||||||
self.scrollable.handle_mouse_event(event)
|
self.scrollable.handle_mouse_event(event)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Instant};
|
|||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
|
|
||||||
use crate::app::event::{does_point_intersect_rect, EventResult};
|
use crate::app::event::EventResult;
|
||||||
|
|
||||||
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, TimeGraph, Widget};
|
use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, TimeGraph, Widget};
|
||||||
|
|
||||||
@ -99,13 +99,10 @@ impl Component for CpuGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||||
let global_x = event.column;
|
if self.graph.does_intersect_mouse(&event) {
|
||||||
let global_y = event.row;
|
|
||||||
|
|
||||||
if does_point_intersect_rect(global_x, global_y, self.graph.bounds()) {
|
|
||||||
self.selected = CpuGraphSelection::Graph;
|
self.selected = CpuGraphSelection::Graph;
|
||||||
self.graph.handle_mouse_event(event)
|
self.graph.handle_mouse_event(event)
|
||||||
} else if does_point_intersect_rect(global_x, global_y, self.legend.bounds()) {
|
} else if self.legend.does_intersect_mouse(&event) {
|
||||||
self.selected = CpuGraphSelection::Legend;
|
self.selected = CpuGraphSelection::Legend;
|
||||||
self.legend.handle_mouse_event(event)
|
self.legend.handle_mouse_event(event)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
use tui::{backend::Backend, layout::Rect, widgets::Block, Frame};
|
use tui::{
|
||||||
|
backend::Backend,
|
||||||
|
layout::Rect,
|
||||||
|
widgets::{Block, Borders},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{event::EventResult, text_table::Column},
|
app::{event::EventResult, text_table::Column},
|
||||||
@ -92,14 +97,29 @@ impl Widget for DiskTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw<B: Backend>(
|
fn draw<B: Backend>(
|
||||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, block: Block<'_>,
|
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, data: &DisplayableData,
|
||||||
data: &DisplayableData,
|
selected: bool,
|
||||||
) {
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.border_style(if selected {
|
||||||
|
painter.colours.highlighted_border_style
|
||||||
|
} else {
|
||||||
|
painter.colours.border_style
|
||||||
|
})
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
|
||||||
|
self.set_bounds(area);
|
||||||
let draw_area = block.inner(area);
|
let draw_area = block.inner(area);
|
||||||
let (table, widths, mut tui_state) =
|
let (table, widths, mut tui_state) =
|
||||||
self.table
|
self.table
|
||||||
.create_draw_table(painter, &data.disk_data, draw_area);
|
.create_draw_table(painter, &data.disk_data, draw_area);
|
||||||
|
|
||||||
|
let table = table.highlight_style(if selected {
|
||||||
|
painter.colours.currently_selected_text_style
|
||||||
|
} else {
|
||||||
|
painter.colours.text_style
|
||||||
|
});
|
||||||
|
|
||||||
f.render_stateful_widget(table.block(block).widths(&widths), area, &mut tui_state);
|
f.render_stateful_widget(table.block(block).widths(&widths), area, &mut tui_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
|
||||||
use unicode_segmentation::GraphemeCursor;
|
use unicode_segmentation::GraphemeCursor;
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
widgets::{Block, TableState},
|
widgets::{Block, Borders, TableState},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
event::{does_point_intersect_rect, EventResult, MultiKey, MultiKeyResult},
|
event::{EventResult, MultiKey, MultiKeyResult},
|
||||||
query::*,
|
query::*,
|
||||||
},
|
},
|
||||||
canvas::{DisplayableData, Painter},
|
canvas::{DisplayableData, Painter},
|
||||||
data_harvester::processes::{self, ProcessSorting},
|
data_harvester::processes::{self, ProcessSorting},
|
||||||
|
options::ProcessDefaults,
|
||||||
};
|
};
|
||||||
use ProcessSorting::*;
|
use ProcessSorting::*;
|
||||||
|
|
||||||
@ -648,7 +649,7 @@ pub struct ProcessManager {
|
|||||||
selected: ProcessManagerSelection,
|
selected: ProcessManagerSelection,
|
||||||
|
|
||||||
in_tree_mode: bool,
|
in_tree_mode: bool,
|
||||||
show_sort: bool,
|
show_sort: bool, // TODO: Add this for temp and disk???
|
||||||
show_search: bool,
|
show_search: bool,
|
||||||
|
|
||||||
search_modifiers: SearchModifiers,
|
search_modifiers: SearchModifiers,
|
||||||
@ -656,19 +657,28 @@ pub struct ProcessManager {
|
|||||||
|
|
||||||
impl ProcessManager {
|
impl ProcessManager {
|
||||||
/// Creates a new [`ProcessManager`].
|
/// Creates a new [`ProcessManager`].
|
||||||
pub fn new(default_in_tree_mode: bool) -> Self {
|
pub fn new(process_defaults: &ProcessDefaults) -> Self {
|
||||||
Self {
|
let process_table_columns = vec![];
|
||||||
|
|
||||||
|
let mut manager = Self {
|
||||||
bounds: Rect::default(),
|
bounds: Rect::default(),
|
||||||
process_table: TextTable::new(vec![]), // TODO: Do this
|
process_table: TextTable::new(process_table_columns), // TODO: Do this
|
||||||
sort_table: TextTable::new(vec![]), // TODO: Do this too
|
sort_table: TextTable::new(vec![]), // TODO: Do this too
|
||||||
search_input: TextInput::new(),
|
search_input: TextInput::new(),
|
||||||
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Use a static arrayvec
|
dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Maybe use something static...
|
||||||
selected: ProcessManagerSelection::Processes,
|
selected: ProcessManagerSelection::Processes,
|
||||||
in_tree_mode: default_in_tree_mode,
|
in_tree_mode: false,
|
||||||
show_sort: false,
|
show_sort: false,
|
||||||
show_search: false,
|
show_search: false,
|
||||||
search_modifiers: SearchModifiers::default(),
|
search_modifiers: SearchModifiers::default(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
manager.set_tree_mode(process_defaults.is_tree);
|
||||||
|
manager
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tree_mode(&mut self, in_tree_mode: bool) {
|
||||||
|
self.in_tree_mode = in_tree_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_search(&mut self) -> EventResult {
|
fn open_search(&mut self) -> EventResult {
|
||||||
@ -800,20 +810,27 @@ impl Component for ProcessManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
|
||||||
let global_x = event.column;
|
match &event.kind {
|
||||||
let global_y = event.row;
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
|
if self.process_table.does_intersect_mouse(&event) {
|
||||||
if does_point_intersect_rect(global_x, global_y, self.process_table.bounds()) {
|
self.selected = ProcessManagerSelection::Processes;
|
||||||
self.selected = ProcessManagerSelection::Processes;
|
self.process_table.handle_mouse_event(event)
|
||||||
self.process_table.handle_mouse_event(event)
|
} else if self.sort_table.does_intersect_mouse(&event) {
|
||||||
} else if does_point_intersect_rect(global_x, global_y, self.sort_table.bounds()) {
|
self.selected = ProcessManagerSelection::Sort;
|
||||||
self.selected = ProcessManagerSelection::Sort;
|
self.sort_table.handle_mouse_event(event)
|
||||||
self.sort_table.handle_mouse_event(event)
|
} else if self.search_input.does_intersect_mouse(&event) {
|
||||||
} else if does_point_intersect_rect(global_x, global_y, self.search_input.bounds()) {
|
self.selected = ProcessManagerSelection::Search;
|
||||||
self.selected = ProcessManagerSelection::Search;
|
self.search_input.handle_mouse_event(event)
|
||||||
self.search_input.handle_mouse_event(event)
|
} else {
|
||||||
} else {
|
EventResult::NoRedraw
|
||||||
EventResult::NoRedraw
|
}
|
||||||
|
}
|
||||||
|
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::Search => self.search_input.handle_mouse_event(event),
|
||||||
|
},
|
||||||
|
_ => EventResult::NoRedraw,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -824,9 +841,18 @@ impl Widget for ProcessManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw<B: Backend>(
|
fn draw<B: Backend>(
|
||||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, block: Block<'_>,
|
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, data: &DisplayableData,
|
||||||
data: &DisplayableData,
|
selected: bool,
|
||||||
) {
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.border_style(if selected {
|
||||||
|
painter.colours.highlighted_border_style
|
||||||
|
} else {
|
||||||
|
painter.colours.border_style
|
||||||
|
})
|
||||||
|
.borders(Borders::ALL);
|
||||||
|
|
||||||
|
self.set_bounds(area);
|
||||||
let draw_area = block.inner(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.create_draw_table(
|
||||||
painter,
|
painter,
|
||||||
@ -834,6 +860,12 @@ impl Widget for ProcessManager {
|
|||||||
draw_area,
|
draw_area,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let process_table = process_table.highlight_style(if selected {
|
||||||
|
painter.colours.currently_selected_text_style
|
||||||
|
} else {
|
||||||
|
painter.colours.text_style
|
||||||
|
});
|
||||||
|
|
||||||
f.render_stateful_widget(
|
f.render_stateful_widget(
|
||||||
process_table.block(block).widths(&widths),
|
process_table.block(block).widths(&widths),
|
||||||
area,
|
area,
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
use tui::{backend::Backend, layout::Rect, widgets::Block, Frame};
|
use tui::{
|
||||||
|
backend::Backend,
|
||||||
|
layout::Rect,
|
||||||
|
widgets::{Block, Borders},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{event::EventResult, text_table::Column},
|
app::{event::EventResult, text_table::Column},
|
||||||
@ -52,7 +57,7 @@ pub struct TempTable {
|
|||||||
impl Default for TempTable {
|
impl Default for TempTable {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let table = TextTable::new(vec![
|
let table = TextTable::new(vec![
|
||||||
Column::new_flex("Sensor", None, false, 1.0),
|
Column::new_flex("Sensor", None, false, 0.8),
|
||||||
Column::new_hard("Temp", None, false, Some(4)),
|
Column::new_hard("Temp", None, false, Some(4)),
|
||||||
])
|
])
|
||||||
.left_to_right(false);
|
.left_to_right(false);
|
||||||
@ -88,14 +93,29 @@ impl Widget for TempTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw<B: Backend>(
|
fn draw<B: Backend>(
|
||||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, block: Block<'_>,
|
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, data: &DisplayableData,
|
||||||
data: &DisplayableData,
|
selected: bool,
|
||||||
) {
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.border_style(if selected {
|
||||||
|
painter.colours.highlighted_border_style
|
||||||
|
} else {
|
||||||
|
painter.colours.border_style
|
||||||
|
})
|
||||||
|
.borders(Borders::ALL); // TODO: Also do the scrolling indicator!
|
||||||
|
|
||||||
|
self.set_bounds(area);
|
||||||
let draw_area = block.inner(area);
|
let draw_area = block.inner(area);
|
||||||
let (table, widths, mut tui_state) =
|
let (table, widths, mut tui_state) =
|
||||||
self.table
|
self.table
|
||||||
.create_draw_table(painter, &data.temp_sensor_data, draw_area);
|
.create_draw_table(painter, &data.temp_sensor_data, draw_area);
|
||||||
|
|
||||||
|
let table = table.highlight_style(if selected {
|
||||||
|
painter.colours.currently_selected_text_style
|
||||||
|
} else {
|
||||||
|
painter.colours.text_style
|
||||||
|
});
|
||||||
|
|
||||||
f.render_stateful_widget(table.block(block).widths(&widths), area, &mut tui_state);
|
f.render_stateful_widget(table.block(block).widths(&widths), area, &mut tui_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
144
src/bin/main.rs
144
src/bin/main.rs
@ -4,7 +4,13 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use bottom::{app::event::EventResult, canvas, constants::*, data_conversion::*, options::*, *};
|
use bottom::{
|
||||||
|
app::event::{EventResult, ReturnSignalResult},
|
||||||
|
canvas,
|
||||||
|
constants::*,
|
||||||
|
options::*,
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
@ -85,7 +91,8 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Event loop
|
// Event loop
|
||||||
let (collection_sender, collection_thread_ctrl_receiver) = mpsc::channel();
|
// TODO: Add back collection sender
|
||||||
|
let (_collection_sender, collection_thread_ctrl_receiver) = mpsc::channel();
|
||||||
let _collection_thread = create_collection_thread(
|
let _collection_thread = create_collection_thread(
|
||||||
sender,
|
sender,
|
||||||
collection_thread_ctrl_receiver,
|
collection_thread_ctrl_receiver,
|
||||||
@ -114,129 +121,30 @@ fn main() -> Result<()> {
|
|||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
ist_clone.store(true, Ordering::SeqCst);
|
ist_clone.store(true, Ordering::SeqCst);
|
||||||
})?;
|
})?;
|
||||||
let mut first_run = true;
|
|
||||||
|
|
||||||
while !is_terminated.load(Ordering::SeqCst) {
|
while !is_terminated.load(Ordering::SeqCst) {
|
||||||
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||||
match recv {
|
match app.handle_event(recv) {
|
||||||
BottomEvent::KeyInput(event) => {
|
EventResult::Quit => {
|
||||||
match handle_key_event(event, &mut app, &collection_sender) {
|
break;
|
||||||
EventResult::Quit => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
EventResult::Redraw => {
|
|
||||||
// TODO: Be even more granular! Maybe the event triggered no change, then we shouldn't redraw.
|
|
||||||
force_redraw(&mut app);
|
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BottomEvent::MouseInput(event) => match handle_mouse_event(event, &mut app) {
|
EventResult::Redraw => {
|
||||||
EventResult::Quit => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
EventResult::Redraw => {
|
|
||||||
// TODO: Be even more granular! Maybe the event triggered no change, then we shouldn't redraw.
|
|
||||||
force_redraw(&mut app);
|
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
BottomEvent::Update(data) => {
|
|
||||||
app.data_collection.eat_data(data);
|
|
||||||
|
|
||||||
// This thing is required as otherwise, some widgets can't draw correctly w/o
|
|
||||||
// some data (or they need to be re-drawn).
|
|
||||||
if first_run {
|
|
||||||
first_run = false;
|
|
||||||
app.is_force_redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !app.is_frozen {
|
|
||||||
// Convert all data into tui-compliant components
|
|
||||||
|
|
||||||
// Network
|
|
||||||
if app.used_widgets.use_net {
|
|
||||||
let network_data = convert_network_data_points(
|
|
||||||
&app.data_collection,
|
|
||||||
false,
|
|
||||||
app.app_config_fields.use_basic_mode
|
|
||||||
|| app.app_config_fields.use_old_network_legend,
|
|
||||||
&app.app_config_fields.network_scale_type,
|
|
||||||
&app.app_config_fields.network_unit_type,
|
|
||||||
app.app_config_fields.network_use_binary_prefix,
|
|
||||||
);
|
|
||||||
app.canvas_data.network_data_rx = network_data.rx;
|
|
||||||
app.canvas_data.network_data_tx = network_data.tx;
|
|
||||||
app.canvas_data.rx_display = network_data.rx_display;
|
|
||||||
app.canvas_data.tx_display = network_data.tx_display;
|
|
||||||
if let Some(total_rx_display) = network_data.total_rx_display {
|
|
||||||
app.canvas_data.total_rx_display = total_rx_display;
|
|
||||||
}
|
|
||||||
if let Some(total_tx_display) = network_data.total_tx_display {
|
|
||||||
app.canvas_data.total_tx_display = total_tx_display;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disk
|
|
||||||
if app.used_widgets.use_disk {
|
|
||||||
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temperatures
|
|
||||||
if app.used_widgets.use_temp {
|
|
||||||
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memory
|
|
||||||
if app.used_widgets.use_mem {
|
|
||||||
app.canvas_data.mem_data =
|
|
||||||
convert_mem_data_points(&app.data_collection, false);
|
|
||||||
app.canvas_data.swap_data =
|
|
||||||
convert_swap_data_points(&app.data_collection, false);
|
|
||||||
let (memory_labels, swap_labels) =
|
|
||||||
convert_mem_labels(&app.data_collection);
|
|
||||||
|
|
||||||
app.canvas_data.mem_labels = memory_labels;
|
|
||||||
app.canvas_data.swap_labels = swap_labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
if app.used_widgets.use_cpu {
|
|
||||||
// CPU
|
|
||||||
|
|
||||||
convert_cpu_data_points(
|
|
||||||
&app.data_collection,
|
|
||||||
&mut app.canvas_data.cpu_data,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes
|
|
||||||
if app.used_widgets.use_proc {
|
|
||||||
update_all_process_lists(&mut app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Battery
|
|
||||||
if app.used_widgets.use_battery {
|
|
||||||
app.canvas_data.battery_data =
|
|
||||||
convert_battery_harvest(&app.data_collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BottomEvent::Resize {
|
|
||||||
width: _,
|
|
||||||
height: _,
|
|
||||||
} => {
|
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||||
}
|
}
|
||||||
BottomEvent::Clean => {
|
EventResult::NoRedraw => {
|
||||||
app.data_collection
|
continue;
|
||||||
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
|
EventResult::Signal(signal) => match app.handle_return_signal(signal) {
|
||||||
|
ReturnSignalResult::Quit => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ReturnSignalResult::Redraw => {
|
||||||
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||||
|
}
|
||||||
|
ReturnSignalResult::NoRedraw => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{borrow::Cow, collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, NodeId};
|
||||||
@ -6,7 +6,7 @@ use tui::{
|
|||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
text::{Span, Spans},
|
text::{Span, Spans},
|
||||||
widgets::{Block, Borders, Paragraph},
|
widgets::Paragraph,
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,11 +41,10 @@ pub struct DisplayableData {
|
|||||||
pub total_tx_display: String,
|
pub total_tx_display: String,
|
||||||
pub network_data_rx: Vec<Point>,
|
pub network_data_rx: Vec<Point>,
|
||||||
pub network_data_tx: Vec<Point>,
|
pub network_data_tx: Vec<Point>,
|
||||||
pub disk_data: Vec<Vec<String>>,
|
pub disk_data: Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>>,
|
||||||
pub temp_sensor_data: Vec<Vec<String>>,
|
pub temp_sensor_data: Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>>,
|
||||||
pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID
|
pub single_process_data: HashMap<Pid, ConvertedProcessData>, // Contains single process data, key is PID
|
||||||
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed, key is the widget ID.
|
pub stringified_process_data_map: HashMap<NodeId, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID
|
||||||
pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled, key is the widget ID
|
|
||||||
|
|
||||||
pub mem_labels: Option<(String, String)>,
|
pub mem_labels: Option<(String, String)>,
|
||||||
pub swap_labels: Option<(String, String)>,
|
pub swap_labels: Option<(String, String)>,
|
||||||
@ -342,10 +341,7 @@ impl Painter {
|
|||||||
.widget_lookup_map
|
.widget_lookup_map
|
||||||
.get_mut(&app_state.selected_widget)
|
.get_mut(&app_state.selected_widget)
|
||||||
{
|
{
|
||||||
let block = Block::default()
|
current_widget.draw(self, f, draw_area, canvas_data, true);
|
||||||
.border_style(self.colours.highlighted_border_style)
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
current_widget.draw(self, f, draw_area, block, canvas_data);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/// A simple traversal through the `arena`.
|
/// A simple traversal through the `arena`.
|
||||||
@ -396,14 +392,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
LayoutNode::Widget => {
|
LayoutNode::Widget => {
|
||||||
if let Some(widget) = lookup_map.get_mut(&node) {
|
if let Some(widget) = lookup_map.get_mut(&node) {
|
||||||
let block = Block::default()
|
widget.draw(painter, f, area, canvas_data, selected_id == node);
|
||||||
.border_style(if selected_id == node {
|
|
||||||
painter.colours.highlighted_border_style
|
|
||||||
} else {
|
|
||||||
painter.colours.border_style
|
|
||||||
})
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
widget.draw(painter, f, area, block, canvas_data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,18 @@ pub mod basic_table_arrows;
|
|||||||
pub mod battery_display;
|
pub mod battery_display;
|
||||||
pub mod cpu_basic;
|
pub mod cpu_basic;
|
||||||
pub mod cpu_graph;
|
pub mod cpu_graph;
|
||||||
pub mod disk_table;
|
|
||||||
pub mod mem_basic;
|
pub mod mem_basic;
|
||||||
pub mod mem_graph;
|
pub mod mem_graph;
|
||||||
pub mod network_basic;
|
pub mod network_basic;
|
||||||
pub mod network_graph;
|
pub mod network_graph;
|
||||||
pub mod process_table;
|
pub mod process_table;
|
||||||
pub mod temp_table;
|
|
||||||
|
|
||||||
pub use basic_table_arrows::*;
|
pub use basic_table_arrows::*;
|
||||||
pub use battery_display::*;
|
pub use battery_display::*;
|
||||||
pub use cpu_basic::*;
|
pub use cpu_basic::*;
|
||||||
pub use cpu_graph::*;
|
pub use cpu_graph::*;
|
||||||
pub use disk_table::*;
|
|
||||||
pub use mem_basic::*;
|
pub use mem_basic::*;
|
||||||
pub use mem_graph::*;
|
pub use mem_graph::*;
|
||||||
pub use network_basic::*;
|
pub use network_basic::*;
|
||||||
pub use network_graph::*;
|
pub use network_graph::*;
|
||||||
pub use process_table::*;
|
pub use process_table::*;
|
||||||
pub use temp_table::*;
|
|
||||||
|
@ -7,6 +7,7 @@ use crate::{
|
|||||||
constants::*,
|
constants::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use indextree::NodeId;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
@ -101,9 +102,9 @@ static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy<Vec<Option<f64>>> = La
|
|||||||
|
|
||||||
pub fn draw_process_features<B: Backend>(
|
pub fn draw_process_features<B: Backend>(
|
||||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||||
draw_border: bool, widget_id: u64,
|
draw_border: bool, widget_id: NodeId,
|
||||||
) {
|
) {
|
||||||
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
|
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&1) {
|
||||||
let search_height = if draw_border { 5 } else { 3 };
|
let search_height = if draw_border { 5 } else { 3 };
|
||||||
let is_sort_open = process_widget_state.is_sort_open;
|
let is_sort_open = process_widget_state.is_sort_open;
|
||||||
let header_len = process_widget_state.columns.longest_header_len;
|
let header_len = process_widget_state.columns.longest_header_len;
|
||||||
@ -122,7 +123,7 @@ pub fn draw_process_features<B: Backend>(
|
|||||||
app_state,
|
app_state,
|
||||||
processes_chunk[1],
|
processes_chunk[1],
|
||||||
draw_border,
|
draw_border,
|
||||||
widget_id + 1,
|
widget_id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,14 +134,7 @@ pub fn draw_process_features<B: Backend>(
|
|||||||
.split(proc_draw_loc);
|
.split(proc_draw_loc);
|
||||||
proc_draw_loc = processes_chunk[1];
|
proc_draw_loc = processes_chunk[1];
|
||||||
|
|
||||||
draw_process_sort(
|
draw_process_sort(painter, f, app_state, processes_chunk[0], draw_border, 1);
|
||||||
painter,
|
|
||||||
f,
|
|
||||||
app_state,
|
|
||||||
processes_chunk[0],
|
|
||||||
draw_border,
|
|
||||||
widget_id + 2,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_processes_table(painter, f, app_state, proc_draw_loc, draw_border, widget_id);
|
draw_processes_table(painter, f, app_state, proc_draw_loc, draw_border, widget_id);
|
||||||
@ -149,17 +143,17 @@ pub fn draw_process_features<B: Backend>(
|
|||||||
|
|
||||||
fn draw_processes_table<B: Backend>(
|
fn draw_processes_table<B: Backend>(
|
||||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||||
draw_border: bool, widget_id: u64,
|
draw_border: bool, widget_id: NodeId,
|
||||||
) {
|
) {
|
||||||
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) {
|
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&1) {
|
||||||
let recalculate_column_widths =
|
let recalculate_column_widths =
|
||||||
should_get_widget_bounds || proc_widget_state.requires_redraw;
|
should_get_widget_bounds || proc_widget_state.requires_redraw;
|
||||||
if proc_widget_state.requires_redraw {
|
if proc_widget_state.requires_redraw {
|
||||||
proc_widget_state.requires_redraw = false;
|
proc_widget_state.requires_redraw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
let is_on_widget = false;
|
||||||
let margined_draw_loc = Layout::default()
|
let margined_draw_loc = Layout::default()
|
||||||
.constraints([Constraint::Percentage(100)])
|
.constraints([Constraint::Percentage(100)])
|
||||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||||
@ -178,7 +172,7 @@ fn draw_processes_table<B: Backend>(
|
|||||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||||
if let Some(finalized_process_data) = app_state
|
if let Some(finalized_process_data) = app_state
|
||||||
.canvas_data
|
.canvas_data
|
||||||
.finalized_process_data_map
|
.stringified_process_data_map
|
||||||
.get(&widget_id)
|
.get(&widget_id)
|
||||||
{
|
{
|
||||||
let title = format!(
|
let title = format!(
|
||||||
@ -511,7 +505,7 @@ fn draw_processes_table<B: Backend>(
|
|||||||
|
|
||||||
if app_state.should_get_widget_bounds() {
|
if app_state.should_get_widget_bounds() {
|
||||||
// Update draw loc in widget map
|
// Update draw loc in widget map
|
||||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
if let Some(widget) = app_state.widget_map.get_mut(&1) {
|
||||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||||
widget.bottom_right_corner = Some((
|
widget.bottom_right_corner = Some((
|
||||||
margined_draw_loc.x + margined_draw_loc.width,
|
margined_draw_loc.x + margined_draw_loc.width,
|
||||||
@ -524,7 +518,7 @@ fn draw_processes_table<B: Backend>(
|
|||||||
|
|
||||||
fn draw_search_field<B: Backend>(
|
fn draw_search_field<B: Backend>(
|
||||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||||
draw_border: bool, widget_id: u64,
|
draw_border: bool, _widget_id: NodeId,
|
||||||
) {
|
) {
|
||||||
fn build_query<'a>(
|
fn build_query<'a>(
|
||||||
is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize,
|
is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize,
|
||||||
@ -565,8 +559,8 @@ fn draw_search_field<B: Backend>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make the cursor scroll back if there's space!
|
// TODO: Make the cursor scroll back if there's space!
|
||||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&(widget_id - 1)) {
|
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&1) {
|
||||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
let is_on_widget = false;
|
||||||
let num_columns = usize::from(draw_loc.width);
|
let num_columns = usize::from(draw_loc.width);
|
||||||
let search_title = "> ";
|
let search_title = "> ";
|
||||||
|
|
||||||
@ -728,7 +722,7 @@ fn draw_search_field<B: Backend>(
|
|||||||
|
|
||||||
if app_state.should_get_widget_bounds() {
|
if app_state.should_get_widget_bounds() {
|
||||||
// Update draw loc in widget map
|
// Update draw loc in widget map
|
||||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
if let Some(widget) = app_state.widget_map.get_mut(&1) {
|
||||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||||
widget.bottom_right_corner = Some((
|
widget.bottom_right_corner = Some((
|
||||||
margined_draw_loc.x + margined_draw_loc.width,
|
margined_draw_loc.x + margined_draw_loc.width,
|
||||||
|
@ -19,7 +19,7 @@ pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u64 = 1000;
|
|||||||
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000;
|
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000;
|
||||||
|
|
||||||
// Limits for when we should stop showing table gaps/labels (anything less means not shown)
|
// Limits for when we should stop showing table gaps/labels (anything less means not shown)
|
||||||
pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 7;
|
pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 5;
|
||||||
pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7;
|
pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7;
|
||||||
|
|
||||||
// For kill signals
|
// For kill signals
|
||||||
|
@ -8,6 +8,7 @@ use crate::{
|
|||||||
use data_harvester::processes::ProcessSorting;
|
use data_harvester::processes::ProcessSorting;
|
||||||
use fxhash::FxBuildHasher;
|
use fxhash::FxBuildHasher;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
/// Point is of time, data
|
/// Point is of time, data
|
||||||
@ -83,81 +84,97 @@ pub struct ConvertedCpuData {
|
|||||||
pub legend_value: String,
|
pub legend_value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_temp_row(app: &AppState) -> Vec<Vec<String>> {
|
pub fn convert_temp_row(
|
||||||
|
app: &AppState,
|
||||||
|
) -> Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>> {
|
||||||
let current_data = &app.data_collection;
|
let current_data = &app.data_collection;
|
||||||
let temp_type = &app.app_config_fields.temperature_type;
|
let temp_type = &app.app_config_fields.temperature_type;
|
||||||
|
|
||||||
let mut sensor_vector: Vec<Vec<String>> = current_data
|
if current_data.temp_harvest.is_empty() {
|
||||||
.temp_harvest
|
vec![vec![
|
||||||
.iter()
|
("No Sensors Found".into(), Some("N/A".into())),
|
||||||
.map(|temp_harvest| {
|
("".into(), None),
|
||||||
vec![
|
]]
|
||||||
temp_harvest.name.clone(),
|
} else {
|
||||||
(temp_harvest.temperature.ceil() as u64).to_string()
|
let (unit_long, unit_short) = match temp_type {
|
||||||
+ match temp_type {
|
data_harvester::temperature::TemperatureType::Celsius => ("°C", "C"),
|
||||||
data_harvester::temperature::TemperatureType::Celsius => "°C",
|
data_harvester::temperature::TemperatureType::Kelvin => ("K", "K"),
|
||||||
data_harvester::temperature::TemperatureType::Kelvin => "K",
|
data_harvester::temperature::TemperatureType::Fahrenheit => ("°F", "F"),
|
||||||
data_harvester::temperature::TemperatureType::Fahrenheit => "°F",
|
};
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if sensor_vector.is_empty() {
|
current_data
|
||||||
sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()]);
|
.temp_harvest
|
||||||
|
.iter()
|
||||||
|
.map(|temp_harvest| {
|
||||||
|
let val = temp_harvest.temperature.ceil().to_string();
|
||||||
|
vec![
|
||||||
|
(temp_harvest.name.clone().into(), None),
|
||||||
|
(
|
||||||
|
format!("{}{}", val, unit_long).into(),
|
||||||
|
Some(format!("{}{}", val, unit_short).into()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
sensor_vector
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<String>> {
|
pub fn convert_disk_row(
|
||||||
let mut disk_vector: Vec<Vec<String>> = Vec::new();
|
current_data: &data_farmer::DataCollection,
|
||||||
|
) -> Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>> {
|
||||||
|
if current_data.disk_harvest.is_empty() {
|
||||||
|
vec![vec![
|
||||||
|
("No Disks Found".into(), Some("N/A".into())),
|
||||||
|
("".into(), None),
|
||||||
|
]]
|
||||||
|
} else {
|
||||||
|
current_data
|
||||||
|
.disk_harvest
|
||||||
|
.iter()
|
||||||
|
.zip(¤t_data.io_labels)
|
||||||
|
.map(|(disk, (io_read, io_write))| {
|
||||||
|
let free_space_fmt = if let Some(free_space) = disk.free_space {
|
||||||
|
let converted_free_space = get_decimal_bytes(free_space);
|
||||||
|
Cow::Owned(format!(
|
||||||
|
"{:.*}{}",
|
||||||
|
0, converted_free_space.0, converted_free_space.1
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
"N/A".into()
|
||||||
|
};
|
||||||
|
let total_space_fmt = if let Some(total_space) = disk.total_space {
|
||||||
|
let converted_total_space = get_decimal_bytes(total_space);
|
||||||
|
Cow::Owned(format!(
|
||||||
|
"{:.*}{}",
|
||||||
|
0, converted_total_space.0, converted_total_space.1
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
"N/A".into()
|
||||||
|
};
|
||||||
|
|
||||||
current_data
|
let usage_fmt = if let (Some(used_space), Some(total_space)) =
|
||||||
.disk_harvest
|
(disk.used_space, disk.total_space)
|
||||||
.iter()
|
{
|
||||||
.zip(¤t_data.io_labels)
|
Cow::Owned(format!(
|
||||||
.for_each(|(disk, (io_read, io_write))| {
|
"{:.0}%",
|
||||||
let free_space_fmt = if let Some(free_space) = disk.free_space {
|
used_space as f64 / total_space as f64 * 100_f64
|
||||||
let converted_free_space = get_decimal_bytes(free_space);
|
))
|
||||||
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1)
|
} else {
|
||||||
} else {
|
"N/A".into()
|
||||||
"N/A".to_string()
|
};
|
||||||
};
|
|
||||||
let total_space_fmt = if let Some(total_space) = disk.total_space {
|
|
||||||
let converted_total_space = get_decimal_bytes(total_space);
|
|
||||||
format!(
|
|
||||||
"{:.*}{}",
|
|
||||||
0, converted_total_space.0, converted_total_space.1
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
"N/A".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let usage_fmt = if let (Some(used_space), Some(total_space)) =
|
vec![
|
||||||
(disk.used_space, disk.total_space)
|
(disk.name.clone().into(), None),
|
||||||
{
|
(disk.mount_point.clone().into(), None),
|
||||||
format!("{:.0}%", used_space as f64 / total_space as f64 * 100_f64)
|
(usage_fmt, None),
|
||||||
} else {
|
(free_space_fmt, None),
|
||||||
"N/A".to_string()
|
(total_space_fmt, None),
|
||||||
};
|
(io_read.clone().into(), None),
|
||||||
|
(io_write.clone().into(), None),
|
||||||
disk_vector.push(vec![
|
]
|
||||||
disk.name.to_string(),
|
})
|
||||||
disk.mount_point.to_string(),
|
.collect::<Vec<_>>()
|
||||||
usage_fmt,
|
|
||||||
free_space_fmt,
|
|
||||||
total_space_fmt,
|
|
||||||
io_read.to_string(),
|
|
||||||
io_write.to_string(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
if disk_vector.is_empty() {
|
|
||||||
disk_vector.push(vec!["No Disks Found".to_string(), "".to_string()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disk_vector
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_cpu_data_points(
|
pub fn convert_cpu_data_points(
|
||||||
|
195
src/lib.rs
195
src/lib.rs
@ -369,108 +369,109 @@ pub fn update_all_process_lists(app: &mut AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_final_process_list(app: &mut AppState, widget_id: u64) {
|
fn update_final_process_list(_app: &mut AppState, _widget_id: u64) {
|
||||||
let process_states = app
|
// TODO: [STATE] FINISH THIS
|
||||||
.proc_state
|
// let process_states = app
|
||||||
.widget_states
|
// .proc_state
|
||||||
.get(&widget_id)
|
// .widget_states
|
||||||
.map(|process_state| {
|
// .get(&widget_id)
|
||||||
(
|
// .map(|process_state| {
|
||||||
process_state
|
// (
|
||||||
.process_search_state
|
// process_state
|
||||||
.search_state
|
// .process_search_state
|
||||||
.is_invalid_or_blank_search(),
|
// .search_state
|
||||||
process_state.is_using_command,
|
// .is_invalid_or_blank_search(),
|
||||||
process_state.is_grouped,
|
// process_state.is_using_command,
|
||||||
process_state.is_tree_mode,
|
// process_state.is_grouped,
|
||||||
)
|
// process_state.is_tree_mode,
|
||||||
});
|
// )
|
||||||
|
// });
|
||||||
|
|
||||||
if let Some((is_invalid_or_blank, is_using_command, is_grouped, is_tree)) = process_states {
|
// if let Some((is_invalid_or_blank, is_using_command, is_grouped, is_tree)) = process_states {
|
||||||
if !app.is_frozen {
|
// if !app.is_frozen {
|
||||||
convert_process_data(
|
// convert_process_data(
|
||||||
&app.data_collection,
|
// &app.data_collection,
|
||||||
&mut app.canvas_data.single_process_data,
|
// &mut app.canvas_data.single_process_data,
|
||||||
#[cfg(target_family = "unix")]
|
// #[cfg(target_family = "unix")]
|
||||||
&mut app.user_table,
|
// &mut app.user_table,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
let process_filter = app.get_process_filter(widget_id);
|
// let process_filter = app.get_process_filter(widget_id);
|
||||||
let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
// let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
||||||
app.canvas_data
|
// app.canvas_data
|
||||||
.single_process_data
|
// .single_process_data
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|(_pid, process)| {
|
// .map(|(_pid, process)| {
|
||||||
let mut process_clone = process.clone();
|
// let mut process_clone = process.clone();
|
||||||
if !is_invalid_or_blank {
|
// if !is_invalid_or_blank {
|
||||||
if let Some(process_filter) = process_filter {
|
// if let Some(process_filter) = process_filter {
|
||||||
process_clone.is_disabled_entry =
|
// process_clone.is_disabled_entry =
|
||||||
!process_filter.check(&process_clone, is_using_command);
|
// !process_filter.check(&process_clone, is_using_command);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
process_clone
|
// process_clone
|
||||||
})
|
// })
|
||||||
.collect::<Vec<_>>()
|
// .collect::<Vec<_>>()
|
||||||
} else {
|
// } else {
|
||||||
app.canvas_data
|
// app.canvas_data
|
||||||
.single_process_data
|
// .single_process_data
|
||||||
.iter()
|
// .iter()
|
||||||
.filter_map(|(_pid, process)| {
|
// .filter_map(|(_pid, process)| {
|
||||||
if !is_invalid_or_blank {
|
// if !is_invalid_or_blank {
|
||||||
if let Some(process_filter) = process_filter {
|
// if let Some(process_filter) = process_filter {
|
||||||
if process_filter.check(process, is_using_command) {
|
// if process_filter.check(process, is_using_command) {
|
||||||
Some(process)
|
// Some(process)
|
||||||
} else {
|
// } else {
|
||||||
None
|
// None
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
Some(process)
|
// Some(process)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
Some(process)
|
// Some(process)
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.cloned()
|
// .cloned()
|
||||||
.collect::<Vec<_>>()
|
// .collect::<Vec<_>>()
|
||||||
};
|
// };
|
||||||
|
|
||||||
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
// if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
||||||
let mut finalized_process_data = if is_tree {
|
// let mut finalized_process_data = if is_tree {
|
||||||
tree_process_data(
|
// tree_process_data(
|
||||||
&filtered_process_data,
|
// &filtered_process_data,
|
||||||
is_using_command,
|
// is_using_command,
|
||||||
&proc_widget_state.process_sorting_type,
|
// &proc_widget_state.process_sorting_type,
|
||||||
proc_widget_state.is_process_sort_descending,
|
// proc_widget_state.is_process_sort_descending,
|
||||||
)
|
// )
|
||||||
} else if is_grouped {
|
// } else if is_grouped {
|
||||||
group_process_data(&filtered_process_data, is_using_command)
|
// group_process_data(&filtered_process_data, is_using_command)
|
||||||
} else {
|
// } else {
|
||||||
filtered_process_data
|
// filtered_process_data
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Note tree mode is sorted well before this, as it's special.
|
// // Note tree mode is sorted well before this, as it's special.
|
||||||
if !is_tree {
|
// if !is_tree {
|
||||||
sort_process_data(&mut finalized_process_data, proc_widget_state);
|
// sort_process_data(&mut finalized_process_data, proc_widget_state);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if proc_widget_state.scroll_state.current_scroll_position
|
// if proc_widget_state.scroll_state.current_scroll_position
|
||||||
>= finalized_process_data.len()
|
// >= finalized_process_data.len()
|
||||||
{
|
// {
|
||||||
proc_widget_state.scroll_state.current_scroll_position =
|
// proc_widget_state.scroll_state.current_scroll_position =
|
||||||
finalized_process_data.len().saturating_sub(1);
|
// finalized_process_data.len().saturating_sub(1);
|
||||||
proc_widget_state.scroll_state.previous_scroll_position = 0;
|
// proc_widget_state.scroll_state.previous_scroll_position = 0;
|
||||||
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
// proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
||||||
}
|
// }
|
||||||
|
|
||||||
app.canvas_data.stringified_process_data_map.insert(
|
// app.canvas_data.stringified_process_data_map.insert(
|
||||||
widget_id,
|
// widget_id,
|
||||||
stringify_process_data(proc_widget_state, &finalized_process_data),
|
// stringify_process_data(proc_widget_state, &finalized_process_data),
|
||||||
);
|
// );
|
||||||
app.canvas_data
|
// app.canvas_data
|
||||||
.finalized_process_data_map
|
// .finalized_process_data_map
|
||||||
.insert(widget_id, finalized_process_data);
|
// .insert(widget_id, finalized_process_data);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_process_data(
|
fn sort_process_data(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user