refactor: finish help menu

This commit is contained in:
ClementTsang 2021-09-22 01:16:33 -04:00
parent e7b9c72912
commit 7ee85a82f7
36 changed files with 1011 additions and 852 deletions

View File

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Changes ## Changes
- [#557](https://github.com/ClementTsang/bottom/pull/557): Add '/s' to network usage legend. - [#557](https://github.com/ClementTsang/bottom/pull/557): Add '/s' to network usage legend to indicate "per second".
## Internal Changes ## Internal Changes

29
Cargo.lock generated
View File

@ -263,6 +263,7 @@ dependencies = [
"serde", "serde",
"smol", "smol",
"sysinfo", "sysinfo",
"textwrap 0.14.2",
"thiserror", "thiserror",
"toml", "toml",
"tui", "tui",
@ -348,7 +349,7 @@ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim",
"textwrap", "textwrap 0.11.0",
"unicode-width", "unicode-width",
"vec_map", "vec_map",
] ]
@ -1410,6 +1411,12 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]] [[package]]
name = "smol" name = "smol"
version = "1.2.5" version = "1.2.5"
@ -1482,6 +1489,17 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.24" version = "1.0.24"
@ -1558,6 +1576,15 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.8.0" version = "1.8.0"

View File

@ -57,6 +57,7 @@ serde = { version = "1.0.125", features = ["derive"] }
# Sysinfo is still used in Linux for the ProcessStatus # Sysinfo is still used in Linux for the ProcessStatus
sysinfo = "0.18.2" sysinfo = "0.18.2"
thiserror = "1.0.24" thiserror = "1.0.24"
textwrap = "0.14.2"
toml = "0.5.8" toml = "0.5.8"
tui = { version = "0.16.0", features = ["crossterm"], default-features = false } tui = { version = "0.16.0", features = ["crossterm"], default-features = false }
typed-builder = "0.9.0" typed-builder = "0.9.0"

View File

@ -42,7 +42,7 @@ Note that key bindings are generally case-sensitive.
| ++q++ , ++ctrl+c++ | Quit | | ++q++ , ++ctrl+c++ | Quit |
| ++esc++ | Close dialog windows, search, widgets, or exit expanded mode | | ++esc++ | Close dialog windows, search, widgets, or exit expanded mode |
| ++ctrl+r++ | Resets any collected data | | ++ctrl+r++ | Resets any collected data |
| ++f++ | Toggles freezing, which stops new data from being shown | | ++f++ | Toggles freezing, stopping new data from being shown |
| ++question++ | Open help menu | | ++question++ | Open help menu |
| ++e++ | Toggle expanding the currently selected widget | | ++e++ | Toggle expanding the currently selected widget |
| ++ctrl+up++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above | | ++ctrl+up++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above |

View File

