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"],