mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +02:00
refactor: delete a bunch of old unused code
This commit is contained in:
parent
fa00dec146
commit
18af6b01bf
513
src/app.rs
513
src/app.rs
@ -2300,87 +2300,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_to_last(&mut self) {
|
||||
// if !self.ignore_normal_keybinds() {
|
||||
// match self.current_widget.widget_type {
|
||||
// BottomWidgetType::Proc => {
|
||||
// if let Some(proc_widget_state) = self
|
||||
// .proc_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||
// {
|
||||
// if let Some(finalized_process_data) = self
|
||||
// .canvas_data
|
||||
// .finalized_process_data_map
|
||||
// .get(&self.current_widget.widget_id)
|
||||
// {
|
||||
// if !self.canvas_data.finalized_process_data_map.is_empty() {
|
||||
// proc_widget_state.scroll_state.current_scroll_position =
|
||||
// finalized_process_data.len() - 1;
|
||||
// proc_widget_state.scroll_state.scroll_direction =
|
||||
// ScrollDirection::Down;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// BottomWidgetType::ProcSort => {
|
||||
// if let Some(proc_widget_state) = self
|
||||
// .proc_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id - 2)
|
||||
// {
|
||||
// proc_widget_state.columns.current_scroll_position =
|
||||
// proc_widget_state.columns.get_enabled_columns_len() - 1;
|
||||
// proc_widget_state.columns.scroll_direction = ScrollDirection::Down;
|
||||
// }
|
||||
// }
|
||||
// BottomWidgetType::Temp => {
|
||||
// if let Some(temp_widget_state) = self
|
||||
// .temp_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||
// {
|
||||
// if !self.canvas_data.temp_sensor_data.is_empty() {
|
||||
// temp_widget_state.scroll_state.current_scroll_position =
|
||||
// self.canvas_data.temp_sensor_data.len() - 1;
|
||||
// temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// BottomWidgetType::Disk => {
|
||||
// if let Some(disk_widget_state) = self
|
||||
// .disk_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||
// {
|
||||
// if !self.canvas_data.disk_data.is_empty() {
|
||||
// disk_widget_state.scroll_state.current_scroll_position =
|
||||
// self.canvas_data.disk_data.len() - 1;
|
||||
// disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// BottomWidgetType::CpuLegend => {
|
||||
// if let Some(cpu_widget_state) = self
|
||||
// .cpu_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id - 1)
|
||||
// {
|
||||
// let cap = self.canvas_data.cpu_data.len();
|
||||
// if cap > 0 {
|
||||
// cpu_widget_state.scroll_state.current_scroll_position = cap - 1;
|
||||
// cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// self.reset_multi_tap_keys();
|
||||
// } else if self.help_dialog_state.is_showing_help {
|
||||
// self.help_dialog_state.scroll_state.current_scroll_index = self
|
||||
// .help_dialog_state
|
||||
// .scroll_state
|
||||
// .max_scroll_index
|
||||
// .saturating_sub(1);
|
||||
// } else if self.delete_dialog_state.is_showing_dd {
|
||||
// self.delete_dialog_state.selected_signal = KillSignal::Kill(MAX_SIGNAL);
|
||||
// }
|
||||
}
|
||||
pub fn skip_to_last(&mut self) {}
|
||||
|
||||
pub fn decrement_position_count(&mut self) {
|
||||
if !self.ignore_normal_keybinds() {
|
||||
@ -2461,35 +2381,6 @@ impl AppState {
|
||||
|
||||
/// Returns the new position.
|
||||
fn increment_process_position(&mut self, _num_to_change_by: i64) -> Option<usize> {
|
||||
// if let Some(proc_widget_state) = self
|
||||
// .proc_state
|
||||
// .get_mut_widget_state(self.current_widget.widget_id)
|
||||
// {
|
||||
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
||||
// if let Some(finalized_process_data) = self
|
||||
// .canvas_data
|
||||
// .finalized_process_data_map
|
||||
// .get(&self.current_widget.widget_id)
|
||||
// {
|
||||
// if current_posn as i64 + num_to_change_by >= 0
|
||||
// && current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64
|
||||
// {
|
||||
// proc_widget_state.scroll_state.current_scroll_position =
|
||||
// (current_posn as i64 + num_to_change_by) as usize;
|
||||
// } else {
|
||||
// return None;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if num_to_change_by < 0 {
|
||||
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
|
||||
// } else {
|
||||
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
|
||||
// }
|
||||
|
||||
// return Some(proc_widget_state.scroll_state.current_scroll_position);
|
||||
// }
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@ -2563,40 +2454,6 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_scroll_up(&mut self) {
|
||||
if self.delete_dialog_state.is_showing_dd {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
self.on_up_key();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.help_dialog_state.is_showing_help {
|
||||
self.help_scroll_up();
|
||||
} else if self.current_widget.widget_type.is_widget_graph() {
|
||||
self.zoom_in();
|
||||
} else if self.current_widget.widget_type.is_widget_table() {
|
||||
self.decrement_position_count();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_scroll_down(&mut self) {
|
||||
if self.delete_dialog_state.is_showing_dd {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
self.on_down_key();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.help_dialog_state.is_showing_help {
|
||||
self.help_scroll_down();
|
||||
} else if self.current_widget.widget_type.is_widget_graph() {
|
||||
self.zoom_out();
|
||||
} else if self.current_widget.widget_type.is_widget_table() {
|
||||
self.increment_position_count();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_plus(&mut self) {
|
||||
if let BottomWidgetType::Proc = self.current_widget.widget_type {
|
||||
// Toggle collapsing if tree
|
||||
@ -2615,34 +2472,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_collapsing_process_branch(&mut self) {
|
||||
// if let Some(proc_widget_state) = self
|
||||
// .proc_state
|
||||
// .widget_states
|
||||
// .get_mut(&self.current_widget.widget_id)
|
||||
// {
|
||||
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
|
||||
|
||||
// if let Some(displayed_process_list) = self
|
||||
// .canvas_data
|
||||
// .finalized_process_data_map
|
||||
// .get(&self.current_widget.widget_id)
|
||||
// {
|
||||
// if let Some(corresponding_process) = displayed_process_list.get(current_posn) {
|
||||
// let corresponding_pid = corresponding_process.pid;
|
||||
|
||||
// if let Some(process_data) = self
|
||||
// .canvas_data
|
||||
// .single_process_data
|
||||
// .get_mut(&corresponding_pid)
|
||||
// {
|
||||
// process_data.is_collapsed_entry = !process_data.is_collapsed_entry;
|
||||
// self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
fn toggle_collapsing_process_branch(&mut self) {}
|
||||
|
||||
fn zoom_out(&mut self) {
|
||||
match self.current_widget.widget_type {
|
||||
@ -2856,343 +2686,4 @@ impl AppState {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the mouse to the widget that was clicked on, then propagates the click down to be
|
||||
/// handled by the widget specifically.
|
||||
pub fn on_left_mouse_up(&mut self, x: u16, y: u16) {
|
||||
// Pretty dead simple - iterate through the widget map and go to the widget where the click
|
||||
// is within.
|
||||
|
||||
// TODO: [REFACTOR] might want to refactor this, it's really ugly.
|
||||
// TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything
|
||||
// is grouped up as an app state. We should separate stuff like event state and gui state and etc.
|
||||
|
||||
// TODO: [MOUSE] double click functionality...? We would do this above all other actions and SC if needed.
|
||||
|
||||
// Short circuit if we're in basic table... we might have to handle the basic table arrow
|
||||
// case here...
|
||||
|
||||
if let Some(bt) = &mut self.basic_table_widget_state {
|
||||
if let (
|
||||
Some((left_tlc_x, left_tlc_y)),
|
||||
Some((left_brc_x, left_brc_y)),
|
||||
Some((right_tlc_x, right_tlc_y)),
|
||||
Some((right_brc_x, right_brc_y)),
|
||||
) = (bt.left_tlc, bt.left_brc, bt.right_tlc, bt.right_brc)
|
||||
{
|
||||
if (x >= left_tlc_x && y >= left_tlc_y) && (x < left_brc_x && y < left_brc_y) {
|
||||
// Case for the left "button" in the simple arrow.
|
||||
if let Some(new_widget) =
|
||||
self.widget_map.get(&(bt.currently_displayed_widget_id))
|
||||
{
|
||||
// We have to move to the current table widget first...
|
||||
self.current_widget = new_widget.clone();
|
||||
|
||||
if let BottomWidgetType::Proc = &new_widget.widget_type {
|
||||
if let Some(proc_widget_state) =
|
||||
self.proc_state.get_widget_state(new_widget.widget_id)
|
||||
{
|
||||
if proc_widget_state.is_sort_open {
|
||||
self.move_widget_selection(&WidgetDirection::Left);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.move_widget_selection(&WidgetDirection::Left);
|
||||
return;
|
||||
}
|
||||
} else if (x >= right_tlc_x && y >= right_tlc_y)
|
||||
&& (x < right_brc_x && y < right_brc_y)
|
||||
{
|
||||
// Case for the right "button" in the simple arrow.
|
||||
if let Some(new_widget) =
|
||||
self.widget_map.get(&(bt.currently_displayed_widget_id))
|
||||
{
|
||||
// We have to move to the current table widget first...
|
||||
self.current_widget = new_widget.clone();
|
||||
|
||||
if let BottomWidgetType::ProcSort = &new_widget.widget_type {
|
||||
if let Some(proc_widget_state) =
|
||||
self.proc_state.get_widget_state(new_widget.widget_id - 2)
|
||||
{
|
||||
if proc_widget_state.is_sort_open {
|
||||
self.move_widget_selection(&WidgetDirection::Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.move_widget_selection(&WidgetDirection::Right);
|
||||
// Bit extra logic to ensure you always land on a proc widget, not the sort
|
||||
if let BottomWidgetType::ProcSort = &self.current_widget.widget_type {
|
||||
self.move_widget_selection(&WidgetDirection::Right);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no/signals
|
||||
// and bail after.
|
||||
if self.is_in_dialog() {
|
||||
match self.delete_dialog_state.button_positions.iter().find(
|
||||
|(tl_x, tl_y, br_x, br_y, _idx)| {
|
||||
(x >= *tl_x && y >= *tl_y) && (x <= *br_x && y <= *br_y)
|
||||
},
|
||||
) {
|
||||
Some((_, _, _, _, 0)) => {
|
||||
self.delete_dialog_state.selected_signal = KillSignal::Cancel
|
||||
}
|
||||
Some((_, _, _, _, idx)) => {
|
||||
if *idx > 31 {
|
||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(*idx + 2)
|
||||
} else {
|
||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(*idx)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut failed_to_get = true;
|
||||
// TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind
|
||||
// traversal through a hashmap, using a 2d binary tree of sorts would be better.
|
||||
// See: https://docs.rs/kdtree/0.6.0/kdtree/
|
||||
for (new_widget_id, widget) in &self.widget_map {
|
||||
if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) =
|
||||
(widget.top_left_corner, widget.bottom_right_corner)
|
||||
{
|
||||
if (x >= tlc_x && y >= tlc_y) && (x < brc_x && y < brc_y) {
|
||||
if let Some(new_widget) = self.widget_map.get(new_widget_id) {
|
||||
self.current_widget = new_widget.clone();
|
||||
|
||||
match &self.current_widget.widget_type {
|
||||
BottomWidgetType::Temp
|
||||
| BottomWidgetType::Proc
|
||||
| BottomWidgetType::ProcSort
|
||||
| BottomWidgetType::Disk
|
||||
| BottomWidgetType::Battery => {
|
||||
if let Some(basic_table_widget_state) =
|
||||
&mut self.basic_table_widget_state
|
||||
{
|
||||
basic_table_widget_state.currently_displayed_widget_id =
|
||||
self.current_widget.widget_id;
|
||||
basic_table_widget_state.currently_displayed_widget_type =
|
||||
self.current_widget.widget_type.clone();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
failed_to_get = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if failed_to_get {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now handle click propagation down to widget.
|
||||
if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) = (
|
||||
&self.current_widget.top_left_corner,
|
||||
&self.current_widget.bottom_right_corner,
|
||||
) {
|
||||
let border_offset = if self.is_drawing_border() { 1 } else { 0 };
|
||||
|
||||
// This check ensures the click isn't actually just clicking on the bottom border.
|
||||
if y < (brc_y - border_offset) {
|
||||
match &self.current_widget.widget_type {
|
||||
BottomWidgetType::Proc
|
||||
| BottomWidgetType::ProcSort
|
||||
| BottomWidgetType::CpuLegend
|
||||
| BottomWidgetType::Temp
|
||||
| BottomWidgetType::Disk => {
|
||||
// Get our index...
|
||||
let clicked_entry = y - *tlc_y;
|
||||
// + 1 so we start at 0.
|
||||
let header_gap_offset = 1 + if self.is_drawing_gap(&self.current_widget) {
|
||||
self.app_config_fields.table_gap
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let offset = border_offset + header_gap_offset;
|
||||
if clicked_entry >= offset {
|
||||
let offset_clicked_entry = clicked_entry - offset;
|
||||
match &self.current_widget.widget_type {
|
||||
BottomWidgetType::Proc => {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
proc_widget_state.scroll_state.table_state.selected()
|
||||
{
|
||||
// If in tree mode, also check to see if this click is on
|
||||
// the same entry as the already selected one - if it is,
|
||||
// then we minimize.
|
||||
|
||||
let previous_scroll_position = proc_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position;
|
||||
let is_tree_mode = proc_widget_state.is_tree_mode;
|
||||
|
||||
let new_position = self.increment_process_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
|
||||
if is_tree_mode {
|
||||
if let Some(new_position) = new_position {
|
||||
if previous_scroll_position == new_position {
|
||||
self.toggle_collapsing_process_branch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::ProcSort => {
|
||||
// TODO: This should sort if you double click!
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_widget_state(self.current_widget.widget_id - 2)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
proc_widget_state.columns.column_state.selected()
|
||||
{
|
||||
self.increment_process_sort_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::CpuLegend => {
|
||||
if let Some(cpu_widget_state) = self
|
||||
.cpu_state
|
||||
.get_widget_state(self.current_widget.widget_id - 1)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
cpu_widget_state.scroll_state.table_state.selected()
|
||||
{
|
||||
self.increment_cpu_legend_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::Temp => {
|
||||
if let Some(temp_widget_state) = self
|
||||
.temp_state
|
||||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
temp_widget_state.scroll_state.table_state.selected()
|
||||
{
|
||||
self.increment_temp_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::Disk => {
|
||||
if let Some(disk_widget_state) = self
|
||||
.disk_state
|
||||
.get_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(visual_index) =
|
||||
disk_widget_state.scroll_state.table_state.selected()
|
||||
{
|
||||
self.increment_disk_position(
|
||||
offset_clicked_entry as i64 - visual_index as i64,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// We might have clicked on a header! Check if we only exceeded the table + border offset, and
|
||||
// it's implied we exceeded the gap offset.
|
||||
if clicked_entry == border_offset {
|
||||
#[allow(clippy::single_match)]
|
||||
match &self.current_widget.widget_type {
|
||||
BottomWidgetType::Proc => {
|
||||
if let Some(proc_widget_state) = self
|
||||
.proc_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
// Let's now check if it's a column header.
|
||||
if let (Some(y_loc), Some(x_locs)) = (
|
||||
&proc_widget_state.columns.column_header_y_loc,
|
||||
&proc_widget_state.columns.column_header_x_locs,
|
||||
) {
|
||||
// debug!("x, y: {}, {}", x, y);
|
||||
// debug!("y_loc: {}", y_loc);
|
||||
// debug!("x_locs: {:?}", x_locs);
|
||||
|
||||
if y == *y_loc {
|
||||
for (itx, (x_left, x_right)) in
|
||||
x_locs.iter().enumerate()
|
||||
{
|
||||
if x >= *x_left && x <= *x_right {
|
||||
// Found our column!
|
||||
proc_widget_state
|
||||
.columns
|
||||
.set_to_sorted_index_from_visual_index(
|
||||
itx,
|
||||
);
|
||||
proc_widget_state
|
||||
.update_sorting_with_columns();
|
||||
self.proc_state.force_update =
|
||||
Some(self.current_widget.widget_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomWidgetType::Battery => {
|
||||
if let Some(battery_widget_state) = self
|
||||
.battery_state
|
||||
.get_mut_widget_state(self.current_widget.widget_id)
|
||||
{
|
||||
if let Some(tab_spacing) = &battery_widget_state.tab_click_locs {
|
||||
for (itx, ((tlc_x, tlc_y), (brc_x, brc_y))) in
|
||||
tab_spacing.iter().enumerate()
|
||||
{
|
||||
if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y)
|
||||
{
|
||||
battery_widget_state.currently_selected_battery_index = itx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_drawing_border(&self) -> bool {
|
||||
self.is_expanded || !self.app_config_fields.use_basic_mode
|
||||
}
|
||||
|
||||
fn is_drawing_gap(&self, widget: &BottomWidget) -> bool {
|
||||
if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) =
|
||||
(widget.top_left_corner, widget.bottom_right_corner)
|
||||
{
|
||||
brc_y - tlc_y >= constants::TABLE_GAP_HEIGHT_LIMIT
|
||||
} else {
|
||||
self.app_config_fields.table_gap == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,17 +35,9 @@ pub struct BatteryState {
|
||||
}
|
||||
|
||||
impl BatteryState {
|
||||
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
|
||||
BatteryState { widget_states }
|
||||
}
|
||||
|
||||
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> {
|
||||
self.widget_states.get_mut(&widget_id)
|
||||
}
|
||||
|
||||
pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A table displaying battery information on a per-battery basis.
|
||||
|
@ -28,19 +28,6 @@ pub struct CpuWidgetState {
|
||||
pub table_width_state: CanvasTableWidthState,
|
||||
}
|
||||
|
||||
impl CpuWidgetState {
|
||||
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
|
||||
CpuWidgetState {
|
||||
current_display_time,
|
||||
is_legend_hidden: false,
|
||||
autohide_timer,
|
||||
scroll_state: AppScrollWidgetState::default(),
|
||||
is_multi_graph_mode: false,
|
||||
table_width_state: CanvasTableWidthState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CpuState {
|
||||
pub force_update: Option<u64>,
|
||||
@ -48,13 +35,6 @@ pub struct CpuState {
|
||||
}
|
||||
|
||||
impl CpuState {
|
||||
pub fn init(widget_states: HashMap<u64, CpuWidgetState>) -> Self {
|
||||
CpuState {
|
||||
force_update: None,
|
||||
widget_states,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> {
|
||||
self.widget_states.get_mut(&widget_id)
|
||||
}
|
||||
@ -64,6 +44,7 @@ impl CpuState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Which part of the [`CpuGraph`] is currently selected.
|
||||
enum CpuGraphSelection {
|
||||
Graph,
|
||||
Legend,
|
||||
|
@ -24,15 +24,6 @@ pub struct DiskWidgetState {
|
||||
pub table_width_state: CanvasTableWidthState,
|
||||
}
|
||||
|
||||
impl DiskWidgetState {
|
||||
pub fn init() -> Self {
|
||||
DiskWidgetState {
|
||||
scroll_state: AppScrollWidgetState::default(),
|
||||
table_width_state: CanvasTableWidthState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DiskState {
|
||||
pub widget_states: HashMap<u64, DiskWidgetState>,
|
||||
|
@ -19,38 +19,12 @@ pub struct MemWidgetState {
|
||||
pub autohide_timer: Option<Instant>,
|
||||
}
|
||||
|
||||
impl MemWidgetState {
|
||||
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
|
||||
MemWidgetState {
|
||||
current_display_time,
|
||||
autohide_timer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemState {
|
||||
pub force_update: Option<u64>,
|
||||
pub widget_states: HashMap<u64, MemWidgetState>,
|
||||
}
|
||||
|
||||
impl MemState {
|
||||
pub fn init(widget_states: HashMap<u64, MemWidgetState>) -> Self {
|
||||
MemState {
|
||||
force_update: None,
|
||||
widget_states,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> {
|
||||
self.widget_states.get_mut(&widget_id)
|
||||
}
|
||||
|
||||
pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that deals with displaying memory usage on a [`TimeGraph`]. Basically just a wrapper
|
||||
/// around [`TimeGraph`] as of now.
|
||||
pub struct MemGraph {
|
||||
|
@ -22,31 +22,6 @@ use crate::{
|
||||
pub struct NetWidgetState {
|
||||
pub current_display_time: u64,
|
||||
pub autohide_timer: Option<Instant>,
|
||||
// pub draw_max_range_cache: f64,
|
||||
// pub draw_labels_cache: Vec<String>,
|
||||
// pub draw_time_start_cache: f64,
|
||||
// TODO: Re-enable these when we move net details state-side!
|
||||
// pub unit_type: DataUnitTypes,
|
||||
// pub scale_type: AxisScaling,
|
||||
}
|
||||
|
||||
impl NetWidgetState {
|
||||
pub fn init(
|
||||
current_display_time: u64,
|
||||
autohide_timer: Option<Instant>,
|
||||
// unit_type: DataUnitTypes,
|
||||
// scale_type: AxisScaling,
|
||||
) -> Self {
|
||||
NetWidgetState {
|
||||
current_display_time,
|
||||
autohide_timer,
|
||||
// draw_max_range_cache: 0.0,
|
||||
// draw_labels_cache: vec![],
|
||||
// draw_time_start_cache: 0.0,
|
||||
// unit_type,
|
||||
// scale_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -55,23 +30,6 @@ pub struct NetState {
|
||||
pub widget_states: HashMap<u64, NetWidgetState>,
|
||||
}
|
||||
|
||||
impl NetState {
|
||||
pub fn init(widget_states: HashMap<u64, NetWidgetState>) -> Self {
|
||||
NetState {
|
||||
force_update: None,
|
||||
widget_states,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> {
|
||||
self.widget_states.get_mut(&widget_id)
|
||||
}
|
||||
|
||||
pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> {
|
||||
self.widget_states.get(&widget_id)
|
||||
}
|
||||
}
|
||||
|
||||
// --- NEW STUFF BELOW ---
|
||||
|
||||
/// Returns the max data point and time given a time.
|
||||
|
@ -119,9 +119,6 @@ impl ProcessSearchState {
|
||||
pub struct ColumnInfo {
|
||||
pub enabled: bool,
|
||||
pub shortcut: Option<&'static str>,
|
||||
// FIXME: Move column width logic here!
|
||||
// pub hard_width: Option<u16>,
|
||||
// pub max_soft_width: Option<f64>,
|
||||
}
|
||||
|
||||
pub struct ProcColumn {
|
||||
@ -419,59 +416,6 @@ pub struct ProcWidgetState {
|
||||
}
|
||||
|
||||
impl ProcWidgetState {
|
||||
pub fn init(
|
||||
is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool,
|
||||
show_memory_as_values: bool, is_tree_mode: bool, is_using_command: bool,
|
||||
) -> Self {
|
||||
let mut process_search_state = ProcessSearchState::default();
|
||||
|
||||
if is_case_sensitive {
|
||||
// By default it's off
|
||||
process_search_state.search_toggle_ignore_case();
|
||||
}
|
||||
if is_match_whole_word {
|
||||
process_search_state.search_toggle_whole_word();
|
||||
}
|
||||
if is_use_regex {
|
||||
process_search_state.search_toggle_regex();
|
||||
}
|
||||
|
||||
let (process_sorting_type, is_process_sort_descending) = if is_tree_mode {
|
||||
(processes::ProcessSorting::Pid, false)
|
||||
} else {
|
||||
(processes::ProcessSorting::CpuPercent, true)
|
||||
};
|
||||
|
||||
// TODO: If we add customizable columns, this should pull from config
|
||||
let mut columns = ProcColumn::default();
|
||||
columns.set_to_sorted_index_from_type(&process_sorting_type);
|
||||
if is_grouped {
|
||||
// Normally defaults to showing by PID, toggle count on instead.
|
||||
columns.toggle(&ProcessSorting::Count);
|
||||
columns.toggle(&ProcessSorting::Pid);
|
||||
}
|
||||
if show_memory_as_values {
|
||||
// Normally defaults to showing by percent, toggle value on instead.
|
||||
columns.toggle(&ProcessSorting::Mem);
|
||||
columns.toggle(&ProcessSorting::MemPercent);
|
||||
}
|
||||
|
||||
ProcWidgetState {
|
||||
process_search_state,
|
||||
is_grouped,
|
||||
scroll_state: AppScrollWidgetState::default(),
|
||||
process_sorting_type,
|
||||
is_process_sort_descending,
|
||||
is_using_command,
|
||||
current_column_index: 0,
|
||||
is_sort_open: false,
|
||||
columns,
|
||||
is_tree_mode,
|
||||
table_width_state: CanvasTableWidthState::default(),
|
||||
requires_redraw: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates sorting when using the column list.
|
||||
/// ...this really should be part of the ProcColumn struct (along with the sorting fields),
|
||||
/// but I'm too lazy.
|
||||
@ -620,14 +564,6 @@ pub struct ProcState {
|
||||
}
|
||||
|
||||
impl ProcState {
|
||||
pub fn init(widget_states: HashMap<u64, ProcWidgetState>) -> Self {
|
||||
ProcState {
|
||||
widget_states,
|
||||
force_update: None,
|
||||
force_update_all: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> {
|
||||
self.widget_states.get_mut(&widget_id)
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ use tui::{
|
||||
Frame, Terminal,
|
||||
};
|
||||
|
||||
// use ordered_float::OrderedFloat;
|
||||
|
||||
use canvas_colours::*;
|
||||
use dialogs::*;
|
||||
|
||||
@ -33,8 +31,6 @@ use crate::{
|
||||
|
||||
mod canvas_colours;
|
||||
mod dialogs;
|
||||
pub mod drawing; // TODO: Remove pub access at some point!
|
||||
mod drawing_utils;
|
||||
|
||||
/// Point is of time, data
|
||||
type Point = (f64, f64);
|
||||
@ -191,7 +187,7 @@ impl Painter {
|
||||
self.styled_help_text = styled_help_spans.into_iter().map(Spans::from).collect();
|
||||
}
|
||||
|
||||
// FIXME: [CONFIG] write this, should call painter init and any changed colour functions...
|
||||
// TODO: [CONFIG] write this, should call painter init and any changed colour functions...
|
||||
pub fn update_painter_colours(&mut self) {}
|
||||
|
||||
fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) {
|
||||
|
@ -1,15 +0,0 @@
|
||||
pub mod basic_table_arrows;
|
||||
pub mod battery_display;
|
||||
pub mod cpu_basic;
|
||||
pub mod mem_basic;
|
||||
pub mod mem_graph;
|
||||
pub mod network_basic;
|
||||
pub mod network_graph;
|
||||
|
||||
pub use basic_table_arrows::*;
|
||||
pub use battery_display::*;
|
||||
pub use cpu_basic::*;
|
||||
pub use mem_basic::*;
|
||||
pub use mem_graph::*;
|
||||
pub use network_basic::*;
|
||||
pub use network_graph::*;
|
@ -1,154 +0,0 @@
|
||||
use crate::{
|
||||
app::{layout_manager::BottomWidgetType, AppState},
|
||||
canvas::Painter,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::Spans,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
pub fn draw_basic_table_arrows<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if let Some(current_table) = app_state.widget_map.get(&widget_id) {
|
||||
let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type {
|
||||
current_table
|
||||
.right_neighbour
|
||||
.map(|id| app_state.widget_map.get(&id).unwrap())
|
||||
.unwrap()
|
||||
} else {
|
||||
current_table
|
||||
};
|
||||
|
||||
let (left_table, right_table) = (
|
||||
{
|
||||
current_table
|
||||
.left_neighbour
|
||||
.map(|left_widget_id| {
|
||||
app_state
|
||||
.widget_map
|
||||
.get(&left_widget_id)
|
||||
.map(|left_widget| {
|
||||
if left_widget.widget_type == BottomWidgetType::ProcSort {
|
||||
left_widget
|
||||
.left_neighbour
|
||||
.map(|left_left_widget_id| {
|
||||
app_state.widget_map.get(&left_left_widget_id).map(
|
||||
|left_left_widget| &left_left_widget.widget_type,
|
||||
)
|
||||
})
|
||||
.unwrap_or(Some(&BottomWidgetType::Temp))
|
||||
.unwrap_or(&BottomWidgetType::Temp)
|
||||
} else {
|
||||
&left_widget.widget_type
|
||||
}
|
||||
})
|
||||
.unwrap_or(&BottomWidgetType::Temp)
|
||||
})
|
||||
.unwrap_or(&BottomWidgetType::Temp)
|
||||
},
|
||||
{
|
||||
current_table
|
||||
.right_neighbour
|
||||
.map(|right_widget_id| {
|
||||
app_state
|
||||
.widget_map
|
||||
.get(&right_widget_id)
|
||||
.map(|right_widget| {
|
||||
if right_widget.widget_type == BottomWidgetType::ProcSort {
|
||||
right_widget
|
||||
.right_neighbour
|
||||
.map(|right_right_widget_id| {
|
||||
app_state.widget_map.get(&right_right_widget_id).map(
|
||||
|right_right_widget| {
|
||||
&right_right_widget.widget_type
|
||||
},
|
||||
)
|
||||
})
|
||||
.unwrap_or(Some(&BottomWidgetType::Disk))
|
||||
.unwrap_or(&BottomWidgetType::Disk)
|
||||
} else {
|
||||
&right_widget.widget_type
|
||||
}
|
||||
})
|
||||
.unwrap_or(&BottomWidgetType::Disk)
|
||||
})
|
||||
.unwrap_or(&BottomWidgetType::Disk)
|
||||
},
|
||||
);
|
||||
|
||||
let left_name = left_table.get_pretty_name();
|
||||
let right_name = right_table.get_pretty_name();
|
||||
|
||||
let num_spaces =
|
||||
usize::from(draw_loc.width).saturating_sub(6 + left_name.len() + right_name.len());
|
||||
|
||||
let left_arrow_text = vec![
|
||||
Spans::default(),
|
||||
Spans::from(Span::styled(
|
||||
format!("◄ {}", left_name),
|
||||
painter.colours.text_style,
|
||||
)),
|
||||
];
|
||||
|
||||
let right_arrow_text = vec![
|
||||
Spans::default(),
|
||||
Spans::from(Span::styled(
|
||||
format!("{} ►", right_name),
|
||||
painter.colours.text_style,
|
||||
)),
|
||||
];
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Length(2 + left_name.len() as u16),
|
||||
Constraint::Length(num_spaces as u16),
|
||||
Constraint::Length(2 + right_name.len() as u16),
|
||||
])
|
||||
.horizontal_margin(1)
|
||||
.split(draw_loc);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(left_arrow_text).block(Block::default()),
|
||||
margined_draw_loc[0],
|
||||
);
|
||||
f.render_widget(
|
||||
Paragraph::new(right_arrow_text)
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Right),
|
||||
margined_draw_loc[2],
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Some explanations for future readers:
|
||||
// - The "height" as of writing of this entire widget is 2. If it's 1, it occasionally doesn't draw.
|
||||
// - As such, the buttons are only on the lower part of this 2-high widget.
|
||||
// - So, we want to only check at one location, the `draw_loc.y + 1`, and that's it.
|
||||
// - But why is it "+2" then? Well, it's because I have a REALLY ugly hack
|
||||
// for mouse button checking, since most button checks are of the form `(draw_loc.y + draw_loc.height)`,
|
||||
// and the same for the x and width. Unfortunately, if you check using >= and <=, the outer bound is
|
||||
// actually too large - so, we assume all of them are one too big and check via < (see
|
||||
// https://github.com/ClementTsang/bottom/pull/459 for details).
|
||||
// - So in other words, to make it simple, we keep this to a standard and overshoot by one here.
|
||||
if let Some(basic_table) = &mut app_state.basic_table_widget_state {
|
||||
basic_table.left_tlc = Some((margined_draw_loc[0].x, margined_draw_loc[0].y + 1));
|
||||
basic_table.left_brc = Some((
|
||||
margined_draw_loc[0].x + margined_draw_loc[0].width,
|
||||
margined_draw_loc[0].y + 2,
|
||||
));
|
||||
basic_table.right_tlc = Some((margined_draw_loc[2].x, margined_draw_loc[2].y + 1));
|
||||
basic_table.right_brc = Some((
|
||||
margined_draw_loc[2].x + margined_draw_loc[2].width,
|
||||
margined_draw_loc[2].y + 2,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
use crate::{
|
||||
app::AppState,
|
||||
canvas::{drawing_utils::calculate_basic_use_bars, Painter},
|
||||
constants::*,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans},
|
||||
widgets::{Block, Borders, Cell, Paragraph, Row, Table, Tabs},
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub fn draw_battery_display<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: u64,
|
||||
) {
|
||||
let should_get_widget_bounds = app_state.should_get_widget_bounds();
|
||||
if let Some(battery_widget_state) = app_state.battery_state.widget_states.get_mut(&widget_id) {
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let border_style = if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " Battery ── Esc to go back ";
|
||||
Spans::from(vec![
|
||||
Span::styled(" Battery ".to_string(), painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(
|
||||
" Battery ".to_string(),
|
||||
painter.colours.widget_title_style,
|
||||
))
|
||||
};
|
||||
|
||||
let battery_block = if draw_border {
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let battery_names = app_state
|
||||
.canvas_data
|
||||
.battery_data
|
||||
.iter()
|
||||
.map(|battery| &battery.battery_name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tab_draw_loc = Layout::default()
|
||||
.constraints([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(2),
|
||||
Constraint::Min(0),
|
||||
])
|
||||
.direction(Direction::Vertical)
|
||||
.split(draw_loc)[1];
|
||||
|
||||
f.render_widget(
|
||||
Tabs::new(
|
||||
battery_names
|
||||
.iter()
|
||||
.map(|name| Spans::from((*name).clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.block(Block::default())
|
||||
.divider(tui::symbols::line::VERTICAL)
|
||||
.style(painter.colours.text_style)
|
||||
.highlight_style(painter.colours.currently_selected_text_style)
|
||||
.select(battery_widget_state.currently_selected_battery_index),
|
||||
tab_draw_loc,
|
||||
);
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
if let Some(battery_details) = app_state
|
||||
.canvas_data
|
||||
.battery_data
|
||||
.get(battery_widget_state.currently_selected_battery_index)
|
||||
{
|
||||
// Assuming a 50/50 split in width
|
||||
let bar_length = usize::from((draw_loc.width.saturating_sub(2) / 2).saturating_sub(8));
|
||||
let charge_percentage = battery_details.charge_percentage;
|
||||
let num_bars = calculate_basic_use_bars(charge_percentage, bar_length);
|
||||
let bars = format!(
|
||||
"[{}{}{:3.0}%]",
|
||||
"|".repeat(num_bars),
|
||||
" ".repeat(bar_length - num_bars),
|
||||
charge_percentage,
|
||||
);
|
||||
|
||||
let battery_rows = vec![
|
||||
Row::new(vec![
|
||||
Cell::from("Charge %").style(painter.colours.text_style),
|
||||
Cell::from(bars).style(if charge_percentage < 10.0 {
|
||||
painter.colours.low_battery_colour
|
||||
} else if charge_percentage < 50.0 {
|
||||
painter.colours.medium_battery_colour
|
||||
} else {
|
||||
painter.colours.high_battery_colour
|
||||
}),
|
||||
]),
|
||||
Row::new(vec!["Consumption", &battery_details.watt_consumption])
|
||||
.style(painter.colours.text_style),
|
||||
// if let Some(duration_until_full) = &battery_details.duration_until_full {
|
||||
// Row::new(vec!["Time to full", duration_until_full])
|
||||
// .style(painter.colours.text_style)
|
||||
// } else if let Some(duration_until_empty) = &battery_details.duration_until_empty {
|
||||
// Row::new(vec!["Time to empty", duration_until_empty])
|
||||
// .style(painter.colours.text_style)
|
||||
// } else {
|
||||
// Row::new(vec!["Time to full/empty", "N/A"]).style(painter.colours.text_style)
|
||||
// },
|
||||
Row::new(vec!["Health %", &battery_details.health])
|
||||
.style(painter.colours.text_style),
|
||||
];
|
||||
|
||||
// Draw
|
||||
f.render_widget(
|
||||
Table::new(battery_rows)
|
||||
.block(battery_block)
|
||||
.header(Row::new(vec![""]).bottom_margin(table_gap))
|
||||
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)]),
|
||||
margined_draw_loc,
|
||||
);
|
||||
} else {
|
||||
let mut contents = vec![Spans::default(); table_gap as usize];
|
||||
|
||||
contents.push(Spans::from(Span::styled(
|
||||
"No data found for this battery",
|
||||
painter.colours.text_style,
|
||||
)));
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(contents).block(battery_block),
|
||||
margined_draw_loc,
|
||||
);
|
||||
}
|
||||
|
||||
if should_get_widget_bounds {
|
||||
// Tab wizardry
|
||||
if !battery_names.is_empty() {
|
||||
let mut current_x = tab_draw_loc.x;
|
||||
let current_y = tab_draw_loc.y;
|
||||
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
|
||||
for battery in battery_names {
|
||||
// +1 because there's a space after the tab label.
|
||||
let width = unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16;
|
||||
tab_click_locs.push(((current_x, current_y), (current_x + width, current_y)));
|
||||
|
||||
// +4 because we want to go one space, then one space past to get to the '|', then 2 more
|
||||
// to start at the blank space before the tab label.
|
||||
current_x += width + 4;
|
||||
}
|
||||
battery_widget_state.tab_click_locs = Some(tab_click_locs);
|
||||
}
|
||||
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
widget.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use crate::{
|
||||
app::AppState,
|
||||
canvas::{drawing_utils::*, Painter},
|
||||
constants::*,
|
||||
data_conversion::ConvertedCpuData,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans},
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
pub fn draw_basic_cpu<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
// Skip the first element, it's the "all" element
|
||||
if app_state.canvas_data.cpu_data.len() > 1 {
|
||||
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data[1..];
|
||||
|
||||
// This is a bit complicated, but basically, we want to draw SOME number
|
||||
// of columns to draw all CPUs. Ideally, as well, we want to not have
|
||||
// to ever scroll.
|
||||
// **General logic** - count number of elements in cpu_data. Then see how
|
||||
// many rows and columns we have in draw_loc (-2 on both sides for border?).
|
||||
// I think what we can do is try to fit in as many in one column as possible.
|
||||
// If not, then add a new column.
|
||||
// Then, from this, split the row space across ALL columns. From there, generate
|
||||
// the desired lengths.
|
||||
|
||||
if app_state.current_widget.widget_id == widget_id {
|
||||
f.render_widget(
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style),
|
||||
draw_loc,
|
||||
);
|
||||
}
|
||||
|
||||
let num_cpus = cpu_data.len();
|
||||
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
||||
|
||||
if draw_loc.height > 0 {
|
||||
let remaining_height = usize::from(draw_loc.height);
|
||||
const REQUIRED_COLUMNS: usize = 4;
|
||||
|
||||
let chunk_vec =
|
||||
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
|
||||
let chunks = Layout::default()
|
||||
.constraints(chunk_vec)
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc);
|
||||
|
||||
const CPU_NAME_SPACE: usize = 3;
|
||||
const BAR_BOUND_SPACE: usize = 2;
|
||||
const PERCENTAGE_SPACE: usize = 4;
|
||||
const MARGIN_SPACE: usize = 2;
|
||||
|
||||
const COMBINED_SPACING: usize =
|
||||
CPU_NAME_SPACE + BAR_BOUND_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
|
||||
const REDUCED_SPACING: usize = CPU_NAME_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
|
||||
let chunk_width = chunks[0].width as usize;
|
||||
|
||||
// Inspired by htop.
|
||||
// We do +4 as if it's too few bars in the bar length, it's kinda pointless.
|
||||
let cpu_bars = if chunk_width >= COMBINED_SPACING + 4 {
|
||||
let bar_length = chunk_width - COMBINED_SPACING;
|
||||
(0..num_cpus)
|
||||
.map(|cpu_index| {
|
||||
let use_percentage =
|
||||
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
|
||||
cpu_usage.1
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let num_bars = calculate_basic_use_bars(use_percentage, bar_length);
|
||||
format!(
|
||||
"{:3}[{}{}{:3.0}%]",
|
||||
if app_state.app_config_fields.show_average_cpu {
|
||||
if cpu_index == 0 {
|
||||
"AVG".to_string()
|
||||
} else {
|
||||
(cpu_index - 1).to_string()
|
||||
}
|
||||
} else {
|
||||
cpu_index.to_string()
|
||||
},
|
||||
"|".repeat(num_bars),
|
||||
" ".repeat(bar_length - num_bars),
|
||||
use_percentage.round(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else if chunk_width >= REDUCED_SPACING {
|
||||
(0..num_cpus)
|
||||
.map(|cpu_index| {
|
||||
let use_percentage =
|
||||
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
|
||||
cpu_usage.1
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
format!(
|
||||
"{:3} {:3.0}%",
|
||||
if app_state.app_config_fields.show_average_cpu {
|
||||
if cpu_index == 0 {
|
||||
"AVG".to_string()
|
||||
} else {
|
||||
(cpu_index - 1).to_string()
|
||||
}
|
||||
} else {
|
||||
cpu_index.to_string()
|
||||
},
|
||||
use_percentage.round(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
(0..num_cpus)
|
||||
.map(|cpu_index| {
|
||||
let use_percentage =
|
||||
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
|
||||
cpu_usage.1
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
format!("{:3.0}%", use_percentage.round(),)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let mut row_counter = num_cpus;
|
||||
let mut start_index = 0;
|
||||
for (itx, chunk) in chunks.iter().enumerate() {
|
||||
// Explicitly check... don't want an accidental DBZ or underflow, this ensures
|
||||
// to_divide is > 0
|
||||
if REQUIRED_COLUMNS > itx {
|
||||
let to_divide = REQUIRED_COLUMNS - itx;
|
||||
let how_many_cpus = min(
|
||||
remaining_height,
|
||||
(row_counter / to_divide)
|
||||
+ (if row_counter % to_divide == 0 { 0 } else { 1 }),
|
||||
);
|
||||
row_counter -= how_many_cpus;
|
||||
let end_index = min(start_index + how_many_cpus, num_cpus);
|
||||
|
||||
let cpu_column = (start_index..end_index)
|
||||
.map(|itx| {
|
||||
Spans::from(Span {
|
||||
content: (&cpu_bars[itx]).into(),
|
||||
style: if show_avg_cpu {
|
||||
if itx == 0 {
|
||||
painter.colours.avg_colour_style
|
||||
} else {
|
||||
painter.colours.cpu_colour_styles
|
||||
[(itx - 1) % painter.colours.cpu_colour_styles.len()]
|
||||
}
|
||||
} else {
|
||||
painter.colours.cpu_colour_styles
|
||||
[itx % painter.colours.cpu_colour_styles.len()]
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
start_index += how_many_cpus;
|
||||
|
||||
let margined_loc = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(1)
|
||||
.split(*chunk)[0];
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(cpu_column).block(Block::default()),
|
||||
margined_loc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,527 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
app::{layout_manager::WidgetDirection, AppState},
|
||||
canvas::{
|
||||
drawing_utils::{get_column_widths, get_start_position, interpolate_points},
|
||||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
data_conversion::ConvertedCpuData,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
symbols::Marker,
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::{Spans, Text},
|
||||
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
|
||||
};
|
||||
|
||||
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
|
||||
const AVG_POSITION: usize = 1;
|
||||
const ALL_POSITION: usize = 0;
|
||||
|
||||
static CPU_LEGEND_HEADER_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
|
||||
CPU_LEGEND_HEADER
|
||||
.iter()
|
||||
.map(|entry| entry.len() as u16)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
pub fn draw_cpu<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if draw_loc.width as f64 * 0.15 <= 6.0 {
|
||||
// Skip drawing legend
|
||||
if app_state.current_widget.widget_id == (widget_id + 1) {
|
||||
if app_state.app_config_fields.left_legend {
|
||||
app_state.move_widget_selection(&WidgetDirection::Right);
|
||||
} else {
|
||||
app_state.move_widget_selection(&WidgetDirection::Left);
|
||||
}
|
||||
}
|
||||
draw_cpu_graph(painter, f, app_state, draw_loc, widget_id);
|
||||
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
|
||||
cpu_widget_state.is_legend_hidden = true;
|
||||
}
|
||||
|
||||
// Update draw loc in widget map
|
||||
if app_state.should_get_widget_bounds() {
|
||||
if let Some(bottom_widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
bottom_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
bottom_widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (graph_index, legend_index, constraints) = if app_state.app_config_fields.left_legend {
|
||||
(
|
||||
1,
|
||||
0,
|
||||
[Constraint::Percentage(15), Constraint::Percentage(85)],
|
||||
)
|
||||
} else {
|
||||
(
|
||||
0,
|
||||
1,
|
||||
[Constraint::Percentage(85), Constraint::Percentage(15)],
|
||||
)
|
||||
};
|
||||
|
||||
let partitioned_draw_loc = Layout::default()
|
||||
.margin(0)
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(constraints)
|
||||
.split(draw_loc);
|
||||
|
||||
draw_cpu_graph(
|
||||
painter,
|
||||
f,
|
||||
app_state,
|
||||
partitioned_draw_loc[graph_index],
|
||||
widget_id,
|
||||
);
|
||||
draw_cpu_legend(
|
||||
painter,
|
||||
f,
|
||||
app_state,
|
||||
partitioned_draw_loc[legend_index],
|
||||
widget_id + 1,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(cpu_widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
cpu_widget.top_left_corner = Some((
|
||||
partitioned_draw_loc[graph_index].x,
|
||||
partitioned_draw_loc[graph_index].y,
|
||||
));
|
||||
cpu_widget.bottom_right_corner = Some((
|
||||
partitioned_draw_loc[graph_index].x + partitioned_draw_loc[graph_index].width,
|
||||
partitioned_draw_loc[graph_index].y + partitioned_draw_loc[graph_index].height,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(legend_widget) = app_state.widget_map.get_mut(&(widget_id + 1)) {
|
||||
legend_widget.top_left_corner = Some((
|
||||
partitioned_draw_loc[legend_index].x,
|
||||
partitioned_draw_loc[legend_index].y,
|
||||
));
|
||||
legend_widget.bottom_right_corner = Some((
|
||||
partitioned_draw_loc[legend_index].x + partitioned_draw_loc[legend_index].width,
|
||||
partitioned_draw_loc[legend_index].y
|
||||
+ partitioned_draw_loc[legend_index].height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_cpu_graph<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
|
||||
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
|
||||
|
||||
let display_time_labels = vec![
|
||||
Span::styled(
|
||||
format!("{}s", cpu_widget_state.current_display_time / 1000),
|
||||
painter.colours.graph_style,
|
||||
),
|
||||
Span::styled("0s".to_string(), painter.colours.graph_style),
|
||||
];
|
||||
|
||||
let y_axis_labels = vec![
|
||||
Span::styled(" 0%", painter.colours.graph_style),
|
||||
Span::styled("100%", painter.colours.graph_style),
|
||||
];
|
||||
|
||||
let time_start = -(cpu_widget_state.current_display_time as f64);
|
||||
|
||||
let x_axis = if app_state.app_config_fields.hide_time
|
||||
|| (app_state.app_config_fields.autohide_time
|
||||
&& cpu_widget_state.autohide_timer.is_none())
|
||||
{
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else if let Some(time) = cpu_widget_state.autohide_timer {
|
||||
if std::time::Instant::now().duration_since(time).as_millis()
|
||||
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
|
||||
{
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
} else {
|
||||
cpu_widget_state.autohide_timer = None;
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
}
|
||||
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else {
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
};
|
||||
|
||||
let y_axis = Axis::default()
|
||||
.style(painter.colours.graph_style)
|
||||
.bounds([0.0, 100.5])
|
||||
.labels(y_axis_labels);
|
||||
|
||||
let use_dot = app_state.app_config_fields.use_dot;
|
||||
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
||||
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
|
||||
|
||||
let interpolated_cpu_points = cpu_data
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.map(|(itx, cpu)| {
|
||||
let to_show = if current_scroll_position == ALL_POSITION {
|
||||
true
|
||||
} else {
|
||||
itx == current_scroll_position
|
||||
};
|
||||
|
||||
if to_show {
|
||||
if let Some(end_pos) = cpu
|
||||
.cpu_data
|
||||
.iter()
|
||||
.position(|(time, _data)| *time >= time_start)
|
||||
{
|
||||
if end_pos > 1 {
|
||||
let start_pos = end_pos - 1;
|
||||
let outside_point = cpu.cpu_data.get(start_pos);
|
||||
let inside_point = cpu.cpu_data.get(end_pos);
|
||||
|
||||
if let (Some(outside_point), Some(inside_point)) =
|
||||
(outside_point, inside_point)
|
||||
{
|
||||
let old = *outside_point;
|
||||
|
||||
let new_point = (
|
||||
time_start,
|
||||
interpolate_points(outside_point, inside_point, time_start),
|
||||
);
|
||||
|
||||
if let Some(to_replace) = cpu.cpu_data.get_mut(start_pos) {
|
||||
*to_replace = new_point;
|
||||
Some((start_pos, old))
|
||||
} else {
|
||||
None // Failed to get mutable reference.
|
||||
}
|
||||
} else {
|
||||
None // Point somehow doesn't exist in our data
|
||||
}
|
||||
} else {
|
||||
None // Point is already "leftmost", no need to interpolate.
|
||||
}
|
||||
} else {
|
||||
None // There is no point.
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let dataset_vector: Vec<Dataset<'_>> = if current_scroll_position == ALL_POSITION {
|
||||
cpu_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.map(|(itx, cpu)| {
|
||||
Dataset::default()
|
||||
.marker(if use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(if show_avg_cpu && itx == AVG_POSITION {
|
||||
painter.colours.avg_colour_style
|
||||
} else if itx == ALL_POSITION {
|
||||
painter.colours.all_colour_style
|
||||
} else {
|
||||
painter.colours.cpu_colour_styles[(itx - 1 // Because of the all position
|
||||
- (if show_avg_cpu {
|
||||
AVG_POSITION
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
% painter.colours.cpu_colour_styles.len()]
|
||||
})
|
||||
.data(&cpu.cpu_data[..])
|
||||
.graph_type(tui::widgets::GraphType::Line)
|
||||
})
|
||||
.collect()
|
||||
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
|
||||
vec![Dataset::default()
|
||||
.marker(if use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(if show_avg_cpu && current_scroll_position == AVG_POSITION {
|
||||
painter.colours.avg_colour_style
|
||||
} else {
|
||||
painter.colours.cpu_colour_styles[(cpu_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
- 1 // Because of the all position
|
||||
- (if show_avg_cpu {
|
||||
AVG_POSITION
|
||||
} else {
|
||||
0
|
||||
}))
|
||||
% painter.colours.cpu_colour_styles.len()]
|
||||
})
|
||||
.data(&cpu.cpu_data[..])
|
||||
.graph_type(tui::widgets::GraphType::Line)]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let border_style = if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
|
||||
let title = if cfg!(target_family = "unix") {
|
||||
let load_avg = app_state.canvas_data.load_avg_data;
|
||||
let load_avg_str = format!(
|
||||
"─ {:.2} {:.2} {:.2} ",
|
||||
load_avg[0], load_avg[1], load_avg[2]
|
||||
);
|
||||
let load_avg_str_size =
|
||||
UnicodeSegmentation::graphemes(load_avg_str.as_str(), true).count();
|
||||
|
||||
if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " CPU ── Esc to go back ";
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(" CPU ", painter.colours.widget_title_style),
|
||||
Span::styled(load_avg_str, painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
load_avg_str_size
|
||||
+ UnicodeSegmentation::graphemes(TITLE_BASE, true).count()
|
||||
+ 2
|
||||
))
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(vec![
|
||||
Span::styled(" CPU ", painter.colours.widget_title_style),
|
||||
Span::styled(load_avg_str, painter.colours.widget_title_style),
|
||||
])
|
||||
}
|
||||
} else if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " CPU ── Esc to go back ";
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(" CPU ", painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(vec![Span::styled(
|
||||
" CPU ",
|
||||
painter.colours.widget_title_style,
|
||||
)])
|
||||
};
|
||||
|
||||
f.render_widget(
|
||||
Chart::new(dataset_vector)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style),
|
||||
)
|
||||
.x_axis(x_axis)
|
||||
.y_axis(y_axis),
|
||||
draw_loc,
|
||||
);
|
||||
|
||||
// Reset interpolated points
|
||||
cpu_data
|
||||
.iter_mut()
|
||||
.zip(interpolated_cpu_points)
|
||||
.for_each(|(cpu, interpolation)| {
|
||||
if let Some((index, old_value)) = interpolation {
|
||||
if let Some(to_replace) = cpu.cpu_data.get_mut(index) {
|
||||
*to_replace = old_value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_cpu_legend<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
// let recalculate_column_widths = app_state.should_get_widget_bounds();
|
||||
// if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1)) {
|
||||
// cpu_widget_state.is_legend_hidden = false;
|
||||
// let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
|
||||
// let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state;
|
||||
// let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
// let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
// 0
|
||||
// } else {
|
||||
// app_state.app_config_fields.table_gap
|
||||
// };
|
||||
// let start_position = get_start_position(
|
||||
// usize::from(
|
||||
// (draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
|
||||
// ),
|
||||
// &cpu_widget_state.scroll_state.scroll_direction,
|
||||
// &mut cpu_widget_state.scroll_state.previous_scroll_position,
|
||||
// cpu_widget_state.scroll_state.current_scroll_position,
|
||||
// app_state.is_force_redraw,
|
||||
// );
|
||||
// cpu_table_state.select(Some(
|
||||
// cpu_widget_state
|
||||
// .scroll_state
|
||||
// .current_scroll_position
|
||||
// .saturating_sub(start_position),
|
||||
// ));
|
||||
|
||||
// let sliced_cpu_data = &cpu_data[start_position..];
|
||||
|
||||
// let offset_scroll_index = cpu_widget_state
|
||||
// .scroll_state
|
||||
// .current_scroll_position
|
||||
// .saturating_sub(start_position);
|
||||
// let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
||||
|
||||
// // Calculate widths
|
||||
// if recalculate_column_widths {
|
||||
// cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
|
||||
// cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
|
||||
// draw_loc.width,
|
||||
// &[None, None],
|
||||
// &(CPU_LEGEND_HEADER_LENS
|
||||
// .iter()
|
||||
// .map(|width| Some(*width))
|
||||
// .collect::<Vec<_>>()),
|
||||
// &[Some(0.5), Some(0.5)],
|
||||
// &(cpu_widget_state
|
||||
// .table_width_state
|
||||
// .desired_column_widths
|
||||
// .iter()
|
||||
// .map(|width| Some(*width))
|
||||
// .collect::<Vec<_>>()),
|
||||
// false,
|
||||
// );
|
||||
// }
|
||||
|
||||
// let dcw = &cpu_widget_state.table_width_state.desired_column_widths;
|
||||
// let ccw = &cpu_widget_state.table_width_state.calculated_column_widths;
|
||||
// let cpu_rows = sliced_cpu_data.iter().enumerate().map(|(itx, cpu)| {
|
||||
// let mut truncated_name =
|
||||
// if let (Some(desired_column_width), Some(calculated_column_width)) =
|
||||
// (dcw.get(0), ccw.get(0))
|
||||
// {
|
||||
// if *desired_column_width > *calculated_column_width {
|
||||
// Text::raw(&cpu.short_cpu_name)
|
||||
// } else {
|
||||
// Text::raw(&cpu.cpu_name)
|
||||
// }
|
||||
// } else {
|
||||
// Text::raw(&cpu.cpu_name)
|
||||
// };
|
||||
|
||||
// let is_first_column_hidden = if let Some(calculated_column_width) = ccw.get(0) {
|
||||
// *calculated_column_width == 0
|
||||
// } else {
|
||||
// false
|
||||
// };
|
||||
|
||||
// let truncated_legend = if is_first_column_hidden && cpu.legend_value.is_empty() {
|
||||
// // For the case where we only have room for one column, display "All" in the normally blank area.
|
||||
// Text::raw("All")
|
||||
// } else {
|
||||
// Text::raw(&cpu.legend_value)
|
||||
// };
|
||||
|
||||
// if !is_first_column_hidden
|
||||
// && itx == offset_scroll_index
|
||||
// && itx + start_position == ALL_POSITION
|
||||
// {
|
||||
// truncated_name.patch_style(painter.colours.currently_selected_text_style);
|
||||
// Row::new(vec![truncated_name, truncated_legend])
|
||||
// } else {
|
||||
// let cpu_string_row = vec![truncated_name, truncated_legend];
|
||||
|
||||
// Row::new(cpu_string_row).style(if itx == offset_scroll_index {
|
||||
// painter.colours.currently_selected_text_style
|
||||
// } else if itx + start_position == ALL_POSITION {
|
||||
// painter.colours.all_colour_style
|
||||
// } else if show_avg_cpu {
|
||||
// if itx + start_position == AVG_POSITION {
|
||||
// painter.colours.avg_colour_style
|
||||
// } else {
|
||||
// painter.colours.cpu_colour_styles[(itx + start_position - AVG_POSITION - 1)
|
||||
// % painter.colours.cpu_colour_styles.len()]
|
||||
// }
|
||||
// } else {
|
||||
// painter.colours.cpu_colour_styles[(itx + start_position - ALL_POSITION - 1)
|
||||
// % painter.colours.cpu_colour_styles.len()]
|
||||
// })
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Note we don't set highlight_style, as it should always be shown for this widget.
|
||||
// let border_and_title_style = if is_on_widget {
|
||||
// painter.colours.highlighted_border_style
|
||||
// } else {
|
||||
// painter.colours.border_style
|
||||
// };
|
||||
|
||||
// // Draw
|
||||
// f.render_stateful_widget(
|
||||
// Table::new(cpu_rows)
|
||||
// .block(
|
||||
// Block::default()
|
||||
// .borders(Borders::ALL)
|
||||
// .border_style(border_and_title_style),
|
||||
// )
|
||||
// .header(
|
||||
// Row::new(CPU_LEGEND_HEADER.to_vec())
|
||||
// .style(painter.colours.table_header_style)
|
||||
// .bottom_margin(table_gap),
|
||||
// )
|
||||
// .widths(
|
||||
// &(cpu_widget_state
|
||||
// .table_width_state
|
||||
// .calculated_column_widths
|
||||
// .iter()
|
||||
// .map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||
// .collect::<Vec<_>>()),
|
||||
// ),
|
||||
// draw_loc,
|
||||
// cpu_table_state,
|
||||
// );
|
||||
// }
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::{Spans, Text},
|
||||
widgets::{Block, Borders, Row, Table},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app,
|
||||
canvas::{
|
||||
drawing_utils::{get_column_widths, get_start_position},
|
||||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
|
||||
|
||||
static DISK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
|
||||
DISK_HEADERS
|
||||
.iter()
|
||||
.map(|entry| entry.len() as u16)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
pub fn draw_disk_table<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: u64,
|
||||
) {
|
||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
||||
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
let start_position = get_start_position(
|
||||
usize::from(
|
||||
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
|
||||
),
|
||||
&disk_widget_state.scroll_state.scroll_direction,
|
||||
&mut disk_widget_state.scroll_state.previous_scroll_position,
|
||||
disk_widget_state.scroll_state.current_scroll_position,
|
||||
app_state.is_force_redraw,
|
||||
);
|
||||
let is_on_widget = app_state.current_widget.widget_id == widget_id;
|
||||
let disk_table_state = &mut disk_widget_state.scroll_state.table_state;
|
||||
disk_table_state.select(Some(
|
||||
disk_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
|
||||
|
||||
// Calculate widths
|
||||
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
|
||||
if recalculate_column_widths {
|
||||
disk_widget_state.table_width_state.desired_column_widths = {
|
||||
let mut column_widths = DISK_HEADERS_LENS.clone();
|
||||
for row in sliced_vec {
|
||||
for (col, entry) in row.iter().enumerate() {
|
||||
if entry.len() as u16 > column_widths[col] {
|
||||
column_widths[col] = entry.len() as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
column_widths
|
||||
};
|
||||
disk_widget_state.table_width_state.desired_column_widths = disk_widget_state
|
||||
.table_width_state
|
||||
.desired_column_widths
|
||||
.iter()
|
||||
.zip(&hard_widths)
|
||||
.map(|(current, hard)| {
|
||||
if let Some(hard) = hard {
|
||||
if *hard > *current {
|
||||
*hard
|
||||
} else {
|
||||
*current
|
||||
}
|
||||
} else {
|
||||
*current
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
disk_widget_state.table_width_state.calculated_column_widths = get_column_widths(
|
||||
draw_loc.width,
|
||||
&hard_widths,
|
||||
&(DISK_HEADERS_LENS
|
||||
.iter()
|
||||
.map(|w| Some(*w))
|
||||
.collect::<Vec<_>>()),
|
||||
&[Some(0.2), Some(0.2), None, None, None, None, None],
|
||||
&(disk_widget_state
|
||||
.table_width_state
|
||||
.desired_column_widths
|
||||
.iter()
|
||||
.map(|w| Some(*w))
|
||||
.collect::<Vec<_>>()),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
let dcw = &disk_widget_state.table_width_state.desired_column_widths;
|
||||
let ccw = &disk_widget_state.table_width_state.calculated_column_widths;
|
||||
let disk_rows = sliced_vec.iter().map(|disk_row| {
|
||||
let truncated_data =
|
||||
disk_row
|
||||
.iter()
|
||||
.zip(&hard_widths)
|
||||
.enumerate()
|
||||
.map(|(itx, (entry, width))| {
|
||||
if width.is_none() {
|
||||
if let (Some(desired_col_width), Some(calculated_col_width)) =
|
||||
(dcw.get(itx), ccw.get(itx))
|
||||
{
|
||||
if *desired_col_width > *calculated_col_width
|
||||
&& *calculated_col_width > 0
|
||||
{
|
||||
let graphemes =
|
||||
UnicodeSegmentation::graphemes(entry.as_str(), true)
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if graphemes.len() > *calculated_col_width as usize
|
||||
&& *calculated_col_width > 1
|
||||
{
|
||||
// Truncate with ellipsis
|
||||
let first_n = graphemes
|
||||
[..(*calculated_col_width as usize - 1)]
|
||||
.concat();
|
||||
return Text::raw(format!("{}…", first_n));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text::raw(entry)
|
||||
});
|
||||
|
||||
Row::new(truncated_data)
|
||||
});
|
||||
|
||||
let (border_style, highlight_style) = if is_on_widget {
|
||||
(
|
||||
painter.colours.highlighted_border_style,
|
||||
painter.colours.currently_selected_text_style,
|
||||
)
|
||||
} else {
|
||||
(painter.colours.border_style, painter.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
let title_string = format!(
|
||||
" Disk ({} of {}) ",
|
||||
disk_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
app_state.canvas_data.disk_data.len()
|
||||
);
|
||||
|
||||
if title_string.len() <= draw_loc.width as usize {
|
||||
title_string
|
||||
} else {
|
||||
" Disk ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Disk ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Disk ".to_string(),
|
||||
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(chosen_title_base, painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let disk_block = if draw_border {
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
// Draw!
|
||||
f.render_stateful_widget(
|
||||
Table::new(disk_rows)
|
||||
.block(disk_block)
|
||||
.header(
|
||||
Row::new(DISK_HEADERS.to_vec())
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap),
|
||||
)
|
||||
.highlight_style(highlight_style)
|
||||
.style(painter.colours.text_style)
|
||||
.widths(
|
||||
&(disk_widget_state
|
||||
.table_width_state
|
||||
.calculated_column_widths
|
||||
.iter()
|
||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||
.collect::<Vec<_>>()),
|
||||
),
|
||||
margined_draw_loc,
|
||||
disk_table_state,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
widget.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
use crate::{
|
||||
app::AppState,
|
||||
canvas::{drawing_utils::*, Painter},
|
||||
constants::*,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::Spans,
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
pub fn draw_basic_memory<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
||||
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
|
||||
|
||||
let margined_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(1)
|
||||
.split(draw_loc);
|
||||
|
||||
if app_state.current_widget.widget_id == widget_id {
|
||||
f.render_widget(
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style),
|
||||
draw_loc,
|
||||
);
|
||||
}
|
||||
|
||||
let ram_use_percentage = if let Some(mem) = mem_data.last() {
|
||||
mem.1
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let swap_use_percentage = if let Some(swap) = swap_data.last() {
|
||||
swap.1
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
const EMPTY_MEMORY_FRAC_STRING: &str = "0.0B/0.0B";
|
||||
|
||||
let trimmed_memory_frac =
|
||||
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
|
||||
label_frac.trim()
|
||||
} else {
|
||||
EMPTY_MEMORY_FRAC_STRING
|
||||
};
|
||||
|
||||
let trimmed_swap_frac =
|
||||
if let Some((_label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
|
||||
label_frac.trim()
|
||||
} else {
|
||||
EMPTY_MEMORY_FRAC_STRING
|
||||
};
|
||||
|
||||
// +7 due to 3 + 2 + 2 columns for the name & space + bar bounds + margin spacing
|
||||
// Then + length of fraction
|
||||
let ram_bar_length =
|
||||
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_memory_frac.len());
|
||||
let swap_bar_length =
|
||||
usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_swap_frac.len());
|
||||
|
||||
let num_bars_ram = calculate_basic_use_bars(ram_use_percentage, ram_bar_length);
|
||||
let num_bars_swap = calculate_basic_use_bars(swap_use_percentage, swap_bar_length);
|
||||
// TODO: Use different styling for the frac.
|
||||
let mem_label = if app_state.basic_mode_use_percent {
|
||||
format!(
|
||||
"RAM[{}{}{:3.0}%]\n",
|
||||
"|".repeat(num_bars_ram),
|
||||
" ".repeat(ram_bar_length - num_bars_ram + trimmed_memory_frac.len() - 4),
|
||||
ram_use_percentage.round()
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"RAM[{}{}{}]\n",
|
||||
"|".repeat(num_bars_ram),
|
||||
" ".repeat(ram_bar_length - num_bars_ram),
|
||||
trimmed_memory_frac
|
||||
)
|
||||
};
|
||||
let swap_label = if app_state.basic_mode_use_percent {
|
||||
format!(
|
||||
"SWP[{}{}{:3.0}%]",
|
||||
"|".repeat(num_bars_swap),
|
||||
" ".repeat(swap_bar_length - num_bars_swap + trimmed_swap_frac.len() - 4),
|
||||
swap_use_percentage.round()
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"SWP[{}{}{}]",
|
||||
"|".repeat(num_bars_swap),
|
||||
" ".repeat(swap_bar_length - num_bars_swap),
|
||||
trimmed_swap_frac
|
||||
)
|
||||
};
|
||||
|
||||
let mem_text = vec![
|
||||
Spans::from(Span::styled(mem_label, painter.colours.ram_style)),
|
||||
Spans::from(Span::styled(swap_label, painter.colours.swap_style)),
|
||||
];
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(mem_text).block(Block::default()),
|
||||
margined_loc[0],
|
||||
);
|
||||
|
||||
// Update draw loc in widget map
|
||||
if app_state.should_get_widget_bounds() {
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
use crate::{
|
||||
app::AppState,
|
||||
canvas::{drawing_utils::interpolate_points, Painter},
|
||||
constants::*,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Rect},
|
||||
symbols::Marker,
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::Spans,
|
||||
widgets::{Axis, Block, Borders, Chart, Dataset},
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub fn draw_memory_graph<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
|
||||
let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data;
|
||||
let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data;
|
||||
|
||||
let time_start = -(mem_widget_state.current_display_time as f64);
|
||||
|
||||
let display_time_labels = vec![
|
||||
Span::styled(
|
||||
format!("{}s", mem_widget_state.current_display_time / 1000),
|
||||
painter.colours.graph_style,
|
||||
),
|
||||
Span::styled("0s".to_string(), painter.colours.graph_style),
|
||||
];
|
||||
let y_axis_label = vec![
|
||||
Span::styled(" 0%", painter.colours.graph_style),
|
||||
Span::styled("100%", painter.colours.graph_style),
|
||||
];
|
||||
|
||||
let x_axis = if app_state.app_config_fields.hide_time
|
||||
|| (app_state.app_config_fields.autohide_time
|
||||
&& mem_widget_state.autohide_timer.is_none())
|
||||
{
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else if let Some(time) = mem_widget_state.autohide_timer {
|
||||
if std::time::Instant::now().duration_since(time).as_millis()
|
||||
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
|
||||
{
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
} else {
|
||||
mem_widget_state.autohide_timer = None;
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
}
|
||||
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else {
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
};
|
||||
|
||||
let y_axis = Axis::default()
|
||||
.style(painter.colours.graph_style)
|
||||
.bounds([0.0, 100.5])
|
||||
.labels(y_axis_label);
|
||||
|
||||
// Interpolate values to avoid ugly gaps
|
||||
let interpolated_mem_point = if let Some(end_pos) = mem_data
|
||||
.iter()
|
||||
.position(|(time, _data)| *time >= time_start)
|
||||
{
|
||||
if end_pos > 1 {
|
||||
let start_pos = end_pos - 1;
|
||||
let outside_point = mem_data.get(start_pos);
|
||||
let inside_point = mem_data.get(end_pos);
|
||||
|
||||
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
|
||||
let old = *outside_point;
|
||||
|
||||
let new_point = (
|
||||
time_start,
|
||||
interpolate_points(outside_point, inside_point, time_start),
|
||||
);
|
||||
|
||||
if let Some(to_replace) = mem_data.get_mut(start_pos) {
|
||||
*to_replace = new_point;
|
||||
Some((start_pos, old))
|
||||
} else {
|
||||
None // Failed to get mutable reference.
|
||||
}
|
||||
} else {
|
||||
None // Point somehow doesn't exist in our data
|
||||
}
|
||||
} else {
|
||||
None // Point is already "leftmost", no need to interpolate.
|
||||
}
|
||||
} else {
|
||||
None // There is no point.
|
||||
};
|
||||
|
||||
let interpolated_swap_point = if let Some(end_pos) = swap_data
|
||||
.iter()
|
||||
.position(|(time, _data)| *time >= time_start)
|
||||
{
|
||||
if end_pos > 1 {
|
||||
let start_pos = end_pos - 1;
|
||||
let outside_point = swap_data.get(start_pos);
|
||||
let inside_point = swap_data.get(end_pos);
|
||||
|
||||
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
|
||||
let old = *outside_point;
|
||||
|
||||
let new_point = (
|
||||
time_start,
|
||||
interpolate_points(outside_point, inside_point, time_start),
|
||||
);
|
||||
|
||||
if let Some(to_replace) = swap_data.get_mut(start_pos) {
|
||||
*to_replace = new_point;
|
||||
Some((start_pos, old))
|
||||
} else {
|
||||
None // Failed to get mutable reference.
|
||||
}
|
||||
} else {
|
||||
None // Point somehow doesn't exist in our data
|
||||
}
|
||||
} else {
|
||||
None // Point is already "leftmost", no need to interpolate.
|
||||
}
|
||||
} else {
|
||||
None // There is no point.
|
||||
};
|
||||
|
||||
let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
|
||||
|
||||
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
|
||||
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
|
||||
mem_canvas_vec.push(
|
||||
Dataset::default()
|
||||
.name(mem_label)
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.ram_style)
|
||||
.data(mem_data)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
|
||||
let swap_label = format!("SWP:{}{}", label_percent, label_frac);
|
||||
mem_canvas_vec.push(
|
||||
Dataset::default()
|
||||
.name(swap_label)
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.swap_style)
|
||||
.data(swap_data)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
);
|
||||
}
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let border_style = if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " Memory ── Esc to go back ";
|
||||
Spans::from(vec![
|
||||
Span::styled(" Memory ", painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(
|
||||
" Memory ".to_string(),
|
||||
painter.colours.widget_title_style,
|
||||
))
|
||||
};
|
||||
|
||||
f.render_widget(
|
||||
Chart::new(mem_canvas_vec)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(if app_state.current_widget.widget_id == widget_id {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
}),
|
||||
)
|
||||
.x_axis(x_axis)
|
||||
.y_axis(y_axis)
|
||||
.hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
|
||||
draw_loc,
|
||||
);
|
||||
|
||||
// Now if you're done, reset any interpolated points!
|
||||
if let Some((index, old_value)) = interpolated_mem_point {
|
||||
if let Some(to_replace) = mem_data.get_mut(index) {
|
||||
*to_replace = old_value;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((index, old_value)) = interpolated_swap_point {
|
||||
if let Some(to_replace) = swap_data.get_mut(index) {
|
||||
*to_replace = old_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
use crate::{app::AppState, canvas::Painter, constants::*};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans},
|
||||
widgets::{Block, Paragraph},
|
||||
};
|
||||
|
||||
pub fn draw_basic_network<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
let divided_loc = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.split(draw_loc);
|
||||
|
||||
let net_loc = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(1)
|
||||
.split(divided_loc[0]);
|
||||
|
||||
let total_loc = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(1)
|
||||
.split(divided_loc[1]);
|
||||
|
||||
if app_state.current_widget.widget_id == widget_id {
|
||||
f.render_widget(
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style),
|
||||
draw_loc,
|
||||
);
|
||||
}
|
||||
|
||||
let rx_label = format!("RX: {}", &app_state.canvas_data.rx_display);
|
||||
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
|
||||
let total_rx_label = format!("Total RX: {}", &app_state.canvas_data.total_rx_display);
|
||||
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
|
||||
|
||||
let net_text = vec![
|
||||
Spans::from(Span::styled(rx_label, painter.colours.rx_style)),
|
||||
Spans::from(Span::styled(tx_label, painter.colours.tx_style)),
|
||||
];
|
||||
|
||||
let total_net_text = vec![
|
||||
Spans::from(Span::styled(total_rx_label, painter.colours.total_rx_style)),
|
||||
Spans::from(Span::styled(total_tx_label, painter.colours.total_tx_style)),
|
||||
];
|
||||
|
||||
f.render_widget(Paragraph::new(net_text).block(Block::default()), net_loc[0]);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(total_net_text).block(Block::default()),
|
||||
total_loc[0],
|
||||
);
|
||||
|
||||
// Update draw loc in widget map
|
||||
if app_state.should_get_widget_bounds() {
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,758 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cmp::max;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
app::{AppState, AxisScaling},
|
||||
canvas::{
|
||||
drawing_utils::{get_column_widths, interpolate_points},
|
||||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
units::data_units::DataUnit,
|
||||
utils::gen_util::*,
|
||||
};
|
||||
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
symbols::Marker,
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::{Spans, Text},
|
||||
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
|
||||
};
|
||||
|
||||
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
|
||||
|
||||
static NETWORK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
|
||||
NETWORK_HEADERS
|
||||
.iter()
|
||||
.map(|entry| entry.len() as u16)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
pub fn draw_network<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
if app_state.app_config_fields.use_old_network_legend {
|
||||
let network_chunk = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(0)
|
||||
.constraints([
|
||||
Constraint::Length(max(draw_loc.height as i64 - 5, 0) as u16),
|
||||
Constraint::Length(5),
|
||||
])
|
||||
.split(draw_loc);
|
||||
|
||||
draw_network_graph(painter, f, app_state, network_chunk[0], widget_id, true);
|
||||
draw_network_labels(painter, f, app_state, network_chunk[1], widget_id);
|
||||
} else {
|
||||
draw_network_graph(painter, f, app_state, draw_loc, widget_id, false);
|
||||
}
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
// Note that in both cases, we always go to the same widget id so it's fine to do it like
|
||||
// this lol.
|
||||
if let Some(network_widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
network_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
|
||||
network_widget.bottom_right_corner =
|
||||
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_network_graph<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64, hide_legend: bool,
|
||||
) {
|
||||
/// Point is of time, data
|
||||
type Point = (f64, f64);
|
||||
|
||||
/// Returns the max data point and time given a time.
|
||||
fn get_max_entry(
|
||||
rx: &[Point], tx: &[Point], time_start: f64, network_scale_type: &AxisScaling,
|
||||
network_use_binary_prefix: bool,
|
||||
) -> (f64, f64) {
|
||||
/// Determines a "fake" max value in circumstances where we couldn't find one from the data.
|
||||
fn calculate_missing_max(
|
||||
network_scale_type: &AxisScaling, network_use_binary_prefix: bool,
|
||||
) -> f64 {
|
||||
match network_scale_type {
|
||||
AxisScaling::Log => {
|
||||
if network_use_binary_prefix {
|
||||
LOG_KIBI_LIMIT
|
||||
} else {
|
||||
LOG_KILO_LIMIT
|
||||
}
|
||||
}
|
||||
AxisScaling::Linear => {
|
||||
if network_use_binary_prefix {
|
||||
KIBI_LIMIT_F64
|
||||
} else {
|
||||
KILO_LIMIT_F64
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First, let's shorten our ranges to actually look. We can abuse the fact that our rx and tx arrays
|
||||
// are sorted, so we can short-circuit our search to filter out only the relevant data points...
|
||||
let filtered_rx = if let (Some(rx_start), Some(rx_end)) = (
|
||||
rx.iter().position(|(time, _data)| *time >= time_start),
|
||||
rx.iter().rposition(|(time, _data)| *time <= 0.0),
|
||||
) {
|
||||
Some(&rx[rx_start..=rx_end])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let filtered_tx = if let (Some(tx_start), Some(tx_end)) = (
|
||||
tx.iter().position(|(time, _data)| *time >= time_start),
|
||||
tx.iter().rposition(|(time, _data)| *time <= 0.0),
|
||||
) {
|
||||
Some(&tx[tx_start..=tx_end])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Then, find the maximal rx/tx so we know how to scale, and return it.
|
||||
match (filtered_rx, filtered_tx) {
|
||||
(None, None) => (
|
||||
time_start,
|
||||
calculate_missing_max(network_scale_type, network_use_binary_prefix),
|
||||
),
|
||||
(None, Some(filtered_tx)) => {
|
||||
match filtered_tx
|
||||
.iter()
|
||||
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
|
||||
{
|
||||
Some((best_time, max_val)) => {
|
||||
if *max_val == 0.0 {
|
||||
(
|
||||
time_start,
|
||||
calculate_missing_max(
|
||||
network_scale_type,
|
||||
network_use_binary_prefix,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(*best_time, *max_val)
|
||||
}
|
||||
}
|
||||
None => (
|
||||
time_start,
|
||||
calculate_missing_max(network_scale_type, network_use_binary_prefix),
|
||||
),
|
||||
}
|
||||
}
|
||||
(Some(filtered_rx), None) => {
|
||||
match filtered_rx
|
||||
.iter()
|
||||
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
|
||||
{
|
||||
Some((best_time, max_val)) => {
|
||||
if *max_val == 0.0 {
|
||||
(
|
||||
time_start,
|
||||
calculate_missing_max(
|
||||
network_scale_type,
|
||||
network_use_binary_prefix,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(*best_time, *max_val)
|
||||
}
|
||||
}
|
||||
None => (
|
||||
time_start,
|
||||
calculate_missing_max(network_scale_type, network_use_binary_prefix),
|
||||
),
|
||||
}
|
||||
}
|
||||
(Some(filtered_rx), Some(filtered_tx)) => {
|
||||
match filtered_rx
|
||||
.iter()
|
||||
.chain(filtered_tx)
|
||||
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
|
||||
{
|
||||
Some((best_time, max_val)) => {
|
||||
if *max_val == 0.0 {
|
||||
(
|
||||
*best_time,
|
||||
calculate_missing_max(
|
||||
network_scale_type,
|
||||
network_use_binary_prefix,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(*best_time, *max_val)
|
||||
}
|
||||
}
|
||||
None => (
|
||||
time_start,
|
||||
calculate_missing_max(network_scale_type, network_use_binary_prefix),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the required max data point and labels.
|
||||
fn adjust_network_data_point(
|
||||
max_entry: f64, network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
|
||||
network_use_binary_prefix: bool,
|
||||
) -> (f64, Vec<String>) {
|
||||
// So, we're going with an approach like this for linear data:
|
||||
// - Main goal is to maximize the amount of information displayed given a specific height.
|
||||
// We don't want to drown out some data if the ranges are too far though! Nor do we want to filter
|
||||
// out too much data...
|
||||
// - Change the y-axis unit (kilo/kibi, mega/mebi...) dynamically based on max load.
|
||||
//
|
||||
// The idea is we take the top value, build our scale such that each "point" is a scaled version of that.
|
||||
// So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and
|
||||
// probably something like 438.75?
|
||||
//
|
||||
// So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max
|
||||
// value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will
|
||||
// properly space them all out... we just work with that and space it out properly.
|
||||
//
|
||||
// Dynamic chart idea based off of FreeNAS's chart design.
|
||||
//
|
||||
// ===
|
||||
//
|
||||
// For log data, we just use the old method of log intervals (kilo/mega/giga/etc.). Keep it nice and simple.
|
||||
|
||||
// Now just check the largest unit we correspond to... then proceed to build some entries from there!
|
||||
|
||||
let unit_char = match network_unit_type {
|
||||
DataUnit::Byte => "B",
|
||||
DataUnit::Bit => "b",
|
||||
};
|
||||
|
||||
match network_scale_type {
|
||||
AxisScaling::Linear => {
|
||||
let (k_limit, m_limit, g_limit, t_limit) = if network_use_binary_prefix {
|
||||
(
|
||||
KIBI_LIMIT_F64,
|
||||
MEBI_LIMIT_F64,
|
||||
GIBI_LIMIT_F64,
|
||||
TEBI_LIMIT_F64,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
KILO_LIMIT_F64,
|
||||
MEGA_LIMIT_F64,
|
||||
GIGA_LIMIT_F64,
|
||||
TERA_LIMIT_F64,
|
||||
)
|
||||
};
|
||||
|
||||
let bumped_max_entry = max_entry * 1.5; // We use the bumped up version to calculate our unit type.
|
||||
let (max_value_scaled, unit_prefix, unit_type): (f64, &str, &str) =
|
||||
if bumped_max_entry < k_limit {
|
||||
(max_entry, "", unit_char)
|
||||
} else if bumped_max_entry < m_limit {
|
||||
(
|
||||
max_entry / k_limit,
|
||||
if network_use_binary_prefix { "Ki" } else { "K" },
|
||||
unit_char,
|
||||
)
|
||||
} else if bumped_max_entry < g_limit {
|
||||
(
|
||||
max_entry / m_limit,
|
||||
if network_use_binary_prefix { "Mi" } else { "M" },
|
||||
unit_char,
|
||||
)
|
||||
} else if bumped_max_entry < t_limit {
|
||||
(
|
||||
max_entry / g_limit,
|
||||
if network_use_binary_prefix { "Gi" } else { "G" },
|
||||
unit_char,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
max_entry / t_limit,
|
||||
if network_use_binary_prefix { "Ti" } else { "T" },
|
||||
unit_char,
|
||||
)
|
||||
};
|
||||
|
||||
// Finally, build an acceptable range starting from there, using the given height!
|
||||
// Note we try to put more of a weight on the bottom section vs. the top, since the top has less data.
|
||||
|
||||
let base_unit = max_value_scaled;
|
||||
let labels: Vec<String> = vec![
|
||||
format!("0{}{}", unit_prefix, unit_type),
|
||||
format!("{:.1}", base_unit * 0.5),
|
||||
format!("{:.1}", base_unit),
|
||||
format!("{:.1}", base_unit * 1.5),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|s| format!("{:>5}", s)) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second)
|
||||
.collect();
|
||||
|
||||
(bumped_max_entry, labels)
|
||||
}
|
||||
AxisScaling::Log => {
|
||||
let (m_limit, g_limit, t_limit) = if network_use_binary_prefix {
|
||||
(LOG_MEBI_LIMIT, LOG_GIBI_LIMIT, LOG_TEBI_LIMIT)
|
||||
} else {
|
||||
(LOG_MEGA_LIMIT, LOG_GIGA_LIMIT, LOG_TERA_LIMIT)
|
||||
};
|
||||
|
||||
fn get_zero(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"{}0{}",
|
||||
if network_use_binary_prefix { " " } else { " " },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
fn get_k(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"1{}{}",
|
||||
if network_use_binary_prefix { "Ki" } else { "K" },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
fn get_m(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"1{}{}",
|
||||
if network_use_binary_prefix { "Mi" } else { "M" },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
fn get_g(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"1{}{}",
|
||||
if network_use_binary_prefix { "Gi" } else { "G" },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
fn get_t(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"1{}{}",
|
||||
if network_use_binary_prefix { "Ti" } else { "T" },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
fn get_p(network_use_binary_prefix: bool, unit_char: &str) -> String {
|
||||
format!(
|
||||
"1{}{}",
|
||||
if network_use_binary_prefix { "Pi" } else { "P" },
|
||||
unit_char
|
||||
)
|
||||
}
|
||||
|
||||
if max_entry < m_limit {
|
||||
(
|
||||
m_limit,
|
||||
vec![
|
||||
get_zero(network_use_binary_prefix, unit_char),
|
||||
get_k(network_use_binary_prefix, unit_char),
|
||||
get_m(network_use_binary_prefix, unit_char),
|
||||
],
|
||||
)
|
||||
} else if max_entry < g_limit {
|
||||
(
|
||||
g_limit,
|
||||
vec![
|
||||
get_zero(network_use_binary_prefix, unit_char),
|
||||
get_k(network_use_binary_prefix, unit_char),
|
||||
get_m(network_use_binary_prefix, unit_char),
|
||||
get_g(network_use_binary_prefix, unit_char),
|
||||
],
|
||||
)
|
||||
} else if max_entry < t_limit {
|
||||
(
|
||||
t_limit,
|
||||
vec![
|
||||
get_zero(network_use_binary_prefix, unit_char),
|
||||
get_k(network_use_binary_prefix, unit_char),
|
||||
get_m(network_use_binary_prefix, unit_char),
|
||||
get_g(network_use_binary_prefix, unit_char),
|
||||
get_t(network_use_binary_prefix, unit_char),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
// I really doubt anyone's transferring beyond petabyte speeds...
|
||||
(
|
||||
if network_use_binary_prefix {
|
||||
LOG_PEBI_LIMIT
|
||||
} else {
|
||||
LOG_PETA_LIMIT
|
||||
},
|
||||
vec![
|
||||
get_zero(network_use_binary_prefix, unit_char),
|
||||
get_k(network_use_binary_prefix, unit_char),
|
||||
get_m(network_use_binary_prefix, unit_char),
|
||||
get_g(network_use_binary_prefix, unit_char),
|
||||
get_t(network_use_binary_prefix, unit_char),
|
||||
get_p(network_use_binary_prefix, unit_char),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) {
|
||||
let network_data_rx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_rx;
|
||||
let network_data_tx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_tx;
|
||||
|
||||
let time_start = -(network_widget_state.current_display_time as f64);
|
||||
|
||||
let display_time_labels = vec![
|
||||
Span::styled(
|
||||
format!("{}s", network_widget_state.current_display_time / 1000),
|
||||
painter.colours.graph_style,
|
||||
),
|
||||
Span::styled("0s".to_string(), painter.colours.graph_style),
|
||||
];
|
||||
let x_axis = if app_state.app_config_fields.hide_time
|
||||
|| (app_state.app_config_fields.autohide_time
|
||||
&& network_widget_state.autohide_timer.is_none())
|
||||
{
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else if let Some(time) = network_widget_state.autohide_timer {
|
||||
if std::time::Instant::now().duration_since(time).as_millis()
|
||||
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
|
||||
{
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
} else {
|
||||
network_widget_state.autohide_timer = None;
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
}
|
||||
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
|
||||
Axis::default().bounds([time_start, 0.0])
|
||||
} else {
|
||||
Axis::default()
|
||||
.bounds([time_start, 0.0])
|
||||
.style(painter.colours.graph_style)
|
||||
.labels(display_time_labels)
|
||||
};
|
||||
|
||||
// Interpolate a point for rx and tx between the last value outside of the left bounds and the first value
|
||||
// inside it.
|
||||
// Because we assume it is all in order for... basically all our code, we can't just append it,
|
||||
// and insertion in the middle seems. So instead, we swap *out* the value that is outside with our
|
||||
// interpolated point, draw and do whatever calculations, then swap back in the old value!
|
||||
//
|
||||
// Note there is some re-used work here! For potential optimizations, we could re-use some work here in/from
|
||||
// get_max_entry...
|
||||
let interpolated_rx_point = if let Some(rx_end_pos) = network_data_rx
|
||||
.iter()
|
||||
.position(|(time, _data)| *time >= time_start)
|
||||
{
|
||||
if rx_end_pos > 1 {
|
||||
let rx_start_pos = rx_end_pos - 1;
|
||||
let outside_rx_point = network_data_rx.get(rx_start_pos);
|
||||
let inside_rx_point = network_data_rx.get(rx_end_pos);
|
||||
|
||||
if let (Some(outside_rx_point), Some(inside_rx_point)) =
|
||||
(outside_rx_point, inside_rx_point)
|
||||
{
|
||||
let old = *outside_rx_point;
|
||||
|
||||
let new_point = (
|
||||
time_start,
|
||||
interpolate_points(outside_rx_point, inside_rx_point, time_start),
|
||||
);
|
||||
|
||||
// debug!(
|
||||
// "Interpolated between {:?} and {:?}, got rx for time {:?}: {:?}",
|
||||
// outside_rx_point, inside_rx_point, time_start, new_point
|
||||
// );
|
||||
|
||||
if let Some(to_replace) = network_data_rx.get_mut(rx_start_pos) {
|
||||
*to_replace = new_point;
|
||||
Some((rx_start_pos, old))
|
||||
} else {
|
||||
None // Failed to get mutable reference.
|
||||
}
|
||||
} else {
|
||||
None // Point somehow doesn't exist in our network_data_rx
|
||||
}
|
||||
} else {
|
||||
None // Point is already "leftmost", no need to interpolate.
|
||||
}
|
||||
} else {
|
||||
None // There is no point.
|
||||
};
|
||||
|
||||
let interpolated_tx_point = if let Some(tx_end_pos) = network_data_tx
|
||||
.iter()
|
||||
.position(|(time, _data)| *time >= time_start)
|
||||
{
|
||||
if tx_end_pos > 1 {
|
||||
let tx_start_pos = tx_end_pos - 1;
|
||||
let outside_tx_point = network_data_tx.get(tx_start_pos);
|
||||
let inside_tx_point = network_data_tx.get(tx_end_pos);
|
||||
|
||||
if let (Some(outside_tx_point), Some(inside_tx_point)) =
|
||||
(outside_tx_point, inside_tx_point)
|
||||
{
|
||||
let old = *outside_tx_point;
|
||||
|
||||
let new_point = (
|
||||
time_start,
|
||||
interpolate_points(outside_tx_point, inside_tx_point, time_start),
|
||||
);
|
||||
|
||||
if let Some(to_replace) = network_data_tx.get_mut(tx_start_pos) {
|
||||
*to_replace = new_point;
|
||||
Some((tx_start_pos, old))
|
||||
} else {
|
||||
None // Failed to get mutable reference.
|
||||
}
|
||||
} else {
|
||||
None // Point somehow doesn't exist in our network_data_tx
|
||||
}
|
||||
} else {
|
||||
None // Point is already "leftmost", no need to interpolate.
|
||||
}
|
||||
} else {
|
||||
None // There is no point.
|
||||
};
|
||||
|
||||
// TODO: Cache network results: Only update if:
|
||||
// - Force update (includes time interval change)
|
||||
// - Old max time is off screen
|
||||
// - A new time interval is better and does not fit (check from end of vector to last checked; we only want to update if it is TOO big!)
|
||||
|
||||
// Find the maximal rx/tx so we know how to scale, and return it.
|
||||
|
||||
let (_best_time, max_entry) = get_max_entry(
|
||||
network_data_rx,
|
||||
network_data_tx,
|
||||
time_start,
|
||||
&app_state.app_config_fields.network_scale_type,
|
||||
app_state.app_config_fields.network_use_binary_prefix,
|
||||
);
|
||||
|
||||
let (max_range, labels) = adjust_network_data_point(
|
||||
max_entry,
|
||||
&app_state.app_config_fields.network_scale_type,
|
||||
&app_state.app_config_fields.network_unit_type,
|
||||
app_state.app_config_fields.network_use_binary_prefix,
|
||||
);
|
||||
|
||||
// Cache results.
|
||||
// network_widget_state.draw_max_range_cache = max_range;
|
||||
// network_widget_state.draw_time_start_cache = best_time;
|
||||
// network_widget_state.draw_labels_cache = labels;
|
||||
|
||||
let y_axis_labels = labels
|
||||
.iter()
|
||||
.map(|label| Span::styled(label, painter.colours.graph_style))
|
||||
.collect::<Vec<_>>();
|
||||
let y_axis = Axis::default()
|
||||
.style(painter.colours.graph_style)
|
||||
.bounds([0.0, max_range])
|
||||
.labels(y_axis_labels);
|
||||
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let border_style = if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " Network ── Esc to go back ";
|
||||
Spans::from(vec![
|
||||
Span::styled(" Network ", painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(
|
||||
" Network ",
|
||||
painter.colours.widget_title_style,
|
||||
))
|
||||
};
|
||||
|
||||
let legend_constraints = if hide_legend {
|
||||
(Constraint::Ratio(0, 1), Constraint::Ratio(0, 1))
|
||||
} else {
|
||||
(Constraint::Ratio(1, 1), Constraint::Ratio(3, 4))
|
||||
};
|
||||
|
||||
// TODO: Add support for clicking on legend to only show that value on chart.
|
||||
let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend {
|
||||
vec![
|
||||
Dataset::default()
|
||||
.name(format!("RX: {:7}", app_state.canvas_data.rx_display))
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.rx_style)
|
||||
.data(network_data_rx)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
Dataset::default()
|
||||
.name(format!("TX: {:7}", app_state.canvas_data.tx_display))
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.tx_style)
|
||||
.data(network_data_tx)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
Dataset::default()
|
||||
.name(format!(
|
||||
"Total RX: {:7}",
|
||||
app_state.canvas_data.total_rx_display
|
||||
))
|
||||
.style(painter.colours.total_rx_style),
|
||||
Dataset::default()
|
||||
.name(format!(
|
||||
"Total TX: {:7}",
|
||||
app_state.canvas_data.total_tx_display
|
||||
))
|
||||
.style(painter.colours.total_tx_style),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Dataset::default()
|
||||
.name(&app_state.canvas_data.rx_display)
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.rx_style)
|
||||
.data(network_data_rx)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
Dataset::default()
|
||||
.name(&app_state.canvas_data.tx_display)
|
||||
.marker(if app_state.app_config_fields.use_dot {
|
||||
Marker::Dot
|
||||
} else {
|
||||
Marker::Braille
|
||||
})
|
||||
.style(painter.colours.tx_style)
|
||||
.data(network_data_tx)
|
||||
.graph_type(tui::widgets::GraphType::Line),
|
||||
]
|
||||
};
|
||||
|
||||
f.render_widget(
|
||||
Chart::new(dataset)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(if app_state.current_widget.widget_id == widget_id {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
}),
|
||||
)
|
||||
.x_axis(x_axis)
|
||||
.y_axis(y_axis)
|
||||
.hidden_legend_constraints(legend_constraints),
|
||||
draw_loc,
|
||||
);
|
||||
|
||||
// Now if you're done, reset any interpolated points!
|
||||
if let Some((index, old_value)) = interpolated_rx_point {
|
||||
if let Some(to_replace) = network_data_rx.get_mut(index) {
|
||||
*to_replace = old_value;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((index, old_value)) = interpolated_tx_point {
|
||||
if let Some(to_replace) = network_data_tx.get_mut(index) {
|
||||
*to_replace = old_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_network_labels<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
widget_id: u64,
|
||||
) {
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
|
||||
let rx_display = &app_state.canvas_data.rx_display;
|
||||
let tx_display = &app_state.canvas_data.tx_display;
|
||||
let total_rx_display = &app_state.canvas_data.total_rx_display;
|
||||
let total_tx_display = &app_state.canvas_data.total_tx_display;
|
||||
|
||||
// Gross but I need it to work...
|
||||
let total_network = vec![vec![
|
||||
Text::raw(rx_display),
|
||||
Text::raw(tx_display),
|
||||
Text::raw(total_rx_display),
|
||||
Text::raw(total_tx_display),
|
||||
]];
|
||||
let mapped_network = total_network
|
||||
.into_iter()
|
||||
.map(|val| Row::new(val).style(painter.colours.text_style));
|
||||
|
||||
// Calculate widths
|
||||
let intrinsic_widths = get_column_widths(
|
||||
draw_loc.width,
|
||||
&[None, None, None, None],
|
||||
&(NETWORK_HEADERS_LENS
|
||||
.iter()
|
||||
.map(|s| Some(*s))
|
||||
.collect::<Vec<_>>()),
|
||||
&[Some(0.25); 4],
|
||||
&(NETWORK_HEADERS_LENS
|
||||
.iter()
|
||||
.map(|s| Some(*s))
|
||||
.collect::<Vec<_>>()),
|
||||
true,
|
||||
);
|
||||
|
||||
// Draw
|
||||
f.render_widget(
|
||||
Table::new(mapped_network)
|
||||
.header(
|
||||
Row::new(NETWORK_HEADERS.to_vec())
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap),
|
||||
)
|
||||
.block(Block::default().borders(Borders::ALL).border_style(
|
||||
if app_state.current_widget.widget_id == widget_id {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
},
|
||||
))
|
||||
.style(painter.colours.text_style)
|
||||
.widths(
|
||||
&(intrinsic_widths
|
||||
.iter()
|
||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||
.collect::<Vec<_>>()),
|
||||
),
|
||||
draw_loc,
|
||||
);
|
||||
}
|
@ -1,856 +0,0 @@
|
||||
use crate::{
|
||||
app::AppState,
|
||||
canvas::{
|
||||
drawing_utils::{get_column_widths, get_search_start_position, get_start_position},
|
||||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
};
|
||||
|
||||
use indextree::NodeId;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Borders, Paragraph, Row, Table},
|
||||
};
|
||||
|
||||
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: Lazy<Vec<Option<u16>>> = Lazy::new(|| {
|
||||
vec![
|
||||
Some(7),
|
||||
None,
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(7),
|
||||
Some(8),
|
||||
#[cfg(target_family = "unix")]
|
||||
None,
|
||||
None,
|
||||
]
|
||||
});
|
||||
static PROCESS_HEADERS_HARD_WIDTH_GROUPED: Lazy<Vec<Option<u16>>> = Lazy::new(|| {
|
||||
vec![
|
||||
Some(7),
|
||||
None,
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(8),
|
||||
Some(7),
|
||||
Some(8),
|
||||
]
|
||||
});
|
||||
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND: Lazy<Vec<Option<f64>>> =
|
||||
Lazy::new(|| vec![None, Some(0.7), None, None, None, None, None, None]);
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE: Lazy<Vec<Option<f64>>> =
|
||||
Lazy::new(|| vec![None, Some(0.3), None, None, None, None, None, None]);
|
||||
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
|
||||
vec![
|
||||
None,
|
||||
Some(0.7),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
|
||||
vec![
|
||||
None,
|
||||
Some(0.5),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
|
||||
vec![
|
||||
None,
|
||||
Some(0.3),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
#[cfg(target_family = "unix")]
|
||||
Some(0.05),
|
||||
Some(0.2),
|
||||
]
|
||||
});
|
||||
|
||||
pub fn draw_process_features<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: NodeId,
|
||||
) {
|
||||
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&1) {
|
||||
let search_height = if draw_border { 5 } else { 3 };
|
||||
let is_sort_open = process_widget_state.is_sort_open;
|
||||
let header_len = process_widget_state.columns.longest_header_len;
|
||||
|
||||
let mut proc_draw_loc = draw_loc;
|
||||
if process_widget_state.is_search_enabled() {
|
||||
let processes_chunk = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(search_height)])
|
||||
.split(draw_loc);
|
||||
proc_draw_loc = processes_chunk[0];
|
||||
|
||||
draw_search_field(
|
||||
painter,
|
||||
f,
|
||||
app_state,
|
||||
processes_chunk[1],
|
||||
draw_border,
|
||||
widget_id,
|
||||
);
|
||||
}
|
||||
|
||||
if is_sort_open {
|
||||
let processes_chunk = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Length(header_len + 4), Constraint::Min(0)])
|
||||
.split(proc_draw_loc);
|
||||
proc_draw_loc = processes_chunk[1];
|
||||
|
||||
draw_process_sort(painter, f, app_state, processes_chunk[0], draw_border, 1);
|
||||
}
|
||||
|
||||
draw_processes_table(painter, f, app_state, proc_draw_loc, draw_border, widget_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_processes_table<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: NodeId,
|
||||
) {
|
||||
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(&1) {
|
||||
let recalculate_column_widths =
|
||||
should_get_widget_bounds || proc_widget_state.requires_redraw;
|
||||
if proc_widget_state.requires_redraw {
|
||||
proc_widget_state.requires_redraw = false;
|
||||
}
|
||||
|
||||
let is_on_widget = false;
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
let (border_style, highlight_style) = if is_on_widget {
|
||||
(
|
||||
painter.colours.highlighted_border_style,
|
||||
painter.colours.currently_selected_text_style,
|
||||
)
|
||||
} else {
|
||||
(painter.colours.border_style, painter.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
if let Some(finalized_process_data) = app_state
|
||||
.canvas_data
|
||||
.stringified_process_data_map
|
||||
.get(&widget_id)
|
||||
{
|
||||
let title = format!(
|
||||
" Processes ({} of {}) ",
|
||||
proc_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
finalized_process_data.len()
|
||||
);
|
||||
|
||||
if title.len() <= draw_loc.width as usize {
|
||||
title
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded
|
||||
&& !proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_enabled
|
||||
&& !proc_widget_state.is_sort_open
|
||||
{
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Processes ".to_string(),
|
||||
format!("{}{}", " Processes ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(chosen_title_base, painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let process_block = if draw_border {
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
if let Some(process_data) = &app_state
|
||||
.canvas_data
|
||||
.stringified_process_data_map
|
||||
.get(&widget_id)
|
||||
{
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
let position = get_start_position(
|
||||
usize::from(
|
||||
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
|
||||
),
|
||||
&proc_widget_state.scroll_state.scroll_direction,
|
||||
&mut proc_widget_state.scroll_state.previous_scroll_position,
|
||||
proc_widget_state.scroll_state.current_scroll_position,
|
||||
app_state.is_force_redraw,
|
||||
);
|
||||
|
||||
// Sanity check
|
||||
let start_position = if position >= process_data.len() {
|
||||
process_data.len().saturating_sub(1)
|
||||
} else {
|
||||
position
|
||||
};
|
||||
|
||||
let sliced_vec = &process_data[start_position..];
|
||||
let processed_sliced_vec = sliced_vec.iter().map(|(data, disabled)| {
|
||||
(
|
||||
data.iter()
|
||||
.map(|(entry, _alternative)| entry)
|
||||
.collect::<Vec<_>>(),
|
||||
disabled,
|
||||
)
|
||||
});
|
||||
|
||||
let proc_table_state = &mut proc_widget_state.scroll_state.table_state;
|
||||
proc_table_state.select(Some(
|
||||
proc_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
|
||||
// Draw!
|
||||
let process_headers = proc_widget_state.columns.get_column_headers(
|
||||
&proc_widget_state.process_sorting_type,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
);
|
||||
|
||||
// Calculate widths
|
||||
// FIXME: See if we can move this into the recalculate block? I want to move column widths into the column widths
|
||||
let hard_widths = if proc_widget_state.is_grouped {
|
||||
&*PROCESS_HEADERS_HARD_WIDTH_GROUPED
|
||||
} else {
|
||||
&*PROCESS_HEADERS_HARD_WIDTH_NO_GROUP
|
||||
};
|
||||
|
||||
if recalculate_column_widths {
|
||||
let mut column_widths = process_headers
|
||||
.iter()
|
||||
.map(|entry| UnicodeWidthStr::width(entry.as_str()) as u16)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let soft_widths_min = column_widths
|
||||
.iter()
|
||||
.map(|width| Some(*width))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
proc_widget_state.table_width_state.desired_column_widths = {
|
||||
for (row, _disabled) in processed_sliced_vec.clone() {
|
||||
for (col, entry) in row.iter().enumerate() {
|
||||
if let Some(col_width) = column_widths.get_mut(col) {
|
||||
let grapheme_len = UnicodeWidthStr::width(entry.as_str());
|
||||
if grapheme_len as u16 > *col_width {
|
||||
*col_width = grapheme_len as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
column_widths
|
||||
};
|
||||
|
||||
proc_widget_state.table_width_state.desired_column_widths = proc_widget_state
|
||||
.table_width_state
|
||||
.desired_column_widths
|
||||
.iter()
|
||||
.zip(hard_widths)
|
||||
.map(|(current, hard)| {
|
||||
if let Some(hard) = hard {
|
||||
if *hard > *current {
|
||||
*hard
|
||||
} else {
|
||||
*current
|
||||
}
|
||||
} else {
|
||||
*current
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let soft_widths_max = if proc_widget_state.is_grouped {
|
||||
// Note grouped trees are not a thing.
|
||||
|
||||
if proc_widget_state.is_using_command {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND
|
||||
} else {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE
|
||||
}
|
||||
} else if proc_widget_state.is_using_command {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND
|
||||
} else if proc_widget_state.is_tree_mode {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE
|
||||
} else {
|
||||
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE
|
||||
};
|
||||
|
||||
proc_widget_state.table_width_state.calculated_column_widths = get_column_widths(
|
||||
draw_loc.width,
|
||||
hard_widths,
|
||||
&soft_widths_min,
|
||||
soft_widths_max,
|
||||
&(proc_widget_state
|
||||
.table_width_state
|
||||
.desired_column_widths
|
||||
.iter()
|
||||
.map(|width| Some(*width))
|
||||
.collect::<Vec<_>>()),
|
||||
true,
|
||||
);
|
||||
|
||||
// debug!(
|
||||
// "DCW: {:?}",
|
||||
// proc_widget_state.table_width_state.desired_column_widths
|
||||
// );
|
||||
// debug!(
|
||||
// "CCW: {:?}",
|
||||
// proc_widget_state.table_width_state.calculated_column_widths
|
||||
// );
|
||||
}
|
||||
|
||||
let dcw = &proc_widget_state.table_width_state.desired_column_widths;
|
||||
let ccw = &proc_widget_state.table_width_state.calculated_column_widths;
|
||||
|
||||
let process_rows = sliced_vec.iter().map(|(data, disabled)| {
|
||||
let truncated_data = data.iter().zip(hard_widths).enumerate().map(
|
||||
|(itx, ((entry, alternative), width))| {
|
||||
if let (Some(desired_col_width), Some(calculated_col_width)) =
|
||||
(dcw.get(itx), ccw.get(itx))
|
||||
{
|
||||
if width.is_none() {
|
||||
if *desired_col_width > *calculated_col_width
|
||||
&& *calculated_col_width > 0
|
||||
{
|
||||
let graphemes =
|
||||
UnicodeSegmentation::graphemes(entry.as_str(), true)
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if let Some(alternative) = alternative {
|
||||
Text::raw(alternative)
|
||||
} else if graphemes.len() > *calculated_col_width as usize
|
||||
&& *calculated_col_width > 1
|
||||
{
|
||||
// Truncate with ellipsis
|
||||
let first_n = graphemes
|
||||
[..(*calculated_col_width as usize - 1)]
|
||||
.concat();
|
||||
Text::raw(format!("{}…", first_n))
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if *disabled {
|
||||
Row::new(truncated_data).style(painter.colours.disabled_text_style)
|
||||
} else {
|
||||
Row::new(truncated_data)
|
||||
}
|
||||
});
|
||||
|
||||
f.render_stateful_widget(
|
||||
Table::new(process_rows)
|
||||
.header(
|
||||
Row::new(process_headers)
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap),
|
||||
)
|
||||
.block(process_block)
|
||||
.highlight_style(highlight_style)
|
||||
.style(painter.colours.text_style)
|
||||
.widths(
|
||||
&(proc_widget_state
|
||||
.table_width_state
|
||||
.calculated_column_widths
|
||||
.iter()
|
||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||
.collect::<Vec<_>>()),
|
||||
),
|
||||
margined_draw_loc,
|
||||
proc_table_state,
|
||||
);
|
||||
} else {
|
||||
f.render_widget(process_block, margined_draw_loc);
|
||||
}
|
||||
|
||||
// Check if we need to update columnar bounds...
|
||||
if recalculate_column_widths
|
||||
|| proc_widget_state.columns.column_header_x_locs.is_none()
|
||||
|| proc_widget_state.columns.column_header_y_loc.is_none()
|
||||
{
|
||||
// y location is just the y location of the widget + border size (1 normally, 0 in basic)
|
||||
proc_widget_state.columns.column_header_y_loc =
|
||||
Some(draw_loc.y + if draw_border { 1 } else { 0 });
|
||||
|
||||
// x location is determined using the x locations of the widget; just offset from the left bound
|
||||
// as appropriate, and use the right bound as limiter.
|
||||
|
||||
let mut current_x_left = draw_loc.x + 1;
|
||||
let max_x_right = draw_loc.x + draw_loc.width - 1;
|
||||
|
||||
let mut x_locs = vec![];
|
||||
|
||||
for width in proc_widget_state
|
||||
.table_width_state
|
||||
.calculated_column_widths
|
||||
.iter()
|
||||
{
|
||||
let right_bound = current_x_left + width;
|
||||
|
||||
if right_bound < max_x_right {
|
||||
x_locs.push((current_x_left, right_bound));
|
||||
current_x_left = right_bound + 1;
|
||||
} else {
|
||||
x_locs.push((current_x_left, max_x_right));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
proc_widget_state.columns.column_header_x_locs = Some(x_locs);
|
||||
}
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
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.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_search_field<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
draw_border: bool, _widget_id: NodeId,
|
||||
) {
|
||||
fn build_query<'a>(
|
||||
is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize,
|
||||
cursor_position: usize, query: &str, currently_selected_text_style: tui::style::Style,
|
||||
text_style: tui::style::Style,
|
||||
) -> Vec<Span<'a>> {
|
||||
let mut current_grapheme_posn = 0;
|
||||
|
||||
if is_on_widget {
|
||||
let mut res = grapheme_indices
|
||||
.filter_map(|grapheme| {
|
||||
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
|
||||
|
||||
if current_grapheme_posn <= start_position {
|
||||
None
|
||||
} else {
|
||||
let styled = if grapheme.0 == cursor_position {
|
||||
Span::styled(grapheme.1, currently_selected_text_style)
|
||||
} else {
|
||||
Span::styled(grapheme.1, text_style)
|
||||
};
|
||||
Some(styled)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if cursor_position == query.len() {
|
||||
res.push(Span::styled(" ", currently_selected_text_style))
|
||||
}
|
||||
|
||||
res
|
||||
} else {
|
||||
// This is easier - we just need to get a range of graphemes, rather than
|
||||
// dealing with possibly inserting a cursor (as none is shown!)
|
||||
|
||||
vec![Span::styled(query.to_string(), text_style)]
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make the cursor scroll back if there's space!
|
||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&1) {
|
||||
let is_on_widget = false;
|
||||
let num_columns = usize::from(draw_loc.width);
|
||||
let search_title = "> ";
|
||||
|
||||
let num_chars_for_text = search_title.len();
|
||||
let cursor_position = proc_widget_state.get_search_cursor_position();
|
||||
let current_cursor_position = proc_widget_state.get_char_cursor_position();
|
||||
|
||||
let start_position: usize = get_search_start_position(
|
||||
num_columns - num_chars_for_text - 5,
|
||||
&proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.cursor_direction,
|
||||
&mut proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.cursor_bar,
|
||||
current_cursor_position,
|
||||
app_state.is_force_redraw,
|
||||
);
|
||||
|
||||
let query = proc_widget_state.get_current_search_query().as_str();
|
||||
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
|
||||
|
||||
// TODO: [CURSOR] blank cursor if not selected
|
||||
// TODO: [CURSOR] blinking cursor?
|
||||
let query_with_cursor = build_query(
|
||||
is_on_widget,
|
||||
grapheme_indices,
|
||||
start_position,
|
||||
cursor_position,
|
||||
query,
|
||||
painter.colours.currently_selected_text_style,
|
||||
painter.colours.text_style,
|
||||
);
|
||||
|
||||
let mut search_text = vec![Spans::from({
|
||||
let mut search_vec = vec![Span::styled(
|
||||
search_title,
|
||||
if is_on_widget {
|
||||
painter.colours.table_header_style
|
||||
} else {
|
||||
painter.colours.text_style
|
||||
},
|
||||
)];
|
||||
search_vec.extend(query_with_cursor);
|
||||
|
||||
search_vec
|
||||
})];
|
||||
|
||||
// Text options shamelessly stolen from VS Code.
|
||||
let case_style = if !proc_widget_state.process_search_state.is_ignoring_case {
|
||||
painter.colours.currently_selected_text_style
|
||||
} else {
|
||||
painter.colours.text_style
|
||||
};
|
||||
|
||||
let whole_word_style = if proc_widget_state
|
||||
.process_search_state
|
||||
.is_searching_whole_word
|
||||
{
|
||||
painter.colours.currently_selected_text_style
|
||||
} else {
|
||||
painter.colours.text_style
|
||||
};
|
||||
|
||||
let regex_style = if proc_widget_state
|
||||
.process_search_state
|
||||
.is_searching_with_regex
|
||||
{
|
||||
painter.colours.currently_selected_text_style
|
||||
} else {
|
||||
painter.colours.text_style
|
||||
};
|
||||
|
||||
// FIXME: [MOUSE] Mouse support for these in search
|
||||
// FIXME: [MOVEMENT] Movement support for these in search
|
||||
let option_text = Spans::from(vec![
|
||||
Span::styled(
|
||||
format!("Case({})", if painter.is_mac_os { "F1" } else { "Alt+C" }),
|
||||
case_style,
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
format!("Whole({})", if painter.is_mac_os { "F2" } else { "Alt+W" }),
|
||||
whole_word_style,
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
format!("Regex({})", if painter.is_mac_os { "F3" } else { "Alt+R" }),
|
||||
regex_style,
|
||||
),
|
||||
]);
|
||||
|
||||
search_text.push(Spans::from(Span::styled(
|
||||
if let Some(err) = &proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.error_message
|
||||
{
|
||||
err.as_str()
|
||||
} else {
|
||||
""
|
||||
},
|
||||
painter.colours.invalid_query_style,
|
||||
)));
|
||||
search_text.push(option_text);
|
||||
|
||||
let current_border_style = if proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_invalid_search
|
||||
{
|
||||
painter.colours.invalid_query_style
|
||||
} else if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
|
||||
let title = Span::styled(
|
||||
if draw_border {
|
||||
const TITLE_BASE: &str = " Esc to close ";
|
||||
let repeat_num =
|
||||
usize::from(draw_loc.width).saturating_sub(TITLE_BASE.chars().count() + 2);
|
||||
format!("{} Esc to close ", "─".repeat(repeat_num))
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
current_border_style,
|
||||
);
|
||||
|
||||
let process_search_block = if draw_border {
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(current_border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(current_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(search_text)
|
||||
.block(process_search_block)
|
||||
.style(painter.colours.text_style)
|
||||
.alignment(Alignment::Left),
|
||||
margined_draw_loc,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
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.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_process_sort<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: u64,
|
||||
) {
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
|
||||
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&(widget_id - 2)) {
|
||||
let current_scroll_position = proc_widget_state.columns.current_scroll_position;
|
||||
let sort_string = proc_widget_state
|
||||
.columns
|
||||
.ordered_columns
|
||||
.iter()
|
||||
.filter(|column_type| {
|
||||
proc_widget_state
|
||||
.columns
|
||||
.column_mapping
|
||||
.get(column_type)
|
||||
.unwrap()
|
||||
.enabled
|
||||
})
|
||||
.map(|column_type| column_type.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
let position = get_start_position(
|
||||
usize::from(
|
||||
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
|
||||
),
|
||||
&proc_widget_state.columns.scroll_direction,
|
||||
&mut proc_widget_state.columns.previous_scroll_position,
|
||||
current_scroll_position,
|
||||
app_state.is_force_redraw,
|
||||
);
|
||||
|
||||
// Sanity check
|
||||
let start_position = if position >= sort_string.len() {
|
||||
sort_string.len().saturating_sub(1)
|
||||
} else {
|
||||
position
|
||||
};
|
||||
|
||||
let sliced_vec = &sort_string[start_position..];
|
||||
|
||||
let sort_options = sliced_vec
|
||||
.iter()
|
||||
.map(|column| Row::new(vec![column.as_str()]));
|
||||
|
||||
let column_state = &mut proc_widget_state.columns.column_state;
|
||||
column_state.select(Some(
|
||||
proc_widget_state
|
||||
.columns
|
||||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
let current_border_style = if proc_widget_state
|
||||
.process_search_state
|
||||
.search_state
|
||||
.is_invalid_search
|
||||
{
|
||||
painter.colours.invalid_query_style
|
||||
} else if is_on_widget {
|
||||
painter.colours.highlighted_border_style
|
||||
} else {
|
||||
painter.colours.border_style
|
||||
};
|
||||
|
||||
let process_sort_block = if draw_border {
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(current_border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(current_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let highlight_style = if is_on_widget {
|
||||
painter.colours.currently_selected_text_style
|
||||
} else {
|
||||
painter.colours.text_style
|
||||
};
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
f.render_stateful_widget(
|
||||
Table::new(sort_options)
|
||||
.header(
|
||||
Row::new(vec!["Sort By"])
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap),
|
||||
)
|
||||
.block(process_sort_block)
|
||||
.highlight_style(highlight_style)
|
||||
.style(painter.colours.text_style)
|
||||
.widths(&[Constraint::Percentage(100)]),
|
||||
margined_draw_loc,
|
||||
column_state,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
widget.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::{Spans, Text},
|
||||
widgets::{Block, Borders, Row, Table},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app,
|
||||
canvas::{
|
||||
drawing_utils::{get_column_widths, get_start_position},
|
||||
Painter,
|
||||
},
|
||||
constants::*,
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
const TEMP_HEADERS: [&str; 2] = ["Sensor", "Temp"];
|
||||
|
||||
static TEMP_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
|
||||
TEMP_HEADERS
|
||||
.iter()
|
||||
.map(|entry| entry.len() as u16)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
pub fn draw_temp_table<B: Backend>(
|
||||
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
|
||||
draw_border: bool, widget_id: u64,
|
||||
) {
|
||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
||||
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
app_state.app_config_fields.table_gap
|
||||
};
|
||||
let start_position = get_start_position(
|
||||
usize::from(
|
||||
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
|
||||
),
|
||||
&temp_widget_state.scroll_state.scroll_direction,
|
||||
&mut temp_widget_state.scroll_state.previous_scroll_position,
|
||||
temp_widget_state.scroll_state.current_scroll_position,
|
||||
app_state.is_force_redraw,
|
||||
);
|
||||
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||
let temp_table_state = &mut temp_widget_state.scroll_state.table_state;
|
||||
temp_table_state.select(Some(
|
||||
temp_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
|
||||
|
||||
// Calculate widths
|
||||
let hard_widths = [None, None];
|
||||
if recalculate_column_widths {
|
||||
temp_widget_state.table_width_state.desired_column_widths = {
|
||||
let mut column_widths = TEMP_HEADERS_LENS.clone();
|
||||
for row in sliced_vec {
|
||||
for (col, entry) in row.iter().enumerate() {
|
||||
if entry.len() as u16 > column_widths[col] {
|
||||
column_widths[col] = entry.len() as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column_widths
|
||||
};
|
||||
temp_widget_state.table_width_state.calculated_column_widths = get_column_widths(
|
||||
draw_loc.width,
|
||||
&hard_widths,
|
||||
&(TEMP_HEADERS_LENS
|
||||
.iter()
|
||||
.map(|width| Some(*width))
|
||||
.collect::<Vec<_>>()),
|
||||
&[Some(0.80), Some(-1.0)],
|
||||
&temp_widget_state
|
||||
.table_width_state
|
||||
.desired_column_widths
|
||||
.iter()
|
||||
.map(|width| Some(*width))
|
||||
.collect::<Vec<_>>(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let dcw = &temp_widget_state.table_width_state.desired_column_widths;
|
||||
let ccw = &temp_widget_state.table_width_state.calculated_column_widths;
|
||||
let temperature_rows = sliced_vec.iter().map(|temp_row| {
|
||||
let truncated_data =
|
||||
temp_row
|
||||
.iter()
|
||||
.zip(&hard_widths)
|
||||
.enumerate()
|
||||
.map(|(itx, (entry, width))| {
|
||||
if width.is_none() {
|
||||
if let (Some(desired_col_width), Some(calculated_col_width)) =
|
||||
(dcw.get(itx), ccw.get(itx))
|
||||
{
|
||||
if *desired_col_width > *calculated_col_width
|
||||
&& *calculated_col_width > 0
|
||||
{
|
||||
let graphemes =
|
||||
UnicodeSegmentation::graphemes(entry.as_str(), true)
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if graphemes.len() > *calculated_col_width as usize
|
||||
&& *calculated_col_width > 1
|
||||
{
|
||||
// Truncate with ellipsis
|
||||
let first_n = graphemes
|
||||
[..(*calculated_col_width as usize - 1)]
|
||||
.concat();
|
||||
Text::raw(format!("{}…", first_n))
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
} else {
|
||||
Text::raw(entry)
|
||||
}
|
||||
});
|
||||
|
||||
Row::new(truncated_data)
|
||||
});
|
||||
|
||||
let (border_style, highlight_style) = if is_on_widget {
|
||||
(
|
||||
painter.colours.highlighted_border_style,
|
||||
painter.colours.currently_selected_text_style,
|
||||
)
|
||||
} else {
|
||||
(painter.colours.border_style, painter.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
let title_string = format!(
|
||||
" Temperatures ({} of {}) ",
|
||||
temp_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
app_state.canvas_data.temp_sensor_data.len()
|
||||
);
|
||||
|
||||
if title_string.len() <= draw_loc.width as usize {
|
||||
title_string
|
||||
} else {
|
||||
" Temperatures ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Temperatures ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Temperatures ".to_string(),
|
||||
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(chosen_title_base, painter.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let temp_block = if draw_border {
|
||||
Block::default()
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
} else if is_on_widget {
|
||||
Block::default()
|
||||
.borders(*SIDE_BORDERS)
|
||||
.border_style(painter.colours.highlighted_border_style)
|
||||
} else {
|
||||
Block::default().borders(Borders::NONE)
|
||||
};
|
||||
|
||||
let margined_draw_loc = Layout::default()
|
||||
.constraints([Constraint::Percentage(100)])
|
||||
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
|
||||
.direction(Direction::Horizontal)
|
||||
.split(draw_loc)[0];
|
||||
|
||||
// Draw
|
||||
f.render_stateful_widget(
|
||||
Table::new(temperature_rows)
|
||||
.header(
|
||||
Row::new(TEMP_HEADERS.to_vec())
|
||||
.style(painter.colours.table_header_style)
|
||||
.bottom_margin(table_gap),
|
||||
)
|
||||
.block(temp_block)
|
||||
.highlight_style(highlight_style)
|
||||
.style(painter.colours.text_style)
|
||||
.widths(
|
||||
&(temp_widget_state
|
||||
.table_width_state
|
||||
.calculated_column_widths
|
||||
.iter()
|
||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||
.collect::<Vec<_>>()),
|
||||
),
|
||||
margined_draw_loc,
|
||||
temp_table_state,
|
||||
);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// Update draw loc in widget map
|
||||
// Note there is no difference between this and using draw_loc, but I'm too lazy to fix it.
|
||||
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
|
||||
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
|
||||
widget.bottom_right_corner = Some((
|
||||
margined_draw_loc.x + margined_draw_loc.width,
|
||||
margined_draw_loc.y + margined_draw_loc.height,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
use std::cmp::{max, min};
|
||||
|
||||
/// Return a (hard)-width vector for column widths.
|
||||
///
|
||||
/// * `total_width` is the, well, total width available. **NOTE:** This function automatically
|
||||
/// takes away 2 from the width as part of the left/right
|
||||
/// bounds.
|
||||
/// * `hard_widths` is inflexible column widths. Use a `None` to represent a soft width.
|
||||
/// * `soft_widths_min` is the lower limit for a soft width. Use `None` if a hard width goes there.
|
||||
/// * `soft_widths_max` is the upper limit for a soft width, in percentage of the total width. Use
|
||||
/// `None` if a hard width goes there.
|
||||
/// * `soft_widths_desired` is the desired soft width. Use `None` if a hard width goes there.
|
||||
/// * `left_to_right` is a boolean whether to go from left to right if true, or right to left if
|
||||
/// false.
|
||||
///
|
||||
/// **NOTE:** This function ASSUMES THAT ALL PASSED SLICES ARE OF THE SAME SIZE.
|
||||
///
|
||||
/// **NOTE:** The returned vector may not be the same size as the slices, this is because including
|
||||
/// 0-constraints breaks tui-rs.
|
||||
pub fn get_column_widths(
|
||||
total_width: u16, hard_widths: &[Option<u16>], soft_widths_min: &[Option<u16>],
|
||||
soft_widths_max: &[Option<f64>], soft_widths_desired: &[Option<u16>], left_to_right: bool,
|
||||
) -> Vec<u16> {
|
||||
debug_assert!(
|
||||
hard_widths.len() == soft_widths_min.len(),
|
||||
"hard width length != soft width min length!"
|
||||
);
|
||||
debug_assert!(
|
||||
soft_widths_min.len() == soft_widths_max.len(),
|
||||
"soft width min length != soft width max length!"
|
||||
);
|
||||
debug_assert!(
|
||||
soft_widths_max.len() == soft_widths_desired.len(),
|
||||
"soft width max length != soft width desired length!"
|
||||
);
|
||||
|
||||
let initial_width = total_width - 2;
|
||||
let mut total_width_left = initial_width;
|
||||
let mut column_widths: Vec<u16> = vec![0; hard_widths.len()];
|
||||
let range: Vec<usize> = if left_to_right {
|
||||
(0..hard_widths.len()).collect()
|
||||
} else {
|
||||
(0..hard_widths.len()).rev().collect()
|
||||
};
|
||||
|
||||
for itx in &range {
|
||||
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
|
||||
// Hard width...
|
||||
let space_taken = min(*hard_width, total_width_left);
|
||||
|
||||
// TODO [COLUMN MOVEMENT]: Remove this
|
||||
if *hard_width > space_taken {
|
||||
break;
|
||||
}
|
||||
|
||||
column_widths[*itx] = space_taken;
|
||||
total_width_left -= space_taken;
|
||||
total_width_left = total_width_left.saturating_sub(1);
|
||||
} else if let (
|
||||
Some(Some(soft_width_max)),
|
||||
Some(Some(soft_width_min)),
|
||||
Some(Some(soft_width_desired)),
|
||||
) = (
|
||||
soft_widths_max.get(*itx),
|
||||
soft_widths_min.get(*itx),
|
||||
soft_widths_desired.get(*itx),
|
||||
) {
|
||||
// Soft width...
|
||||
let soft_limit = max(
|
||||
if soft_width_max.is_sign_negative() {
|
||||
*soft_width_desired
|
||||
} else {
|
||||
(*soft_width_max * initial_width as f64).ceil() as u16
|
||||
},
|
||||
*soft_width_min,
|
||||
);
|
||||
let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left);
|
||||
|
||||
// TODO [COLUMN MOVEMENT]: Remove this
|
||||
if *soft_width_min > space_taken {
|
||||
break;
|
||||
}
|
||||
|
||||
column_widths[*itx] = space_taken;
|
||||
total_width_left -= space_taken;
|
||||
total_width_left = total_width_left.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Redistribute remaining.
|
||||
while total_width_left > 0 {
|
||||
for itx in &range {
|
||||
if column_widths[*itx] > 0 {
|
||||
column_widths[*itx] += 1;
|
||||
total_width_left -= 1;
|
||||
if total_width_left == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut filtered_column_widths: Vec<u16> = vec![];
|
||||
let mut still_seeing_zeros = true;
|
||||
column_widths.iter().rev().for_each(|width| {
|
||||
if still_seeing_zeros {
|
||||
if *width != 0 {
|
||||
still_seeing_zeros = false;
|
||||
filtered_column_widths.push(*width);
|
||||
}
|
||||
} else {
|
||||
filtered_column_widths.push(*width);
|
||||
}
|
||||
});
|
||||
filtered_column_widths.reverse();
|
||||
filtered_column_widths
|
||||
}
|
||||
|
||||
// pub fn get_search_start_position(
|
||||
// num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
|
||||
// current_cursor_position: usize, is_force_redraw: bool,
|
||||
// ) -> usize {
|
||||
// if is_force_redraw {
|
||||
// *cursor_bar = 0;
|
||||
// }
|
||||
|
||||
// match cursor_direction {
|
||||
// app::CursorDirection::Right => {
|
||||
// if current_cursor_position < *cursor_bar + num_columns {
|
||||
// // If, using previous_scrolled_position, we can see the element
|
||||
// // (so within that and + num_rows) just reuse the current previously scrolled position
|
||||
// *cursor_bar
|
||||
// } else if current_cursor_position >= num_columns {
|
||||
// // Else if the current position past the last element visible in the list, omit
|
||||
// // until we can see that element
|
||||
// *cursor_bar = current_cursor_position - num_columns;
|
||||
// *cursor_bar
|
||||
// } else {
|
||||
// // Else, if it is not past the last element visible, do not omit anything
|
||||
// 0
|
||||
// }
|
||||
// }
|
||||
// app::CursorDirection::Left => {
|
||||
// if current_cursor_position <= *cursor_bar {
|
||||
// // If it's past the first element, then show from that element downwards
|
||||
// *cursor_bar = current_cursor_position;
|
||||
// } else if current_cursor_position >= *cursor_bar + num_columns {
|
||||
// *cursor_bar = current_cursor_position - num_columns;
|
||||
// }
|
||||
// // Else, don't change what our start position is from whatever it is set to!
|
||||
// *cursor_bar
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn get_start_position(
|
||||
// num_rows: usize, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut usize,
|
||||
// currently_selected_position: usize, is_force_redraw: bool,
|
||||
// ) -> usize {
|
||||
// if is_force_redraw {
|
||||
// *scroll_position_bar = 0;
|
||||
// }
|
||||
|
||||
// match scroll_direction {
|
||||
// app::ScrollDirection::Down => {
|
||||
// if currently_selected_position < *scroll_position_bar + num_rows {
|
||||
// // If, using previous_scrolled_position, we can see the element
|
||||
// // (so within that and + num_rows) just reuse the current previously scrolled position
|
||||
// *scroll_position_bar
|
||||
// } else if currently_selected_position >= num_rows {
|
||||
// // Else if the current position past the last element visible in the list, omit
|
||||
// // until we can see that element
|
||||
// *scroll_position_bar = currently_selected_position - num_rows;
|
||||
// *scroll_position_bar
|
||||
// } else {
|
||||
// // Else, if it is not past the last element visible, do not omit anything
|
||||
// 0
|
||||
// }
|
||||
// }
|
||||
// app::ScrollDirection::Up => {
|
||||
// if currently_selected_position <= *scroll_position_bar {
|
||||
// // If it's past the first element, then show from that element downwards
|
||||
// *scroll_position_bar = currently_selected_position;
|
||||
// } else if currently_selected_position >= *scroll_position_bar + num_rows {
|
||||
// *scroll_position_bar = currently_selected_position - num_rows;
|
||||
// }
|
||||
// // Else, don't change what our start position is from whatever it is set to!
|
||||
// *scroll_position_bar
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Calculate how many bars are to be
|
||||
/// drawn within basic mode's components.
|
||||
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
|
||||
std::cmp::min(
|
||||
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
|
||||
num_bars_available,
|
||||
)
|
||||
}
|
||||
|
||||
/// Interpolates between two points. Mainly used to help fill in tui-rs blanks in certain situations.
|
||||
/// It is expected point_one is "further left" compared to point_two.
|
||||
/// A point is two floats, in (x, y) form. x is time, y is value.
|
||||
pub fn interpolate_points(point_one: &(f64, f64), point_two: &(f64, f64), time: f64) -> f64 {
|
||||
let delta_x = point_two.0 - point_one.0;
|
||||
let delta_y = point_two.1 - point_one.1;
|
||||
let slope = delta_y / delta_x;
|
||||
|
||||
(point_one.1 + (time - point_one.0) * slope).max(0.0)
|
||||
}
|
@ -739,7 +739,7 @@ const BRANCH_VERTICAL: char = '│';
|
||||
const BRANCH_SPLIT: char = '├';
|
||||
const BRANCH_HORIZONTAL: char = '─';
|
||||
|
||||
pub fn tree_process_data(
|
||||
fn tree_process_data(
|
||||
filtered_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||
sorting_type: &ProcessSorting, is_sort_descending: bool,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
@ -1207,7 +1207,7 @@ pub fn tree_process_data(
|
||||
}
|
||||
|
||||
// FIXME: [OPT] This is an easy target for optimization, too many to_strings!
|
||||
pub fn stringify_process_data(
|
||||
fn stringify_process_data(
|
||||
proc_widget_state: &ProcWidgetState, finalized_process_data: &[ConvertedProcessData],
|
||||
) -> Vec<(Vec<(String, Option<String>)>, bool)> {
|
||||
let is_proc_widget_grouped = proc_widget_state.is_grouped;
|
||||
@ -1282,7 +1282,7 @@ pub fn stringify_process_data(
|
||||
/// Takes a set of converted process data and groups it together.
|
||||
///
|
||||
/// To be honest, I really don't like how this is done, even though I've rewritten this like 3 times.
|
||||
pub fn group_process_data(
|
||||
fn group_process_data(
|
||||
single_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||
) -> Vec<ConvertedProcessData> {
|
||||
#[derive(Clone, Default, Debug)]
|
||||
|
313
src/lib.rs
313
src/lib.rs
@ -21,7 +21,7 @@ use std::{
|
||||
|
||||
use crossterm::{
|
||||
event::{
|
||||
read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent,
|
||||
read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
|
||||
MouseEventKind,
|
||||
},
|
||||
execute,
|
||||
@ -30,13 +30,12 @@ use crossterm::{
|
||||
};
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
data_harvester::{self},
|
||||
event::EventResult,
|
||||
layout_manager::WidgetDirection,
|
||||
AppState, UsedWidgets,
|
||||
};
|
||||
use constants::*;
|
||||
use data_conversion::*;
|
||||
use options::*;
|
||||
use utils::error;
|
||||
|
||||
@ -76,24 +75,6 @@ pub enum ThreadControlEvent {
|
||||
UpdateUpdateTime(u64),
|
||||
}
|
||||
|
||||
pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> EventResult {
|
||||
match event.kind {
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
app.on_left_mouse_up(event.column, event.row);
|
||||
EventResult::Redraw
|
||||
}
|
||||
MouseEventKind::ScrollUp => {
|
||||
app.handle_scroll_up();
|
||||
EventResult::Redraw
|
||||
}
|
||||
MouseEventKind::ScrollDown => {
|
||||
app.handle_scroll_down();
|
||||
EventResult::Redraw
|
||||
}
|
||||
_ => EventResult::NoRedraw,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_key_event(
|
||||
event: KeyEvent, app: &mut AppState, reset_sender: &std::sync::mpsc::Sender<ThreadControlEvent>,
|
||||
) -> EventResult {
|
||||
@ -309,296 +290,6 @@ pub fn panic_hook(panic_info: &PanicInfo<'_>) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn force_redraw(app: &mut AppState) {
|
||||
// Currently we use an Option... because we might want to future-proof this
|
||||
// if we eventually get widget-specific redrawing!
|
||||
if app.proc_state.force_update_all {
|
||||
update_all_process_lists(app);
|
||||
app.proc_state.force_update_all = false;
|
||||
} else if let Some(widget_id) = app.proc_state.force_update {
|
||||
update_final_process_list(app, widget_id);
|
||||
app.proc_state.force_update = None;
|
||||
}
|
||||
|
||||
if app.cpu_state.force_update.is_some() {
|
||||
convert_cpu_data_points(
|
||||
&app.data_collection,
|
||||
&mut app.canvas_data.cpu_data,
|
||||
app.is_frozen,
|
||||
);
|
||||
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
|
||||
app.cpu_state.force_update = None;
|
||||
}
|
||||
|
||||
// FIXME: [OPT] Prefer reassignment over new vectors?
|
||||
if app.mem_state.force_update.is_some() {
|
||||
app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection, app.is_frozen);
|
||||
app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection, app.is_frozen);
|
||||
app.mem_state.force_update = None;
|
||||
}
|
||||
|
||||
if app.net_state.force_update.is_some() {
|
||||
let (rx, tx) = get_rx_tx_data_points(
|
||||
&app.data_collection,
|
||||
app.is_frozen,
|
||||
&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 = rx;
|
||||
app.canvas_data.network_data_tx = tx;
|
||||
app.net_state.force_update = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
pub fn update_all_process_lists(app: &mut AppState) {
|
||||
// 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 !app.is_frozen {
|
||||
let widget_ids = app
|
||||
.proc_state
|
||||
.widget_states
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
widget_ids.into_iter().for_each(|widget_id| {
|
||||
update_final_process_list(app, widget_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_final_process_list(_app: &mut AppState, _widget_id: u64) {
|
||||
// TODO: [STATE] FINISH THIS
|
||||
// let process_states = app
|
||||
// .proc_state
|
||||
// .widget_states
|
||||
// .get(&widget_id)
|
||||
// .map(|process_state| {
|
||||
// (
|
||||
// process_state
|
||||
// .process_search_state
|
||||
// .search_state
|
||||
// .is_invalid_or_blank_search(),
|
||||
// process_state.is_using_command,
|
||||
// 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 !app.is_frozen {
|
||||
// convert_process_data(
|
||||
// &app.data_collection,
|
||||
// &mut app.canvas_data.single_process_data,
|
||||
// #[cfg(target_family = "unix")]
|
||||
// &mut app.user_table,
|
||||
// );
|
||||
// }
|
||||
// let process_filter = app.get_process_filter(widget_id);
|
||||
// let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
||||
// app.canvas_data
|
||||
// .single_process_data
|
||||
// .iter()
|
||||
// .map(|(_pid, process)| {
|
||||
// let mut process_clone = process.clone();
|
||||
// if !is_invalid_or_blank {
|
||||
// if let Some(process_filter) = process_filter {
|
||||
// process_clone.is_disabled_entry =
|
||||
// !process_filter.check(&process_clone, is_using_command);
|
||||
// }
|
||||
// }
|
||||
// process_clone
|
||||
// })
|
||||
// .collect::<Vec<_>>()
|
||||
// } else {
|
||||
// app.canvas_data
|
||||
// .single_process_data
|
||||
// .iter()
|
||||
// .filter_map(|(_pid, process)| {
|
||||
// if !is_invalid_or_blank {
|
||||
// if let Some(process_filter) = process_filter {
|
||||
// if process_filter.check(process, is_using_command) {
|
||||
// Some(process)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// } else {
|
||||
// Some(process)
|
||||
// }
|
||||
// } else {
|
||||
// Some(process)
|
||||
// }
|
||||
// })
|
||||
// .cloned()
|
||||
// .collect::<Vec<_>>()
|
||||
// };
|
||||
|
||||
// if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
|
||||
// let mut finalized_process_data = if is_tree {
|
||||
// tree_process_data(
|
||||
// &filtered_process_data,
|
||||
// is_using_command,
|
||||
// &proc_widget_state.process_sorting_type,
|
||||
// proc_widget_state.is_process_sort_descending,
|
||||
// )
|
||||
// } else if is_grouped {
|
||||
// group_process_data(&filtered_process_data, is_using_command)
|
||||
// } else {
|
||||
// filtered_process_data
|
||||
// };
|
||||
|
||||
// // Note tree mode is sorted well before this, as it's special.
|
||||
// if !is_tree {
|
||||
// sort_process_data(&mut finalized_process_data, proc_widget_state);
|
||||
// }
|
||||
|
||||
// if proc_widget_state.scroll_state.current_scroll_position
|
||||
// >= finalized_process_data.len()
|
||||
// {
|
||||
// proc_widget_state.scroll_state.current_scroll_position =
|
||||
// finalized_process_data.len().saturating_sub(1);
|
||||
// proc_widget_state.scroll_state.previous_scroll_position = 0;
|
||||
// proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
||||
// }
|
||||
|
||||
// app.canvas_data.stringified_process_data_map.insert(
|
||||
// widget_id,
|
||||
// stringify_process_data(proc_widget_state, &finalized_process_data),
|
||||
// );
|
||||
// app.canvas_data
|
||||
// .finalized_process_data_map
|
||||
// .insert(widget_id, finalized_process_data);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn _sort_process_data(
|
||||
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
|
||||
) {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
|
||||
|
||||
match &proc_widget_state.process_sorting_type {
|
||||
ProcessSorting::CpuPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.cpu_percent_usage,
|
||||
b.cpu_percent_usage,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::Mem => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.mem_usage_bytes,
|
||||
b.mem_usage_bytes,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::MemPercent => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.mem_percent_usage,
|
||||
b.mem_percent_usage,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::ProcessName => {
|
||||
// Don't repeat if false... it sorts by name by default anyways.
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
ProcessSorting::Command => {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.command.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
ProcessSorting::Pid => {
|
||||
if !proc_widget_state.is_grouped {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.pid,
|
||||
b.pid,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
ProcessSorting::ReadPerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.rps_f64,
|
||||
b.rps_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::WritePerSecond => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.wps_f64,
|
||||
b.wps_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalRead => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.tr_f64,
|
||||
b.tr_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::TotalWrite => {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.tw_f64,
|
||||
b.tw_f64,
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
ProcessSorting::State => {
|
||||
to_sort_vec.sort_by_cached_key(|c| c.process_state.to_lowercase());
|
||||
if proc_widget_state.is_process_sort_descending {
|
||||
to_sort_vec.reverse();
|
||||
}
|
||||
}
|
||||
ProcessSorting::User => to_sort_vec.sort_by(|a, b| match (&a.user, &b.user) {
|
||||
(Some(user_a), Some(user_b)) => utils::gen_util::get_ordering(
|
||||
user_a.to_lowercase(),
|
||||
user_b.to_lowercase(),
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Less,
|
||||
}),
|
||||
ProcessSorting::Count => {
|
||||
if proc_widget_state.is_grouped {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
utils::gen_util::get_ordering(
|
||||
a.group_pids.len(),
|
||||
b.group_pids.len(),
|
||||
proc_widget_state.is_process_sort_descending,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_input_thread(
|
||||
sender: std::sync::mpsc::Sender<BottomEvent>, termination_ctrl_lock: Arc<Mutex<bool>>,
|
||||
) -> std::thread::JoinHandle<()> {
|
||||
|
153
src/options.rs
153
src/options.rs
@ -1,6 +1,6 @@
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
app::{layout_manager::*, *},
|
||||
@ -105,18 +105,6 @@ pub struct WidgetIdEnabled {
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl WidgetIdEnabled {
|
||||
pub fn create_from_hashmap(hashmap: &HashMap<u64, bool>) -> Vec<WidgetIdEnabled> {
|
||||
hashmap
|
||||
.iter()
|
||||
.map(|(id, enabled)| WidgetIdEnabled {
|
||||
id: *id,
|
||||
enabled: *enabled,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct ConfigColours {
|
||||
pub table_header_color: Option<String>,
|
||||
@ -275,27 +263,6 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
|
||||
net_filter,
|
||||
};
|
||||
|
||||
// Ok(AppState::builder()
|
||||
// .app_config_fields(app_config_fields)
|
||||
// .cpu_state(CpuState::init(cpu_state_map))
|
||||
// .mem_state(MemState::init(mem_state_map))
|
||||
// .net_state(NetState::init(net_state_map))
|
||||
// .proc_state(ProcState::init(proc_state_map))
|
||||
// .disk_state(DiskState::init(disk_state_map))
|
||||
// .temp_state(TempState::init(temp_state_map))
|
||||
// .battery_state(BatteryState::init(battery_state_map))
|
||||
// .basic_table_widget_state(basic_table_widget_state)
|
||||
// .current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // TODO: [UNWRAP] - many of the unwraps are fine (like this one) but do a once-over and/or switch to expect?
|
||||
// .widget_map(widget_map)
|
||||
// .used_widgets(used_widgets)
|
||||
// .filters(DataFilters {
|
||||
// disk_filter,
|
||||
// mount_filter,
|
||||
// temp_filter,
|
||||
// net_filter,
|
||||
// })
|
||||
// .build())
|
||||
|
||||
Ok(AppState::new(
|
||||
app_config_fields,
|
||||
data_filter,
|
||||
@ -303,71 +270,6 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
|
||||
))
|
||||
}
|
||||
|
||||
// pub fn get_widget_layout(
|
||||
// matches: &clap::ArgMatches<'static>, config: &Config,
|
||||
// ) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
|
||||
// let left_legend = get_use_left_legend(matches, config);
|
||||
// let (default_widget_type, mut default_widget_count) =
|
||||
// get_default_widget_and_count(matches, config)?;
|
||||
// let mut default_widget_id = 1;
|
||||
|
||||
// let bottom_layout = if get_use_basic_mode(matches, config) {
|
||||
// default_widget_id = DEFAULT_WIDGET_ID;
|
||||
|
||||
// BottomLayout::init_basic_default(get_use_battery(matches, config))
|
||||
// } else {
|
||||
// let ref_row: Vec<Row>; // Required to handle reference
|
||||
// let rows = match &config.row {
|
||||
// Some(r) => r,
|
||||
// None => {
|
||||
// // This cannot (like it really shouldn't) fail!
|
||||
// ref_row = toml::from_str::<Config>(if get_use_battery(matches, config) {
|
||||
// DEFAULT_BATTERY_LAYOUT
|
||||
// } else {
|
||||
// DEFAULT_LAYOUT
|
||||
// })?
|
||||
// .row
|
||||
// .unwrap();
|
||||
// &ref_row
|
||||
// }
|
||||
// };
|
||||
|
||||
// let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
// let mut total_height_ratio = 0;
|
||||
|
||||
// let mut ret_bottom_layout = BottomLayout {
|
||||
// rows: rows
|
||||
// .iter()
|
||||
// .map(|row| {
|
||||
// row.convert_row_to_bottom_row(
|
||||
// &mut iter_id,
|
||||
// &mut total_height_ratio,
|
||||
// &mut default_widget_id,
|
||||
// &default_widget_type,
|
||||
// &mut default_widget_count,
|
||||
// left_legend,
|
||||
// )
|
||||
// })
|
||||
// .collect::<error::Result<Vec<_>>>()?,
|
||||
// total_row_height_ratio: total_height_ratio,
|
||||
// };
|
||||
|
||||
// // Confirm that we have at least ONE widget left - if not, error out!
|
||||
// if iter_id > 0 {
|
||||
// ret_bottom_layout.get_movement_mappings();
|
||||
// // debug!("Bottom layout: {:#?}", ret_bottom_layout);
|
||||
|
||||
// ret_bottom_layout
|
||||
// } else {
|
||||
// return Err(error::BottomError::ConfigError(
|
||||
// "please have at least one widget under the '[[row]]' section.".to_string(),
|
||||
// ));
|
||||
// }
|
||||
// };
|
||||
|
||||
// Ok((bottom_layout, default_widget_id, default_widget_type))
|
||||
// }
|
||||
|
||||
fn get_update_rate_in_milliseconds(
|
||||
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||
) -> error::Result<u64> {
|
||||
@ -605,59 +507,6 @@ fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
|
||||
false
|
||||
}
|
||||
|
||||
fn get_default_widget_and_count(
|
||||
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||
) -> error::Result<(Option<BottomWidgetType>, u64)> {
|
||||
let widget_type = if let Some(widget_type) = matches.value_of("default_widget_type") {
|
||||
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
|
||||
if let BottomWidgetType::Empty = parsed_widget {
|
||||
None
|
||||
} else {
|
||||
Some(parsed_widget)
|
||||
}
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(widget_type) = &flags.default_widget_type {
|
||||
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
|
||||
if let BottomWidgetType::Empty = parsed_widget {
|
||||
None
|
||||
} else {
|
||||
Some(parsed_widget)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let widget_count = if let Some(widget_count) = matches.value_of("default_widget_count") {
|
||||
Some(widget_count.parse::<u128>()?)
|
||||
} else if let Some(flags) = &config.flags {
|
||||
flags
|
||||
.default_widget_count
|
||||
.map(|widget_count| widget_count as u128)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match (widget_type, widget_count) {
|
||||
(Some(widget_type), Some(widget_count)) => {
|
||||
if widget_count > std::u64::MAX as u128 {
|
||||
Err(BottomError::ConfigError(
|
||||
"set your widget count to be at most unsigned INT_MAX.".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok((Some(widget_type), widget_count as u64))
|
||||
}
|
||||
}
|
||||
(Some(widget_type), None) => Ok((Some(widget_type), 1)),
|
||||
(None, Some(_widget_count)) => Err(BottomError::ConfigError(
|
||||
"cannot set 'default_widget_count' by itself, it must be used with 'default_widget_type'.".to_string(),
|
||||
)),
|
||||
(None, None) => Ok((None, 1))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||
if matches.is_present("disable_click") {
|
||||
return true;
|
||||
|
@ -9,328 +9,6 @@ pub struct Row {
|
||||
pub ratio: Option<u32>,
|
||||
}
|
||||
|
||||
// impl Row {
|
||||
// pub fn convert_row_to_bottom_row(
|
||||
// &self, iter_id: &mut u64, total_height_ratio: &mut u32, default_widget_id: &mut u64,
|
||||
// default_widget_type: &Option<BottomWidgetType>, default_widget_count: &mut u64,
|
||||
// left_legend: bool,
|
||||
// ) -> Result<OldBottomRow> {
|
||||
// // TODO: In the future we want to also add percentages.
|
||||
// // But for MVP, we aren't going to bother.
|
||||
// let row_ratio = self.ratio.unwrap_or(1);
|
||||
// let mut children = Vec::new();
|
||||
|
||||
// *total_height_ratio += row_ratio;
|
||||
|
||||
// let mut total_col_ratio = 0;
|
||||
// if let Some(row_children) = &self.child {
|
||||
// for row_child in row_children {
|
||||
// match row_child {
|
||||
// RowChildren::Widget(widget) => {
|
||||
// *iter_id += 1;
|
||||
// let width_ratio = widget.ratio.unwrap_or(1);
|
||||
// total_col_ratio += width_ratio;
|
||||
// let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
|
||||
|
||||
// if let Some(default_widget_type_val) = default_widget_type {
|
||||
// if *default_widget_type_val == widget_type && *default_widget_count > 0
|
||||
// {
|
||||
// *default_widget_count -= 1;
|
||||
// if *default_widget_count == 0 {
|
||||
// *default_widget_id = *iter_id;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Check default flag
|
||||
// if let Some(default_widget_flag) = widget.default {
|
||||
// if default_widget_flag {
|
||||
// *default_widget_id = *iter_id;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// children.push(match widget_type {
|
||||
// BottomWidgetType::Cpu => {
|
||||
// let cpu_id = *iter_id;
|
||||
// *iter_id += 1;
|
||||
// OldBottomCol::builder()
|
||||
// .col_width_ratio(width_ratio)
|
||||
// .children(if left_legend {
|
||||
// vec![BottomColRow::builder()
|
||||
// .total_widget_ratio(20)
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(3)
|
||||
// .widget_type(BottomWidgetType::CpuLegend)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Right,
|
||||
// 1,
|
||||
// )))
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(17)
|
||||
// .widget_type(BottomWidgetType::Cpu)
|
||||
// .widget_id(cpu_id)
|
||||
// .flex_grow(true)
|
||||
// .build(),
|
||||
// ])
|
||||
// .build()]
|
||||
// } else {
|
||||
// vec![BottomColRow::builder()
|
||||
// .total_widget_ratio(20)
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(17)
|
||||
// .widget_type(BottomWidgetType::Cpu)
|
||||
// .widget_id(cpu_id)
|
||||
// .flex_grow(true)
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(3)
|
||||
// .widget_type(BottomWidgetType::CpuLegend)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Left,
|
||||
// 1,
|
||||
// )))
|
||||
// .build(),
|
||||
// ])
|
||||
// .build()]
|
||||
// })
|
||||
// .build()
|
||||
// }
|
||||
// BottomWidgetType::Proc => {
|
||||
// let proc_id = *iter_id;
|
||||
// let proc_search_id = *iter_id + 1;
|
||||
// *iter_id += 2;
|
||||
// OldBottomCol::builder()
|
||||
// .total_col_row_ratio(2)
|
||||
// .col_width_ratio(width_ratio)
|
||||
// .children(vec![
|
||||
// BottomColRow::builder()
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::ProcSort)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Right,
|
||||
// 2,
|
||||
// )))
|
||||
// .width_ratio(1)
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::Proc)
|
||||
// .widget_id(proc_id)
|
||||
// .width_ratio(2)
|
||||
// .build(),
|
||||
// ])
|
||||
// .total_widget_ratio(3)
|
||||
// .flex_grow(true)
|
||||
// .build(),
|
||||
// BottomColRow::builder()
|
||||
// .children(vec![BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::ProcSearch)
|
||||
// .widget_id(proc_search_id)
|
||||
// .parent_reflector(Some((WidgetDirection::Up, 1)))
|
||||
// .build()])
|
||||
// .canvas_handle_height(true)
|
||||
// .build(),
|
||||
// ])
|
||||
// .build()
|
||||
// }
|
||||
// _ => OldBottomCol::builder()
|
||||
// .col_width_ratio(width_ratio)
|
||||
// .children(vec![BottomColRow::builder()
|
||||
// .children(vec![BottomWidget::builder()
|
||||
// .widget_type(widget_type)
|
||||
// .widget_id(*iter_id)
|
||||
// .build()])
|
||||
// .build()])
|
||||
// .build(),
|
||||
// });
|
||||
// }
|
||||
// RowChildren::Col { ratio, child } => {
|
||||
// let col_width_ratio = ratio.unwrap_or(1);
|
||||
// total_col_ratio += col_width_ratio;
|
||||
// let mut total_col_row_ratio = 0;
|
||||
// let mut contains_proc = false;
|
||||
|
||||
// let mut col_row_children: Vec<BottomColRow> = Vec::new();
|
||||
|
||||
// for widget in child {
|
||||
// let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
|
||||
// *iter_id += 1;
|
||||
// let col_row_height_ratio = widget.ratio.unwrap_or(1);
|
||||
// total_col_row_ratio += col_row_height_ratio;
|
||||
|
||||
// if let Some(default_widget_type_val) = default_widget_type {
|
||||
// if *default_widget_type_val == widget_type
|
||||
// && *default_widget_count > 0
|
||||
// {
|
||||
// *default_widget_count -= 1;
|
||||
// if *default_widget_count == 0 {
|
||||
// *default_widget_id = *iter_id;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Check default flag
|
||||
// if let Some(default_widget_flag) = widget.default {
|
||||
// if default_widget_flag {
|
||||
// *default_widget_id = *iter_id;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// match widget_type {
|
||||
// BottomWidgetType::Cpu => {
|
||||
// let cpu_id = *iter_id;
|
||||
// *iter_id += 1;
|
||||
// if left_legend {
|
||||
// col_row_children.push(
|
||||
// BottomColRow::builder()
|
||||
// .col_row_height_ratio(col_row_height_ratio)
|
||||
// .total_widget_ratio(20)
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(3)
|
||||
// .widget_type(BottomWidgetType::CpuLegend)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Right,
|
||||
// 1,
|
||||
// )))
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(17)
|
||||
// .widget_type(BottomWidgetType::Cpu)
|
||||
// .widget_id(cpu_id)
|
||||
// .flex_grow(true)
|
||||
// .build(),
|
||||
// ])
|
||||
// .build(),
|
||||
// );
|
||||
// } else {
|
||||
// col_row_children.push(
|
||||
// BottomColRow::builder()
|
||||
// .col_row_height_ratio(col_row_height_ratio)
|
||||
// .total_widget_ratio(20)
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(17)
|
||||
// .widget_type(BottomWidgetType::Cpu)
|
||||
// .widget_id(cpu_id)
|
||||
// .flex_grow(true)
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .width_ratio(3)
|
||||
// .widget_type(BottomWidgetType::CpuLegend)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Left,
|
||||
// 1,
|
||||
// )))
|
||||
// .build(),
|
||||
// ])
|
||||
// .build(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// BottomWidgetType::Proc => {
|
||||
// contains_proc = true;
|
||||
// let proc_id = *iter_id;
|
||||
// let proc_search_id = *iter_id + 1;
|
||||
// *iter_id += 2;
|
||||
// col_row_children.push(
|
||||
// BottomColRow::builder()
|
||||
// .children(vec![
|
||||
// BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::ProcSort)
|
||||
// .widget_id(*iter_id)
|
||||
// .canvas_handle_width(true)
|
||||
// .parent_reflector(Some((
|
||||
// WidgetDirection::Right,
|
||||
// 2,
|
||||
// )))
|
||||
// .width_ratio(1)
|
||||
// .build(),
|
||||
// BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::Proc)
|
||||
// .widget_id(proc_id)
|
||||
// .width_ratio(2)
|
||||
// .build(),
|
||||
// ])
|
||||
// .col_row_height_ratio(col_row_height_ratio)
|
||||
// .total_widget_ratio(3)
|
||||
// .build(),
|
||||
// );
|
||||
// col_row_children.push(
|
||||
// BottomColRow::builder()
|
||||
// .col_row_height_ratio(col_row_height_ratio)
|
||||
// .children(vec![BottomWidget::builder()
|
||||
// .widget_type(BottomWidgetType::ProcSearch)
|
||||
// .widget_id(proc_search_id)
|
||||
// .parent_reflector(Some((WidgetDirection::Up, 1)))
|
||||
// .build()])
|
||||
// .canvas_handle_height(true)
|
||||
// .build(),
|
||||
// );
|
||||
// }
|
||||
// _ => col_row_children.push(
|
||||
// BottomColRow::builder()
|
||||
// .col_row_height_ratio(col_row_height_ratio)
|
||||
// .children(vec![BottomWidget::builder()
|
||||
// .widget_type(widget_type)
|
||||
// .widget_id(*iter_id)
|
||||
// .build()])
|
||||
// .build(),
|
||||
// ),
|
||||
// }
|
||||
// }
|
||||
|
||||
// if contains_proc {
|
||||
// // Must adjust ratios to work with proc
|
||||
// total_col_row_ratio *= 2;
|
||||
// for child in &mut col_row_children {
|
||||
// // Multiply all non-proc or proc-search ratios by 2
|
||||
// if !child.children.is_empty() {
|
||||
// match child.children[0].widget_type {
|
||||
// BottomWidgetType::ProcSearch => {}
|
||||
// _ => child.col_row_height_ratio *= 2,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// children.push(
|
||||
// OldBottomCol::builder()
|
||||
// .total_col_row_ratio(total_col_row_ratio)
|
||||
// .col_width_ratio(col_width_ratio)
|
||||
// .children(col_row_children)
|
||||
// .build(),
|
||||
// );
|
||||
// }
|
||||
// RowChildren::Carousel {
|
||||
// carousel_children: _,
|
||||
// default: _,
|
||||
// } => {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ok(OldBottomRow::builder()
|
||||
// .total_col_ratio(total_col_ratio)
|
||||
// .row_height_ratio(row_ratio)
|
||||
// .children(children)
|
||||
// .build())
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Represents a child of a Row - either a Col (column) or a FinalWidget.
|
||||
///
|
||||
/// A Col can also have an optional length and children. We only allow columns
|
||||
|
Loading…
x
Reference in New Issue
Block a user