From 64c6d0c8984616b2b1dba32ea2fc18d46caf540a Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Mon, 23 Aug 2021 16:50:02 -0400 Subject: [PATCH] refactor + change: write new movement logic --- Cargo.lock | 7 + Cargo.toml | 1 + src/app/event.rs | 27 ++- src/app/widgets/base/carousel.rs | 0 src/app/widgets/base/mod.rs | 2 +- src/app/widgets/base/scrollable.rs | 54 +++-- src/app/widgets/base/text_input.rs | 139 +++++++++++- src/app/widgets/base/text_table.rs | 103 ++++++--- src/app/widgets/base/time_graph.rs | 28 +-- src/app/widgets/basic_cpu.rs | 0 src/app/widgets/basic_mem.rs | 0 src/app/widgets/basic_net.rs | 0 src/app/widgets/battery.rs | 31 +++ src/app/widgets/cpu.rs | 27 +-- src/app/widgets/disk.rs | 15 +- src/app/widgets/mem.rs | 8 +- src/app/widgets/mod.rs | 339 +++++++++++++++++++++++++++-- src/app/widgets/net.rs | 39 +++- src/app/widgets/process.rs | 212 +++++++++++++++++- src/app/widgets/temp.rs | 15 +- src/bin/main.rs | 4 +- src/lib.rs | 8 +- 22 files changed, 900 insertions(+), 159 deletions(-) create mode 100644 src/app/widgets/base/carousel.rs create mode 100644 src/app/widgets/basic_cpu.rs create mode 100644 src/app/widgets/basic_mem.rs create mode 100644 src/app/widgets/basic_net.rs diff --git a/Cargo.lock b/Cargo.lock index e890a692..bb8569a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,6 +251,7 @@ dependencies = [ "fxhash", "heim", "indexmap", + "indextree", "itertools", "libc", "log", @@ -882,6 +883,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indextree" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990980c3d268c9b99df35e813eca2b8d1ee08606f6d2bb325edbd0b0c68f9ffe" + [[package]] name = "instant" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 4554ab27..a0505a43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ futures = "0.3.14" futures-timer = "3.0.2" fxhash = "0.2.1" indexmap = "1.6.2" +indextree = "4.3.1" itertools = "0.10.0" once_cell = "1.5.2" regex = "1.5.4" diff --git a/src/app/event.rs b/src/app/event.rs index 79efd9f5..d51a490e 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -1,11 +1,30 @@ use std::time::{Duration, Instant}; +const MAX_TIMEOUT: Duration = Duration::from_millis(400); + +/// The results of handling some user input event, like a mouse or key event, signifying what +/// the program should then do next. pub enum EventResult { + /// Kill the program. Quit, + + /// Trigger a redraw. Redraw, - Continue, + + /// Don't trigger a redraw. + NoRedraw, } +/// How a widget should handle a widget selection request. +pub enum SelectionAction { + /// This event occurs if the widget internally handled the selection action. + Handled, + + /// This event occurs if the widget did not handle the selection action; the caller must handle it. + NotHandled, +} + +/// The states a [`MultiKey`] can be in. enum MultiKeyState { /// Currently not waiting on any next input. Idle, @@ -36,16 +55,14 @@ pub enum MultiKeyResult { pub struct MultiKey { state: MultiKeyState, pattern: Vec, - timeout: Duration, } impl MultiKey { /// Creates a new [`MultiKey`] with a given pattern and timeout. - pub fn register(pattern: Vec, timeout: Duration) -> Self { + pub fn register(pattern: Vec) -> Self { Self { state: MultiKeyState::Idle, pattern, - timeout, } } @@ -81,7 +98,7 @@ impl MultiKey { trigger_instant, checked_index, } => { - if trigger_instant.elapsed() > self.timeout { + if trigger_instant.elapsed() > MAX_TIMEOUT { // Just reset and recursively call (putting it into Idle). self.reset(); self.input(c) diff --git a/src/app/widgets/base/carousel.rs b/src/app/widgets/base/carousel.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/app/widgets/base/mod.rs b/src/app/widgets/base/mod.rs index 2a833b77..7176c1cc 100644 --- a/src/app/widgets/base/mod.rs +++ b/src/app/widgets/base/mod.rs @@ -1,4 +1,4 @@ -//! A collection of basic widgets. +//! A collection of basic components. pub mod text_table; pub use text_table::TextTable; diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs index 675dc558..4606b02b 100644 --- a/src/app/widgets/base/scrollable.rs +++ b/src/app/widgets/base/scrollable.rs @@ -1,11 +1,9 @@ -use std::time::Duration; - use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent}; use tui::{layout::Rect, widgets::TableState}; use crate::app::{ event::{EventResult, MultiKey, MultiKeyResult}, - Widget, + Component, }; pub enum ScrollDirection { @@ -36,7 +34,7 @@ impl Scrollable { scroll_direction: ScrollDirection::Down, num_items, tui_state: TableState::default(), - gg_manager: MultiKey::register(vec!['g', 'g'], Duration::from_millis(400)), + gg_manager: MultiKey::register(vec!['g', 'g']), // TODO: Use a static arrayvec bounds: Rect::default(), } } @@ -79,7 +77,7 @@ impl Scrollable { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } @@ -90,7 +88,7 @@ impl Scrollable { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } @@ -104,7 +102,7 @@ impl Scrollable { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } else { self.update_index(new_index); @@ -121,18 +119,28 @@ impl Scrollable { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } else { self.update_index(new_index); EventResult::Redraw } } + + pub fn update_num_items(&mut self, num_items: usize) { + self.num_items = num_items; + + if num_items <= self.current_index { + self.current_index = num_items - 1; + } + + if num_items <= self.previous_index { + self.previous_index = num_items - 1; + } + } } -impl Widget for Scrollable { - type UpdateData = usize; - +impl Component for Scrollable { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { use crossterm::event::KeyCode::{Char, Down, Up}; @@ -144,14 +152,14 @@ impl Widget for Scrollable { Char('k') => self.move_up(1), Char('g') => match self.gg_manager.input('g') { MultiKeyResult::Completed => self.skip_to_first(), - MultiKeyResult::Accepted => EventResult::Continue, - MultiKeyResult::Rejected => EventResult::Continue, + MultiKeyResult::Accepted => EventResult::NoRedraw, + MultiKeyResult::Rejected => EventResult::NoRedraw, }, Char('G') => self.skip_to_last(), - _ => EventResult::Continue, + _ => EventResult::NoRedraw, } } else { - EventResult::Continue + EventResult::NoRedraw } } @@ -176,23 +184,11 @@ impl Widget for Scrollable { } } - EventResult::Continue + EventResult::NoRedraw } crossterm::event::MouseEventKind::ScrollDown => self.move_down(1), crossterm::event::MouseEventKind::ScrollUp => self.move_up(1), - _ => EventResult::Continue, - } - } - - fn update(&mut self, new_num_items: usize) { - self.num_items = new_num_items; - - if new_num_items <= self.current_index { - self.current_index = new_num_items - 1; - } - - if new_num_items <= self.previous_index { - self.previous_index = new_num_items - 1; + _ => EventResult::NoRedraw, } } diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs index 2f9e648e..432c04b9 100644 --- a/src/app/widgets/base/text_input.rs +++ b/src/app/widgets/base/text_input.rs @@ -1 +1,138 @@ -pub struct TextInput {} +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; +use tui::layout::Rect; + +use crate::app::{ + event::EventResult::{self}, + Component, +}; + +#[derive(Default)] +/// A single-line component for taking text inputs. +pub struct TextInput { + text: String, + cursor_index: usize, + bounds: Rect, +} + +impl TextInput { + /// Creates a new [`TextInput`]. + pub fn new() -> Self { + Self { + ..Default::default() + } + } + + fn set_cursor(&mut self, new_cursor_index: usize) -> EventResult { + if self.cursor_index == new_cursor_index { + EventResult::NoRedraw + } else { + self.cursor_index = new_cursor_index; + EventResult::Redraw + } + } + + fn move_back(&mut self, amount_to_subtract: usize) -> EventResult { + self.set_cursor(self.cursor_index.saturating_sub(amount_to_subtract)) + } + + fn move_forward(&mut self, amount_to_add: usize) -> EventResult { + let new_cursor = self.cursor_index + amount_to_add; + if new_cursor >= self.text.len() { + self.set_cursor(self.text.len() - 1) + } else { + self.set_cursor(new_cursor) + } + } + + fn clear_text(&mut self) -> EventResult { + if self.text.is_empty() { + EventResult::NoRedraw + } else { + self.text = String::default(); + self.cursor_index = 0; + EventResult::Redraw + } + } + + fn move_word_forward(&mut self) -> EventResult { + // TODO: Implement this + EventResult::NoRedraw + } + + fn move_word_back(&mut self) -> EventResult { + // TODO: Implement this + EventResult::NoRedraw + } + + fn clear_previous_word(&mut self) -> EventResult { + // TODO: Implement this + EventResult::NoRedraw + } + + fn clear_previous_grapheme(&mut self) -> EventResult { + // TODO: Implement this + EventResult::NoRedraw + } + + pub fn update(&mut self, new_text: String) { + self.text = new_text; + + if self.cursor_index >= self.text.len() { + self.cursor_index = self.text.len() - 1; + } + } +} + +impl Component for TextInput { + fn bounds(&self) -> Rect { + self.bounds + } + + fn set_bounds(&mut self, new_bounds: Rect) { + self.bounds = new_bounds; + } + + fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { + if event.modifiers.is_empty() { + match event.code { + KeyCode::Left => self.move_back(1), + KeyCode::Right => self.move_forward(1), + KeyCode::Backspace => self.clear_previous_grapheme(), + _ => EventResult::NoRedraw, + } + } else if let KeyModifiers::CONTROL = event.modifiers { + match event.code { + KeyCode::Char('a') => self.set_cursor(0), + KeyCode::Char('e') => self.set_cursor(self.text.len()), + KeyCode::Char('u') => self.clear_text(), + KeyCode::Char('w') => self.clear_previous_word(), + KeyCode::Char('h') => self.clear_previous_grapheme(), + _ => EventResult::NoRedraw, + } + } else if let KeyModifiers::ALT = event.modifiers { + match event.code { + KeyCode::Char('b') => self.move_word_forward(), + KeyCode::Char('f') => self.move_word_back(), + _ => EventResult::NoRedraw, + } + } else { + EventResult::NoRedraw + } + } + + fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { + // We are assuming this is within bounds... + + let x = event.column; + let widget_x = self.bounds.x; + let new_cursor_index = usize::from(x.saturating_sub(widget_x)); + + if new_cursor_index >= self.text.len() { + self.cursor_index = self.text.len() - 1; + } else { + self.cursor_index = new_cursor_index; + } + + EventResult::Redraw + } +} diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index 3e113568..6d6088bc 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -1,24 +1,32 @@ use crossterm::event::{KeyEvent, MouseEvent}; use tui::layout::Rect; -use crate::app::{event::EventResult, Scrollable, Widget}; +use crate::app::{event::EventResult, Component, Scrollable}; -struct Column { - name: &'static str, +/// A [`Column`] represents some column in a [`TextTable`]. +pub struct Column { + pub name: &'static str, + pub shortcut: Option, + pub default_descending: bool, // TODO: I would remove these in the future, storing them here feels weird... - desired_column_width: u16, - calculated_column_width: u16, - - x_bounds: (u16, u16), + pub desired_column_width: u16, + pub calculated_column_width: u16, + pub x_bounds: (u16, u16), } -impl Column {} - -/// The [`Widget::UpdateState`] of a [`TextTable`]. -pub struct TextTableUpdateData { - num_items: Option, - columns: Option>, +impl Column { + /// Creates a new [`Column`], given a name and optional shortcut. + pub fn new(name: &'static str, shortcut: Option, default_descending: bool) -> Self { + Self { + name, + desired_column_width: 0, + calculated_column_width: 0, + x_bounds: (0, 0), + shortcut, + default_descending, + } + } } /// A sortable, scrollable table with columns. @@ -37,24 +45,30 @@ pub struct TextTable { /// Which index we're sorting by. sort_index: usize, + + /// Whether we're sorting by ascending order. + sort_ascending: bool, } impl TextTable { - pub fn new(num_items: usize, columns: Vec<&'static str>) -> Self { + pub fn new(num_items: usize, columns: Vec<(&'static str, Option, bool)>) -> Self { Self { scrollable: Scrollable::new(num_items), columns: columns .into_iter() - .map(|name| Column { + .map(|(name, shortcut, default_descending)| Column { name, desired_column_width: 0, calculated_column_width: 0, x_bounds: (0, 0), + shortcut, + default_descending, }) .collect(), show_gap: true, bounds: Rect::default(), sort_index: 0, + sort_ascending: true, } } @@ -89,12 +103,42 @@ impl TextTable { pub fn column_names(&self) -> Vec<&'static str> { self.columns.iter().map(|column| column.name).collect() } + + pub fn update_num_items(&mut self, num_items: usize) { + self.scrollable.update_num_items(num_items); + } + + pub fn update_a_column(&mut self, index: usize, column: Column) { + if let Some(c) = self.columns.get_mut(index) { + *c = column; + } + } + + pub fn update_columns(&mut self, columns: Vec) { + self.columns = columns; + if self.columns.len() <= self.sort_index { + self.sort_index = self.columns.len() - 1; + } + } } -impl Widget for TextTable { - type UpdateData = TextTableUpdateData; - +impl Component for TextTable { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { + for (index, column) in self.columns.iter().enumerate() { + if let Some(shortcut) = column.shortcut { + if shortcut == event { + if self.sort_index == index { + // Just flip the sort if we're already sorting by this. + self.sort_ascending = !self.sort_ascending; + } else { + self.sort_index = index; + self.sort_ascending = !column.default_descending; + } + return EventResult::Redraw; + } + } + } + self.scrollable.handle_key_event(event) } @@ -107,29 +151,22 @@ impl Widget for TextTable { for (index, column) in self.columns.iter().enumerate() { let (start, end) = column.x_bounds; if start >= x && end <= y { - self.sort_index = index; + if self.sort_index == index { + // Just flip the sort if we're already sorting by this. + self.sort_ascending = !self.sort_ascending; + } else { + self.sort_index = index; + self.sort_ascending = !column.default_descending; + } } } - EventResult::Continue + EventResult::NoRedraw } else { self.scrollable.handle_mouse_event(event) } } - fn update(&mut self, update_data: Self::UpdateData) { - if let Some(num_items) = update_data.num_items { - self.scrollable.update(num_items); - } - - if let Some(columns) = update_data.columns { - self.columns = columns; - if self.columns.len() <= self.sort_index { - self.sort_index = self.columns.len() - 1; - } - } - } - fn bounds(&self) -> Rect { self.bounds } diff --git a/src/app/widgets/base/time_graph.rs b/src/app/widgets/base/time_graph.rs index 26adfcb4..3c19bb0a 100644 --- a/src/app/widgets/base/time_graph.rs +++ b/src/app/widgets/base/time_graph.rs @@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent}; use tui::layout::Rect; -use crate::app::{event::EventResult, Widget}; +use crate::app::{event::EventResult, Component}; #[derive(Clone)] pub enum AutohideTimerState { @@ -23,7 +23,9 @@ pub enum AutohideTimer { impl AutohideTimer { fn trigger_display_timer(&mut self) { match self { - AutohideTimer::Disabled => todo!(), + AutohideTimer::Disabled => { + // Does nothing. + } AutohideTimer::Enabled { state, show_duration: _, @@ -35,7 +37,9 @@ impl AutohideTimer { pub fn update_display_timer(&mut self) { match self { - AutohideTimer::Disabled => {} + AutohideTimer::Disabled => { + // Does nothing. + } AutohideTimer::Enabled { state, show_duration, @@ -87,7 +91,7 @@ impl TimeGraph { '-' => self.zoom_out(), '+' => self.zoom_in(), '=' => self.reset_zoom(), - _ => EventResult::Continue, + _ => EventResult::NoRedraw, } } @@ -105,7 +109,7 @@ impl TimeGraph { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } @@ -123,13 +127,13 @@ impl TimeGraph { EventResult::Redraw } else { - EventResult::Continue + EventResult::NoRedraw } } fn reset_zoom(&mut self) -> EventResult { if self.current_display_time == self.default_time_value { - EventResult::Continue + EventResult::NoRedraw } else { self.current_display_time = self.default_time_value; self.autohide_timer.trigger_display_timer(); @@ -138,19 +142,17 @@ impl TimeGraph { } } -impl Widget for TimeGraph { - type UpdateData = (); - +impl Component for TimeGraph { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { use crossterm::event::KeyCode::Char; if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT { match event.code { Char(c) => self.handle_char(c), - _ => EventResult::Continue, + _ => EventResult::NoRedraw, } } else { - EventResult::Continue + EventResult::NoRedraw } } @@ -158,7 +160,7 @@ impl Widget for TimeGraph { match event.kind { crossterm::event::MouseEventKind::ScrollDown => self.zoom_out(), crossterm::event::MouseEventKind::ScrollUp => self.zoom_in(), - _ => EventResult::Continue, + _ => EventResult::NoRedraw, } } diff --git a/src/app/widgets/basic_cpu.rs b/src/app/widgets/basic_cpu.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/app/widgets/basic_mem.rs b/src/app/widgets/basic_mem.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/app/widgets/basic_net.rs b/src/app/widgets/basic_net.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/app/widgets/battery.rs b/src/app/widgets/battery.rs index 62f27d93..64236163 100644 --- a/src/app/widgets/battery.rs +++ b/src/app/widgets/battery.rs @@ -1,5 +1,9 @@ use std::collections::HashMap; +use tui::layout::Rect; + +use super::{Component, Widget}; + #[derive(Default)] pub struct BatteryWidgetState { pub currently_selected_battery_index: usize, @@ -23,3 +27,30 @@ impl BatteryState { self.widget_states.get(&widget_id) } } + +// TODO: Implement battery widget. +/// A table displaying battery information on a per-battery basis. +pub struct BatteryTable { + bounds: Rect, +} + +impl BatteryTable { + /// Creates a new [`BatteryTable`]. + pub fn new() -> Self { + Self { + bounds: Rect::default(), + } + } +} + +impl Component for BatteryTable { + fn bounds(&self) -> tui::layout::Rect { + self.bounds + } + + fn set_bounds(&mut self, new_bounds: tui::layout::Rect) { + self.bounds = new_bounds; + } +} + +impl Widget for BatteryTable {} diff --git a/src/app/widgets/cpu.rs b/src/app/widgets/cpu.rs index 48c97dbc..ceb5e0d5 100644 --- a/src/app/widgets/cpu.rs +++ b/src/app/widgets/cpu.rs @@ -6,8 +6,8 @@ use tui::layout::Rect; use crate::app::event::EventResult; use super::{ - does_point_intersect_rect, text_table::TextTableUpdateData, AppScrollWidgetState, - CanvasTableWidthState, TextTable, TimeGraph, Widget, + does_point_intersect_rect, AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, + TimeGraph, Widget, }; pub struct CpuWidgetState { @@ -66,10 +66,6 @@ pub enum CpuGraphLegendPosition { Right, } -pub struct CpuGraphUpdateData { - pub legend_data: Option, -} - /// A widget designed to show CPU usage via a graph, along with a side legend implemented as a [`TextTable`]. pub struct CpuGraph { graph: TimeGraph, @@ -95,21 +91,16 @@ impl CpuGraph { } } -impl Widget for CpuGraph { - type UpdateData = CpuGraphUpdateData; - +impl Component for CpuGraph { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { match self.selected { CpuGraphSelection::Graph => self.graph.handle_key_event(event), CpuGraphSelection::Legend => self.legend.handle_key_event(event), - CpuGraphSelection::None => EventResult::Continue, + CpuGraphSelection::None => EventResult::NoRedraw, } } fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { - // Check where we clicked (and switch the selected field if required) and fire the handler from there. - // Note we assume that the - let global_x = event.column; let global_y = event.row; @@ -120,13 +111,7 @@ impl Widget for CpuGraph { self.selected = CpuGraphSelection::Legend; self.legend.handle_mouse_event(event) } else { - EventResult::Continue - } - } - - fn update(&mut self, update_data: Self::UpdateData) { - if let Some(legend_data) = update_data.legend_data { - self.legend.update(legend_data); + EventResult::NoRedraw } } @@ -138,3 +123,5 @@ impl Widget for CpuGraph { self.bounds = new_bounds; } } + +impl Widget for CpuGraph {} diff --git a/src/app/widgets/disk.rs b/src/app/widgets/disk.rs index 9142dac0..7ae70229 100644 --- a/src/app/widgets/disk.rs +++ b/src/app/widgets/disk.rs @@ -5,10 +5,7 @@ use tui::layout::Rect; use crate::app::event::EventResult; -use super::{ - text_table::TextTableUpdateData, AppScrollWidgetState, CanvasTableWidthState, TextTable, - Widget, -}; +use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget}; pub struct DiskWidgetState { pub scroll_state: AppScrollWidgetState, @@ -58,9 +55,7 @@ impl DiskTable { } } -impl Widget for DiskTable { - type UpdateData = TextTableUpdateData; - +impl Component for DiskTable { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { self.table.handle_key_event(event) } @@ -69,10 +64,6 @@ impl Widget for DiskTable { self.table.handle_mouse_event(event) } - fn update(&mut self, update_data: Self::UpdateData) { - self.table.update(update_data); - } - fn bounds(&self) -> Rect { self.bounds } @@ -81,3 +72,5 @@ impl Widget for DiskTable { self.bounds = new_bounds; } } + +impl Widget for DiskTable {} diff --git a/src/app/widgets/mem.rs b/src/app/widgets/mem.rs index 9409b94c..a11d48a0 100644 --- a/src/app/widgets/mem.rs +++ b/src/app/widgets/mem.rs @@ -5,7 +5,7 @@ use tui::layout::Rect; use crate::app::event::EventResult; -use super::{TimeGraph, Widget}; +use super::{Component, TimeGraph, Widget}; pub struct MemWidgetState { pub current_display_time: u64, @@ -56,9 +56,7 @@ impl MemGraph { } } -impl Widget for MemGraph { - type UpdateData = (); - +impl Component for MemGraph { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { self.graph.handle_key_event(event) } @@ -75,3 +73,5 @@ impl Widget for MemGraph { self.graph.set_bounds(new_bounds); } } + +impl Widget for MemGraph {} diff --git a/src/app/widgets/mod.rs b/src/app/widgets/mod.rs index e7f63b45..a80aa5dc 100644 --- a/src/app/widgets/mod.rs +++ b/src/app/widgets/mod.rs @@ -2,10 +2,14 @@ use std::time::Instant; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; +use indextree::{Arena, NodeId}; use tui::{layout::Rect, widgets::TableState}; use crate::{ - app::{event::EventResult, layout_manager::BottomWidgetType}, + app::{ + event::{EventResult, SelectionAction}, + layout_manager::BottomWidgetType, + }, constants, }; @@ -33,37 +37,65 @@ pub use self::battery::*; pub mod temp; pub use temp::*; +/// A trait for things that are drawn with state. #[enum_dispatch] #[allow(unused_variables)] -pub trait Widget { - type UpdateData; - +pub trait Component { /// Handles a [`KeyEvent`]. /// - /// Defaults to returning [`EventResult::Continue`], indicating nothing should be done. + /// Defaults to returning [`EventResult::NoRedraw`], indicating nothing should be done. fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { - EventResult::Continue + EventResult::NoRedraw } /// Handles a [`MouseEvent`]. /// /// Defaults to returning [`EventResult::Continue`], indicating nothing should be done. fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { - EventResult::Continue + EventResult::NoRedraw } - /// Updates a [`Widget`] with new data from some state outside of its control. Defaults to doing nothing. - fn update(&mut self, update_data: Self::UpdateData) {} - - /// Returns a [`Widget`]'s bounding box. Note that these are defined in *global*, *absolute* + /// Returns a [`Component`]'s bounding box. Note that these are defined in *global*, *absolute* /// coordinates. fn bounds(&self) -> Rect; - /// Updates a [`Widget`]s bounding box. + /// Updates a [`Component`]s bounding box to `new_bounds`. fn set_bounds(&mut self, new_bounds: Rect); } -#[enum_dispatch(BottomWidget)] +/// A trait for actual fully-fledged widgets to be displayed in bottom. +#[enum_dispatch] +pub trait Widget { + /// Updates a [`Widget`] given some data. Defaults to doing nothing. + fn update(&mut self) {} + + /// Handles what to do when trying to respond to a widget selection movement to the left. + /// Defaults to just moving to the next-possible widget in that direction. + fn handle_widget_selection_left(&mut self) -> SelectionAction { + SelectionAction::NotHandled + } + + /// Handles what to do when trying to respond to a widget selection movement to the right. + /// Defaults to just moving to the next-possible widget in that direction. + fn handle_widget_selection_right(&mut self) -> SelectionAction { + SelectionAction::NotHandled + } + + /// Handles what to do when trying to respond to a widget selection movement upward. + /// Defaults to just moving to the next-possible widget in that direction. + fn handle_widget_selection_up(&mut self) -> SelectionAction { + SelectionAction::NotHandled + } + + /// Handles what to do when trying to respond to a widget selection movement downward. + /// Defaults to just moving to the next-possible widget in that direction. + fn handle_widget_selection_down(&mut self) -> SelectionAction { + SelectionAction::NotHandled + } +} + +/// The "main" widgets that are used by bottom to display information! +#[enum_dispatch(Component, Widget)] enum BottomWidget { MemGraph, TempTable, @@ -71,12 +103,293 @@ enum BottomWidget { CpuGraph, NetGraph, OldNetGraph, + ProcessManager, + BatteryTable, } +/// Checks whether points `(x, y)` intersect a given [`Rect`]. pub fn does_point_intersect_rect(x: u16, y: u16, rect: Rect) -> bool { x >= rect.left() && x <= rect.right() && y >= rect.top() && y <= rect.bottom() } +/// A [`LayoutNode`] represents a single node in the overall widget hierarchy. Each node is one of: +/// - [`LayoutNode::Row`] (a a non-leaf that distributes its children horizontally) +/// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically) +/// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with) +#[derive(PartialEq, Eq)] +pub enum LayoutNode { + Row(BottomRow), + Col(BottomCol), + Widget, +} + +/// A simple struct representing a row and its state. +#[derive(PartialEq, Eq)] +pub struct BottomRow { + last_selected_index: usize, +} + +/// A simple struct representing a column and its state. +#[derive(PartialEq, Eq)] +pub struct BottomCol { + last_selected_index: usize, +} + +/// Relative movement direction from the currently selected widget. +pub enum MovementDirection { + Left, + Right, + Up, + Down, +} + +/// 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. +fn move_widget_selection( + layout_tree: &mut Arena, current_widget: &mut BottomWidget, + current_widget_id: NodeId, direction: MovementDirection, +) -> NodeId { + // 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(), + }; + + match handled { + SelectionAction::Handled => { + // If it was handled by the widget, then we don't have to do anything - return the current one. + current_widget_id + } + SelectionAction::NotHandled => { + /// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent + /// is a [`LayoutNode::Row`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order). + /// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`]. + fn find_first_row( + layout_tree: &Arena, current_id: NodeId, + ) -> Option<(NodeId, NodeId)> { + layout_tree + .get(current_id) + .and_then(|current_node| current_node.parent()) + .and_then(|parent_id| { + layout_tree + .get(parent_id) + .map(|parent_node| (parent_id, parent_node)) + }) + .and_then(|(parent_id, parent_node)| match parent_node.get() { + LayoutNode::Row(_) => Some((parent_id, current_id)), + LayoutNode::Col(_) => find_first_row(layout_tree, parent_id), + LayoutNode::Widget => None, + }) + } + + /// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent + /// is a [`LayoutNode::Col`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order). + /// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`]. + fn find_first_col( + layout_tree: &Arena, current_id: NodeId, + ) -> Option<(NodeId, NodeId)> { + layout_tree + .get(current_id) + .and_then(|current_node| current_node.parent()) + .and_then(|parent_id| { + layout_tree + .get(parent_id) + .map(|parent_node| (parent_id, parent_node)) + }) + .and_then(|(parent_id, parent_node)| match parent_node.get() { + LayoutNode::Row(_) => find_first_col(layout_tree, parent_id), + LayoutNode::Col(_) => Some((parent_id, current_id)), + LayoutNode::Widget => None, + }) + } + + /// Descends to a leaf. + fn descend_to_leaf(layout_tree: &Arena, current_id: NodeId) -> NodeId { + if let Some(current_node) = layout_tree.get(current_id) { + match current_node.get() { + LayoutNode::Row(BottomRow { + last_selected_index, + }) + | LayoutNode::Col(BottomCol { + last_selected_index, + }) => { + if let Some(next_child) = + current_id.children(layout_tree).nth(*last_selected_index) + { + descend_to_leaf(layout_tree, next_child) + } else { + current_id + } + } + LayoutNode::Widget => { + // Halt! + current_id + } + } + } else { + current_id + } + } + + // If it was NOT handled by the current widget, then move in the correct direction; we can rely + // on the tree layout to help us decide where to go. + // Movement logic is inspired by i3. When we enter a new column/row, we go to the *last* selected + // element; if we can't, go to the nearest one. + match direction { + MovementDirection::Left => { + // When we move "left": + // 1. Look for the parent of the current widget. + // 2. Depending on whether it is a Row or Col: + // a) If we are in a Row, try to move to the child (it can be a Row, Col, or Widget) before it, + // and update the last-selected index. If we can't (i.e. we are the first element), then + // instead move to the parent, and try again to select the element before it. If there is + // no parent (i.e. we hit the root), then just return the original index. + // b) If we are in a Col, then just try to move to the parent. If there is no + // parent (i.e. we hit the root), then just return the original index. + // c) A Widget should be impossible to select. + // 3. Assuming we have now selected a new child, then depending on what the child is: + // a) If we are in a Row or Col, then take the last selected index, and repeat step 3 until you hit + // a Widget. + // b) If we are in a Widget, return the corresponding NodeId. + + fn find_left( + layout_tree: &mut Arena, current_id: NodeId, + ) -> NodeId { + if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id) + { + 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_index = + row.last_selected_index.saturating_sub(1); + } + } + + // Now descend downwards! + descend_to_leaf(layout_tree, prev_sibling) + } else { + // Darn, we can't go further back! Recurse on this ID. + find_left(layout_tree, child_id) + } + } else { + // Failed, just return the current ID. + current_id + } + } + find_left(layout_tree, current_widget_id) + } + MovementDirection::Right => { + // When we move "right", repeat the steps for "left", but instead try to move to the child *after* + // it in all cases. + + fn find_right( + layout_tree: &mut Arena, current_id: NodeId, + ) -> NodeId { + if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id) + { + if let Some(prev_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_index += 1; + } + } + + // Now descend downwards! + descend_to_leaf(layout_tree, prev_sibling) + } else { + // Darn, we can't go further back! Recurse on this ID. + find_right(layout_tree, child_id) + } + } else { + // Failed, just return the current ID. + current_id + } + } + find_right(layout_tree, current_widget_id) + } + MovementDirection::Up => { + // When we move "up", copy the steps for "left", but switch "Row" and "Col". We instead want to move + // vertically, so we want to now avoid Rows and look for Cols! + + fn find_above( + layout_tree: &mut Arena, current_id: NodeId, + ) -> NodeId { + if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id) + { + 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_index = + row.last_selected_index.saturating_sub(1); + } + } + + // Now descend downwards! + descend_to_leaf(layout_tree, prev_sibling) + } else { + // Darn, we can't go further back! Recurse on this ID. + find_above(layout_tree, child_id) + } + } else { + // Failed, just return the current ID. + current_id + } + } + find_above(layout_tree, current_widget_id) + } + MovementDirection::Down => { + // See "up"'s steps, but now we're going for the child *after* the currently selected one in all + // cases. + + fn find_below( + layout_tree: &mut Arena, current_id: NodeId, + ) -> NodeId { + if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id) + { + if let Some(prev_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_index += 1; + } + } + + // Now descend downwards! + descend_to_leaf(layout_tree, prev_sibling) + } else { + // Darn, we can't go further back! Recurse on this ID. + find_below(layout_tree, child_id) + } + } else { + // Failed, just return the current ID. + current_id + } + } + find_below(layout_tree, current_widget_id) + } + } + } + } +} + +// ----- Old stuff below ----- + #[derive(Debug)] pub enum ScrollDirection { // UP means scrolling up --- this usually DECREMENTS diff --git a/src/app/widgets/net.rs b/src/app/widgets/net.rs index 5865858c..e2bd3db7 100644 --- a/src/app/widgets/net.rs +++ b/src/app/widgets/net.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, time::Instant}; use tui::layout::Rect; -use super::{TimeGraph, Widget}; +use super::{Component, TimeGraph, Widget}; pub struct NetWidgetState { pub current_display_time: u64, @@ -55,7 +55,9 @@ impl NetState { } } -struct NetGraphCache { +/// A struct containing useful cached information for a [`NetGraph`]. +#[derive(Clone)] +pub struct NetGraphCache { max_range: f64, labels: Vec, time_start: f64, @@ -71,9 +73,10 @@ enum NetGraphCacheState { /// /// As of now, this is essentially just a wrapper around a [`TimeGraph`]. pub struct NetGraph { + /// The graph itself. Just a [`TimeGraph`]. graph: TimeGraph, - // Cached details; probably want to move at some point... + // Cached details for drawing purposes; probably want to move at some point... draw_cache: NetGraphCacheState, } @@ -86,6 +89,7 @@ impl NetGraph { } } + /// Updates the associated cache on a [`NetGraph`]. pub fn set_cache(&mut self, max_range: f64, labels: Vec, time_start: f64) { self.draw_cache = NetGraphCacheState::Cached(NetGraphCache { max_range, @@ -94,17 +98,32 @@ impl NetGraph { }) } + /// Returns whether the [`NetGraph`] contains a cache from drawing. pub fn is_cached(&self) -> bool { match self.draw_cache { NetGraphCacheState::Uncached => false, NetGraphCacheState::Cached(_) => true, } } + + /// Returns a reference to the [`NetGraphCache`] tied to the [`NetGraph`] if there is one. + pub fn get_cache(&self) -> Option<&NetGraphCache> { + match &self.draw_cache { + NetGraphCacheState::Uncached => None, + NetGraphCacheState::Cached(cache) => Some(cache), + } + } + + /// Returns an owned copy of the [`NetGraphCache`] tied to the [`NetGraph`] if there is one. + pub fn get_cache_owned(&self) -> Option { + match &self.draw_cache { + NetGraphCacheState::Uncached => None, + NetGraphCacheState::Cached(cache) => Some(cache.clone()), + } + } } -impl Widget for NetGraph { - type UpdateData = (); - +impl Component for NetGraph { fn bounds(&self) -> Rect { self.graph.bounds() } @@ -126,6 +145,8 @@ impl Widget for NetGraph { } } +impl Widget for NetGraph {} + /// A widget denoting network usage via a graph and a separate, single row table. This is built on [`NetGraph`], /// and the main difference is that it also contains a bounding box for the graph + text. pub struct OldNetGraph { @@ -143,9 +164,7 @@ impl OldNetGraph { } } -impl Widget for OldNetGraph { - type UpdateData = (); - +impl Component for OldNetGraph { fn bounds(&self) -> Rect { self.bounds } @@ -166,3 +185,5 @@ impl Widget for OldNetGraph { self.graph.handle_mouse_event(event) } } + +impl Widget for OldNetGraph {} diff --git a/src/app/widgets/process.rs b/src/app/widgets/process.rs index dedc7b83..48070f11 100644 --- a/src/app/widgets/process.rs +++ b/src/app/widgets/process.rs @@ -1,16 +1,23 @@ use std::collections::HashMap; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; use unicode_segmentation::GraphemeCursor; -use tui::widgets::TableState; +use tui::{layout::Rect, widgets::TableState}; use crate::{ - app::query::*, + app::{ + event::{EventResult, MultiKey, MultiKeyResult}, + query::*, + }, data_harvester::processes::{self, ProcessSorting}, }; use ProcessSorting::*; -use super::{AppScrollWidgetState, CanvasTableWidthState, CursorDirection, ScrollDirection}; +use super::{ + does_point_intersect_rect, AppScrollWidgetState, CanvasTableWidthState, Component, + CursorDirection, ScrollDirection, TextInput, TextTable, Widget, +}; /// AppSearchState deals with generic searching (I might do this in the future). pub struct AppSearchState { @@ -606,3 +613,202 @@ impl ProcState { self.widget_states.get(&widget_id) } } + +/// The currently selected part of a [`ProcessManager`] +enum ProcessManagerSelection { + Processes, + Sort, + Search, +} + +#[derive(Default)] +/// The state of the search modifiers. +struct SearchModifiers { + enable_case_sensitive: bool, + enable_whole_word: bool, + enable_regex: bool, +} + +/// A searchable, sortable table to manage processes. +pub struct ProcessManager { + bounds: Rect, + process_table: TextTable, + sort_table: TextTable, + search_input: TextInput, + + dd_multi: MultiKey, + + selected: ProcessManagerSelection, + + in_tree_mode: bool, + show_sort: bool, + show_search: bool, + + search_modifiers: SearchModifiers, +} + +impl ProcessManager { + /// Creates a new [`ProcessManager`]. + pub fn new(default_in_tree_mode: bool) -> Self { + Self { + bounds: Rect::default(), + process_table: TextTable::new(0, vec![]), // TODO: Do this + sort_table: TextTable::new(0, vec![]), // TODO: Do this too + search_input: TextInput::new(), + dd_multi: MultiKey::register(vec!['d', 'd']), // TODO: Use a static arrayvec + selected: ProcessManagerSelection::Processes, + in_tree_mode: default_in_tree_mode, + show_sort: false, + show_search: false, + search_modifiers: SearchModifiers::default(), + } + } + + fn open_search(&mut self) -> EventResult { + if let ProcessManagerSelection::Search = self.selected { + EventResult::NoRedraw + } else { + self.show_search = true; + self.selected = ProcessManagerSelection::Search; + EventResult::Redraw + } + } + + fn open_sort(&mut self) -> EventResult { + if let ProcessManagerSelection::Sort = self.selected { + EventResult::NoRedraw + } else { + self.show_sort = true; + self.selected = ProcessManagerSelection::Sort; + EventResult::Redraw + } + } + + /// Returns whether the process manager is searching the current term with the restriction that it must + /// match entire word. + pub fn is_searching_whole_word(&self) -> bool { + self.search_modifiers.enable_whole_word + } + + /// Returns whether the process manager is searching the current term using regex. + pub fn is_searching_with_regex(&self) -> bool { + self.search_modifiers.enable_regex + } + + /// Returns whether the process manager is searching the current term with the restriction that case-sensitivity + /// matters. + pub fn is_case_sensitive(&self) -> bool { + self.search_modifiers.enable_case_sensitive + } +} + +impl Component for ProcessManager { + fn bounds(&self) -> Rect { + self.bounds + } + + fn set_bounds(&mut self, new_bounds: Rect) { + self.bounds = new_bounds; + } + + fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { + match self.selected { + ProcessManagerSelection::Processes => { + // Try to catch some stuff first... + if event.modifiers.is_empty() { + match event.code { + KeyCode::Tab => { + // Handle grouping/ungrouping + } + KeyCode::Char('P') => { + // Show full command/process name + } + KeyCode::Char('d') => { + match self.dd_multi.input('d') { + MultiKeyResult::Completed => { + // Kill the selected process(es) + } + MultiKeyResult::Accepted | MultiKeyResult::Rejected => { + return EventResult::NoRedraw; + } + } + } + KeyCode::Char('/') => { + return self.open_search(); + } + KeyCode::Char('%') => { + // Handle switching memory usage type + } + KeyCode::Char('+') => { + // Expand a branch + } + KeyCode::Char('-') => { + // Collapse a branch + } + KeyCode::Char('t') | KeyCode::F(5) => { + self.in_tree_mode = !self.in_tree_mode; + return EventResult::Redraw; + } + KeyCode::F(6) => { + return self.open_sort(); + } + KeyCode::F(9) => { + // Kill the selected process(es) + } + _ => {} + } + } else if let KeyModifiers::CONTROL = event.modifiers { + if let KeyCode::Char('f') = event.code { + return self.open_search(); + } + } else if let KeyModifiers::SHIFT = event.modifiers { + if let KeyCode::Char('P') = event.code { + // Show full command/process name + } + } + + self.process_table.handle_key_event(event) + } + ProcessManagerSelection::Sort => { + if event.modifiers.is_empty() { + match event.code { + KeyCode::F(1) => {} + KeyCode::F(2) => {} + KeyCode::F(3) => {} + _ => {} + } + } else if let KeyModifiers::ALT = event.modifiers { + match event.code { + KeyCode::Char('c') | KeyCode::Char('C') => {} + KeyCode::Char('w') | KeyCode::Char('W') => {} + KeyCode::Char('r') | KeyCode::Char('R') => {} + _ => {} + } + } + + self.sort_table.handle_key_event(event) + } + ProcessManagerSelection::Search => self.search_input.handle_key_event(event), + } + } + + fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { + let global_x = event.column; + let global_y = event.row; + + if does_point_intersect_rect(global_x, global_y, self.process_table.bounds()) { + self.selected = ProcessManagerSelection::Processes; + self.process_table.handle_mouse_event(event) + } else if does_point_intersect_rect(global_x, global_y, self.sort_table.bounds()) { + self.selected = ProcessManagerSelection::Sort; + self.sort_table.handle_mouse_event(event) + } else if does_point_intersect_rect(global_x, global_y, self.search_input.bounds()) { + self.selected = ProcessManagerSelection::Search; + self.search_input.handle_mouse_event(event) + } else { + EventResult::NoRedraw + } + } +} + +impl Widget for ProcessManager {} diff --git a/src/app/widgets/temp.rs b/src/app/widgets/temp.rs index 5259cfe8..1234e149 100644 --- a/src/app/widgets/temp.rs +++ b/src/app/widgets/temp.rs @@ -5,10 +5,7 @@ use tui::layout::Rect; use crate::app::event::EventResult; -use super::{ - text_table::TextTableUpdateData, AppScrollWidgetState, CanvasTableWidthState, TextTable, - Widget, -}; +use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget}; pub struct TempWidgetState { pub scroll_state: AppScrollWidgetState, @@ -58,9 +55,7 @@ impl TempTable { } } -impl Widget for TempTable { - type UpdateData = TextTableUpdateData; - +impl Component for TempTable { fn handle_key_event(&mut self, event: KeyEvent) -> EventResult { self.table.handle_key_event(event) } @@ -69,10 +64,6 @@ impl Widget for TempTable { self.table.handle_mouse_event(event) } - fn update(&mut self, update_data: Self::UpdateData) { - self.table.update(update_data); - } - fn bounds(&self) -> Rect { self.bounds } @@ -81,3 +72,5 @@ impl Widget for TempTable { self.bounds = new_bounds; } } + +impl Widget for TempTable {} diff --git a/src/bin/main.rs b/src/bin/main.rs index ede90339..b258f466 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -140,7 +140,7 @@ fn main() -> Result<()> { force_redraw(&mut app); try_drawing(&mut terminal, &mut app, &mut painter)?; } - EventResult::Continue => {} + EventResult::NoRedraw => {} } } BottomEvent::MouseInput(event) => match handle_mouse_event(event, &mut app) { @@ -152,7 +152,7 @@ fn main() -> Result<()> { force_redraw(&mut app); try_drawing(&mut terminal, &mut app, &mut painter)?; } - EventResult::Continue => {} + EventResult::NoRedraw => {} }, BottomEvent::Update(data) => { app.data_collection.eat_data(data); diff --git a/src/lib.rs b/src/lib.rs index e9b53806..cee739dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> EventResult app.handle_scroll_down(); EventResult::Redraw } - _ => EventResult::Continue, + _ => EventResult::NoRedraw, } } @@ -128,7 +128,7 @@ pub fn handle_key_event( KeyCode::F(6) => app.toggle_sort(), KeyCode::F(9) => app.start_killing_process(), _ => { - return EventResult::Continue; + return EventResult::NoRedraw; } } } else { @@ -171,7 +171,7 @@ pub fn handle_key_event( // are hard to iter while truncating last (eloquently). // KeyCode::Backspace => app.skip_word_backspace(), _ => { - return EventResult::Continue; + return EventResult::NoRedraw; } } } else if let KeyModifiers::SHIFT = event.modifiers { @@ -182,7 +182,7 @@ pub fn handle_key_event( KeyCode::Down => app.move_widget_selection(&WidgetDirection::Down), KeyCode::Char(caught_char) => app.on_char_key(caught_char), _ => { - return EventResult::Continue; + return EventResult::NoRedraw; } } }