From 35ec66eaa70383cda591eeeaccc6560cfa921317 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 26 Sep 2021 00:30:16 -0400 Subject: [PATCH] refactor: cover almost all keybinds except killing processes --- docs/content/usage/widgets/process.md | 1 - src/app.rs | 80 +- src/app/event.rs | 4 +- src/app/layout_manager.rs | 895 ++------------------- src/app/widgets.rs | 24 +- src/app/widgets/base/text_input.rs | 38 +- src/app/widgets/bottom_widgets/carousel.rs | 25 +- src/app/widgets/bottom_widgets/empty.rs | 8 +- src/app/widgets/bottom_widgets/process.rs | 20 +- src/canvas.rs | 13 +- src/canvas/dialogs/dd_dialog.rs | 1 + src/constants.rs | 6 +- 12 files changed, 196 insertions(+), 919 deletions(-) diff --git a/docs/content/usage/widgets/process.md b/docs/content/usage/widgets/process.md index c5f9c89b..ee21188f 100644 --- a/docs/content/usage/widgets/process.md +++ b/docs/content/usage/widgets/process.md @@ -198,7 +198,6 @@ Note that key bindings are generally case-sensitive. | ------------------------------------- | -------------------------------------------- | | ++left++
++h++
++alt+h++ | Moves the cursor left | | ++right++
++l++
++alt+l++ | Moves the cursor right | -| ++tab++ | Toggle between searching by PID or name | | ++esc++ | Close the search widget (retains the filter) | | ++ctrl+a++ | Skip to the start of the search query | | ++ctrl+e++ | Skip to the end of the search query | diff --git a/src/app.rs b/src/app.rs index 3b95a536..f40ea123 100644 --- a/src/app.rs +++ b/src/app.rs @@ -308,26 +308,36 @@ impl AppState { /// Moves to a widget. fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult { - let layout_tree = &mut self.layout_tree; - let previous_selected = self.selected_widget; - if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { - match move_widget_selection(layout_tree, widget, self.selected_widget, direction) { - MoveWidgetResult::ForceRedraw(new_widget_id) => { - self.selected_widget = new_widget_id; - EventResult::Redraw - } - MoveWidgetResult::NodeId(new_widget_id) => { - self.selected_widget = new_widget_id; + match if self.is_expanded { + move_expanded_widget_selection( + &mut self.widget_lookup_map, + self.selected_widget, + direction, + ) + } else { + let layout_tree = &mut self.layout_tree; - if previous_selected != self.selected_widget { - EventResult::Redraw - } else { - EventResult::NoRedraw - } + move_widget_selection( + layout_tree, + &mut self.widget_lookup_map, + self.selected_widget, + direction, + ) + } { + MoveWidgetResult::ForceRedraw(new_widget_id) => { + self.selected_widget = new_widget_id; + EventResult::Redraw + } + MoveWidgetResult::NodeId(new_widget_id) => { + let previous_selected = self.selected_widget; + self.selected_widget = new_widget_id; + + if previous_selected != self.selected_widget { + EventResult::Redraw + } else { + EventResult::NoRedraw } } - } else { - EventResult::NoRedraw } } @@ -448,33 +458,29 @@ impl AppState { for (id, widget) in self.widget_lookup_map.iter_mut() { if widget.does_border_intersect_mouse(&event) { let result = widget.handle_mouse_event(event); - - let new_id; match widget.selectable_type() { SelectableType::Selectable => { - new_id = *id; + let was_id_already_selected = self.selected_widget == *id; + self.selected_widget = *id; + + if was_id_already_selected { + returned_result = self.convert_widget_event_result(result); + break; + } else { + // If the weren't equal, *force* a redraw, and correct the layout tree. + correct_layout_last_selections( + &mut self.layout_tree, + self.selected_widget, + ); + let _ = self.convert_widget_event_result(result); + returned_result = EventResult::Redraw; + break; + } } SelectableType::Unselectable => { let result = widget.handle_mouse_event(event); return self.convert_widget_event_result(result); } - SelectableType::Redirect(redirected_id) => { - new_id = redirected_id; - } - } - - let was_id_already_selected = self.selected_widget == new_id; - self.selected_widget = new_id; - - if was_id_already_selected { - returned_result = self.convert_widget_event_result(result); - break; - } else { - // If the weren't equal, *force* a redraw, and correct the layout tree. - correct_layout_last_selections(&mut self.layout_tree, self.selected_widget); - let _ = self.convert_widget_event_result(result); - returned_result = EventResult::Redraw; - break; } } } diff --git a/src/app/event.rs b/src/app/event.rs index f727050d..31763159 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -44,8 +44,8 @@ pub enum ComponentEventResult { /// How a widget should handle a widget selection request. pub enum SelectionAction { - /// This event occurs if the widget internally handled the selection action. A redraw is required. + /// This occurs if the widget internally handled the selection action. A redraw is required. Handled, - /// This event occurs if the widget did not handle the selection action; the caller must handle it. + /// This occurs if the widget did not handle the selection action; the caller must handle it. NotHandled, } diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 9f2c287b..b066a755 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1,7 +1,7 @@ use crate::{ app::{ BasicCpu, BasicMem, BasicNet, BatteryTable, Carousel, DiskTable, Empty, MemGraph, NetGraph, - OldNetGraph, ProcessManager, TempTable, + OldNetGraph, ProcessManager, SelectableType, TempTable, }, error::{BottomError, Result}, options::{ @@ -11,12 +11,11 @@ use crate::{ }; use fxhash::FxHashMap; use indextree::{Arena, NodeId}; -use std::{cmp::min, collections::BTreeMap}; +use std::cmp::min; use tui::layout::Rect; use typed_builder::*; use crate::app::widgets::Widget; -use crate::constants::DEFAULT_WIDGET_ID; use super::{ event::SelectionAction, AppConfigFields, CpuGraph, TimeGraph, TmpBottomWidget, UsedWidgets, @@ -30,759 +29,6 @@ pub struct BottomLayout { pub total_row_height_ratio: u32, } -// Represents a start and end coordinate in some dimension. -type LineSegment = (u32, u32); - -type WidgetMappings = (u32, BTreeMap); -type ColumnRowMappings = (u32, BTreeMap); -type ColumnMappings = (u32, BTreeMap); - -impl BottomLayout { - pub fn get_movement_mappings(&mut self) { - #[allow(clippy::suspicious_operation_groupings)] // Have to enable this, clippy really doesn't like me doing this with tuples... - fn is_intersecting(a: LineSegment, b: LineSegment) -> bool { - a.0 >= b.0 && a.1 <= b.1 - || a.1 >= b.1 && a.0 <= b.0 - || a.0 <= b.0 && a.1 >= b.0 - || a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1 - } - - fn get_distance(target: LineSegment, candidate: LineSegment) -> u32 { - if candidate.0 < target.0 { - candidate.1 - target.0 - } else if candidate.1 < target.1 { - candidate.1 - candidate.0 - } else { - target.1 - candidate.0 - } - } - - // Now we need to create the correct mapping for moving from a specific - // widget to another - - let mut layout_mapping: BTreeMap = BTreeMap::new(); - let mut total_height = 0; - for row in &self.rows { - let mut row_width = 0; - let mut row_mapping: BTreeMap = BTreeMap::new(); - let mut is_valid_row = false; - for col in &row.children { - let mut col_row_height = 0; - let mut col_mapping: BTreeMap = BTreeMap::new(); - let mut is_valid_col = false; - - for col_row in &col.children { - let mut widget_width = 0; - let mut col_row_mapping: BTreeMap = BTreeMap::new(); - let mut is_valid_col_row = false; - for widget in &col_row.children { - match widget.widget_type { - BottomWidgetType::Empty => {} - _ => { - is_valid_col_row = true; - col_row_mapping.insert( - ( - widget_width * 100 / col_row.total_widget_ratio, - (widget_width + widget.width_ratio) * 100 - / col_row.total_widget_ratio, - ), - widget.widget_id, - ); - } - } - widget_width += widget.width_ratio; - } - if is_valid_col_row { - col_mapping.insert( - ( - col_row_height * 100 / col.total_col_row_ratio, - (col_row_height + col_row.col_row_height_ratio) * 100 - / col.total_col_row_ratio, - ), - (col.total_col_row_ratio, col_row_mapping), - ); - is_valid_col = true; - } - - col_row_height += col_row.col_row_height_ratio; - } - if is_valid_col { - row_mapping.insert( - ( - row_width * 100 / row.total_col_ratio, - (row_width + col.col_width_ratio) * 100 / row.total_col_ratio, - ), - (row.total_col_ratio, col_mapping), - ); - is_valid_row = true; - } - - row_width += col.col_width_ratio; - } - if is_valid_row { - layout_mapping.insert( - ( - total_height * 100 / self.total_row_height_ratio, - (total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio, - ), - (self.total_row_height_ratio, row_mapping), - ); - } - total_height += row.row_height_ratio; - } - - // Now pass through a second time; this time we want to build up - // our neighbour profile. - let mut height_cursor = 0; - for row in &mut self.rows { - let mut col_cursor = 0; - let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio; - let row_height_percentage_end = - (height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio; - - for col in &mut row.children { - let mut col_row_cursor = 0; - let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio; - let col_width_percentage_end = - (col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio; - - for col_row in &mut col.children { - let mut widget_cursor = 0; - let col_row_height_percentage_start = - col_row_cursor * 100 / col.total_col_row_ratio; - let col_row_height_percentage_end = - (col_row_cursor + col_row.col_row_height_ratio) * 100 - / col.total_col_row_ratio; - let col_row_children_len = col_row.children.len(); - - for widget in &mut col_row.children { - // Bail if empty. - if let BottomWidgetType::Empty = widget.widget_type { - continue; - } - - let widget_width_percentage_start = - widget_cursor * 100 / col_row.total_widget_ratio; - let widget_width_percentage_end = - (widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio; - - if let Some(current_row) = layout_mapping - .get(&(row_height_percentage_start, row_height_percentage_end)) - { - // First check for within the same col_row for left and right - if let Some(current_col) = current_row - .1 - .get(&(col_width_percentage_start, col_width_percentage_end)) - { - if let Some(current_col_row) = current_col.1.get(&( - col_row_height_percentage_start, - col_row_height_percentage_end, - )) { - if let Some(to_left_widget) = current_col_row - .1 - .range( - ..( - widget_width_percentage_start, - widget_width_percentage_start, - ), - ) - .next_back() - { - widget.left_neighbour = Some(*to_left_widget.1); - } - - // Right - if let Some(to_right_neighbour) = current_col_row - .1 - .range( - ( - widget_width_percentage_end, - widget_width_percentage_end, - ).., - ) - .next() - { - widget.right_neighbour = Some(*to_right_neighbour.1); - } - } - } - - if widget.left_neighbour.is_none() { - if let Some(to_left_col) = current_row - .1 - .range( - ..(col_width_percentage_start, col_width_percentage_start), - ) - .next_back() - { - // Check left in same row - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - - for widget_position in &(to_left_col.1).1 { - let candidate_start = (widget_position.0).0; - let candidate_end = (widget_position.0).1; - - if is_intersecting( - ( - col_row_height_percentage_start, - col_row_height_percentage_end, - ), - (candidate_start, candidate_end), - ) { - let candidate_distance = get_distance( - ( - col_row_height_percentage_start, - col_row_height_percentage_end, - ), - (candidate_start, candidate_end), - ); - - if current_best_distance < candidate_distance { - if let Some(new_best_widget) = - (widget_position.1).1.iter().next_back() - { - current_best_distance = candidate_distance + 1; - current_best_widget_id = *(new_best_widget.1); - } - } - } - } - if current_best_distance > 0 { - widget.left_neighbour = Some(current_best_widget_id); - } - } - } - - if widget.right_neighbour.is_none() { - if let Some(to_right_col) = current_row - .1 - .range((col_width_percentage_end, col_width_percentage_end)..) - .next() - { - // Check right in same row - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - - for widget_position in &(to_right_col.1).1 { - let candidate_start = (widget_position.0).0; - let candidate_end = (widget_position.0).1; - - if is_intersecting( - ( - col_row_height_percentage_start, - col_row_height_percentage_end, - ), - (candidate_start, candidate_end), - ) { - let candidate_distance = get_distance( - ( - col_row_height_percentage_start, - col_row_height_percentage_end, - ), - (candidate_start, candidate_end), - ); - - if current_best_distance < candidate_distance { - if let Some(new_best_widget) = - (widget_position.1).1.iter().next() - { - current_best_distance = candidate_distance + 1; - current_best_widget_id = *(new_best_widget.1); - } - } - } - } - if current_best_distance > 0 { - widget.right_neighbour = Some(current_best_widget_id); - } - } - } - - // Check up/down within same row; - // else check up/down with other rows - if let Some(current_col) = current_row - .1 - .get(&(col_width_percentage_start, col_width_percentage_end)) - { - if let Some(to_up) = current_col - .1 - .range( - ..( - col_row_height_percentage_start, - col_row_height_percentage_start, - ), - ) - .next_back() - { - // Now check each widget_width and pick the best - for candidate_widget in &(to_up.1).1 { - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - if is_intersecting( - ( - widget_width_percentage_start, - widget_width_percentage_end, - ), - ((candidate_widget.0).0, (candidate_widget.0).1), - ) { - let candidate_best_distance = get_distance( - ( - widget_width_percentage_start, - widget_width_percentage_end, - ), - ((candidate_widget.0).0, (candidate_widget.0).1), - ); - - if current_best_distance < candidate_best_distance { - current_best_distance = candidate_best_distance + 1; - current_best_widget_id = *candidate_widget.1; - } - } - - if current_best_distance > 0 { - widget.up_neighbour = Some(current_best_widget_id); - } - } - } else { - for next_row_up in layout_mapping - .range( - ..( - row_height_percentage_start, - row_height_percentage_start, - ), - ) - .rev() - { - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - let (target_start_width, target_end_width) = - if col_row_children_len > 1 { - ( - col_width_percentage_start - + widget_width_percentage_start - * (col_width_percentage_end - - col_width_percentage_start) - / 100, - col_width_percentage_start - + widget_width_percentage_end - * (col_width_percentage_end - - col_width_percentage_start) - / 100, - ) - } else { - ( - col_width_percentage_start, - col_width_percentage_end, - ) - }; - - for col_position in &(next_row_up.1).1 { - if let Some(next_col_row) = - (col_position.1).1.iter().next_back() - { - let (candidate_col_start, candidate_col_end) = - ((col_position.0).0, (col_position.0).1); - let candidate_difference = - candidate_col_end - candidate_col_start; - for candidate_widget in &(next_col_row.1).1 { - let candidate_start = candidate_col_start - + (candidate_widget.0).0 - * candidate_difference - / 100; - let candidate_end = candidate_col_start - + (candidate_widget.0).1 - * candidate_difference - / 100; - - if is_intersecting( - (target_start_width, target_end_width), - (candidate_start, candidate_end), - ) { - let candidate_distance = get_distance( - (target_start_width, target_end_width), - (candidate_start, candidate_end), - ); - - if current_best_distance - < candidate_distance - { - current_best_distance = - candidate_distance + 1; - current_best_widget_id = - *(candidate_widget.1); - } - } - } - } - } - - if current_best_distance > 0 { - widget.up_neighbour = Some(current_best_widget_id); - break; - } - } - } - - if let Some(to_down) = current_col - .1 - .range( - ( - col_row_height_percentage_start + 1, - col_row_height_percentage_start + 1, - ).., - ) - .next() - { - for candidate_widget in &(to_down.1).1 { - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - if is_intersecting( - ( - widget_width_percentage_start, - widget_width_percentage_end, - ), - ((candidate_widget.0).0, (candidate_widget.0).1), - ) { - let candidate_best_distance = get_distance( - ( - widget_width_percentage_start, - widget_width_percentage_end, - ), - ((candidate_widget.0).0, (candidate_widget.0).1), - ); - - if current_best_distance < candidate_best_distance { - current_best_distance = candidate_best_distance + 1; - current_best_widget_id = *candidate_widget.1; - } - } - - if current_best_distance > 0 { - widget.down_neighbour = Some(current_best_widget_id); - } - } - } else { - for next_row_down in layout_mapping.range( - ( - row_height_percentage_start + 1, - row_height_percentage_start + 1, - ).., - ) { - let mut current_best_distance = 0; - let mut current_best_widget_id = widget.widget_id; - let (target_start_width, target_end_width) = - if col_row_children_len > 1 { - ( - col_width_percentage_start - + widget_width_percentage_start - * (col_width_percentage_end - - col_width_percentage_start) - / 100, - col_width_percentage_start - + widget_width_percentage_end - * (col_width_percentage_end - - col_width_percentage_start) - / 100, - ) - } else { - ( - col_width_percentage_start, - col_width_percentage_end, - ) - }; - - for col_position in &(next_row_down.1).1 { - if let Some(next_col_row) = - (col_position.1).1.iter().next() - { - let (candidate_col_start, candidate_col_end) = - ((col_position.0).0, (col_position.0).1); - let candidate_difference = - candidate_col_end - candidate_col_start; - for candidate_widget in &(next_col_row.1).1 { - let candidate_start = candidate_col_start - + (candidate_widget.0).0 - * candidate_difference - / 100; - let candidate_end = candidate_col_start - + (candidate_widget.0).1 - * candidate_difference - / 100; - - if is_intersecting( - (target_start_width, target_end_width), - (candidate_start, candidate_end), - ) { - let candidate_distance = get_distance( - (target_start_width, target_end_width), - (candidate_start, candidate_end), - ); - - if current_best_distance - < candidate_distance - { - current_best_distance = - candidate_distance + 1; - current_best_widget_id = - *(candidate_widget.1); - } - } - } - } - } - - if current_best_distance > 0 { - widget.down_neighbour = Some(current_best_widget_id); - break; - } - } - } - } - } - widget_cursor += widget.width_ratio; - } - col_row_cursor += col_row.col_row_height_ratio; - } - col_cursor += col.col_width_ratio; - } - height_cursor += row.row_height_ratio; - } - } - - pub fn init_basic_default(use_battery: bool) -> Self { - let table_widgets = if use_battery { - vec![ - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Disk) - .widget_id(4) - .up_neighbour(Some(100)) - .left_neighbour(Some(8)) - .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .build()]) - .build()]) - .build(), - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![ - BottomColRow::builder() - .canvas_handle_height(true) - .total_widget_ratio(3) - .children(vec![ - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::ProcSort) - .widget_id(DEFAULT_WIDGET_ID + 2) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(4)) - .right_neighbour(Some(DEFAULT_WIDGET_ID)) - .width_ratio(1) - .parent_reflector(Some((WidgetDirection::Right, 2))) - .build(), - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Proc) - .widget_id(DEFAULT_WIDGET_ID) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .right_neighbour(Some(7)) - .width_ratio(2) - .build(), - ]) - .build(), - BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::ProcSearch) - .widget_id(DEFAULT_WIDGET_ID + 1) - .up_neighbour(Some(DEFAULT_WIDGET_ID)) - .left_neighbour(Some(4)) - .right_neighbour(Some(7)) - .parent_reflector(Some((WidgetDirection::Up, 1))) - .build()]) - .build(), - ]) - .build(), - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Temp) - .widget_id(7) - .up_neighbour(Some(100)) - .left_neighbour(Some(DEFAULT_WIDGET_ID)) - .right_neighbour(Some(8)) - .build()]) - .build()]) - .build(), - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Battery) - .widget_id(8) - .up_neighbour(Some(100)) - .left_neighbour(Some(7)) - .right_neighbour(Some(4)) - .build()]) - .build()]) - .build(), - ] - } else { - vec![ - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Disk) - .widget_id(4) - .up_neighbour(Some(100)) - .left_neighbour(Some(7)) - .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .build()]) - .build()]) - .build(), - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![ - BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![ - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::ProcSort) - .widget_id(DEFAULT_WIDGET_ID + 2) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(4)) - .right_neighbour(Some(DEFAULT_WIDGET_ID)) - .parent_reflector(Some((WidgetDirection::Right, 2))) - .build(), - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Proc) - .widget_id(DEFAULT_WIDGET_ID) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .right_neighbour(Some(7)) - .build(), - ]) - .build(), - BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::ProcSearch) - .widget_id(DEFAULT_WIDGET_ID + 1) - .up_neighbour(Some(DEFAULT_WIDGET_ID)) - .left_neighbour(Some(4)) - .right_neighbour(Some(7)) - .parent_reflector(Some((WidgetDirection::Up, 1))) - .build()]) - .build(), - ]) - .build(), - OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::Temp) - .widget_id(7) - .up_neighbour(Some(100)) - .left_neighbour(Some(DEFAULT_WIDGET_ID)) - .right_neighbour(Some(4)) - .build()]) - .build()]) - .build(), - ] - }; - - BottomLayout { - total_row_height_ratio: 3, - rows: vec![ - OldBottomRow::builder() - .canvas_handle_height(true) - .children(vec![OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::BasicCpu) - .widget_id(1) - .down_neighbour(Some(2)) - .build()]) - .build()]) - .build()]) - .build(), - OldBottomRow::builder() - .canvas_handle_height(true) - .children(vec![OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![ - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::BasicMem) - .widget_id(2) - .up_neighbour(Some(1)) - .down_neighbour(Some(100)) - .right_neighbour(Some(3)) - .build(), - BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::BasicNet) - .widget_id(3) - .up_neighbour(Some(1)) - .down_neighbour(Some(100)) - .left_neighbour(Some(2)) - .build(), - ]) - .build()]) - .build()]) - .build(), - OldBottomRow::builder() - .canvas_handle_height(true) - .children(vec![OldBottomCol::builder() - .canvas_handle_width(true) - .children(vec![BottomColRow::builder() - .canvas_handle_height(true) - .children(vec![BottomWidget::builder() - .canvas_handle_width(true) - .widget_type(BottomWidgetType::BasicTables) - .widget_id(100) - .up_neighbour(Some(2)) - .build()]) - .build()]) - .build()]) - .build(), - OldBottomRow::builder() - .canvas_handle_height(true) - .children(table_widgets) - .build(), - ], - } - } -} - /// Represents a single row in the layout. #[derive(Clone, Debug, TypedBuilder)] pub struct OldBottomRow { @@ -919,32 +165,6 @@ pub enum BottomWidgetType { Carousel, } -impl BottomWidgetType { - pub fn is_widget_table(&self) -> bool { - use BottomWidgetType::*; - matches!(self, Disk | Proc | ProcSort | Temp | CpuLegend) - } - - pub fn is_widget_graph(&self) -> bool { - use BottomWidgetType::*; - matches!(self, Cpu | Net | Mem) - } - - pub fn get_pretty_name(&self) -> &str { - use BottomWidgetType::*; - match self { - Cpu => "CPU", - Mem => "Memory", - Net => "Network", - Proc => "Processes", - Temp => "Temperature", - Disk => "Disks", - Battery => "Battery", - _ => "", - } - } -} - impl Default for BottomWidgetType { fn default() -> Self { BottomWidgetType::Empty @@ -1259,39 +479,16 @@ pub fn create_layout_tree( arena.new_node(LayoutNode::Widget(WidgetLayout::default())); row_id.append(carousel_widget_id, &mut arena); - // Add the first widget as a default widget if needed. - { - let widget_id = - arena.new_node(LayoutNode::Widget(WidgetLayout::default())); - carousel_widget_id.append(widget_id, &mut arena); + if let Some(true) = default { + first_selected = Some(carousel_widget_id); + } - let widget_type = - carousel_children[0].parse::()?; - used_widgets.add(&widget_type); - - if let Some(true) = default { - first_selected = Some(widget_id); - } - - if first_widget_seen.is_none() { - first_widget_seen = Some(widget_id); - } - - add_widget_to_map( - &mut widget_lookup_map, - widget_type, - widget_id, - &process_defaults, - app_config_fields, - LayoutRule::default(), - LayoutRule::default(), - )?; - - child_ids.push(widget_id); + if first_widget_seen.is_none() { + first_widget_seen = Some(carousel_widget_id); } // Handle the rest of the children. - for child in carousel_children[1..].iter() { + for child in carousel_children { let widget_id = arena.new_node(LayoutNode::Widget(WidgetLayout::default())); carousel_widget_id.append(widget_id, &mut arena); @@ -1393,7 +590,7 @@ pub fn create_layout_tree( }) } -/// We may have situations where we also have to make sure the correct layout indices are selected. +/// We may have situations where we also have to make sure the correct layout indices are selected. /// For example, when we select a widget by clicking, we want to update the layout so that it's as if a user /// manually moved to it via keybinds. /// @@ -1422,25 +619,51 @@ pub enum MoveWidgetResult { NodeId(NodeId), } +/// A more restricted movement, only within a single widget. +pub fn move_expanded_widget_selection( + widget_lookup_map: &mut FxHashMap, current_widget_id: NodeId, + direction: MovementDirection, +) -> MoveWidgetResult { + if let Some(current_widget) = widget_lookup_map.get_mut(¤t_widget_id) { + match match direction { + MovementDirection::Left => current_widget.handle_widget_selection_left(), + MovementDirection::Right => current_widget.handle_widget_selection_right(), + MovementDirection::Up => current_widget.handle_widget_selection_up(), + MovementDirection::Down => current_widget.handle_widget_selection_down(), + } { + SelectionAction::Handled => MoveWidgetResult::ForceRedraw(current_widget_id), + SelectionAction::NotHandled => MoveWidgetResult::NodeId(current_widget_id), + } + } else { + MoveWidgetResult::NodeId(current_widget_id) + } +} + /// Attempts to find and return the selected [`BottomWidgetId`] after moving in a direction. /// /// Note this function assumes a properly built tree - if not, bad things may happen! We generally assume that: /// - Only [`LayoutNode::Widget`]s are leaves. /// - Only [`LayoutNode::Row`]s or [`LayoutNode::Col`]s are non-leaves. pub fn move_widget_selection( - layout_tree: &mut Arena, current_widget: &mut TmpBottomWidget, - current_widget_id: NodeId, direction: MovementDirection, + layout_tree: &mut Arena, + widget_lookup_map: &mut FxHashMap, current_widget_id: NodeId, + direction: MovementDirection, ) -> MoveWidgetResult { // We first give our currently-selected widget a chance to react to the movement - it may handle it internally! - let handled = match direction { - MovementDirection::Left => current_widget.handle_widget_selection_left(), - MovementDirection::Right => current_widget.handle_widget_selection_right(), - MovementDirection::Up => current_widget.handle_widget_selection_up(), - MovementDirection::Down => current_widget.handle_widget_selection_down(), + let handled = { + if let Some(current_widget) = widget_lookup_map.get_mut(¤t_widget_id) { + match direction { + MovementDirection::Left => current_widget.handle_widget_selection_left(), + MovementDirection::Right => current_widget.handle_widget_selection_right(), + MovementDirection::Up => current_widget.handle_widget_selection_up(), + MovementDirection::Down => current_widget.handle_widget_selection_down(), + } + } else { + // Short circuit. + return MoveWidgetResult::NodeId(current_widget_id); + } }; - // TODO: Do testing. - match handled { SelectionAction::Handled => { // If it was handled by the widget, then we don't have to do anything - return the current one. @@ -1506,7 +729,10 @@ pub fn move_widget_selection( if let Some(next_child) = *last_selected { descend_to_leaf(layout_tree, next_child) } else { - current_node.first_child().unwrap_or(current_id) + descend_to_leaf( + layout_tree, + current_node.first_child().unwrap_or(current_id), + ) } } LayoutNode::Widget(_) => { @@ -1549,14 +775,12 @@ pub fn move_widget_selection( if let Some(prev_sibling) = child_id.preceding_siblings(layout_tree).nth(1) { - // Subtract one from the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Row(row) = parent.get_mut() { row.last_selected = Some(prev_sibling); } } - // Now descend downwards! descend_to_leaf(layout_tree, prev_sibling) } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. @@ -1584,14 +808,12 @@ pub fn move_widget_selection( if let Some(following_sibling) = child_id.following_siblings(layout_tree).nth(1) { - // Add one to the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Row(row) = parent.get_mut() { row.last_selected = Some(following_sibling); } } - // Now descend downwards! descend_to_leaf(layout_tree, following_sibling) } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. @@ -1619,14 +841,12 @@ pub fn move_widget_selection( if let Some(prev_sibling) = child_id.preceding_siblings(layout_tree).nth(1) { - // Subtract one from the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Col(row) = parent.get_mut() { row.last_selected = Some(prev_sibling); } } - // Now descend downwards! descend_to_leaf(layout_tree, prev_sibling) } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. @@ -1654,14 +874,12 @@ pub fn move_widget_selection( if let Some(following_sibling) = child_id.following_siblings(layout_tree).nth(1) { - // Add one to the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Col(row) = parent.get_mut() { row.last_selected = Some(following_sibling); } } - // Now descend downwards! descend_to_leaf(layout_tree, following_sibling) } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. @@ -1679,7 +897,22 @@ pub fn move_widget_selection( }; if let Some(LayoutNode::Widget(_)) = layout_tree.get(proposed_id).map(|n| n.get()) { - MoveWidgetResult::NodeId(proposed_id) + if let Some(proposed_widget) = widget_lookup_map.get_mut(&proposed_id) { + match proposed_widget.selectable_type() { + SelectableType::Unselectable => { + // Try to move again recursively. + move_widget_selection( + layout_tree, + widget_lookup_map, + proposed_id, + direction, + ) + } + SelectableType::Selectable => MoveWidgetResult::NodeId(proposed_id), + } + } else { + MoveWidgetResult::NodeId(current_widget_id) + } } else { MoveWidgetResult::NodeId(current_widget_id) } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index f49a3890..3e31b966 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -1,8 +1,7 @@ -use std::time::Instant; +use std::{fmt::Debug, time::Instant}; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; -use indextree::NodeId; use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; use crate::{ @@ -160,7 +159,6 @@ pub trait Widget { pub enum SelectableType { Selectable, Unselectable, - Redirect(NodeId), } /// The "main" widgets that are used by bottom to display information! @@ -182,6 +180,26 @@ pub enum TmpBottomWidget { Empty, } +impl Debug for TmpBottomWidget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MemGraph(_) => write!(f, "MemGraph"), + Self::TempTable(_) => write!(f, "TempTable"), + Self::DiskTable(_) => write!(f, "DiskTable"), + Self::CpuGraph(_) => write!(f, "CpuGraph"), + Self::NetGraph(_) => write!(f, "NetGraph"), + Self::OldNetGraph(_) => write!(f, "OldNetGraph"), + Self::ProcessManager(_) => write!(f, "ProcessManager"), + Self::BatteryTable(_) => write!(f, "BatteryTable"), + Self::BasicCpu(_) => write!(f, "BasicCpu"), + Self::BasicMem(_) => write!(f, "BasicMem"), + Self::BasicNet(_) => write!(f, "BasicNet"), + Self::Carousel(_) => write!(f, "Carousel"), + Self::Empty(_) => write!(f, "Empty"), + } + } +} + /// The states a dialog can be in. Consists of either: /// - [`DialogState::Hidden`] - the dialog is currently not showing. /// - [`DialogState::Shown`] - the dialog is showing. diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs index 1ca60910..32106a15 100644 --- a/src/app/widgets/base/text_input.rs +++ b/src/app/widgets/base/text_input.rs @@ -285,6 +285,24 @@ impl TextInput { area, ); } + + fn move_left(&mut self) -> ComponentEventResult { + let original_cursor = self.cursor.cur_cursor(); + if self.move_back() == original_cursor { + ComponentEventResult::NoRedraw + } else { + ComponentEventResult::Redraw + } + } + + fn move_right(&mut self) -> ComponentEventResult { + let original_cursor = self.cursor.cur_cursor(); + if self.move_forward() == original_cursor { + ComponentEventResult::NoRedraw + } else { + ComponentEventResult::Redraw + } + } } impl Component for TextInput { @@ -299,22 +317,8 @@ impl Component for TextInput { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { if event.modifiers.is_empty() || event.modifiers == KeyModifiers::SHIFT { match event.code { - KeyCode::Left => { - let original_cursor = self.cursor.cur_cursor(); - if self.move_back() == original_cursor { - ComponentEventResult::NoRedraw - } else { - ComponentEventResult::Redraw - } - } - KeyCode::Right => { - let original_cursor = self.cursor.cur_cursor(); - if self.move_forward() == original_cursor { - ComponentEventResult::NoRedraw - } else { - ComponentEventResult::Redraw - } - } + KeyCode::Left => self.move_left(), + KeyCode::Right => self.move_right(), KeyCode::Backspace => self.clear_previous_grapheme(), KeyCode::Delete => self.clear_current_grapheme(), KeyCode::Char(c) => self.insert_character(c), @@ -349,6 +353,8 @@ impl Component for TextInput { match event.code { KeyCode::Char('b') => self.move_word_back(), KeyCode::Char('f') => self.move_word_forward(), + KeyCode::Char('h') => self.move_left(), + KeyCode::Char('l') => self.move_right(), _ => ComponentEventResult::Unhandled, } } else { diff --git a/src/app/widgets/bottom_widgets/carousel.rs b/src/app/widgets/bottom_widgets/carousel.rs index 92d3614c..3788cf97 100644 --- a/src/app/widgets/bottom_widgets/carousel.rs +++ b/src/app/widgets/bottom_widgets/carousel.rs @@ -12,14 +12,15 @@ use tui::{ use crate::{ app::{ - does_bound_intersect_coordinate, event::ComponentEventResult, Component, SelectableType, - Widget, + does_bound_intersect_coordinate, + event::{ComponentEventResult, SelectionAction}, + Component, Widget, }, canvas::Painter, options::layout_options::LayoutRule, }; -/// A container that "holds"" multiple [`BottomWidget`]s through their [`NodeId`]s. +/// A container that "holds" multiple [`BottomWidget`]s through their [`NodeId`]s. #[derive(PartialEq, Eq)] pub struct Carousel { index: usize, @@ -109,7 +110,7 @@ impl Carousel { .direction(tui::layout::Direction::Vertical) .split(area); - self.set_bounds(split_area[0]); + self.set_bounds(area); if let Some((_prev_id, prev_element_name)) = self.get_prev() { let prev_arrow_text = Spans::from(Span::styled( @@ -198,11 +199,15 @@ impl Widget for Carousel { self.height } - fn selectable_type(&self) -> SelectableType { - if let Some(node) = self.get_currently_selected() { - SelectableType::Redirect(node) - } else { - SelectableType::Unselectable - } + fn handle_widget_selection_left(&mut self) -> SelectionAction { + // Override to move to the left widget + self.decrement_index(); + SelectionAction::Handled + } + + fn handle_widget_selection_right(&mut self) -> SelectionAction { + // Override to move to the right widget + self.increment_index(); + SelectionAction::Handled } } diff --git a/src/app/widgets/bottom_widgets/empty.rs b/src/app/widgets/bottom_widgets/empty.rs index 4c33523b..e444ac2b 100644 --- a/src/app/widgets/bottom_widgets/empty.rs +++ b/src/app/widgets/bottom_widgets/empty.rs @@ -1,7 +1,7 @@ use tui::layout::Rect; use crate::{ - app::{Component, Widget}, + app::{Component, SelectableType, Widget}, options::layout_options::LayoutRule, }; @@ -35,8 +35,6 @@ impl Empty { impl Component for Empty { fn bounds(&self) -> Rect { - // TODO: Maybe think of how to store this without making it available for clicking. Separate bounds out to the layout? Might - // need to keep the bounds calculations for some components, so maybe implement it specifically for them. Rect::default() } @@ -55,4 +53,8 @@ impl Widget for Empty { fn height(&self) -> LayoutRule { self.height } + + fn selectable_type(&self) -> SelectableType { + SelectableType::Unselectable + } } diff --git a/src/app/widgets/bottom_widgets/process.rs b/src/app/widgets/bottom_widgets/process.rs index 8a704848..3851bfcd 100644 --- a/src/app/widgets/bottom_widgets/process.rs +++ b/src/app/widgets/bottom_widgets/process.rs @@ -984,6 +984,24 @@ impl ProcessManager { ComponentEventResult::Signal(ReturnSignal::Update) } + fn toggle_memory(&mut self) -> ComponentEventResult { + if matches!( + self.process_table.columns()[3].sort_type, + ProcessSortType::MemPercent + ) { + self.process_table + .set_column(ProcessSortColumn::new(ProcessSortType::Mem), 3); + } else { + self.process_table + .set_column(ProcessSortColumn::new(ProcessSortType::MemPercent), 3); + } + + // Invalidate row cache. + self.process_table.invalidate_cached_columns(); // TODO: This should be automatically called somehow after sets/removes to avoid forgetting it - maybe do a queue system? + + ComponentEventResult::Signal(ReturnSignal::Update) + } + fn hide_sort(&mut self) { self.show_sort = false; if let ProcessManagerSelection::Sort = self.selected { @@ -1081,7 +1099,7 @@ impl Component for ProcessManager { return self.open_search(); } KeyCode::Char('%') => { - // Handle switching memory usage type + return self.toggle_memory(); } KeyCode::Char('+') => { // Expand a branch diff --git a/src/canvas.rs b/src/canvas.rs index a494f898..1adc3965 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -215,6 +215,8 @@ impl Painter { // line-wrapping is NOT the same as taking the width of the text and dividing by width. // So, I need the height AFTER wrapping. // See: https://github.com/fdehau/tui-rs/pull/349. Land this after this pushes to release. + // + // ADDENDUM: I could probably use the same textwrap trick I did with the help menu for this. let dd_text = self.get_dd_spans(app_state); @@ -334,15 +336,6 @@ impl Painter { ); if let Some(widget) = lookup_map.get_mut(&node) { - // debug!( - // "Original bound: {:?}, offset_x: {}, offset_y: {}, area: {:?}, widget: {}", - // bound, - // offset_x, - // offset_y, - // area, - // widget.get_pretty_name() - // ); - if let TmpBottomWidget::Carousel(carousel) = widget { let remaining_area: Rect = carousel.draw_carousel(painter, f, area); @@ -357,7 +350,7 @@ impl Painter { painter, f, remaining_area, - selected_id == to_draw_node, + selected_id == node, false, ); } diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index 8a187e8f..ab5222ea 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -140,6 +140,7 @@ impl KillDialog for Painter { } else { #[cfg(target_family = "unix")] { + // TODO: Can probably make this const. let signal_text; #[cfg(target_os = "linux")] { diff --git a/src/constants.rs b/src/constants.rs index 5418a84a..57e17861 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,9 +1,6 @@ use crate::options::ConfigColours; use once_cell::sync::Lazy; -// Default widget ID -pub const DEFAULT_WIDGET_ID: u64 = 56709; - // How long to store data. pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data. @@ -320,8 +317,7 @@ pub const PROCESS_HELP_TEXT: [[&str; 2]; 14] = [ ]; pub const SEARCH_TEXT_HELP_TITLE: &str = "Process Search"; -pub const SEARCH_HELP_TEXT: [[&str; 2]; 48] = [ - ["Tab", "Toggle between searching for PID and name"], +pub const SEARCH_HELP_TEXT: [[&str; 2]; 47] = [ ["Esc", "Close the search widget (retains the filter)"], ["Ctrl-a", "Skip to the start of the search query"], ["Ctrl-e", "Skip to the end of the search query"],