@ -13,7 +13,7 @@ use std::{
time::Instant, time::Instant,
}; };
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent};
use fxhash::FxHashMap; use fxhash::FxHashMap;
use indextree::{Arena, NodeId}; use indextree::{Arena, NodeId};
use unicode_segmentation::GraphemeCursor; use unicode_segmentation::GraphemeCursor;
@ -33,7 +33,7 @@ use crate::{
BottomEvent, Pid, BottomEvent, Pid,
}; };
use self::event::{EventResult, ReturnSignal, WidgetEventResult}; use self::event::{ComponentEventResult, EventResult, ReturnSignal};
const MAX_SEARCH_LENGTH: usize = 200; const MAX_SEARCH_LENGTH: usize = 200;
@ -140,8 +140,6 @@ pub struct AppState {
// --- Eventually delete/rewrite --- // --- Eventually delete/rewrite ---
pub delete_dialog_state: AppDeleteDialogState, pub delete_dialog_state: AppDeleteDialogState,
pub help_dialog_state: AppHelpDialogState,
// --- TO DELETE --- // --- TO DELETE ---
pub cpu_state: CpuState, pub cpu_state: CpuState,
pub mem_state: MemState, pub mem_state: MemState,
@ -172,6 +170,8 @@ pub struct AppState {
pub layout_tree: Arena<LayoutNode>, pub layout_tree: Arena<LayoutNode>,
pub layout_tree_root: NodeId, pub layout_tree_root: NodeId,
frozen_state: FrozenState, frozen_state: FrozenState,
pub help_dialog: DialogState<HelpDialog>,
} }
impl AppState { impl AppState {
@ -204,7 +204,6 @@ impl AppState {
data_collection: Default::default(), data_collection: Default::default(),
is_expanded: Default::default(), is_expanded: Default::default(),
delete_dialog_state: Default::default(), delete_dialog_state: Default::default(),
help_dialog_state: Default::default(),
cpu_state: Default::default(), cpu_state: Default::default(),
mem_state: Default::default(), mem_state: Default::default(),
net_state: Default::default(), net_state: Default::default(),
@ -222,6 +221,7 @@ impl AppState {
is_force_redraw: Default::default(), is_force_redraw: Default::default(),
is_determining_widget_boundary: Default::default(), is_determining_widget_boundary: Default::default(),
frozen_state: Default::default(), frozen_state: Default::default(),
help_dialog: Default::default(),
} }
} }
@ -277,7 +277,7 @@ impl AppState {
let c = c.to_ascii_lowercase(); let c = c.to_ascii_lowercase();
match c { match c {
'q' => EventResult::Quit, 'q' => EventResult::Quit,
'e' => { 'e' if !self.help_dialog.is_showing() => {
if self.app_config_fields.use_basic_mode { if self.app_config_fields.use_basic_mode {
EventResult::NoRedraw EventResult::NoRedraw
} else { } else {
@ -285,11 +285,11 @@ impl AppState {
EventResult::Redraw EventResult::Redraw
} }
} }
'?' => { '?' if !self.help_dialog.is_showing() => {
self.help_dialog_state.is_showing_help = true; self.help_dialog.show();
EventResult::Redraw EventResult::Redraw
} }
'f' => { 'f' if !self.help_dialog.is_showing() => {
self.toggle_freeze(); self.toggle_freeze();
if !self.is_frozen() { if !self.is_frozen() {
let data_collection = &self.data_collection; let data_collection = &self.data_collection;
@ -331,17 +331,59 @@ impl AppState {
} }
} }
/// Quick and dirty handler to convert [`ComponentEventResult`]s to [`EventResult`]s, and handle [`ReturnSignal`]s.
fn convert_widget_event_result(&mut self, w: ComponentEventResult) -> EventResult {
match w {
ComponentEventResult::Unhandled => EventResult::NoRedraw,
ComponentEventResult::Redraw => EventResult::Redraw,
ComponentEventResult::NoRedraw => EventResult::NoRedraw,
ComponentEventResult::Signal(signal) => match signal {
ReturnSignal::KillProcess => {
todo!()
}
ReturnSignal::Update => {
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
match &self.frozen_state {
FrozenState::NotFrozen => {
widget.update_data(&self.data_collection);
}
FrozenState::Frozen(frozen_data) => {
widget.update_data(frozen_data);
}
}
}
EventResult::Redraw
}
},
}
}
/// Handles a [`KeyEvent`], and returns an [`EventResult`].
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
let result = if let DialogState::Shown(help_dialog) = &mut self.help_dialog {
help_dialog.handle_key_event(event)
} else if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
widget.handle_key_event(event)
} else {
ComponentEventResult::Unhandled
};
match result {
ComponentEventResult::Unhandled => self.handle_global_key_event(event),
_ => self.convert_widget_event_result(result),
}
}
/// Handles a global [`KeyEvent`], and returns an [`EventResult`]. /// Handles a global [`KeyEvent`], and returns an [`EventResult`].
fn handle_global_shortcut(&mut self, event: KeyEvent) -> EventResult { fn handle_global_key_event(&mut self, event: KeyEvent) -> EventResult {
if event.modifiers.is_empty() { if event.modifiers.is_empty() {
match event.code { match event.code {
KeyCode::Esc => { KeyCode::Esc => {
if self.is_expanded { if self.is_expanded {
self.is_expanded = false; self.is_expanded = false;
EventResult::Redraw EventResult::Redraw
} else if self.help_dialog_state.is_showing_help { } else if self.help_dialog.is_showing() {
self.help_dialog_state.is_showing_help = false; self.help_dialog.hide();
self.help_dialog_state.scroll_state.current_scroll_index = 0;
EventResult::Redraw EventResult::Redraw
} else if self.delete_dialog_state.is_showing_dd { } else if self.delete_dialog_state.is_showing_dd {
self.close_dd(); self.close_dd();
@ -350,8 +392,13 @@ impl AppState {
EventResult::NoRedraw EventResult::NoRedraw
} }
} }
KeyCode::Char(c) => self.handle_global_char(c), _ => {
_ => EventResult::NoRedraw, if let KeyCode::Char(c) = event.code {
self.handle_global_char(c)
} else {
EventResult::NoRedraw
}
}
} }
} else if let KeyModifiers::CONTROL = event.modifiers { } else if let KeyModifiers::CONTROL = event.modifiers {
match event.code { match event.code {
@ -384,30 +431,55 @@ impl AppState {
} }
} }
/// Quick and dirty handler to convert [`WidgetEventResult`]s to [`EventResult`]s, and handle [`ReturnSignal`]s. /// Handles a [`MouseEvent`].
fn convert_widget_event_result(&mut self, w: WidgetEventResult) -> EventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
match w { if let DialogState::Shown(help_dialog) = &mut self.help_dialog {
WidgetEventResult::Quit => EventResult::Quit, let result = help_dialog.handle_mouse_event(event);
WidgetEventResult::Redraw => EventResult::Redraw, self.convert_widget_event_result(result)
WidgetEventResult::NoRedraw => EventResult::NoRedraw, } else if self.is_expanded {
WidgetEventResult::Signal(signal) => match signal { if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
ReturnSignal::KillProcess => { let result = widget.handle_mouse_event(event);
todo!() self.convert_widget_event_result(result)
} } else {
ReturnSignal::Update => { EventResult::NoRedraw
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { }
match &self.frozen_state { } else {
FrozenState::NotFrozen => { let mut returned_result = EventResult::NoRedraw;
widget.update_data(&self.data_collection); for (id, widget) in self.widget_lookup_map.iter_mut() {
} if widget.does_border_intersect_mouse(&event) {
FrozenState::Frozen(frozen_data) => { let result = widget.handle_mouse_event(event);
widget.update_data(frozen_data);
} let new_id;
match widget.selectable_type() {
SelectableType::Selectable => {
new_id = *id;
}
SelectableType::Unselectable => {
let result = widget.handle_mouse_event(event);
return self.convert_widget_event_result(result);
}
SelectableType::Redirect(redirected_id) => {
new_id = redirected_id;
} }
} }
EventResult::Redraw
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;
}
} }
}, }
returned_result
} }
} }
@ -415,83 +487,16 @@ impl AppState {
/// whether the app now requires a redraw. /// whether the app now requires a redraw.
pub fn handle_event(&mut self, event: BottomEvent) -> EventResult { pub fn handle_event(&mut self, event: BottomEvent) -> EventResult {
match event { match event {
BottomEvent::KeyInput(event) => { BottomEvent::KeyInput(event) => self.handle_key_event(event),
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
let result = widget.handle_key_event(event);
match self.convert_widget_event_result(result) {
EventResult::Quit => EventResult::Quit,
EventResult::Redraw => EventResult::Redraw,
EventResult::NoRedraw => self.handle_global_shortcut(event),
}
} else {
self.handle_global_shortcut(event)
}
}
BottomEvent::MouseInput(event) => { BottomEvent::MouseInput(event) => {
// Not great, but basically a blind lookup through the table for anything that clips the click location. // Not great, but basically a blind lookup through the table for anything that clips the click location.
// TODO: Would be cool to use a kd-tree or something like that in the future. self.handle_mouse_event(event)
match &event.kind {
MouseEventKind::Down(MouseButton::Left) => {
if self.is_expanded {
if let Some(widget) =
self.widget_lookup_map.get_mut(&self.selected_widget)
{
let result = widget.handle_mouse_event(event);
return self.convert_widget_event_result(result);
}
} else {
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;
}
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 {
return self.convert_widget_event_result(result);
} 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);
return EventResult::Redraw;
}
}
}
}
}
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget)
{
let result = widget.handle_mouse_event(event);
return self.convert_widget_event_result(result);
}
}
_ => {}
}
EventResult::NoRedraw
} }
BottomEvent::Update(new_data) => { BottomEvent::Update(new_data) => {
self.data_collection.eat_data(new_data); self.data_collection.eat_data(new_data);
// TODO: Optimization for dialogs; don't redraw here.
if !self.is_frozen() { if !self.is_frozen() {
let data_collection = &self.data_collection; let data_collection = &self.data_collection;
self.widget_lookup_map self.widget_lookup_map
@ -515,78 +520,6 @@ impl AppState {
} }
} }
pub fn on_esc(&mut self) {
self.reset_multi_tap_keys();
if self.is_in_dialog() {
if self.help_dialog_state.is_showing_help {
self.help_dialog_state.is_showing_help = false;
self.help_dialog_state.scroll_state.current_scroll_index = 0;
} else {
self.close_dd();
}
self.is_force_redraw = true;
} else {
match self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(current_proc_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
if current_proc_state.is_search_enabled() || current_proc_state.is_sort_open
{
current_proc_state
.process_search_state
.search_state
.is_enabled = false;
current_proc_state.is_sort_open = false;
self.is_force_redraw = true;
return;
}
}
}
BottomWidgetType::ProcSearch => {
if let Some(current_proc_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id - 1)
{
if current_proc_state.is_search_enabled() {
current_proc_state
.process_search_state
.search_state
.is_enabled = false;
self.move_widget_selection(&WidgetDirection::Up);
self.is_force_redraw = true;
return;
}
}
}
BottomWidgetType::ProcSort => {
if let Some(current_proc_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id - 2)
{
if current_proc_state.is_sort_open {
current_proc_state.columns.current_scroll_position =
current_proc_state.columns.backup_prev_scroll_position;
current_proc_state.is_sort_open = false;
self.move_widget_selection(&WidgetDirection::Right);
self.is_force_redraw = true;
return;
}
}
}
_ => {}
}
if self.is_expanded {
self.is_expanded = false;
self.is_force_redraw = true;
}
}
}
pub fn is_in_search_widget(&self) -> bool { pub fn is_in_search_widget(&self) -> bool {
matches!( matches!(
self.current_widget.widget_type, self.current_widget.widget_type,
@ -600,7 +533,7 @@ impl AppState {
} }
fn is_in_dialog(&self) -> bool { fn is_in_dialog(&self) -> bool {
self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd self.delete_dialog_state.is_showing_dd
} }
fn ignore_normal_keybinds(&self) -> bool { fn ignore_normal_keybinds(&self) -> bool {
@ -1097,7 +1030,7 @@ impl AppState {
pub fn on_up_key(&mut self) { pub fn on_up_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
self.decrement_position_count(); self.decrement_position_count();
} else if self.help_dialog_state.is_showing_help { } else if self.help_dialog.is_showing() {
self.help_scroll_up(); self.help_scroll_up();
} else if self.delete_dialog_state.is_showing_dd { } else if self.delete_dialog_state.is_showing_dd {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -1118,7 +1051,7 @@ impl AppState {
pub fn on_down_key(&mut self) { pub fn on_down_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
self.increment_position_count(); self.increment_position_count();
} else if self.help_dialog_state.is_showing_help { } else if self.help_dialog.is_showing() {
self.help_scroll_down(); self.help_scroll_down();
} else if self.delete_dialog_state.is_showing_dd { } else if self.delete_dialog_state.is_showing_dd {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -1596,19 +1529,9 @@ impl AppState {
} }
} }
self.handle_char(caught_char); self.handle_char(caught_char);
} else if self.help_dialog_state.is_showing_help { } else if self.help_dialog.is_showing() {
match caught_char { match caught_char {
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {}
let potential_index = caught_char.to_digit(10);
if let Some(potential_index) = potential_index {
if (potential_index as usize) < self.help_dialog_state.index_shortcuts.len()
{
self.help_scroll_to_or_max(
self.help_dialog_state.index_shortcuts[potential_index as usize],
);
}
}
}
'j' | 'k' | 'g' | 'G' => self.handle_char(caught_char), 'j' | 'k' | 'g' | 'G' => self.handle_char(caught_char),
_ => {} _ => {}
} }
@ -1792,7 +1715,7 @@ impl AppState {
} }
} }
'?' => { '?' => {
self.help_dialog_state.is_showing_help = true; self.help_dialog.show();
self.is_force_redraw = true; self.is_force_redraw = true;
} }
'H' | 'A' => self.move_widget_selection(&WidgetDirection::Left), 'H' | 'A' => self.move_widget_selection(&WidgetDirection::Left),
@ -1863,7 +1786,6 @@ impl AppState {
} }
fn expand_widget(&mut self) { fn expand_widget(&mut self) {
// TODO: [BASIC] Expansion in basic mode.
if !self.ignore_normal_keybinds() && !self.app_config_fields.use_basic_mode { if !self.ignore_normal_keybinds() && !self.app_config_fields.use_basic_mode {
// Pop-out mode. We ignore if in process search. // Pop-out mode. We ignore if in process search.
@ -2256,7 +2178,7 @@ impl AppState {
{ {
if proc_widget_state.is_sort_open { if proc_widget_state.is_sort_open {
if let Some(proc_sort_widget) = self.widget_map.get(&new_widget_id) { if let Some(proc_sort_widget) = self.widget_map.get(&new_widget_id) {
self.current_widget = proc_sort_widget.clone(); // TODO: Could I remove this clone w/ static references? self.current_widget = proc_sort_widget.clone();
} }
} }
} }
@ -2378,8 +2300,8 @@ impl AppState {
_ => {} _ => {}
} }
self.reset_multi_tap_keys(); self.reset_multi_tap_keys();
} else if self.help_dialog_state.is_showing_help { } else if self.help_dialog.is_showing() {
self.help_dialog_state.scroll_state.current_scroll_index = 0; // self.help_dialog_state.scroll_state.current_scroll_index = 0;
} else if self.delete_dialog_state.is_showing_dd { } else if self.delete_dialog_state.is_showing_dd {
self.delete_dialog_state.selected_signal = KillSignal::Cancel; self.delete_dialog_state.selected_signal = KillSignal::Cancel;
} }
@ -2517,26 +2439,17 @@ impl AppState {
} }
fn help_scroll_up(&mut self) { fn help_scroll_up(&mut self) {
if self.help_dialog_state.scroll_state.current_scroll_index > 0 { // if self.help_dialog_state.scroll_state.current_scroll_index > 0 {
self.help_dialog_state.scroll_state.current_scroll_index -= 1; // self.help_dialog_state.scroll_state.current_scroll_index -= 1;
} // }
} }
fn help_scroll_down(&mut self) { fn help_scroll_down(&mut self) {
if self.help_dialog_state.scroll_state.current_scroll_index + 1 // if self.help_dialog_state.scroll_state.current_scroll_index + 1
< self.help_dialog_state.scroll_state.max_scroll_index // < self.help_dialog_state.scroll_state.max_scroll_index
{ // {
self.help_dialog_state.scroll_state.current_scroll_index += 1; // self.help_dialog_state.scroll_state.current_scroll_index += 1;
} // }
}
fn help_scroll_to_or_max(&mut self, new_position: u16) {
if new_position < self.help_dialog_state.scroll_state.max_scroll_index {
self.help_dialog_state.scroll_state.current_scroll_index = new_position;
} else {
self.help_dialog_state.scroll_state.current_scroll_index =
self.help_dialog_state.scroll_state.max_scroll_index - 1;
}
} }
fn on_plus(&mut self) { fn on_plus(&mut self) {

View File

@ -224,15 +224,12 @@ impl DataCollection {
} }
fn eat_temp(&mut self, temperature_sensors: Vec<temperature::TempHarvest>) { fn eat_temp(&mut self, temperature_sensors: Vec<temperature::TempHarvest>) {
// TODO: [PO] To implement
self.temp_harvest = temperature_sensors.to_vec(); self.temp_harvest = temperature_sensors.to_vec();
} }
fn eat_disks( fn eat_disks(
&mut self, disks: Vec<disks::DiskHarvest>, io: disks::IoHarvest, harvested_time: Instant, &mut self, disks: Vec<disks::DiskHarvest>, io: disks::IoHarvest, harvested_time: Instant,
) { ) {
// TODO: [PO] To implement
let time_since_last_harvest = harvested_time let time_since_last_harvest = harvested_time
.duration_since(self.current_instant) .duration_since(self.current_instant)
.as_secs_f64(); .as_secs_f64();

View File

@ -27,7 +27,6 @@ use std::borrow::Cow;
use crate::Pid; use crate::Pid;
// TODO: Add value so we know if it's sorted ascending or descending by default?
#[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum ProcessSorting { pub enum ProcessSorting {
CpuPercent, CpuPercent,

View File

@ -57,7 +57,6 @@ fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool {
fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) { fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) {
// By default, sort temperature, then by alphabetically! // By default, sort temperature, then by alphabetically!
// TODO: [TEMPS] Allow users to control this.
// Note we sort in reverse here; we want greater temps to be higher priority. // Note we sort in reverse here; we want greater temps to be higher priority.
temperature_vec.sort_by(|a, b| match a.temperature.partial_cmp(&b.temperature) { temperature_vec.sort_by(|a, b| match a.temperature.partial_cmp(&b.temperature) {

View File

@ -1,6 +1,5 @@
use std::time::{Duration, Instant}; pub mod multi_key;
pub use multi_key::*;
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
/// These are "signals" that are sent along with an [`WidgetEventResult`] to signify a potential additional action /// These are "signals" that are sent along with an [`WidgetEventResult`] to signify a potential additional action
/// that the caller must do, along with the "core" result of either drawing or redrawing. /// that the caller must do, along with the "core" result of either drawing or redrawing.
@ -32,9 +31,9 @@ pub enum EventResult {
/// The results of a widget handling some event, like a mouse or key event, /// The results of a widget handling some event, like a mouse or key event,
/// signifying what the program should then do next. /// signifying what the program should then do next.
#[derive(Debug)] #[derive(Debug)]
pub enum WidgetEventResult { pub enum ComponentEventResult {
/// Kill the program. /// The event isn't handled by the widget, and should be propagated to the parent.
Quit, Unhandled,
/// Trigger a redraw. /// Trigger a redraw.
Redraw, Redraw,
/// Don't trigger a redraw. /// Don't trigger a redraw.
@ -50,101 +49,3 @@ pub enum SelectionAction {
/// This event occurs if the widget did not handle the selection action; the caller must handle it. /// This event occurs if the widget did not handle the selection action; the caller must handle it.
NotHandled, NotHandled,
} }
/// The states a [`MultiKey`] can be in.
enum MultiKeyState {
/// Currently not waiting on any next input.
Idle,
/// Waiting for the next input, with a given trigger [`Instant`].
Waiting {
/// When it was triggered.
trigger_instant: Instant,
/// What part of the pattern it is at.
checked_index: usize,
},
}
/// The possible outcomes of calling [`MultiKey::input`].
pub enum MultiKeyResult {
/// Returned when a character was *accepted*, but has not completed the sequence required.
Accepted,
/// Returned when a character is accepted and completes the sequence.
Completed,
/// Returned if a character breaks the sequence or if it has already timed out.
Rejected,
}
/// A struct useful for managing multi-key keybinds.
pub struct MultiKey {
state: MultiKeyState,
pattern: Vec<char>,
}
impl MultiKey {
/// Creates a new [`MultiKey`] with a given pattern and timeout.
pub fn register(pattern: Vec<char>) -> Self {
Self {
state: MultiKeyState::Idle,
pattern,
}
}
/// Resets to an idle state.
fn reset(&mut self) {
self.state = MultiKeyState::Idle;
}
/// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
/// - Accepting the char and moving to the next state
/// - Completing the multi-key pattern
/// - Rejecting it
///
/// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
/// before trying to check the char.
pub fn input(&mut self, c: char) -> MultiKeyResult {
match &mut self.state {
MultiKeyState::Idle => {
if let Some(first) = self.pattern.first() {
if *first == c {
self.state = MultiKeyState::Waiting {
trigger_instant: Instant::now(),
checked_index: 0,
};
return MultiKeyResult::Accepted;
}
}
MultiKeyResult::Rejected
}
MultiKeyState::Waiting {
trigger_instant,
checked_index,
} => {
if trigger_instant.elapsed() > MAX_TIMEOUT {
// Just reset and recursively call (putting it into Idle).
self.reset();
self.input(c)
} else if let Some(next) = self.pattern.get(*checked_index + 1) {
if *next == c {
*checked_index += 1;
if *checked_index == self.pattern.len() - 1 {
self.reset();
MultiKeyResult::Completed
} else {
MultiKeyResult::Accepted
}
} else {
self.reset();
MultiKeyResult::Rejected
}
} else {
self.reset();
MultiKeyResult::Rejected
}
}
}
}
}

101
src/app/event/multi_key.rs Normal file
View File

@ -0,0 +1,101 @@
use std::time::{Duration, Instant};
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
/// The states a [`MultiKey`] can be in.
enum MultiKeyState {
/// Currently not waiting on any next input.
Idle,
/// Waiting for the next input, with a given trigger [`Instant`].
Waiting {
/// When it was triggered.
trigger_instant: Instant,
/// What part of the pattern it is at.
checked_index: usize,
},
}
/// The possible outcomes of calling [`MultiKey::input`].
pub enum MultiKeyResult {
/// Returned when a character was *accepted*, but has not completed the sequence required.
Accepted,
/// Returned when a character is accepted and completes the sequence.
Completed,
/// Returned if a character breaks the sequence or if it has already timed out.
Rejected,
}
/// A struct useful for managing multi-key keybinds.
pub struct MultiKey {
state: MultiKeyState,
pattern: Vec<char>,
}
impl MultiKey {
/// Creates a new [`MultiKey`] with a given pattern and timeout.
pub fn register(pattern: Vec<char>) -> Self {
Self {
state: MultiKeyState::Idle,
pattern,
}
}
/// Resets to an idle state.
fn reset(&mut self) {
self.state = MultiKeyState::Idle;
}
/// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
/// - Accepting the char and moving to the next state
/// - Completing the multi-key pattern
/// - Rejecting it
///
/// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
/// before trying to check the char.
pub fn input(&mut self, c: char) -> MultiKeyResult {
match &mut self.state {
MultiKeyState::Idle => {
if let Some(first) = self.pattern.first() {
if *first == c {
self.state = MultiKeyState::Waiting {
trigger_instant: Instant::now(),
checked_index: 0,
};
return MultiKeyResult::Accepted;
}
}
MultiKeyResult::Rejected
}
MultiKeyState::Waiting {
trigger_instant,
checked_index,
} => {
if trigger_instant.elapsed() > MAX_TIMEOUT {
// Just reset and recursively call (putting it into Idle).
self.reset();
self.input(c)
} else if let Some(next) = self.pattern.get(*checked_index + 1) {
if *next == c {
*checked_index += 1;
if *checked_index == self.pattern.len() - 1 {
self.reset();
MultiKeyResult::Completed
} else {
MultiKeyResult::Accepted
}
} else {
self.reset();
MultiKeyResult::Rejected
}
} else {
self.reset();
MultiKeyResult::Rejected
}
}
}
}
}

View File

@ -1511,7 +1511,6 @@ pub fn move_widget_selection(
} }
LayoutNode::Widget(_) => { LayoutNode::Widget(_) => {
// Halt! // Halt!
// TODO: How does this handle carousel?
current_id current_id
} }
} }

View File

@ -7,11 +7,10 @@ use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame};
use crate::{ use crate::{
app::{ app::{
event::{SelectionAction, WidgetEventResult}, event::{ComponentEventResult, SelectionAction},
layout_manager::BottomWidgetType, layout_manager::BottomWidgetType,
}, },
canvas::Painter, canvas::Painter,
constants,
options::layout_options::LayoutRule, options::layout_options::LayoutRule,
}; };
@ -20,12 +19,15 @@ mod tui_stuff;
pub mod base; pub mod base;
pub use base::*; pub use base::*;
pub mod dialogs;
pub use dialogs::*;
pub mod bottom_widgets; pub mod bottom_widgets;
pub use bottom_widgets::*; pub use bottom_widgets::*;
use self::tui_stuff::BlockBuilder; use self::tui_stuff::BlockBuilder;
use super::data_farmer::DataCollection; use super::{data_farmer::DataCollection, event::EventResult};
/// A trait for things that are drawn with state. /// A trait for things that are drawn with state.
#[enum_dispatch] #[enum_dispatch]
@ -33,16 +35,16 @@ use super::data_farmer::DataCollection;
pub trait Component { pub trait Component {
/// Handles a [`KeyEvent`]. /// Handles a [`KeyEvent`].
/// ///
/// Defaults to returning [`EventResult::NoRedraw`], indicating nothing should be done. /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event.
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
/// Handles a [`MouseEvent`]. /// Handles a [`MouseEvent`].
/// ///
/// Defaults to returning [`EventResult::Continue`], indicating nothing should be done. /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event.
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
/// Returns a [`Component`]'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*
@ -180,6 +182,41 @@ pub enum TmpBottomWidget {
Empty, 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.
#[derive(Debug)]
pub enum DialogState<D: Default + Component> {
Hidden,
Shown(D),
}
impl<D> Default for DialogState<D>
where
D: Default + Component,
{
fn default() -> Self {
DialogState::Hidden
}
}
impl<D> DialogState<D>
where
D: Default + Component,
{
pub fn is_showing(&self) -> bool {
matches!(self, DialogState::Shown(_))
}
pub fn hide(&mut self) {
*self = DialogState::Hidden;
}
pub fn show(&mut self) {
*self = DialogState::Shown(D::default());
}
}
// ----- Old stuff below ----- // ----- Old stuff below -----
#[derive(Debug)] #[derive(Debug)]
@ -250,7 +287,27 @@ impl Default for AppHelpDialogState {
AppHelpDialogState { AppHelpDialogState {
is_showing_help: false, is_showing_help: false,
scroll_state: ParagraphScrollState::default(), scroll_state: ParagraphScrollState::default(),
index_shortcuts: vec![0; constants::HELP_TEXT.len()], index_shortcuts: vec![],
}
}
}
impl AppHelpDialogState {
pub fn increment(&mut self) -> EventResult {
if self.scroll_state.current_scroll_index < self.scroll_state.max_scroll_index {
self.scroll_state.current_scroll_index += 1;
EventResult::Redraw
} else {
EventResult::NoRedraw
}
}
pub fn decrement(&mut self) -> EventResult {
if self.scroll_state.current_scroll_index > 0 {
self.scroll_state.current_scroll_index -= 1;
EventResult::Redraw
} else {
EventResult::NoRedraw
} }
} }
} }

View File

@ -4,7 +4,7 @@ use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEve
use tui::{layout::Rect, widgets::TableState}; use tui::{layout::Rect, widgets::TableState};
use crate::app::{ use crate::app::{
event::{MultiKey, MultiKeyResult, WidgetEventResult}, event::{ComponentEventResult, MultiKey, MultiKeyResult},
Component, Component,
}; };
@ -138,54 +138,54 @@ impl Scrollable {
} }
} }
fn skip_to_first(&mut self) -> WidgetEventResult { fn skip_to_first(&mut self) -> ComponentEventResult {
if self.current_index != 0 { if self.current_index != 0 {
self.set_index(0); self.set_index(0);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
fn skip_to_last(&mut self) -> WidgetEventResult { fn skip_to_last(&mut self) -> ComponentEventResult {
let last_index = self.num_items - 1; let last_index = self.num_items - 1;
if self.current_index != last_index { if self.current_index != last_index {
self.set_index(last_index); self.set_index(last_index);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
/// Moves *downward* by *incrementing* the current index. /// Moves *downward* by *incrementing* the current index.
fn move_down(&mut self, change_by: usize) -> WidgetEventResult { fn move_down(&mut self, change_by: usize) -> ComponentEventResult {
if self.num_items == 0 { if self.num_items == 0 {
return WidgetEventResult::NoRedraw; return ComponentEventResult::NoRedraw;
} }
let new_index = self.current_index + change_by; let new_index = self.current_index + change_by;
if new_index >= self.num_items || self.current_index == new_index { if new_index >= self.num_items || self.current_index == new_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.set_index(new_index); self.set_index(new_index);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
/// Moves *upward* by *decrementing* the current index. /// Moves *upward* by *decrementing* the current index.
fn move_up(&mut self, change_by: usize) -> WidgetEventResult { fn move_up(&mut self, change_by: usize) -> ComponentEventResult {
if self.num_items == 0 { if self.num_items == 0 {
return WidgetEventResult::NoRedraw; return ComponentEventResult::NoRedraw;
} }
let new_index = self.current_index.saturating_sub(change_by); let new_index = self.current_index.saturating_sub(change_by);
if self.current_index == new_index { if self.current_index == new_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.set_index(new_index); self.set_index(new_index);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
@ -207,7 +207,7 @@ impl Scrollable {
} }
impl Component for Scrollable { impl Component for Scrollable {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
use crossterm::event::KeyCode::{Char, Down, Up}; use crossterm::event::KeyCode::{Char, Down, Up};
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT { if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
@ -218,18 +218,19 @@ impl Component for Scrollable {
Char('k') => self.move_up(1), Char('k') => self.move_up(1),
Char('g') => match self.gg_manager.input('g') { Char('g') => match self.gg_manager.input('g') {
MultiKeyResult::Completed => self.skip_to_first(), MultiKeyResult::Completed => self.skip_to_first(),
MultiKeyResult::Accepted => WidgetEventResult::NoRedraw, MultiKeyResult::Accepted | MultiKeyResult::Rejected => {
MultiKeyResult::Rejected => WidgetEventResult::NoRedraw, ComponentEventResult::NoRedraw
}
}, },
Char('G') => self.skip_to_last(), Char('G') => self.skip_to_last(),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
match event.kind { match event.kind {
MouseEventKind::Down(MouseButton::Left) => { MouseEventKind::Down(MouseButton::Left) => {
if self.does_bounds_intersect_mouse(&event) { if self.does_bounds_intersect_mouse(&event) {
@ -256,11 +257,11 @@ impl Component for Scrollable {
} }
} }
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
MouseEventKind::ScrollDown => self.move_down(1), MouseEventKind::ScrollDown => self.move_down(1),
MouseEventKind::ScrollUp => self.move_up(1), MouseEventKind::ScrollUp => self.move_up(1),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} }

View File

@ -3,7 +3,7 @@ use tui::{backend::Backend, layout::Rect, Frame};
use crate::{ use crate::{
app::{ app::{
event::WidgetEventResult, text_table::SimpleColumn, widgets::tui_stuff::BlockBuilder, event::ComponentEventResult, text_table::SimpleColumn, widgets::tui_stuff::BlockBuilder,
Component, TextTable, Component, TextTable,
}, },
canvas::Painter, canvas::Painter,
@ -69,11 +69,11 @@ impl Component for SortMenu {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
self.table.handle_key_event(event) self.table.handle_key_event(event)
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
self.table.handle_mouse_event(event) self.table.handle_mouse_event(event)
} }
} }

View File

@ -5,7 +5,7 @@ use tui::{backend::Backend, layout::Rect, Frame};
use crate::{ use crate::{
app::{ app::{
event::{ReturnSignal, WidgetEventResult}, event::{ReturnSignal, ComponentEventResult},
widgets::tui_stuff::BlockBuilder, widgets::tui_stuff::BlockBuilder,
Component, TextTable, Component, TextTable,
}, },
@ -391,12 +391,12 @@ impl<S> Component for SortableTextTable<S>
where where
S: SortableColumn, S: SortableColumn,
{ {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
for (index, column) in self.table.columns.iter().enumerate() { for (index, column) in self.table.columns.iter().enumerate() {
if let Some((shortcut, _)) = *column.shortcut() { if let Some((shortcut, _)) = *column.shortcut() {
if shortcut == event { if shortcut == event {
self.set_sort_index(index); self.set_sort_index(index);
return WidgetEventResult::Signal(ReturnSignal::Update); return ComponentEventResult::Signal(ReturnSignal::Update);
} }
} }
} }
@ -404,10 +404,10 @@ where
self.table.scrollable.handle_key_event(event) self.table.scrollable.handle_key_event(event)
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
if let MouseEventKind::Down(MouseButton::Left) = event.kind { if let MouseEventKind::Down(MouseButton::Left) = event.kind {
if !self.does_bounds_intersect_mouse(&event) { if !self.does_bounds_intersect_mouse(&event) {
return WidgetEventResult::NoRedraw; return ComponentEventResult::NoRedraw;
} }
// Note these are representing RELATIVE coordinates! They *need* the above intersection check for validity! // Note these are representing RELATIVE coordinates! They *need* the above intersection check for validity!
@ -419,7 +419,7 @@ where
if let Some((start, end)) = column.get_x_bounds() { if let Some((start, end)) = column.get_x_bounds() {
if x >= start && x <= end { if x >= start && x <= end {
self.set_sort_index(index); self.set_sort_index(index);
return WidgetEventResult::Signal(ReturnSignal::Update); return ComponentEventResult::Signal(ReturnSignal::Update);
} }
} }
} }

View File

@ -13,8 +13,8 @@ use unicode_width::UnicodeWidthStr;
use crate::{ use crate::{
app::{ app::{
event::{ event::{
ComponentEventResult::{self},
ReturnSignal, ReturnSignal,
WidgetEventResult::{self},
}, },
Component, Component,
}, },
@ -85,19 +85,19 @@ impl TextInput {
} }
} }
fn clear_text(&mut self) -> WidgetEventResult { fn clear_text(&mut self) -> ComponentEventResult {
if self.text.is_empty() { if self.text.is_empty() {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.text = String::default(); self.text = String::default();
self.cursor = GraphemeCursor::new(0, 0, true); self.cursor = GraphemeCursor::new(0, 0, true);
self.window_index = Default::default(); self.window_index = Default::default();
self.cursor_direction = CursorDirection::Left; self.cursor_direction = CursorDirection::Left;
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} }
} }
fn move_word_forward(&mut self) -> WidgetEventResult { fn move_word_forward(&mut self) -> ComponentEventResult {
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
if current_index < self.text.len() { if current_index < self.text.len() {
@ -105,30 +105,30 @@ impl TextInput {
if index > 0 { if index > 0 {
self.cursor.set_cursor(index + current_index); self.cursor.set_cursor(index + current_index);
self.cursor_direction = CursorDirection::Right; self.cursor_direction = CursorDirection::Right;
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
} }
self.cursor.set_cursor(self.text.len()); self.cursor.set_cursor(self.text.len());
} }
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
fn move_word_back(&mut self) -> WidgetEventResult { fn move_word_back(&mut self) -> ComponentEventResult {
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
for (index, _word) in self.text[..current_index].unicode_word_indices().rev() { for (index, _word) in self.text[..current_index].unicode_word_indices().rev() {
if index < current_index { if index < current_index {
self.cursor.set_cursor(index); self.cursor.set_cursor(index);
self.cursor_direction = CursorDirection::Left; self.cursor_direction = CursorDirection::Left;
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
} }
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
fn clear_word_from_cursor(&mut self) -> WidgetEventResult { fn clear_word_from_cursor(&mut self) -> ComponentEventResult {
// Fairly simple logic - create the word index iterator, skip the word that intersects with the current // Fairly simple logic - create the word index iterator, skip the word that intersects with the current
// cursor location, draw the rest, update the string. // cursor location, draw the rest, update the string.
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
@ -147,16 +147,16 @@ impl TextInput {
} }
if start_delete_index == current_index { if start_delete_index == current_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.text.drain(start_delete_index..current_index); self.text.drain(start_delete_index..current_index);
self.cursor = GraphemeCursor::new(start_delete_index, self.text.len(), true); self.cursor = GraphemeCursor::new(start_delete_index, self.text.len(), true);
self.cursor_direction = CursorDirection::Left; self.cursor_direction = CursorDirection::Left;
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} }
} }
fn clear_previous_grapheme(&mut self) -> WidgetEventResult { fn clear_previous_grapheme(&mut self) -> ComponentEventResult {
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
if current_index > 0 { if current_index > 0 {
@ -166,13 +166,13 @@ impl TextInput {
self.cursor = GraphemeCursor::new(new_index, self.text.len(), true); self.cursor = GraphemeCursor::new(new_index, self.text.len(), true);
self.cursor_direction = CursorDirection::Left; self.cursor_direction = CursorDirection::Left;
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
fn clear_current_grapheme(&mut self) -> WidgetEventResult { fn clear_current_grapheme(&mut self) -> ComponentEventResult {
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
if current_index < self.text.len() { if current_index < self.text.len() {
@ -182,19 +182,19 @@ impl TextInput {
self.cursor = GraphemeCursor::new(current_index, self.text.len(), true); self.cursor = GraphemeCursor::new(current_index, self.text.len(), true);
self.cursor_direction = CursorDirection::Left; self.cursor_direction = CursorDirection::Left;
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
fn insert_character(&mut self, c: char) -> WidgetEventResult { fn insert_character(&mut self, c: char) -> ComponentEventResult {
let current_index = self.cursor.cur_cursor(); let current_index = self.cursor.cur_cursor();
self.text.insert(current_index, c); self.text.insert(current_index, c);
self.cursor = GraphemeCursor::new(current_index, self.text.len(), true); self.cursor = GraphemeCursor::new(current_index, self.text.len(), true);
self.move_forward(); self.move_forward();
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} }
/// Updates the window indexes and returns the start index. /// Updates the window indexes and returns the start index.
@ -296,29 +296,29 @@ impl Component for TextInput {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
if event.modifiers.is_empty() { if event.modifiers.is_empty() {
match event.code { match event.code {
KeyCode::Left => { KeyCode::Left => {
let original_cursor = self.cursor.cur_cursor(); let original_cursor = self.cursor.cur_cursor();
if self.move_back() == original_cursor { if self.move_back() == original_cursor {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
KeyCode::Right => { KeyCode::Right => {
let original_cursor = self.cursor.cur_cursor(); let original_cursor = self.cursor.cur_cursor();
if self.move_forward() == original_cursor { if self.move_forward() == original_cursor {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
KeyCode::Backspace => self.clear_previous_grapheme(), KeyCode::Backspace => self.clear_previous_grapheme(),
KeyCode::Delete => self.clear_current_grapheme(), KeyCode::Delete => self.clear_current_grapheme(),
KeyCode::Char(c) => self.insert_character(c), KeyCode::Char(c) => self.insert_character(c),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else if let KeyModifiers::CONTROL = event.modifiers { } else if let KeyModifiers::CONTROL = event.modifiers {
match event.code { match event.code {
@ -326,46 +326,46 @@ impl Component for TextInput {
let prev_index = self.cursor.cur_cursor(); let prev_index = self.cursor.cur_cursor();
self.cursor.set_cursor(0); self.cursor.set_cursor(0);
if self.cursor.cur_cursor() == prev_index { if self.cursor.cur_cursor() == prev_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
KeyCode::Char('e') => { KeyCode::Char('e') => {
let prev_index = self.cursor.cur_cursor(); let prev_index = self.cursor.cur_cursor();
self.cursor.set_cursor(self.text.len()); self.cursor.set_cursor(self.text.len());
if self.cursor.cur_cursor() == prev_index { if self.cursor.cur_cursor() == prev_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
KeyCode::Char('u') => self.clear_text(), KeyCode::Char('u') => self.clear_text(),
KeyCode::Char('w') => self.clear_word_from_cursor(), KeyCode::Char('w') => self.clear_word_from_cursor(),
KeyCode::Char('h') => self.clear_previous_grapheme(), KeyCode::Char('h') => self.clear_previous_grapheme(),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else if let KeyModifiers::ALT = event.modifiers { } else if let KeyModifiers::ALT = event.modifiers {
match event.code { match event.code {
KeyCode::Char('b') => self.move_word_back(), KeyCode::Char('b') => self.move_word_back(),
KeyCode::Char('f') => self.move_word_forward(), KeyCode::Char('f') => self.move_word_forward(),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
// We are assuming this is within bounds... // We are assuming this is within bounds...
let x = event.column; let x = event.column;
let widget_x = self.bounds.x + 2; let widget_x = self.bounds.x + 2;
if x >= widget_x { if x >= widget_x {
// TODO: do this // TODO: Do this at some point after refactor
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
} }

View File

@ -15,7 +15,7 @@ use tui::{
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{
app::{event::WidgetEventResult, widgets::tui_stuff::BlockBuilder, Component, Scrollable}, app::{event::ComponentEventResult, widgets::tui_stuff::BlockBuilder, Component, Scrollable},
canvas::Painter, canvas::Painter,
constants::TABLE_GAP_HEIGHT_LIMIT, constants::TABLE_GAP_HEIGHT_LIMIT,
}; };
@ -130,7 +130,7 @@ where
pub show_gap: bool, pub show_gap: bool,
/// The bounding box of the [`TextTable`]. /// The bounding box of the [`TextTable`].
pub bounds: Rect, // TODO: Consider moving bounds to something else??? pub bounds: Rect, // TODO: Consider moving bounds to something else?
/// The bounds including the border, if there is one. /// The bounds including the border, if there is one.
pub border_bounds: Rect, pub border_bounds: Rect,
@ -492,19 +492,19 @@ impl<C> Component for TextTable<C>
where where
C: TableColumn, C: TableColumn,
{ {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
if self.selectable { if self.selectable {
self.scrollable.handle_key_event(event) self.scrollable.handle_key_event(event)
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
if self.selectable { if self.selectable {
self.scrollable.handle_mouse_event(event) self.scrollable.handle_mouse_event(event)
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }

View File

@ -15,7 +15,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
event::WidgetEventResult, event::ComponentEventResult,
widgets::tui_stuff::{ widgets::tui_stuff::{
custom_legend_chart::{Axis, Dataset}, custom_legend_chart::{Axis, Dataset},
TimeChart, TimeChart,
@ -160,62 +160,62 @@ impl TimeGraph {
} }
/// Handles a char `c`. /// Handles a char `c`.
fn handle_char(&mut self, c: char) -> WidgetEventResult { fn handle_char(&mut self, c: char) -> ComponentEventResult {
match c { match c {
'-' => self.zoom_out(), '-' => self.zoom_out(),
'+' => self.zoom_in(), '+' => self.zoom_in(),
'=' => self.reset_zoom(), '=' => self.reset_zoom(),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::NoRedraw,
} }
} }
fn zoom_in(&mut self) -> WidgetEventResult { fn zoom_in(&mut self) -> ComponentEventResult {
let new_time = self.current_display_time.saturating_sub(self.time_interval); let new_time = self.current_display_time.saturating_sub(self.time_interval);
if self.current_display_time == new_time { if self.current_display_time == new_time {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else if new_time >= self.min_duration { } else if new_time >= self.min_duration {
self.current_display_time = new_time; self.current_display_time = new_time;
self.autohide_timer.start_display_timer(); self.autohide_timer.start_display_timer();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else if new_time != self.min_duration { } else if new_time != self.min_duration {
self.current_display_time = self.min_duration; self.current_display_time = self.min_duration;
self.autohide_timer.start_display_timer(); self.autohide_timer.start_display_timer();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
fn zoom_out(&mut self) -> WidgetEventResult { fn zoom_out(&mut self) -> ComponentEventResult {
let new_time = self.current_display_time + self.time_interval; let new_time = self.current_display_time + self.time_interval;
if self.current_display_time == new_time { if self.current_display_time == new_time {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else if new_time <= self.max_duration { } else if new_time <= self.max_duration {
self.current_display_time = new_time; self.current_display_time = new_time;
self.autohide_timer.start_display_timer(); self.autohide_timer.start_display_timer();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else if new_time != self.max_duration { } else if new_time != self.max_duration {
self.current_display_time = self.max_duration; self.current_display_time = self.max_duration;
self.autohide_timer.start_display_timer(); self.autohide_timer.start_display_timer();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} }
} }
fn reset_zoom(&mut self) -> WidgetEventResult { fn reset_zoom(&mut self) -> ComponentEventResult {
if self.current_display_time == self.default_time_value { if self.current_display_time == self.default_time_value {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.current_display_time = self.default_time_value; self.current_display_time = self.default_time_value;
self.autohide_timer.start_display_timer(); self.autohide_timer.start_display_timer();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
@ -307,24 +307,24 @@ impl TimeGraph {
} }
impl Component for TimeGraph { impl Component for TimeGraph {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
use crossterm::event::KeyCode::Char; use crossterm::event::KeyCode::Char;
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT { if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
match event.code { match event.code {
Char(c) => self.handle_char(c), Char(c) => self.handle_char(c),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
match event.kind { match event.kind {
MouseEventKind::ScrollDown => self.zoom_out(), MouseEventKind::ScrollDown => self.zoom_out(),
MouseEventKind::ScrollUp => self.zoom_in(), MouseEventKind::ScrollUp => self.zoom_in(),
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} }

View File

@ -8,7 +8,8 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
event::WidgetEventResult, widgets::tui_stuff::PipeGauge, Component, DataCollection, Widget, event::ComponentEventResult, widgets::tui_stuff::PipeGauge, Component, DataCollection,
Widget,
}, },
canvas::Painter, canvas::Painter,
constants::SIDE_BORDERS, constants::SIDE_BORDERS,
@ -54,13 +55,13 @@ impl Component for BasicMem {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
match event.code { match event.code {
KeyCode::Char('%') if event.modifiers.is_empty() => { KeyCode::Char('%') => {
self.use_percent = !self.use_percent; self.use_percent = !self.use_percent;
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} }
} }

View File

@ -14,7 +14,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
data_farmer::DataCollection, does_bound_intersect_coordinate, event::WidgetEventResult, data_farmer::DataCollection, does_bound_intersect_coordinate, event::ComponentEventResult,
widgets::tui_stuff::PipeGauge, Component, Widget, widgets::tui_stuff::PipeGauge, Component, Widget,
}, },
canvas::Painter, canvas::Painter,
@ -114,44 +114,44 @@ impl Component for BatteryTable {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
if event.modifiers.is_empty() { if event.modifiers.is_empty() {
match event.code { match event.code {
KeyCode::Left => { KeyCode::Left => {
let current_index = self.selected_index; let current_index = self.selected_index;
self.decrement_index(); self.decrement_index();
if current_index == self.selected_index { if current_index == self.selected_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
KeyCode::Right => { KeyCode::Right => {
let current_index = self.selected_index; let current_index = self.selected_index;
self.increment_index(); self.increment_index();
if current_index == self.selected_index { if current_index == self.selected_index {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
for (itx, bound) in self.tab_bounds.iter().enumerate() { for (itx, bound) in self.tab_bounds.iter().enumerate() {
if does_bound_intersect_coordinate(event.column, event.row, *bound) if does_bound_intersect_coordinate(event.column, event.row, *bound)
&& itx < self.battery_data.len() && itx < self.battery_data.len()
{ {
self.selected_index = itx; self.selected_index = itx;
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
} }
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
@ -183,7 +183,7 @@ impl Widget for BatteryTable {
.block() .block()
.selected(selected) .selected(selected)
.borders(self.block_border) .borders(self.block_border)
.expanded(expanded) .show_esc(expanded)
.build(painter, area); .build(painter, area);
let inner_area = block.inner(area); let inner_area = block.inner(area);

View File

@ -12,7 +12,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
does_bound_intersect_coordinate, event::WidgetEventResult, Component, SelectableType, does_bound_intersect_coordinate, event::ComponentEventResult, Component, SelectableType,
Widget, Widget,
}, },
canvas::Painter, canvas::Painter,
@ -164,7 +164,7 @@ impl Component for Carousel {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
match event.kind { match event.kind {
crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left) => { crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left) => {
let x = event.column; let x = event.column;
@ -172,15 +172,15 @@ impl Component for Carousel {
if does_bound_intersect_coordinate(x, y, self.left_button_bounds) { if does_bound_intersect_coordinate(x, y, self.left_button_bounds) {
self.decrement_index(); self.decrement_index();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else if does_bound_intersect_coordinate(x, y, self.right_button_bounds) { } else if does_bound_intersect_coordinate(x, y, self.right_button_bounds) {
self.increment_index(); self.increment_index();
WidgetEventResult::Redraw ComponentEventResult::Redraw
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} }
} }

View File

@ -9,7 +9,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
event::{SelectionAction, WidgetEventResult}, event::{ComponentEventResult, SelectionAction},
text_table::SimpleColumn, text_table::SimpleColumn,
time_graph::TimeGraphData, time_graph::TimeGraphData,
AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection, AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection,
@ -118,21 +118,21 @@ impl CpuGraph {
} }
impl Component for CpuGraph { impl Component for CpuGraph {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
match self.selected { match self.selected {
CpuGraphSelection::Graph => self.graph.handle_key_event(event), CpuGraphSelection::Graph => self.graph.handle_key_event(event),
CpuGraphSelection::Legend => self.legend.handle_key_event(event), CpuGraphSelection::Legend => self.legend.handle_key_event(event),
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
if self.graph.does_border_intersect_mouse(&event) { if self.graph.does_border_intersect_mouse(&event) {
if let CpuGraphSelection::Graph = self.selected { if let CpuGraphSelection::Graph = self.selected {
self.graph.handle_mouse_event(event) self.graph.handle_mouse_event(event)
} else { } else {
self.selected = CpuGraphSelection::Graph; self.selected = CpuGraphSelection::Graph;
self.graph.handle_mouse_event(event); self.graph.handle_mouse_event(event);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} else if self.legend.does_border_intersect_mouse(&event) { } else if self.legend.does_border_intersect_mouse(&event) {
if let CpuGraphSelection::Legend = self.selected { if let CpuGraphSelection::Legend = self.selected {
@ -140,10 +140,10 @@ impl Component for CpuGraph {
} else { } else {
self.selected = CpuGraphSelection::Legend; self.selected = CpuGraphSelection::Legend;
self.legend.handle_mouse_event(event); self.legend.handle_mouse_event(event);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
@ -193,7 +193,7 @@ impl Widget for CpuGraph {
let legend_block = self let legend_block = self
.block() .block()
.selected(selected && matches!(&self.selected, CpuGraphSelection::Legend)) .selected(selected && matches!(&self.selected, CpuGraphSelection::Legend))
.expanded(expanded) .show_esc(expanded)
.hide_title(true); .hide_title(true);
let legend_data = self let legend_data = self
@ -279,7 +279,7 @@ impl Widget for CpuGraph {
let graph_block = self let graph_block = self
.block() .block()
.selected(selected && matches!(&self.selected, CpuGraphSelection::Graph)) .selected(selected && matches!(&self.selected, CpuGraphSelection::Graph))
.expanded(expanded) .show_esc(expanded)
.build(painter, graph_block_area); .build(painter, graph_block_area);
self.graph.draw_tui_chart( self.graph.draw_tui_chart(

View File

@ -5,7 +5,7 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame};
use crate::{ use crate::{
app::{ app::{
data_farmer::DataCollection, event::WidgetEventResult, data_farmer::DataCollection, event::ComponentEventResult,
sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppScrollWidgetState, sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppScrollWidgetState,
CanvasTableWidthState, Component, TextTable, Widget, CanvasTableWidthState, Component, TextTable, Widget,
}, },
@ -105,11 +105,11 @@ impl DiskTable {
} }
impl Component for DiskTable { impl Component for DiskTable {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
self.table.handle_key_event(event) self.table.handle_key_event(event)
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
self.table.handle_mouse_event(event) self.table.handle_mouse_event(event)
} }
@ -135,7 +135,7 @@ impl Widget for DiskTable {
.block() .block()
.selected(selected) .selected(selected)
.borders(self.block_border) .borders(self.block_border)
.expanded(expanded); .show_esc(expanded);
self.table.draw_tui_table( self.table.draw_tui_table(
painter, painter,

View File

@ -4,7 +4,7 @@ use crossterm::event::{KeyEvent, MouseEvent};
use tui::{backend::Backend, layout::Rect}; use tui::{backend::Backend, layout::Rect};
use crate::{ use crate::{
app::{event::WidgetEventResult, time_graph::TimeGraphData, DataCollection}, app::{event::ComponentEventResult, time_graph::TimeGraphData, DataCollection},
app::{Component, TimeGraph, Widget}, app::{Component, TimeGraph, Widget},
data_conversion::{convert_mem_data_points, convert_mem_labels, convert_swap_data_points}, data_conversion::{convert_mem_data_points, convert_mem_labels, convert_swap_data_points},
options::layout_options::LayoutRule, options::layout_options::LayoutRule,
@ -63,11 +63,11 @@ impl MemGraph {
} }
impl Component for MemGraph { impl Component for MemGraph {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
self.graph.handle_key_event(event) self.graph.handle_key_event(event)
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
self.graph.handle_mouse_event(event) self.graph.handle_mouse_event(event)
} }
@ -92,7 +92,7 @@ impl Widget for MemGraph {
let block = self let block = self
.block() .block()
.selected(selected) .selected(selected)
.expanded(expanded) .show_esc(expanded)
.build(painter, area); .build(painter, area);
let mut chart_data = Vec::with_capacity(2); let mut chart_data = Vec::with_capacity(2);

View File

@ -1,5 +1,6 @@
use std::{borrow::Cow, collections::HashMap, time::Instant}; use std::{borrow::Cow, collections::HashMap, time::Instant};
use crossterm::event::{KeyEvent, MouseEvent};
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -8,9 +9,9 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
data_farmer::DataCollection, text_table::SimpleColumn, time_graph::TimeGraphData, data_farmer::DataCollection, event::ComponentEventResult, text_table::SimpleColumn,
widgets::tui_stuff::BlockBuilder, AppConfigFields, AxisScaling, Component, TextTable, time_graph::TimeGraphData, widgets::tui_stuff::BlockBuilder, AppConfigFields, AxisScaling,
TimeGraph, Widget, Component, TextTable, TimeGraph, Widget,
}, },
canvas::Painter, canvas::Painter,
data_conversion::convert_network_data_points, data_conversion::convert_network_data_points,
@ -497,15 +498,11 @@ impl Component for NetGraph {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event( fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
&mut self, event: crossterm::event::KeyEvent,
) -> crate::app::event::WidgetEventResult {
self.graph.handle_key_event(event) self.graph.handle_key_event(event)
} }
fn handle_mouse_event( fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
&mut self, event: crossterm::event::MouseEvent,
) -> crate::app::event::WidgetEventResult {
self.graph.handle_mouse_event(event) self.graph.handle_mouse_event(event)
} }
} }
@ -522,7 +519,7 @@ impl Widget for NetGraph {
let block = self let block = self
.block() .block()
.selected(selected) .selected(selected)
.expanded(expanded) .show_esc(expanded)
.build(painter, area); .build(painter, area);
self.set_draw_cache(); self.set_draw_cache();
@ -644,15 +641,11 @@ impl Component for OldNetGraph {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event( fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
&mut self, event: crossterm::event::KeyEvent,
) -> crate::app::event::WidgetEventResult {
self.net_graph.handle_key_event(event) self.net_graph.handle_key_event(event)
} }
fn handle_mouse_event( fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
&mut self, event: crossterm::event::MouseEvent,
) -> crate::app::event::WidgetEventResult {
self.net_graph.handle_mouse_event(event) self.net_graph.handle_mouse_event(event)
} }
} }

View File

@ -15,7 +15,7 @@ use tui::{
use crate::{ use crate::{
app::{ app::{
data_harvester::processes::ProcessHarvest, data_harvester::processes::ProcessHarvest,
event::{MultiKey, MultiKeyResult, ReturnSignal, SelectionAction, WidgetEventResult}, event::{ComponentEventResult, MultiKey, MultiKeyResult, ReturnSignal, SelectionAction},
query::*, query::*,
text_table::DesiredColumnWidth, text_table::DesiredColumnWidth,
widgets::tui_stuff::BlockBuilder, widgets::tui_stuff::BlockBuilder,
@ -869,27 +869,27 @@ impl ProcessManager {
self self
} }
fn open_search(&mut self) -> WidgetEventResult { fn open_search(&mut self) -> ComponentEventResult {
if let ProcessManagerSelection::Search = self.selected { if let ProcessManagerSelection::Search = self.selected {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.show_search = true; self.show_search = true;
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = ProcessManagerSelection::Search; self.selected = ProcessManagerSelection::Search;
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
fn open_sort(&mut self) -> WidgetEventResult { fn open_sort(&mut self) -> ComponentEventResult {
if let ProcessManagerSelection::Sort = self.selected { if let ProcessManagerSelection::Sort = self.selected {
WidgetEventResult::NoRedraw ComponentEventResult::NoRedraw
} else { } else {
self.sort_menu self.sort_menu
.set_index(self.process_table.current_sorting_column_index()); .set_index(self.process_table.current_sorting_column_index());
self.show_sort = true; self.show_sort = true;
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = ProcessManagerSelection::Sort; self.selected = ProcessManagerSelection::Sort;
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} }
@ -917,7 +917,7 @@ impl ProcessManager {
) )
} }
fn toggle_command(&mut self) -> WidgetEventResult { fn toggle_command(&mut self) -> ComponentEventResult {
if self.is_using_command() { if self.is_using_command() {
self.process_table self.process_table
.set_column(ProcessSortColumn::new(ProcessSortType::Name), 1); .set_column(ProcessSortColumn::new(ProcessSortType::Name), 1);
@ -929,7 +929,7 @@ impl ProcessManager {
// Invalidate row cache. // Invalidate row cache.
self.process_table.invalidate_cached_columns(); self.process_table.invalidate_cached_columns();
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} }
fn is_grouped(&self) -> bool { fn is_grouped(&self) -> bool {
@ -939,7 +939,7 @@ impl ProcessManager {
) )
} }
fn toggle_grouped(&mut self) -> WidgetEventResult { fn toggle_grouped(&mut self) -> ComponentEventResult {
if self.is_grouped() { if self.is_grouped() {
self.process_table self.process_table
.set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0); .set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0);
@ -965,7 +965,7 @@ impl ProcessManager {
// Invalidate row cache. // Invalidate row cache.
self.process_table.invalidate_cached_columns(); self.process_table.invalidate_cached_columns();
WidgetEventResult::Signal(ReturnSignal::Update) ComponentEventResult::Signal(ReturnSignal::Update)
} }
fn hide_sort(&mut self) { fn hide_sort(&mut self) {
@ -994,7 +994,7 @@ impl Component for ProcessManager {
self.bounds = new_bounds; self.bounds = new_bounds;
} }
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
// "Global" handling: // "Global" handling:
if let KeyCode::Esc = event.code { if let KeyCode::Esc = event.code {
@ -1002,19 +1002,19 @@ impl Component for ProcessManager {
ProcessManagerSelection::Processes => { ProcessManagerSelection::Processes => {
if self.show_sort { if self.show_sort {
self.hide_sort(); self.hide_sort();
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} else if self.show_search { } else if self.show_search {
self.hide_search(); self.hide_search();
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
} }
ProcessManagerSelection::Sort if self.show_sort => { ProcessManagerSelection::Sort if self.show_sort => {
self.hide_sort(); self.hide_sort();
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
ProcessManagerSelection::Search if self.show_search => { ProcessManagerSelection::Search if self.show_search => {
self.hide_search(); self.hide_search();
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
_ => {} _ => {}
} }
@ -1039,7 +1039,7 @@ impl Component for ProcessManager {
// Kill the selected process(es) // Kill the selected process(es)
} }
MultiKeyResult::Accepted | MultiKeyResult::Rejected => { MultiKeyResult::Accepted | MultiKeyResult::Rejected => {
return WidgetEventResult::NoRedraw; return ComponentEventResult::NoRedraw;
} }
} }
} }
@ -1057,7 +1057,7 @@ impl Component for ProcessManager {
} }
KeyCode::Char('t') | KeyCode::F(5) => { KeyCode::Char('t') | KeyCode::F(5) => {
self.in_tree_mode = !self.in_tree_mode; self.in_tree_mode = !self.in_tree_mode;
return WidgetEventResult::Redraw; return ComponentEventResult::Redraw;
} }
KeyCode::Char('s') | KeyCode::F(6) => { KeyCode::Char('s') | KeyCode::F(6) => {
return self.open_sort(); return self.open_sort();
@ -1086,7 +1086,7 @@ impl Component for ProcessManager {
KeyCode::Enter => { KeyCode::Enter => {
self.process_table self.process_table
.set_sort_index(self.sort_menu.current_index()); .set_sort_index(self.sort_menu.current_index());
return WidgetEventResult::Signal(ReturnSignal::Update); return ComponentEventResult::Signal(ReturnSignal::Update);
} }
KeyCode::Char('/') => { KeyCode::Char('/') => {
return self.open_search(); return self.open_search();
@ -1115,7 +1115,7 @@ impl Component for ProcessManager {
} }
let handle_output = self.search_input.handle_key_event(event); let handle_output = self.search_input.handle_key_event(event);
if let WidgetEventResult::Signal(ReturnSignal::Update) = handle_output { if let ComponentEventResult::Signal(ReturnSignal::Update) = handle_output {
self.process_filter = Some(parse_query( self.process_filter = Some(parse_query(
self.search_input.query(), self.search_input.query(),
self.is_searching_whole_word(), self.is_searching_whole_word(),
@ -1129,7 +1129,7 @@ impl Component for ProcessManager {
} }
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
match &event.kind { match &event.kind {
MouseEventKind::Down(MouseButton::Left) => { MouseEventKind::Down(MouseButton::Left) => {
if self.process_table.does_border_intersect_mouse(&event) { if self.process_table.does_border_intersect_mouse(&event) {
@ -1139,11 +1139,10 @@ impl Component for ProcessManager {
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = ProcessManagerSelection::Processes; self.selected = ProcessManagerSelection::Processes;
match self.process_table.handle_mouse_event(event) { match self.process_table.handle_mouse_event(event) {
WidgetEventResult::Quit => WidgetEventResult::Quit, ComponentEventResult::Unhandled
WidgetEventResult::Redraw | WidgetEventResult::NoRedraw => { | ComponentEventResult::Redraw
WidgetEventResult::Redraw | ComponentEventResult::NoRedraw => ComponentEventResult::Redraw,
} ComponentEventResult::Signal(s) => ComponentEventResult::Signal(s),
WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s),
} }
} }
} else if self.sort_menu.does_border_intersect_mouse(&event) { } else if self.sort_menu.does_border_intersect_mouse(&event) {
@ -1153,7 +1152,7 @@ impl Component for ProcessManager {
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = ProcessManagerSelection::Sort; self.selected = ProcessManagerSelection::Sort;
self.sort_menu.handle_mouse_event(event); self.sort_menu.handle_mouse_event(event);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} else if does_bound_intersect_coordinate( } else if does_bound_intersect_coordinate(
event.column, event.column,
@ -1166,10 +1165,10 @@ impl Component for ProcessManager {
self.prev_selected = self.selected; self.prev_selected = self.selected;
self.selected = ProcessManagerSelection::Search; self.selected = ProcessManagerSelection::Search;
self.search_input.handle_mouse_event(event); self.search_input.handle_mouse_event(event);
WidgetEventResult::Redraw ComponentEventResult::Redraw
} }
} else { } else {
WidgetEventResult::NoRedraw ComponentEventResult::Unhandled
} }
} }
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected { MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => match self.selected {
@ -1177,7 +1176,7 @@ impl Component for ProcessManager {
ProcessManagerSelection::Sort => self.sort_menu.handle_mouse_event(event), ProcessManagerSelection::Sort => self.sort_menu.handle_mouse_event(event),
ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event), ProcessManagerSelection::Search => self.search_input.handle_mouse_event(event),
}, },
_ => WidgetEventResult::NoRedraw, _ => ComponentEventResult::Unhandled,
} }
} }
} }
@ -1195,9 +1194,9 @@ impl Widget for ProcessManager {
let search_constraints: [Constraint; 2] = [ let search_constraints: [Constraint; 2] = [
Constraint::Min(0), Constraint::Min(0),
if self.block_border.contains(Borders::TOP) { if self.block_border.contains(Borders::TOP) {
Constraint::Length(4) Constraint::Length(5)
} else { } else {
Constraint::Length(2) Constraint::Length(3)
}, },
]; ];
const INTERNAL_SEARCH_CONSTRAINTS: [Constraint; 2] = [Constraint::Length(1); 2]; const INTERNAL_SEARCH_CONSTRAINTS: [Constraint; 2] = [Constraint::Length(1); 2];
@ -1277,7 +1276,7 @@ impl Widget for ProcessManager {
.block() .block()
.selected(process_selected) .selected(process_selected)
.borders(self.block_border) .borders(self.block_border)
.expanded(expanded && !self.show_sort && !self.show_search); .show_esc(expanded && !self.show_sort && !self.show_search);
self.process_table.draw_tui_table( self.process_table.draw_tui_table(
painter, painter,

View File

@ -6,8 +6,9 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame};
use crate::{ use crate::{
app::{ app::{
data_farmer::DataCollection, data_harvester::temperature::TemperatureType, data_farmer::DataCollection, data_harvester::temperature::TemperatureType,
event::WidgetEventResult, sort_text_table::SimpleSortableColumn, text_table::TextTableData, event::ComponentEventResult, sort_text_table::SimpleSortableColumn,
AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget, text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component,
TextTable, Widget,
}, },
canvas::Painter, canvas::Painter,
data_conversion::convert_temp_row, data_conversion::convert_temp_row,
@ -116,11 +117,11 @@ impl TempTable {
} }
impl Component for TempTable { impl Component for TempTable {
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
self.table.handle_key_event(event) self.table.handle_key_event(event)
} }
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
self.table.handle_mouse_event(event) self.table.handle_mouse_event(event)
} }
@ -146,7 +147,7 @@ impl Widget for TempTable {
.block() .block()
.selected(selected) .selected(selected)
.borders(self.block_border) .borders(self.block_border)
.expanded(expanded); .show_esc(expanded);
self.table.draw_tui_table( self.table.draw_tui_table(
painter, painter,

View File

@ -0,0 +1,2 @@
pub mod help;
pub use help::HelpDialog;

View File

@ -0,0 +1,304 @@
use std::cmp::min;
use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use fxhash::FxHashMap;
use itertools::{EitherOrBoth, Itertools};
use tui::{
backend::Backend,
layout::{Constraint, Layout, Rect},
text::{Span, Spans},
widgets::{Borders, Paragraph},
Frame,
};
use unicode_width::UnicodeWidthStr;
use crate::{
app::{
event::{ComponentEventResult, MultiKey, MultiKeyResult},
widgets::tui_stuff::BlockBuilder,
Component,
},
canvas::Painter,
constants::*,
};
pub struct HelpDialog {
current_index: usize,
max_index: usize,
bounds: Rect,
wrapped_text: Vec<Vec<Spans<'static>>>,
left_column_width: Constraint,
right_column_width: Constraint,
/// Manages the `gg` double-tap shortcut.
gg_manager: MultiKey,
/// A jury-rigged solution for shortcut indices.
/// TODO: THIS DOES NOT SCALE WELL!
shortcut_indices: FxHashMap<u32, usize>,
}
impl Default for HelpDialog {
fn default() -> Self {
Self {
current_index: Default::default(),
max_index: Default::default(),
bounds: Default::default(),
left_column_width: Constraint::Length(0),
right_column_width: Constraint::Length(0),
wrapped_text: Default::default(),
gg_manager: MultiKey::register(vec!['g', 'g']),
shortcut_indices: FxHashMap::default(),
}
}
}
impl HelpDialog {
pub fn rebuild_wrapped_text(&mut self, painter: &Painter) {
let left_column_width = HELP_TEXT
.iter()
.map(|text| {
text.iter()
.map(|[labels, _details]| {
labels
.lines()
.map(|line| UnicodeWidthStr::width(line))
.max()
.unwrap_or(0)
})
.max()
.unwrap_or(0)
})
.max()
.unwrap_or(0)
+ 2;
let right_column_width = (self.bounds.width as usize).saturating_sub(left_column_width);
self.left_column_width = Constraint::Length(left_column_width as u16);
self.right_column_width = Constraint::Length(right_column_width as u16);
let mut shortcut_index = 1;
let mut current_index = HELP_TITLES.len() + 2;
// let mut section_indices: Vec<usize> = Vec::with_capacity(HELP_TITLES.len());
// let mut help_title_index = HELP_CONTENTS_TEXT.len() + 1;
// Behold, this monstrosity of an iterator (I'm sorry).
// Be warned, for when you stare into the iterator, the iterator stares back.
let wrapped_details_iter = HELP_TEXT.iter().map(|text| {
text.iter()
.map(|[labels, details]| {
let labels = textwrap::fill(labels, left_column_width);
let details = textwrap::fill(details, right_column_width);
labels
.lines()
.zip_longest(details.lines())
.map(|z| match z {
EitherOrBoth::Both(a, b) => vec![
Spans::from(Span::styled(
a.to_string(),
painter.colours.text_style,
)),
Spans::from(Span::styled(
b.to_string(),
painter.colours.text_style,
)),
],
EitherOrBoth::Left(s) => {
vec![Spans::from(Span::styled(
s.to_string(),
painter.colours.text_style,
))]
}
EitherOrBoth::Right(s) => vec![
Spans::default(),
Spans::from(Span::styled(
s.to_string(),
painter.colours.text_style,
)),
],
})
.collect::<Vec<_>>()
})
.flatten()
.collect::<Vec<_>>()
});
self.wrapped_text = HELP_CONTENTS_TEXT
.iter()
.map(|t| vec![Spans::from(Span::styled(*t, painter.colours.text_style))])
.chain(
HELP_TITLES
.iter()
.zip(wrapped_details_iter)
.map(|(title, text)| {
self.shortcut_indices.insert(shortcut_index, current_index);
shortcut_index += 1;
current_index += 2 + text.len();
std::iter::once(vec![Spans::default()])
.chain(std::iter::once(vec![Spans::from(Span::styled(
*title,
painter.colours.highlighted_border_style,
))]))
.chain(text)
})
.flatten(),
)
.collect();
self.max_index = self
.wrapped_text
.len()
.saturating_sub(self.bounds.height as usize);
for value in self.shortcut_indices.values_mut() {
*value = min(*value, self.max_index);
}
if self.current_index > self.max_index {
self.current_index = self.max_index;
}
}
pub fn draw_help<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, block_area: Rect,
) {
let block = BlockBuilder::new("Help")
.borders(Borders::all())
.show_esc(true)
.build(painter, block_area);
let inner_area = block.inner(block_area);
if inner_area != self.bounds {
self.bounds = inner_area;
self.rebuild_wrapped_text(painter);
}
let end_index = self.current_index + inner_area.height as usize;
let split_area = Layout::default()
.constraints(vec![Constraint::Length(1); inner_area.height as usize])
.direction(tui::layout::Direction::Vertical)
.split(inner_area);
self.wrapped_text[self.current_index..end_index]
.iter()
.zip(split_area)
.for_each(|(row, area)| {
if row.len() > 1 {
let row_split_area = Layout::default()
.constraints(vec![self.left_column_width, self.right_column_width])
.direction(tui::layout::Direction::Horizontal)
.split(area);
let left_area = row_split_area[0];
let right_area = row_split_area[1];
f.render_widget(Paragraph::new(row[0].clone()), left_area);
f.render_widget(Paragraph::new(row[1].clone()), right_area);
} else if let Some(line) = row.get(0) {
f.render_widget(Paragraph::new(line.clone()), area);
}
});
f.render_widget(block, block_area);
}
fn move_up(&mut self, amount: usize) -> ComponentEventResult {
let new_index = self.current_index.saturating_sub(amount);
if self.current_index == new_index {
ComponentEventResult::NoRedraw
} else {
self.current_index = new_index;
ComponentEventResult::Redraw
}
}
fn move_down(&mut self, amount: usize) -> ComponentEventResult {
let new_index = self.current_index + amount;
if new_index > self.max_index || self.current_index == new_index {
ComponentEventResult::NoRedraw
} else {
self.current_index = new_index;
ComponentEventResult::Redraw
}
}
fn skip_to_first(&mut self) -> ComponentEventResult {
if self.current_index == 0 {
ComponentEventResult::NoRedraw
} else {
self.current_index = 0;
ComponentEventResult::Redraw
}
}
fn skip_to_last(&mut self) -> ComponentEventResult {
if self.current_index == self.max_index {
ComponentEventResult::NoRedraw
} else {
self.current_index = self.max_index;
ComponentEventResult::Redraw
}
}
}
impl Component for HelpDialog {
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) -> ComponentEventResult {
use crossterm::event::KeyCode::{Char, Down, Up};
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
match event.code {
Down if event.modifiers == KeyModifiers::NONE => self.move_down(1),
Up if event.modifiers == KeyModifiers::NONE => self.move_up(1),
Char(c) => match c {
'j' => self.move_down(1),
'k' => self.move_up(1),
'g' => match self.gg_manager.input('g') {
MultiKeyResult::Completed => self.skip_to_first(),
MultiKeyResult::Accepted | MultiKeyResult::Rejected => {
ComponentEventResult::NoRedraw
}
},
'G' => self.skip_to_last(),
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
if let Some(potential_index) = c.to_digit(10) {
if let Some(&new_index) = self.shortcut_indices.get(&potential_index) {
if new_index != self.current_index {
self.current_index = new_index;
ComponentEventResult::Redraw
} else {
ComponentEventResult::NoRedraw
}
} else {
ComponentEventResult::Unhandled
}
} else {
ComponentEventResult::Unhandled
}
}
_ => ComponentEventResult::Unhandled,
},
_ => ComponentEventResult::Unhandled,
}
} else {
ComponentEventResult::Unhandled
}
}
fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
match event.kind {
MouseEventKind::ScrollDown => self.move_down(1),
MouseEventKind::ScrollUp => self.move_up(1),
_ => ComponentEventResult::Unhandled,
}
}
}

View File

@ -10,7 +10,7 @@ use crate::canvas::Painter;
pub struct BlockBuilder { pub struct BlockBuilder {
borders: Borders, borders: Borders,
selected: bool, selected: bool,
expanded: bool, show_esc: bool,
name: &'static str, name: &'static str,
hide_title: bool, hide_title: bool,
extra_text: Option<String>, extra_text: Option<String>,
@ -22,7 +22,7 @@ impl BlockBuilder {
Self { Self {
borders: Borders::ALL, borders: Borders::ALL,
selected: false, selected: false,
expanded: false, show_esc: false,
name, name,
hide_title: false, hide_title: false,
extra_text: None, extra_text: None,
@ -35,9 +35,9 @@ impl BlockBuilder {
self self
} }
/// Indicates that this block is currently expanded, and should be drawn as such. /// Indicates that this block should show esc, and should be drawn as such.
pub fn expanded(mut self, expanded: bool) -> Self { pub fn show_esc(mut self, show_esc: bool) -> Self {
self.expanded = expanded; self.show_esc = show_esc;
self self
} }
@ -64,12 +64,14 @@ impl BlockBuilder {
let has_title = !self.hide_title let has_title = !self.hide_title
&& (self.borders.contains(Borders::TOP) || self.borders.contains(Borders::BOTTOM)); && (self.borders.contains(Borders::TOP) || self.borders.contains(Borders::BOTTOM));
let border_style = if self.selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let block = Block::default() let block = Block::default()
.border_style(if self.selected { .border_style(border_style)
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
})
.borders(self.borders); .borders(self.borders);
let inner_width = block.inner(area).width as usize; let inner_width = block.inner(area).width as usize;
@ -82,12 +84,11 @@ impl BlockBuilder {
let mut title_len = name.width(); let mut title_len = name.width();
let mut title = vec![name, Span::from(""), Span::from(""), Span::from("")]; let mut title = vec![name, Span::from(""), Span::from(""), Span::from("")];
if self.expanded { if self.show_esc {
const EXPAND_TEXT: &str = " Esc to go back "; const EXPAND_TEXT: &str = " Esc to go back ";
const EXPAND_TEXT_LEN: usize = EXPAND_TEXT.len(); const EXPAND_TEXT_LEN: usize = EXPAND_TEXT.len();
let expand_span = let expand_span = Span::styled(EXPAND_TEXT, border_style);
Span::styled(EXPAND_TEXT, painter.colours.highlighted_border_style);
if title_len + EXPAND_TEXT_LEN <= inner_width { if title_len + EXPAND_TEXT_LEN <= inner_width {
title_len += EXPAND_TEXT_LEN; title_len += EXPAND_TEXT_LEN;
@ -107,16 +108,9 @@ impl BlockBuilder {
} }
} }
if self.expanded { if self.show_esc {
let difference = inner_width.saturating_sub(title_len); let difference = inner_width.saturating_sub(title_len);
title[2] = Span::styled( title[2] = Span::styled("".repeat(difference), border_style);
"".repeat(difference),
if self.selected {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
},
);
} }
block.title(title) block.title(title)

View File

@ -50,7 +50,7 @@ fn main() -> Result<()> {
let thread_termination_cvar = Arc::new(Condvar::new()); let thread_termination_cvar = Arc::new(Condvar::new());
// Set up input handling // Set up input handling
let (sender, receiver) = mpsc::channel(); // FIXME: Make this bounded, prevents overloading. let (sender, receiver) = mpsc::channel();
let input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone()); let input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
// Cleaning loop // Cleaning loop

View File

@ -5,7 +5,7 @@ use indextree::{Arena, NodeId};
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
text::{Span, Spans}, text::Span,
widgets::Paragraph, widgets::Paragraph,
Frame, Terminal, Frame, Terminal,
}; };
@ -19,7 +19,7 @@ use crate::{
layout_manager::{generate_layout, ColLayout, LayoutNode, RowLayout}, layout_manager::{generate_layout, ColLayout, LayoutNode, RowLayout},
text_table::TextTableData, text_table::TextTableData,
widgets::{Component, Widget}, widgets::{Component, Widget},
TmpBottomWidget, DialogState, TmpBottomWidget,
}, },
constants::*, constants::*,
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData}, data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
@ -92,14 +92,12 @@ impl FromStr for ColourScheme {
/// Handles the canvas' state. /// Handles the canvas' state.
pub struct Painter { pub struct Painter {
pub colours: CanvasColours, pub colours: CanvasColours,
styled_help_text: Vec<Spans<'static>>,
} }
impl Painter { impl Painter {
pub fn init(config: &Config, colour_scheme: ColourScheme) -> anyhow::Result<Self> { pub fn init(config: &Config, colour_scheme: ColourScheme) -> anyhow::Result<Self> {
let mut painter = Painter { let mut painter = Painter {
colours: CanvasColours::default(), colours: CanvasColours::default(),
styled_help_text: Vec::default(),
}; };
if let ColourScheme::Custom = colour_scheme { if let ColourScheme::Custom = colour_scheme {
@ -107,7 +105,6 @@ impl Painter {
} else { } else {
painter.generate_colour_scheme(colour_scheme)?; painter.generate_colour_scheme(colour_scheme)?;
} }
painter.complete_painter_init();
Ok(painter) Ok(painter)
} }
@ -153,43 +150,6 @@ impl Painter {
Ok(()) Ok(())
} }
/// Must be run once before drawing, but after setting colours.
/// This is to set some remaining styles and text.
fn complete_painter_init(&mut self) {
let mut styled_help_spans = Vec::new();
// Init help text:
(*HELP_TEXT).iter().enumerate().for_each(|(itx, section)| {
if itx == 0 {
styled_help_spans.extend(
section
.iter()
.map(|&text| Span::styled(text, self.colours.text_style))
.collect::<Vec<_>>(),
);
} else {
// Not required check but it runs only a few times... so whatever ig, prevents me from
// being dumb and leaving a help text section only one line long.
if section.len() > 1 {
styled_help_spans.push(Span::raw(""));
styled_help_spans
.push(Span::styled(section[0], self.colours.table_header_style));
styled_help_spans.extend(
section[1..]
.iter()
.map(|&text| Span::styled(text, self.colours.text_style))
.collect::<Vec<_>>(),
);
}
}
});
self.styled_help_text = styled_help_spans.into_iter().map(Spans::from).collect();
}
// TODO: [CONFIG] write this, should call painter init and any changed colour functions...
pub fn update_painter_colours(&mut self) {}
fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) { fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) {
f.render_widget( f.render_widget(
Paragraph::new(Span::styled( Paragraph::new(Span::styled(
@ -218,7 +178,7 @@ impl Painter {
let terminal_height = draw_area.height; let terminal_height = draw_area.height;
let terminal_width = draw_area.width; let terminal_width = draw_area.width;
if app_state.help_dialog_state.is_showing_help { if let DialogState::Shown(help_dialog) = &mut app_state.help_dialog {
let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3; let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3;
let border_len = terminal_height.saturating_sub(gen_help_len) / 2; let border_len = terminal_height.saturating_sub(gen_help_len) / 2;
let vertical_dialog_chunk = Layout::default() let vertical_dialog_chunk = Layout::default()
@ -248,7 +208,7 @@ impl Painter {
}) })
.split(vertical_dialog_chunk[1]); .split(vertical_dialog_chunk[1]);
self.draw_help_dialog(&mut f, app_state, middle_dialog_chunk[1]); help_dialog.draw_help(&self, f, middle_dialog_chunk[1]);
} else if app_state.delete_dialog_state.is_showing_dd { } else if app_state.delete_dialog_state.is_showing_dd {
// TODO: This needs the paragraph wrap feature from tui-rs to be pushed to complete... but for now it's pretty close! // TODO: This needs the paragraph wrap feature from tui-rs to be pushed to complete... but for now it's pretty close!
// The main problem right now is that I cannot properly calculate the height offset since // The main problem right now is that I cannot properly calculate the height offset since

View File

@ -1,5 +1,2 @@
pub mod dd_dialog; pub mod dd_dialog;
pub mod help_dialog;
pub use dd_dialog::KillDialog; pub use dd_dialog::KillDialog;
pub use help_dialog::HelpDialog;

View File

@ -1,121 +0,0 @@
use unicode_width::UnicodeWidthStr;
use crate::{app::AppState, canvas::Painter, constants};
use tui::{
backend::Backend,
layout::{Alignment, Rect},
terminal::Frame,
text::Span,
text::Spans,
widgets::{Block, Borders, Paragraph, Wrap},
};
const HELP_BASE: &str = " Help ── Esc to close ";
pub trait HelpDialog {
fn draw_help_dialog<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
);
}
// TODO: [REFACTOR] Make generic dialog boxes to build off of instead?
impl HelpDialog for Painter {
fn draw_help_dialog<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
) {
let help_title = Spans::from(vec![
Span::styled(" Help ", self.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to close ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(HELP_BASE.chars().count() + 2)
)
),
self.colours.border_style,
),
]);
if app_state.should_get_widget_bounds() {
// We must also recalculate how many lines are wrapping to properly get scrolling to work on
// small terminal sizes... oh joy.
let mut overflow_buffer = 0;
let paragraph_width = std::cmp::max(draw_loc.width.saturating_sub(2), 1);
let mut prev_section_len = 0;
constants::HELP_TEXT
.iter()
.enumerate()
.for_each(|(itx, section)| {
let mut buffer = 0;
if itx == 0 {
section.iter().for_each(|text_line| {
buffer += UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16
/ paragraph_width;
});
app_state.help_dialog_state.index_shortcuts[itx] = 0;
} else {
section.iter().for_each(|text_line| {
buffer += UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16
/ paragraph_width;
});
app_state.help_dialog_state.index_shortcuts[itx] =
app_state.help_dialog_state.index_shortcuts[itx - 1]
+ 1
+ prev_section_len;
}
prev_section_len = section.len() as u16 + buffer;
overflow_buffer += buffer;
});
app_state.help_dialog_state.scroll_state.max_scroll_index =
(self.styled_help_text.len() as u16
+ (constants::HELP_TEXT.len() as u16 - 5)
+ overflow_buffer)
.saturating_sub(draw_loc.height);
// Fix if over-scrolled
if app_state
.help_dialog_state
.scroll_state
.current_scroll_index
>= app_state.help_dialog_state.scroll_state.max_scroll_index
{
app_state
.help_dialog_state
.scroll_state
.current_scroll_index = app_state
.help_dialog_state
.scroll_state
.max_scroll_index
.saturating_sub(1);
}
}
f.render_widget(
Paragraph::new(self.styled_help_text.clone())
.block(
Block::default()
.title(help_title)
.style(self.colours.border_style)
.borders(Borders::ALL)
.border_style(self.colours.border_style),
)
.style(self.colours.text_style)
.alignment(Alignment::Left)
.wrap(Wrap { trim: true })
.scroll((
app_state
.help_dialog_state
.scroll_state
.current_scroll_index,
0,
)),
draw_loc,
);
}
}

View File

@ -227,159 +227,193 @@ pub static NORD_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigC
// Help text // Help text
pub const HELP_CONTENTS_TEXT: [&str; 8] = [ pub const HELP_CONTENTS_TEXT: [&str; 8] = [
"Press the corresponding numbers to jump to the section, or scroll:", "Press the corresponding numbers to jump to that section, or just scroll down:",
"1 - General", "[1] General",
"2 - CPU widget", "[2] CPU widget",
"3 - Process widget", "[3] Process widget",
"4 - Process search widget", "[4] Process search widget",
"5 - Process sort widget", "[5] Process sort widget",
"6 - Battery widget", "[6] Battery widget",
"7 - Basic memory widget", "[7] Basic memory widget",
]; ];
// TODO [Help]: Search in help? pub const GENERAL_HELP_TITLE: &str = "General";
// TODO [Help]: Move to using tables for easier formatting? pub const GENERAL_HELP_TEXT: [[&str; 2]; 21] = [
pub const GENERAL_HELP_TEXT: [&str; 30] = [ ["q, Ctrl-c", "Quit"],
"1 - General", [
"q, Ctrl-c Quit", "Esc",
"Esc Close dialog windows, search, widgets, or exit expanded mode", "Close dialog windows, search, widgets, or exit expanded mode",
"Ctrl-r Resets any collected data", ],
"f Toggles freezing, which stops new data from being shown", ["Ctrl-r", "Resets any collected data"],
"Ctrl-Left, ", ["f", "Toggles freezing, stopping new data from being shown"],
"Shift-Left, Move widget selection left", ["Ctrl-Left\nShift-Left\nH, A", "Move widget selection left"],
"H, A ", [
"Ctrl-Right, ", "Ctrl-Right\nShift-Right\nL, D",
"Shift-Right, Move widget selection right", "Move widget selection right",
"L, D ", ],
"Ctrl-Up, ", ["Ctrl-Up\nShift-Up\nK, W", "Move widget selection up"],
"Shift-Up, Move widget selection up", ["Ctrl-Down\nShift-Dow\nJ, S", "Move widget selection down"],
"K, W ", ["Left, h", "Move left within widget"],
"Ctrl-Down, ", ["Down, j", "Move down within widget"],
"Shift-Down, Move widget selection down", ["Up, k", "Move up within widget"],
"J, S ", ["Right, l", "Move right within widget"],
"Left, h Move left within widget", ["?", "Open help menu"],
"Down, j Move down within widget", ["gg", "Jump to the first entry"],
"Up, k Move up within widget", ["G", "Jump to the last entry"],
"Right, l Move right within widget", ["e", "Toggle expanding the currently selected widget"],
"? Open help menu", ["+", "Zoom in on chart (decrease time range)"],
"gg Jump to the first entry", ["-", "Zoom out on chart (increase time range)"],
"G Jump to the last entry", ["=", "Reset zoom"],
"e Toggle expanding the currently selected widget", [
"+ Zoom in on chart (decrease time range)", "Mouse scroll",
"- Zoom out on chart (increase time range)", "Scroll through the tables or zoom in/out of charts by scrolling up/down",
"= Reset zoom", ],
"Mouse scroll Scroll through the tables or zoom in/out of charts by scrolling up/down", [
"Mouse click Selects the clicked widget, table entry, dialog option, or tab", "Mouse click",
"Selects the clicked widget, table entry, dialog option, or tab",
],
]; ];
pub const CPU_HELP_TEXT: [&str; 2] = [ pub const CPU_HELP_TITLE: &str = "CPU Widget";
"2 - CPU widget\n", pub const CPU_HELP_TEXT: [[&str; 2]; 1] = [[
"Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", "Mouse scroll",
"Scrolling over an CPU core/average shows only that entry on the chart",
]];
pub const PROCESS_HELP_TITLE: &str = "Process Widget";
pub const PROCESS_HELP_TEXT: [[&str; 2]; 14] = [
["dd, F9", "Kill the selected process"],
[
"c",
"Sort by CPU usage, press again to reverse sorting order",
],
[
"m",
"Sort by memory usage, press again to reverse sorting order",
],
[
"p",
"Sort by PID name, press again to reverse sorting order",
],
[
"n",
"Sort by process name, press again to reverse sorting order",
],
["Tab", "Group/un-group processes with the same name"],
["Ctrl-f, /", "Open process search widget"],
[
"P",
"Toggle between showing the full command or just the process name",
],
["s, F6", "Open process sort widget"],
["I", "Invert current sort"],
[
"%",
"Toggle between values and percentages for memory usage",
],
["t, F5", "Toggle tree mode"],
["+, -, click", "Collapse/expand a branch while in tree mode"],
[
"click on header",
"Sorts the entries by that column, click again to invert the sort",
],
]; ];
pub const PROCESS_HELP_TEXT: [&str; 15] = [ pub const SEARCH_TEXT_HELP_TITLE: &str = "Process Search";
"3 - Process widget", pub const SEARCH_HELP_TEXT: [[&str; 2]; 48] = [
"dd, F9 Kill the selected process", ["Tab", "Toggle between searching for PID and name"],
"c Sort by CPU usage, press again to reverse sorting order", ["Esc", "Close the search widget (retains the filter)"],
"m Sort by memory usage, press again to reverse sorting order", ["Ctrl-a", "Skip to the start of the search query"],
"p Sort by PID name, press again to reverse sorting order", ["Ctrl-e", "Skip to the end of the search query"],
"n Sort by process name, press again to reverse sorting order", ["Ctrl-u", "Clear the current search query"],
"Tab Group/un-group processes with the same name", ["Ctrl-w", "Delete a word behind the cursor"],
"Ctrl-f, / Open process search widget", ["Ctrl-h", "Delete the character behind the cursor"],
"P Toggle between showing the full command or just the process name", ["Backspace", "Delete the character behind the cursor"],
"s, F6 Open process sort widget", ["Delete", "Delete the character at the cursor"],
"I Invert current sort", ["Alt-c, F1", "Toggle matching case"],
"% Toggle between values and percentages for memory usage", ["Alt-w, F2", "Toggle matching the entire word"],
"t, F5 Toggle tree mode", ["Alt-r, F3", "Toggle using regex"],
"+, -, click Collapse/expand a branch while in tree mode", ["Left, Alt-h", "Move cursor left"],
"click on header Sorts the entries by that column, click again to invert the sort", ["Right, Alt-l", "Move cursor right"],
["\n", "\n"],
["Supported search types:", ""],
["<by name/cmd>", "ex: btm"],
["pid", "ex: pid 825"],
["cpu, cpu%", "ex: cpu > 4.2"],
["mem, mem%", "ex: mem < 4.2"],
["memb", "ex: memb < 100 kb"],
["read, r/s", "ex: read >= 1 b"],
["write, w/s", "ex: write <= 1 tb"],
["tread, t.read", "ex: tread = 1"],
["twrite, t.write", "ex: twrite = 1"],
["user", "ex: user = root"],
["state", "ex: state = running"],
["\n", "\n"],
["Comparison operators:", ""],
["=", "ex: cpu = 1"],
[">", "ex: cpu > 1"],
["<", "ex: cpu < 1"],
[">=", "ex: cpu >= 1"],
["<=", "ex: cpu <= 1"],
["\n", "\n"],
["Logical operators:", ""],
["and, &&, <Space> ", ": btm and cpu > 1 and mem > 1"],
["or, ||", "ex: btm or firefox"],
["\n", "\n"],
["Supported units:", ""],
["B", "ex: read > 1 b"],
["KB", "ex: read > 1 kb"],
["MB", "ex: read > 1 mb"],
["TB", "ex: read > 1 tb"],
["KiB", "ex: read > 1 kib"],
["MiB", "ex: read > 1 mib"],
["GiB", "ex: read > 1 gib"],
["TiB", "ex: read > 1 tib"],
]; ];
pub const SEARCH_HELP_TEXT: [&str; 49] = [ pub const PROCESS_SORT_HELP_TITLE: &str = "Process Sort";
"4 - Process search widget", pub const PROCESS_SORT_HELP_TEXT: [[&str; 2]; 5] = [
"Tab Toggle between searching for PID and name", ["Down, 'j'", "Scroll down in list"],
"Esc Close the search widget (retains the filter)", ["Up, 'k'", "Scroll up in list"],
"Ctrl-a Skip to the start of the search query", ["Mouse scroll", "Scroll through sort widget"],
"Ctrl-e Skip to the end of the search query", ["Esc", "Close the sort widget"],
"Ctrl-u Clear the current search query", ["Enter", "Sort by current selected column"],
"Ctrl-w Delete a word behind the cursor",
"Ctrl-h Delete the character behind the cursor",
"Backspace Delete the character behind the cursor",
"Delete Delete the character at the cursor",
"Alt-c, F1 Toggle matching case",
"Alt-w, F2 Toggle matching the entire word",
"Alt-r, F3 Toggle using regex",
"Left, Alt-h Move cursor left",
"Right, Alt-l Move cursor right",
"",
"Supported search types:",
"<by name/cmd> ex: btm",
"pid ex: pid 825",
"cpu, cpu% ex: cpu > 4.2",
"mem, mem% ex: mem < 4.2",
"memb ex: memb < 100 kb",
"read, r/s ex: read >= 1 b",
"write, w/s ex: write <= 1 tb",
"tread, t.read ex: tread = 1",
"twrite, t.write ex: twrite = 1",
"user ex: user = root",
"state ex: state = running",
"",
"Comparison operators:",
"= ex: cpu = 1",
"> ex: cpu > 1",
"< ex: cpu < 1",
">= ex: cpu >= 1",
"<= ex: cpu <= 1",
"",
"Logical operators:",
"and, &&, <Space> ex: btm and cpu > 1 and mem > 1",
"or, || ex: btm or firefox",
"",
"Supported units:",
"B ex: read > 1 b",
"KB ex: read > 1 kb",
"MB ex: read > 1 mb",
"TB ex: read > 1 tb",
"KiB ex: read > 1 kib",
"MiB ex: read > 1 mib",
"GiB ex: read > 1 gib",
"TiB ex: read > 1 tib",
]; ];
pub const SORT_HELP_TEXT: [&str; 6] = [ pub const BATTERY_HELP_TITLE: &str = "Battery Widget";
"5 - Sort widget\n", pub const BATTERY_HELP_TEXT: [[&str; 2]; 2] = [
"Down, 'j' Scroll down in list", ["Left", "Go to previous battery"],
"Up, 'k' Scroll up in list", ["Right", "Go to next battery"],
"Mouse scroll Scroll through sort widget",
"Esc Close the sort widget",
"Enter Sort by current selected column",
]; ];
pub const BATTERY_HELP_TEXT: [&str; 3] = [ pub const BASIC_MEM_HELP_TITLE: &str = "Basic Memory Widget";
"6 - Battery widget", pub const BASIC_MEM_HELP_TEXT: [[&str; 2]; 1] = [[
"Left Go to previous battery", "%",
"Right Go to next battery", "Toggle between values and percentages for memory usage",
]; ]];
pub const BASIC_MEM_HELP_TEXT: [&str; 2] = [ pub static HELP_TEXT: Lazy<[Vec<[&'static str; 2]>; 7]> = Lazy::new(|| {
"7 - Basic memory widget", [
"% Toggle between values and percentages for memory usage",
];
pub static HELP_TEXT: Lazy<Vec<Vec<&'static str>>> = Lazy::new(|| {
vec![
HELP_CONTENTS_TEXT.to_vec(),
GENERAL_HELP_TEXT.to_vec(), GENERAL_HELP_TEXT.to_vec(),
CPU_HELP_TEXT.to_vec(), CPU_HELP_TEXT.to_vec(),
PROCESS_HELP_TEXT.to_vec(), PROCESS_HELP_TEXT.to_vec(),
SEARCH_HELP_TEXT.to_vec(), SEARCH_HELP_TEXT.to_vec(),
SORT_HELP_TEXT.to_vec(), PROCESS_SORT_HELP_TEXT.to_vec(),
BATTERY_HELP_TEXT.to_vec(), BATTERY_HELP_TEXT.to_vec(),
BASIC_MEM_HELP_TEXT.to_vec(), BASIC_MEM_HELP_TEXT.to_vec(),
] ]
}); });
pub const HELP_TITLES: [&'static str; 7] = [
GENERAL_HELP_TITLE,
CPU_HELP_TITLE,
PROCESS_HELP_TITLE,
SEARCH_TEXT_HELP_TITLE,
PROCESS_SORT_HELP_TITLE,
BATTERY_HELP_TITLE,
BASIC_MEM_HELP_TITLE,
];
// Default layouts // Default layouts
pub const DEFAULT_LAYOUT: &str = r##" pub const DEFAULT_LAYOUT: &str = r##"
[[row]] [[row]]

View File

@ -98,7 +98,7 @@ pub fn handle_key_event(
KeyCode::Left => app.on_left_key(), KeyCode::Left => app.on_left_key(),
KeyCode::Right => app.on_right_key(), KeyCode::Right => app.on_right_key(),
KeyCode::Char(caught_char) => app.on_char_key(caught_char), KeyCode::Char(caught_char) => app.on_char_key(caught_char),
KeyCode::Esc => app.on_esc(), // KeyCode::Esc => app.on_esc(),
KeyCode::Enter => app.on_enter(), KeyCode::Enter => app.on_enter(),
KeyCode::Tab => app.on_tab(), KeyCode::Tab => app.on_tab(),
KeyCode::Backspace => app.on_backspace(), KeyCode::Backspace => app.on_backspace(),