From 7ee85a82f794c44b3a0bb6a50e8075eff96bb4c8 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Wed, 22 Sep 2021 01:16:33 -0400 Subject: [PATCH] refactor: finish help menu --- CHANGELOG.md | 2 +- Cargo.lock | 29 +- Cargo.toml | 1 + docs/content/usage/general-usage.md | 2 +- src/app.rs | 345 ++++++++------------ src/app/data_farmer.rs | 3 - src/app/data_harvester/processes.rs | 1 - src/app/data_harvester/temperature.rs | 1 - src/app/event.rs | 109 +------ src/app/event/multi_key.rs | 101 ++++++ src/app/layout_manager.rs | 1 - src/app/widgets.rs | 77 ++++- src/app/widgets/base/scrollable.rs | 47 +-- src/app/widgets/base/sort_menu.rs | 6 +- src/app/widgets/base/sort_text_table.rs | 12 +- src/app/widgets/base/text_input.rs | 76 ++--- src/app/widgets/base/text_table.rs | 12 +- src/app/widgets/base/time_graph.rs | 42 +-- src/app/widgets/bottom_widgets/basic_mem.rs | 11 +- src/app/widgets/bottom_widgets/battery.rs | 24 +- src/app/widgets/bottom_widgets/carousel.rs | 12 +- src/app/widgets/bottom_widgets/cpu.rs | 16 +- src/app/widgets/bottom_widgets/disk.rs | 8 +- src/app/widgets/bottom_widgets/mem.rs | 8 +- src/app/widgets/bottom_widgets/net.rs | 25 +- src/app/widgets/bottom_widgets/process.rs | 65 ++-- src/app/widgets/bottom_widgets/temp.rs | 11 +- src/app/widgets/dialogs.rs | 2 + src/app/widgets/dialogs/help.rs | 304 +++++++++++++++++ src/app/widgets/tui_stuff/block_builder.rs | 38 +-- src/bin/main.rs | 2 +- src/canvas.rs | 48 +-- src/canvas/dialogs.rs | 3 - src/canvas/dialogs/help_dialog.rs | 121 ------- src/constants.rs | 296 +++++++++-------- src/lib.rs | 2 +- 36 files changed, 1011 insertions(+), 852 deletions(-) create mode 100644 src/app/event/multi_key.rs create mode 100644 src/app/widgets/dialogs.rs create mode 100644 src/app/widgets/dialogs/help.rs delete mode 100644 src/canvas/dialogs/help_dialog.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0a1e5e..0fb9fb69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 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 diff --git a/Cargo.lock b/Cargo.lock index f8dbb36c..0c75735d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,6 +263,7 @@ dependencies = [ "serde", "smol", "sysinfo", + "textwrap 0.14.2", "thiserror", "toml", "tui", @@ -348,7 +349,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -1410,6 +1411,12 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "smol" version = "1.2.5" @@ -1482,6 +1489,17 @@ dependencies = [ "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]] name = "thiserror" version = "1.0.24" @@ -1558,6 +1576,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "unicode-segmentation" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index c2370011..58ac69e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ serde = { version = "1.0.125", features = ["derive"] } # Sysinfo is still used in Linux for the ProcessStatus sysinfo = "0.18.2" thiserror = "1.0.24" +textwrap = "0.14.2" toml = "0.5.8" tui = { version = "0.16.0", features = ["crossterm"], default-features = false } typed-builder = "0.9.0" diff --git a/docs/content/usage/general-usage.md b/docs/content/usage/general-usage.md index eb457aef..de134a69 100644 --- a/docs/content/usage/general-usage.md +++ b/docs/content/usage/general-usage.md @@ -42,7 +42,7 @@ Note that key bindings are generally case-sensitive. | ++q++ , ++ctrl+c++ | Quit | | ++esc++ | 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 | +| ++f++ | Toggles freezing, stopping new data from being shown | | ++question++ | Open help menu | | ++e++ | Toggle expanding the currently selected widget | | ++ctrl+up++
++shift+up++
++K++
++W++ | Select the widget above | diff --git a/src/app.rs b/src/app.rs index b3409d3f..3b95a536 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,7 +13,7 @@ use std::{ time::Instant, }; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; use fxhash::FxHashMap; use indextree::{Arena, NodeId}; use unicode_segmentation::GraphemeCursor; @@ -33,7 +33,7 @@ use crate::{ BottomEvent, Pid, }; -use self::event::{EventResult, ReturnSignal, WidgetEventResult}; +use self::event::{ComponentEventResult, EventResult, ReturnSignal}; const MAX_SEARCH_LENGTH: usize = 200; @@ -140,8 +140,6 @@ pub struct AppState { // --- Eventually delete/rewrite --- pub delete_dialog_state: AppDeleteDialogState, - pub help_dialog_state: AppHelpDialogState, - // --- TO DELETE --- pub cpu_state: CpuState, pub mem_state: MemState, @@ -172,6 +170,8 @@ pub struct AppState { pub layout_tree: Arena, pub layout_tree_root: NodeId, frozen_state: FrozenState, + + pub help_dialog: DialogState, } impl AppState { @@ -204,7 +204,6 @@ impl AppState { data_collection: Default::default(), is_expanded: Default::default(), delete_dialog_state: Default::default(), - help_dialog_state: Default::default(), cpu_state: Default::default(), mem_state: Default::default(), net_state: Default::default(), @@ -222,6 +221,7 @@ impl AppState { is_force_redraw: Default::default(), is_determining_widget_boundary: Default::default(), frozen_state: Default::default(), + help_dialog: Default::default(), } } @@ -277,7 +277,7 @@ impl AppState { let c = c.to_ascii_lowercase(); match c { 'q' => EventResult::Quit, - 'e' => { + 'e' if !self.help_dialog.is_showing() => { if self.app_config_fields.use_basic_mode { EventResult::NoRedraw } else { @@ -285,11 +285,11 @@ impl AppState { EventResult::Redraw } } - '?' => { - self.help_dialog_state.is_showing_help = true; + '?' if !self.help_dialog.is_showing() => { + self.help_dialog.show(); EventResult::Redraw } - 'f' => { + 'f' if !self.help_dialog.is_showing() => { self.toggle_freeze(); if !self.is_frozen() { 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`]. - fn handle_global_shortcut(&mut self, event: KeyEvent) -> EventResult { + fn handle_global_key_event(&mut self, event: KeyEvent) -> EventResult { if event.modifiers.is_empty() { match event.code { KeyCode::Esc => { if self.is_expanded { self.is_expanded = false; EventResult::Redraw - } else 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 if self.help_dialog.is_showing() { + self.help_dialog.hide(); EventResult::Redraw } else if self.delete_dialog_state.is_showing_dd { self.close_dd(); @@ -350,8 +392,13 @@ impl AppState { 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 { match event.code { @@ -384,30 +431,55 @@ impl AppState { } } - /// Quick and dirty handler to convert [`WidgetEventResult`]s to [`EventResult`]s, and handle [`ReturnSignal`]s. - fn convert_widget_event_result(&mut self, w: WidgetEventResult) -> EventResult { - match w { - WidgetEventResult::Quit => EventResult::Quit, - WidgetEventResult::Redraw => EventResult::Redraw, - WidgetEventResult::NoRedraw => EventResult::NoRedraw, - WidgetEventResult::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); - } + /// Handles a [`MouseEvent`]. + fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult { + if let DialogState::Shown(help_dialog) = &mut self.help_dialog { + let result = help_dialog.handle_mouse_event(event); + self.convert_widget_event_result(result) + } else if self.is_expanded { + if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { + let result = widget.handle_mouse_event(event); + self.convert_widget_event_result(result) + } else { + EventResult::NoRedraw + } + } else { + let mut returned_result = EventResult::NoRedraw; + 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; } } - 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. pub fn handle_event(&mut self, event: BottomEvent) -> EventResult { match event { - BottomEvent::KeyInput(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::KeyInput(event) => self.handle_key_event(event), BottomEvent::MouseInput(event) => { // 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. - - 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 + self.handle_mouse_event(event) } BottomEvent::Update(new_data) => { self.data_collection.eat_data(new_data); + // TODO: Optimization for dialogs; don't redraw here. + if !self.is_frozen() { let data_collection = &self.data_collection; 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 { matches!( self.current_widget.widget_type, @@ -600,7 +533,7 @@ impl AppState { } 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 { @@ -1097,7 +1030,7 @@ impl AppState { pub fn on_up_key(&mut self) { if !self.is_in_dialog() { self.decrement_position_count(); - } else if self.help_dialog_state.is_showing_help { + } else if self.help_dialog.is_showing() { self.help_scroll_up(); } else if self.delete_dialog_state.is_showing_dd { #[cfg(target_os = "windows")] @@ -1118,7 +1051,7 @@ impl AppState { pub fn on_down_key(&mut self) { if !self.is_in_dialog() { self.increment_position_count(); - } else if self.help_dialog_state.is_showing_help { + } else if self.help_dialog.is_showing() { self.help_scroll_down(); } else if self.delete_dialog_state.is_showing_dd { #[cfg(target_os = "windows")] @@ -1596,19 +1529,9 @@ impl AppState { } } self.handle_char(caught_char); - } else if self.help_dialog_state.is_showing_help { + } else if self.help_dialog.is_showing() { match caught_char { - '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], - ); - } - } - } + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {} '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; } 'H' | 'A' => self.move_widget_selection(&WidgetDirection::Left), @@ -1863,7 +1786,6 @@ impl AppState { } fn expand_widget(&mut self) { - // TODO: [BASIC] Expansion in basic mode. if !self.ignore_normal_keybinds() && !self.app_config_fields.use_basic_mode { // Pop-out mode. We ignore if in process search. @@ -2256,7 +2178,7 @@ impl AppState { { if proc_widget_state.is_sort_open { 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(); - } else if self.help_dialog_state.is_showing_help { - self.help_dialog_state.scroll_state.current_scroll_index = 0; + } else if self.help_dialog.is_showing() { + // self.help_dialog_state.scroll_state.current_scroll_index = 0; } else if self.delete_dialog_state.is_showing_dd { self.delete_dialog_state.selected_signal = KillSignal::Cancel; } @@ -2517,26 +2439,17 @@ impl AppState { } fn help_scroll_up(&mut self) { - if self.help_dialog_state.scroll_state.current_scroll_index > 0 { - self.help_dialog_state.scroll_state.current_scroll_index -= 1; - } + // if self.help_dialog_state.scroll_state.current_scroll_index > 0 { + // self.help_dialog_state.scroll_state.current_scroll_index -= 1; + // } } fn help_scroll_down(&mut self) { - 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.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; - } + // 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.current_scroll_index += 1; + // } } fn on_plus(&mut self) { diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index c2747289..dc6ee1ab 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -224,15 +224,12 @@ impl DataCollection { } fn eat_temp(&mut self, temperature_sensors: Vec) { - // TODO: [PO] To implement self.temp_harvest = temperature_sensors.to_vec(); } fn eat_disks( &mut self, disks: Vec, io: disks::IoHarvest, harvested_time: Instant, ) { - // TODO: [PO] To implement - let time_since_last_harvest = harvested_time .duration_since(self.current_instant) .as_secs_f64(); diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 3d8d7d5c..151d3a55 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -27,7 +27,6 @@ use std::borrow::Cow; use crate::Pid; -// TODO: Add value so we know if it's sorted ascending or descending by default? #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum ProcessSorting { CpuPercent, diff --git a/src/app/data_harvester/temperature.rs b/src/app/data_harvester/temperature.rs index 8f3b776e..3426a053 100644 --- a/src/app/data_harvester/temperature.rs +++ b/src/app/data_harvester/temperature.rs @@ -57,7 +57,6 @@ fn is_temp_filtered(filter: &Option, text: &str) -> bool { fn temp_vec_sort(temperature_vec: &mut Vec) { // 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. temperature_vec.sort_by(|a, b| match a.temperature.partial_cmp(&b.temperature) { diff --git a/src/app/event.rs b/src/app/event.rs index 9201e2c0..f727050d 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -1,6 +1,5 @@ -use std::time::{Duration, Instant}; - -const MAX_TIMEOUT: Duration = Duration::from_millis(400); +pub mod multi_key; +pub use multi_key::*; /// 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. @@ -32,9 +31,9 @@ pub enum EventResult { /// The results of a widget handling some event, like a mouse or key event, /// signifying what the program should then do next. #[derive(Debug)] -pub enum WidgetEventResult { - /// Kill the program. - Quit, +pub enum ComponentEventResult { + /// The event isn't handled by the widget, and should be propagated to the parent. + Unhandled, /// Trigger a redraw. 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. 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, -} - -impl MultiKey { - /// Creates a new [`MultiKey`] with a given pattern and timeout. - pub fn register(pattern: Vec) -> 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 - } - } - } - } -} diff --git a/src/app/event/multi_key.rs b/src/app/event/multi_key.rs new file mode 100644 index 00000000..1ab2c1b7 --- /dev/null +++ b/src/app/event/multi_key.rs @@ -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, +} + +impl MultiKey { + /// Creates a new [`MultiKey`] with a given pattern and timeout. + pub fn register(pattern: Vec) -> 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 + } + } + } + } +} diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 61d17924..9f2c287b 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1511,7 +1511,6 @@ pub fn move_widget_selection( } LayoutNode::Widget(_) => { // Halt! - // TODO: How does this handle carousel? current_id } } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index ffc08cd6..f49a3890 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -7,11 +7,10 @@ use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; use crate::{ app::{ - event::{SelectionAction, WidgetEventResult}, + event::{ComponentEventResult, SelectionAction}, layout_manager::BottomWidgetType, }, canvas::Painter, - constants, options::layout_options::LayoutRule, }; @@ -20,12 +19,15 @@ mod tui_stuff; pub mod base; pub use base::*; +pub mod dialogs; +pub use dialogs::*; + pub mod bottom_widgets; pub use bottom_widgets::*; 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. #[enum_dispatch] @@ -33,16 +35,16 @@ use super::data_farmer::DataCollection; pub trait Component { /// Handles a [`KeyEvent`]. /// - /// Defaults to returning [`EventResult::NoRedraw`], indicating nothing should be done. - fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { - WidgetEventResult::NoRedraw + /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event. + fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { + ComponentEventResult::Unhandled } /// Handles a [`MouseEvent`]. /// - /// Defaults to returning [`EventResult::Continue`], indicating nothing should be done. - fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { - WidgetEventResult::NoRedraw + /// Defaults to returning [`ComponentEventResult::Unhandled`], indicating the component does not handle this event. + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { + ComponentEventResult::Unhandled } /// Returns a [`Component`]'s bounding box. Note that these are defined in *global*, *absolute* @@ -180,6 +182,41 @@ pub enum TmpBottomWidget { 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 { + Hidden, + Shown(D), +} + +impl Default for DialogState +where + D: Default + Component, +{ + fn default() -> Self { + DialogState::Hidden + } +} + +impl DialogState +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 ----- #[derive(Debug)] @@ -250,7 +287,27 @@ impl Default for AppHelpDialogState { AppHelpDialogState { is_showing_help: false, 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 } } } diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs index ba4faa5f..b8652a9f 100644 --- a/src/app/widgets/base/scrollable.rs +++ b/src/app/widgets/base/scrollable.rs @@ -4,7 +4,7 @@ use crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEve use tui::{layout::Rect, widgets::TableState}; use crate::app::{ - event::{MultiKey, MultiKeyResult, WidgetEventResult}, + event::{ComponentEventResult, MultiKey, MultiKeyResult}, 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 { self.set_index(0); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } 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; if self.current_index != last_index { self.set_index(last_index); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } else { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } } /// 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 { - return WidgetEventResult::NoRedraw; + return ComponentEventResult::NoRedraw; } let new_index = self.current_index + change_by; if new_index >= self.num_items || self.current_index == new_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.set_index(new_index); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } /// 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 { - return WidgetEventResult::NoRedraw; + return ComponentEventResult::NoRedraw; } let new_index = self.current_index.saturating_sub(change_by); if self.current_index == new_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.set_index(new_index); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } @@ -207,7 +207,7 @@ impl 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}; if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT { @@ -218,18 +218,19 @@ impl Component for Scrollable { Char('k') => self.move_up(1), Char('g') => match self.gg_manager.input('g') { MultiKeyResult::Completed => self.skip_to_first(), - MultiKeyResult::Accepted => WidgetEventResult::NoRedraw, - MultiKeyResult::Rejected => WidgetEventResult::NoRedraw, + MultiKeyResult::Accepted | MultiKeyResult::Rejected => { + ComponentEventResult::NoRedraw + } }, Char('G') => self.skip_to_last(), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } 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 { MouseEventKind::Down(MouseButton::Left) => { 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::ScrollUp => self.move_up(1), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } diff --git a/src/app/widgets/base/sort_menu.rs b/src/app/widgets/base/sort_menu.rs index b893a418..548bbf77 100644 --- a/src/app/widgets/base/sort_menu.rs +++ b/src/app/widgets/base/sort_menu.rs @@ -3,7 +3,7 @@ use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ app::{ - event::WidgetEventResult, text_table::SimpleColumn, widgets::tui_stuff::BlockBuilder, + event::ComponentEventResult, text_table::SimpleColumn, widgets::tui_stuff::BlockBuilder, Component, TextTable, }, canvas::Painter, @@ -69,11 +69,11 @@ impl Component for SortMenu { 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) } - fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.table.handle_mouse_event(event) } } diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs index 64766041..f1f3b0e4 100644 --- a/src/app/widgets/base/sort_text_table.rs +++ b/src/app/widgets/base/sort_text_table.rs @@ -5,7 +5,7 @@ use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ app::{ - event::{ReturnSignal, WidgetEventResult}, + event::{ReturnSignal, ComponentEventResult}, widgets::tui_stuff::BlockBuilder, Component, TextTable, }, @@ -391,12 +391,12 @@ impl Component for SortableTextTable where 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() { if let Some((shortcut, _)) = *column.shortcut() { if shortcut == event { 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) } - 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 !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! @@ -419,7 +419,7 @@ where if let Some((start, end)) = column.get_x_bounds() { if x >= start && x <= end { self.set_sort_index(index); - return WidgetEventResult::Signal(ReturnSignal::Update); + return ComponentEventResult::Signal(ReturnSignal::Update); } } } diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs index f3480c0e..47a5cb7b 100644 --- a/src/app/widgets/base/text_input.rs +++ b/src/app/widgets/base/text_input.rs @@ -13,8 +13,8 @@ use unicode_width::UnicodeWidthStr; use crate::{ app::{ event::{ + ComponentEventResult::{self}, ReturnSignal, - WidgetEventResult::{self}, }, Component, }, @@ -85,19 +85,19 @@ impl TextInput { } } - fn clear_text(&mut self) -> WidgetEventResult { + fn clear_text(&mut self) -> ComponentEventResult { if self.text.is_empty() { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.text = String::default(); self.cursor = GraphemeCursor::new(0, 0, true); self.window_index = Default::default(); 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(); if current_index < self.text.len() { @@ -105,30 +105,30 @@ impl TextInput { if index > 0 { self.cursor.set_cursor(index + current_index); self.cursor_direction = CursorDirection::Right; - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } } 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(); for (index, _word) in self.text[..current_index].unicode_word_indices().rev() { if index < current_index { self.cursor.set_cursor(index); 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 // cursor location, draw the rest, update the string. let current_index = self.cursor.cur_cursor(); @@ -147,16 +147,16 @@ impl TextInput { } if start_delete_index == current_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.text.drain(start_delete_index..current_index); self.cursor = GraphemeCursor::new(start_delete_index, self.text.len(), true); 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(); if current_index > 0 { @@ -166,13 +166,13 @@ impl TextInput { self.cursor = GraphemeCursor::new(new_index, self.text.len(), true); self.cursor_direction = CursorDirection::Left; - WidgetEventResult::Signal(ReturnSignal::Update) + ComponentEventResult::Signal(ReturnSignal::Update) } 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(); if current_index < self.text.len() { @@ -182,19 +182,19 @@ impl TextInput { self.cursor = GraphemeCursor::new(current_index, self.text.len(), true); self.cursor_direction = CursorDirection::Left; - WidgetEventResult::Signal(ReturnSignal::Update) + ComponentEventResult::Signal(ReturnSignal::Update) } 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(); self.text.insert(current_index, c); self.cursor = GraphemeCursor::new(current_index, self.text.len(), true); self.move_forward(); - WidgetEventResult::Signal(ReturnSignal::Update) + ComponentEventResult::Signal(ReturnSignal::Update) } /// Updates the window indexes and returns the start index. @@ -296,29 +296,29 @@ impl Component for TextInput { 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() { match event.code { KeyCode::Left => { let original_cursor = self.cursor.cur_cursor(); if self.move_back() == original_cursor { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } KeyCode::Right => { let original_cursor = self.cursor.cur_cursor(); if self.move_forward() == original_cursor { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } KeyCode::Backspace => self.clear_previous_grapheme(), KeyCode::Delete => self.clear_current_grapheme(), KeyCode::Char(c) => self.insert_character(c), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } else if let KeyModifiers::CONTROL = event.modifiers { match event.code { @@ -326,46 +326,46 @@ impl Component for TextInput { let prev_index = self.cursor.cur_cursor(); self.cursor.set_cursor(0); if self.cursor.cur_cursor() == prev_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } KeyCode::Char('e') => { let prev_index = self.cursor.cur_cursor(); self.cursor.set_cursor(self.text.len()); if self.cursor.cur_cursor() == prev_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } KeyCode::Char('u') => self.clear_text(), KeyCode::Char('w') => self.clear_word_from_cursor(), KeyCode::Char('h') => self.clear_previous_grapheme(), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } else if let KeyModifiers::ALT = event.modifiers { match event.code { KeyCode::Char('b') => self.move_word_back(), KeyCode::Char('f') => self.move_word_forward(), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } 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... let x = event.column; let widget_x = self.bounds.x + 2; if x >= widget_x { - // TODO: do this - WidgetEventResult::Redraw + // TODO: Do this at some point after refactor + ComponentEventResult::Redraw } else { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } } } diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index 0814555c..e6d42001 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -15,7 +15,7 @@ use tui::{ use unicode_segmentation::UnicodeSegmentation; use crate::{ - app::{event::WidgetEventResult, widgets::tui_stuff::BlockBuilder, Component, Scrollable}, + app::{event::ComponentEventResult, widgets::tui_stuff::BlockBuilder, Component, Scrollable}, canvas::Painter, constants::TABLE_GAP_HEIGHT_LIMIT, }; @@ -130,7 +130,7 @@ where pub show_gap: bool, /// 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. pub border_bounds: Rect, @@ -492,19 +492,19 @@ impl Component for TextTable where C: TableColumn, { - fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { + fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { if self.selectable { self.scrollable.handle_key_event(event) } 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 { self.scrollable.handle_mouse_event(event) } else { - WidgetEventResult::NoRedraw + ComponentEventResult::Unhandled } } diff --git a/src/app/widgets/base/time_graph.rs b/src/app/widgets/base/time_graph.rs index 94e5f847..81ca9e15 100644 --- a/src/app/widgets/base/time_graph.rs +++ b/src/app/widgets/base/time_graph.rs @@ -15,7 +15,7 @@ use tui::{ use crate::{ app::{ - event::WidgetEventResult, + event::ComponentEventResult, widgets::tui_stuff::{ custom_legend_chart::{Axis, Dataset}, TimeChart, @@ -160,62 +160,62 @@ impl TimeGraph { } /// Handles a char `c`. - fn handle_char(&mut self, c: char) -> WidgetEventResult { + fn handle_char(&mut self, c: char) -> ComponentEventResult { match c { '-' => self.zoom_out(), '+' => self.zoom_in(), '=' => 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); if self.current_display_time == new_time { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else if new_time >= self.min_duration { self.current_display_time = new_time; self.autohide_timer.start_display_timer(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } else if new_time != self.min_duration { self.current_display_time = self.min_duration; self.autohide_timer.start_display_timer(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } 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; if self.current_display_time == new_time { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else if new_time <= self.max_duration { self.current_display_time = new_time; self.autohide_timer.start_display_timer(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } else if new_time != self.max_duration { self.current_display_time = self.max_duration; self.autohide_timer.start_display_timer(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } 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 { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.current_display_time = self.default_time_value; self.autohide_timer.start_display_timer(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } @@ -307,24 +307,24 @@ impl 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; if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT { match event.code { Char(c) => self.handle_char(c), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } 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 { MouseEventKind::ScrollDown => self.zoom_out(), MouseEventKind::ScrollUp => self.zoom_in(), - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } diff --git a/src/app/widgets/bottom_widgets/basic_mem.rs b/src/app/widgets/bottom_widgets/basic_mem.rs index d70d0e04..3658ee7c 100644 --- a/src/app/widgets/bottom_widgets/basic_mem.rs +++ b/src/app/widgets/bottom_widgets/basic_mem.rs @@ -8,7 +8,8 @@ use tui::{ use crate::{ app::{ - event::WidgetEventResult, widgets::tui_stuff::PipeGauge, Component, DataCollection, Widget, + event::ComponentEventResult, widgets::tui_stuff::PipeGauge, Component, DataCollection, + Widget, }, canvas::Painter, constants::SIDE_BORDERS, @@ -54,13 +55,13 @@ impl Component for BasicMem { 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 { - KeyCode::Char('%') if event.modifiers.is_empty() => { + KeyCode::Char('%') => { self.use_percent = !self.use_percent; - WidgetEventResult::Redraw + ComponentEventResult::Redraw } - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } } diff --git a/src/app/widgets/bottom_widgets/battery.rs b/src/app/widgets/bottom_widgets/battery.rs index 1d0c9c8b..8c1d0721 100644 --- a/src/app/widgets/bottom_widgets/battery.rs +++ b/src/app/widgets/bottom_widgets/battery.rs @@ -14,7 +14,7 @@ use tui::{ use crate::{ 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, }, canvas::Painter, @@ -114,44 +114,44 @@ impl Component for BatteryTable { 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() { match event.code { KeyCode::Left => { let current_index = self.selected_index; self.decrement_index(); if current_index == self.selected_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } KeyCode::Right => { let current_index = self.selected_index; self.increment_index(); if current_index == self.selected_index { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } 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() { if does_bound_intersect_coordinate(event.column, event.row, *bound) && itx < self.battery_data.len() { self.selected_index = itx; - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } } - WidgetEventResult::NoRedraw + ComponentEventResult::Unhandled } } @@ -183,7 +183,7 @@ impl Widget for BatteryTable { .block() .selected(selected) .borders(self.block_border) - .expanded(expanded) + .show_esc(expanded) .build(painter, area); let inner_area = block.inner(area); diff --git a/src/app/widgets/bottom_widgets/carousel.rs b/src/app/widgets/bottom_widgets/carousel.rs index 9c8356fb..92d3614c 100644 --- a/src/app/widgets/bottom_widgets/carousel.rs +++ b/src/app/widgets/bottom_widgets/carousel.rs @@ -12,7 +12,7 @@ use tui::{ use crate::{ app::{ - does_bound_intersect_coordinate, event::WidgetEventResult, Component, SelectableType, + does_bound_intersect_coordinate, event::ComponentEventResult, Component, SelectableType, Widget, }, canvas::Painter, @@ -164,7 +164,7 @@ impl Component for Carousel { 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 { crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left) => { let x = event.column; @@ -172,15 +172,15 @@ impl Component for Carousel { if does_bound_intersect_coordinate(x, y, self.left_button_bounds) { self.decrement_index(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } else if does_bound_intersect_coordinate(x, y, self.right_button_bounds) { self.increment_index(); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } else { - WidgetEventResult::NoRedraw + ComponentEventResult::Unhandled } } - _ => WidgetEventResult::NoRedraw, + _ => ComponentEventResult::Unhandled, } } } diff --git a/src/app/widgets/bottom_widgets/cpu.rs b/src/app/widgets/bottom_widgets/cpu.rs index 9397a7e1..6e5e18a5 100644 --- a/src/app/widgets/bottom_widgets/cpu.rs +++ b/src/app/widgets/bottom_widgets/cpu.rs @@ -9,7 +9,7 @@ use tui::{ use crate::{ app::{ - event::{SelectionAction, WidgetEventResult}, + event::{ComponentEventResult, SelectionAction}, text_table::SimpleColumn, time_graph::TimeGraphData, AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection, @@ -118,21 +118,21 @@ impl 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 { CpuGraphSelection::Graph => self.graph.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 let CpuGraphSelection::Graph = self.selected { self.graph.handle_mouse_event(event) } else { self.selected = CpuGraphSelection::Graph; self.graph.handle_mouse_event(event); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } else if self.legend.does_border_intersect_mouse(&event) { if let CpuGraphSelection::Legend = self.selected { @@ -140,10 +140,10 @@ impl Component for CpuGraph { } else { self.selected = CpuGraphSelection::Legend; self.legend.handle_mouse_event(event); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } else { - WidgetEventResult::NoRedraw + ComponentEventResult::Unhandled } } @@ -193,7 +193,7 @@ impl Widget for CpuGraph { let legend_block = self .block() .selected(selected && matches!(&self.selected, CpuGraphSelection::Legend)) - .expanded(expanded) + .show_esc(expanded) .hide_title(true); let legend_data = self @@ -279,7 +279,7 @@ impl Widget for CpuGraph { let graph_block = self .block() .selected(selected && matches!(&self.selected, CpuGraphSelection::Graph)) - .expanded(expanded) + .show_esc(expanded) .build(painter, graph_block_area); self.graph.draw_tui_chart( diff --git a/src/app/widgets/bottom_widgets/disk.rs b/src/app/widgets/bottom_widgets/disk.rs index 1f5383da..74b63f86 100644 --- a/src/app/widgets/bottom_widgets/disk.rs +++ b/src/app/widgets/bottom_widgets/disk.rs @@ -5,7 +5,7 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame}; use crate::{ app::{ - data_farmer::DataCollection, event::WidgetEventResult, + data_farmer::DataCollection, event::ComponentEventResult, sort_text_table::SimpleSortableColumn, text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget, }, @@ -105,11 +105,11 @@ impl 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) } - fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.table.handle_mouse_event(event) } @@ -135,7 +135,7 @@ impl Widget for DiskTable { .block() .selected(selected) .borders(self.block_border) - .expanded(expanded); + .show_esc(expanded); self.table.draw_tui_table( painter, diff --git a/src/app/widgets/bottom_widgets/mem.rs b/src/app/widgets/bottom_widgets/mem.rs index 11880421..465e3ab9 100644 --- a/src/app/widgets/bottom_widgets/mem.rs +++ b/src/app/widgets/bottom_widgets/mem.rs @@ -4,7 +4,7 @@ use crossterm::event::{KeyEvent, MouseEvent}; use tui::{backend::Backend, layout::Rect}; use crate::{ - app::{event::WidgetEventResult, time_graph::TimeGraphData, DataCollection}, + app::{event::ComponentEventResult, time_graph::TimeGraphData, DataCollection}, app::{Component, TimeGraph, Widget}, data_conversion::{convert_mem_data_points, convert_mem_labels, convert_swap_data_points}, options::layout_options::LayoutRule, @@ -63,11 +63,11 @@ impl 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) } - fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.graph.handle_mouse_event(event) } @@ -92,7 +92,7 @@ impl Widget for MemGraph { let block = self .block() .selected(selected) - .expanded(expanded) + .show_esc(expanded) .build(painter, area); let mut chart_data = Vec::with_capacity(2); diff --git a/src/app/widgets/bottom_widgets/net.rs b/src/app/widgets/bottom_widgets/net.rs index b02b957f..1449aa6a 100644 --- a/src/app/widgets/bottom_widgets/net.rs +++ b/src/app/widgets/bottom_widgets/net.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, collections::HashMap, time::Instant}; +use crossterm::event::{KeyEvent, MouseEvent}; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, @@ -8,9 +9,9 @@ use tui::{ use crate::{ app::{ - data_farmer::DataCollection, text_table::SimpleColumn, time_graph::TimeGraphData, - widgets::tui_stuff::BlockBuilder, AppConfigFields, AxisScaling, Component, TextTable, - TimeGraph, Widget, + data_farmer::DataCollection, event::ComponentEventResult, text_table::SimpleColumn, + time_graph::TimeGraphData, widgets::tui_stuff::BlockBuilder, AppConfigFields, AxisScaling, + Component, TextTable, TimeGraph, Widget, }, canvas::Painter, data_conversion::convert_network_data_points, @@ -497,15 +498,11 @@ impl Component for NetGraph { self.bounds = new_bounds; } - fn handle_key_event( - &mut self, event: crossterm::event::KeyEvent, - ) -> crate::app::event::WidgetEventResult { + fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { self.graph.handle_key_event(event) } - fn handle_mouse_event( - &mut self, event: crossterm::event::MouseEvent, - ) -> crate::app::event::WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.graph.handle_mouse_event(event) } } @@ -522,7 +519,7 @@ impl Widget for NetGraph { let block = self .block() .selected(selected) - .expanded(expanded) + .show_esc(expanded) .build(painter, area); self.set_draw_cache(); @@ -644,15 +641,11 @@ impl Component for OldNetGraph { self.bounds = new_bounds; } - fn handle_key_event( - &mut self, event: crossterm::event::KeyEvent, - ) -> crate::app::event::WidgetEventResult { + fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { self.net_graph.handle_key_event(event) } - fn handle_mouse_event( - &mut self, event: crossterm::event::MouseEvent, - ) -> crate::app::event::WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.net_graph.handle_mouse_event(event) } } diff --git a/src/app/widgets/bottom_widgets/process.rs b/src/app/widgets/bottom_widgets/process.rs index eeff0b54..ba1dcf2e 100644 --- a/src/app/widgets/bottom_widgets/process.rs +++ b/src/app/widgets/bottom_widgets/process.rs @@ -15,7 +15,7 @@ use tui::{ use crate::{ app::{ data_harvester::processes::ProcessHarvest, - event::{MultiKey, MultiKeyResult, ReturnSignal, SelectionAction, WidgetEventResult}, + event::{ComponentEventResult, MultiKey, MultiKeyResult, ReturnSignal, SelectionAction}, query::*, text_table::DesiredColumnWidth, widgets::tui_stuff::BlockBuilder, @@ -869,27 +869,27 @@ impl ProcessManager { self } - fn open_search(&mut self) -> WidgetEventResult { + fn open_search(&mut self) -> ComponentEventResult { if let ProcessManagerSelection::Search = self.selected { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.show_search = true; self.prev_selected = self.selected; 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 { - WidgetEventResult::NoRedraw + ComponentEventResult::NoRedraw } else { self.sort_menu .set_index(self.process_table.current_sorting_column_index()); self.show_sort = true; self.prev_selected = self.selected; 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() { self.process_table .set_column(ProcessSortColumn::new(ProcessSortType::Name), 1); @@ -929,7 +929,7 @@ impl ProcessManager { // Invalidate row cache. self.process_table.invalidate_cached_columns(); - WidgetEventResult::Signal(ReturnSignal::Update) + ComponentEventResult::Signal(ReturnSignal::Update) } 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() { self.process_table .set_column(ProcessSortColumn::new(ProcessSortType::Pid), 0); @@ -965,7 +965,7 @@ impl ProcessManager { // Invalidate row cache. self.process_table.invalidate_cached_columns(); - WidgetEventResult::Signal(ReturnSignal::Update) + ComponentEventResult::Signal(ReturnSignal::Update) } fn hide_sort(&mut self) { @@ -994,7 +994,7 @@ impl Component for ProcessManager { self.bounds = new_bounds; } - fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { + fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult { // "Global" handling: if let KeyCode::Esc = event.code { @@ -1002,19 +1002,19 @@ impl Component for ProcessManager { ProcessManagerSelection::Processes => { if self.show_sort { self.hide_sort(); - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } else if self.show_search { self.hide_search(); - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } } ProcessManagerSelection::Sort if self.show_sort => { self.hide_sort(); - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } ProcessManagerSelection::Search if self.show_search => { self.hide_search(); - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } _ => {} } @@ -1039,7 +1039,7 @@ impl Component for ProcessManager { // Kill the selected process(es) } MultiKeyResult::Accepted | MultiKeyResult::Rejected => { - return WidgetEventResult::NoRedraw; + return ComponentEventResult::NoRedraw; } } } @@ -1057,7 +1057,7 @@ impl Component for ProcessManager { } KeyCode::Char('t') | KeyCode::F(5) => { self.in_tree_mode = !self.in_tree_mode; - return WidgetEventResult::Redraw; + return ComponentEventResult::Redraw; } KeyCode::Char('s') | KeyCode::F(6) => { return self.open_sort(); @@ -1086,7 +1086,7 @@ impl Component for ProcessManager { KeyCode::Enter => { self.process_table .set_sort_index(self.sort_menu.current_index()); - return WidgetEventResult::Signal(ReturnSignal::Update); + return ComponentEventResult::Signal(ReturnSignal::Update); } KeyCode::Char('/') => { return self.open_search(); @@ -1115,7 +1115,7 @@ impl Component for ProcessManager { } 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.search_input.query(), 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 { MouseEventKind::Down(MouseButton::Left) => { if self.process_table.does_border_intersect_mouse(&event) { @@ -1139,11 +1139,10 @@ impl Component for ProcessManager { self.prev_selected = self.selected; self.selected = ProcessManagerSelection::Processes; match self.process_table.handle_mouse_event(event) { - WidgetEventResult::Quit => WidgetEventResult::Quit, - WidgetEventResult::Redraw | WidgetEventResult::NoRedraw => { - WidgetEventResult::Redraw - } - WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s), + ComponentEventResult::Unhandled + | ComponentEventResult::Redraw + | ComponentEventResult::NoRedraw => ComponentEventResult::Redraw, + ComponentEventResult::Signal(s) => ComponentEventResult::Signal(s), } } } else if self.sort_menu.does_border_intersect_mouse(&event) { @@ -1153,7 +1152,7 @@ impl Component for ProcessManager { self.prev_selected = self.selected; self.selected = ProcessManagerSelection::Sort; self.sort_menu.handle_mouse_event(event); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } else if does_bound_intersect_coordinate( event.column, @@ -1166,10 +1165,10 @@ impl Component for ProcessManager { self.prev_selected = self.selected; self.selected = ProcessManagerSelection::Search; self.search_input.handle_mouse_event(event); - WidgetEventResult::Redraw + ComponentEventResult::Redraw } } else { - WidgetEventResult::NoRedraw + ComponentEventResult::Unhandled } } 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::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] = [ Constraint::Min(0), if self.block_border.contains(Borders::TOP) { - Constraint::Length(4) + Constraint::Length(5) } else { - Constraint::Length(2) + Constraint::Length(3) }, ]; const INTERNAL_SEARCH_CONSTRAINTS: [Constraint; 2] = [Constraint::Length(1); 2]; @@ -1277,7 +1276,7 @@ impl Widget for ProcessManager { .block() .selected(process_selected) .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( painter, diff --git a/src/app/widgets/bottom_widgets/temp.rs b/src/app/widgets/bottom_widgets/temp.rs index 9d5ef4f9..be8623f6 100644 --- a/src/app/widgets/bottom_widgets/temp.rs +++ b/src/app/widgets/bottom_widgets/temp.rs @@ -6,8 +6,9 @@ use tui::{backend::Backend, layout::Rect, widgets::Borders, Frame}; use crate::{ app::{ data_farmer::DataCollection, data_harvester::temperature::TemperatureType, - event::WidgetEventResult, sort_text_table::SimpleSortableColumn, text_table::TextTableData, - AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, Widget, + event::ComponentEventResult, sort_text_table::SimpleSortableColumn, + text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, + TextTable, Widget, }, canvas::Painter, data_conversion::convert_temp_row, @@ -116,11 +117,11 @@ impl 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) } - fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { + fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult { self.table.handle_mouse_event(event) } @@ -146,7 +147,7 @@ impl Widget for TempTable { .block() .selected(selected) .borders(self.block_border) - .expanded(expanded); + .show_esc(expanded); self.table.draw_tui_table( painter, diff --git a/src/app/widgets/dialogs.rs b/src/app/widgets/dialogs.rs new file mode 100644 index 00000000..e2dc26bb --- /dev/null +++ b/src/app/widgets/dialogs.rs @@ -0,0 +1,2 @@ +pub mod help; +pub use help::HelpDialog; diff --git a/src/app/widgets/dialogs/help.rs b/src/app/widgets/dialogs/help.rs new file mode 100644 index 00000000..fba8a77c --- /dev/null +++ b/src/app/widgets/dialogs/help.rs @@ -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>>, + 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, +} + +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 = 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::>() + }) + .flatten() + .collect::>() + }); + + 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( + &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, + } + } +} diff --git a/src/app/widgets/tui_stuff/block_builder.rs b/src/app/widgets/tui_stuff/block_builder.rs index 6f9801c8..cec835e3 100644 --- a/src/app/widgets/tui_stuff/block_builder.rs +++ b/src/app/widgets/tui_stuff/block_builder.rs @@ -10,7 +10,7 @@ use crate::canvas::Painter; pub struct BlockBuilder { borders: Borders, selected: bool, - expanded: bool, + show_esc: bool, name: &'static str, hide_title: bool, extra_text: Option, @@ -22,7 +22,7 @@ impl BlockBuilder { Self { borders: Borders::ALL, selected: false, - expanded: false, + show_esc: false, name, hide_title: false, extra_text: None, @@ -35,9 +35,9 @@ impl BlockBuilder { self } - /// Indicates that this block is currently expanded, and should be drawn as such. - pub fn expanded(mut self, expanded: bool) -> Self { - self.expanded = expanded; + /// Indicates that this block should show esc, and should be drawn as such. + pub fn show_esc(mut self, show_esc: bool) -> Self { + self.show_esc = show_esc; self } @@ -64,12 +64,14 @@ impl BlockBuilder { let has_title = !self.hide_title && (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() - .border_style(if self.selected { - painter.colours.highlighted_border_style - } else { - painter.colours.border_style - }) + .border_style(border_style) .borders(self.borders); let inner_width = block.inner(area).width as usize; @@ -82,12 +84,11 @@ impl BlockBuilder { let mut title_len = name.width(); 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_LEN: usize = EXPAND_TEXT.len(); - let expand_span = - Span::styled(EXPAND_TEXT, painter.colours.highlighted_border_style); + let expand_span = Span::styled(EXPAND_TEXT, border_style); if title_len + EXPAND_TEXT_LEN <= inner_width { 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); - title[2] = Span::styled( - "─".repeat(difference), - if self.selected { - painter.colours.highlighted_border_style - } else { - painter.colours.border_style - }, - ); + title[2] = Span::styled("─".repeat(difference), border_style); } block.title(title) diff --git a/src/bin/main.rs b/src/bin/main.rs index 358b95be..84b88d0b 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -50,7 +50,7 @@ fn main() -> Result<()> { let thread_termination_cvar = Arc::new(Condvar::new()); // 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()); // Cleaning loop diff --git a/src/canvas.rs b/src/canvas.rs index 0c91978d..a494f898 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -5,7 +5,7 @@ use indextree::{Arena, NodeId}; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, - text::{Span, Spans}, + text::Span, widgets::Paragraph, Frame, Terminal, }; @@ -19,7 +19,7 @@ use crate::{ layout_manager::{generate_layout, ColLayout, LayoutNode, RowLayout}, text_table::TextTableData, widgets::{Component, Widget}, - TmpBottomWidget, + DialogState, TmpBottomWidget, }, constants::*, data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData}, @@ -92,14 +92,12 @@ impl FromStr for ColourScheme { /// Handles the canvas' state. pub struct Painter { pub colours: CanvasColours, - styled_help_text: Vec>, } impl Painter { pub fn init(config: &Config, colour_scheme: ColourScheme) -> anyhow::Result { let mut painter = Painter { colours: CanvasColours::default(), - styled_help_text: Vec::default(), }; if let ColourScheme::Custom = colour_scheme { @@ -107,7 +105,6 @@ impl Painter { } else { painter.generate_colour_scheme(colour_scheme)?; } - painter.complete_painter_init(); Ok(painter) } @@ -153,43 +150,6 @@ impl Painter { 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::>(), - ); - } 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::>(), - ); - } - } - }); - - 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(&self, f: &mut Frame<'_, B>, draw_loc: Rect) { f.render_widget( Paragraph::new(Span::styled( @@ -218,7 +178,7 @@ impl Painter { let terminal_height = draw_area.height; 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 border_len = terminal_height.saturating_sub(gen_help_len) / 2; let vertical_dialog_chunk = Layout::default() @@ -248,7 +208,7 @@ impl Painter { }) .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 { // 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 diff --git a/src/canvas/dialogs.rs b/src/canvas/dialogs.rs index 7a2a7e20..af15e931 100644 --- a/src/canvas/dialogs.rs +++ b/src/canvas/dialogs.rs @@ -1,5 +1,2 @@ pub mod dd_dialog; -pub mod help_dialog; - pub use dd_dialog::KillDialog; -pub use help_dialog::HelpDialog; diff --git a/src/canvas/dialogs/help_dialog.rs b/src/canvas/dialogs/help_dialog.rs deleted file mode 100644 index 54b47000..00000000 --- a/src/canvas/dialogs/help_dialog.rs +++ /dev/null @@ -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( - &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( - &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, - ); - } -} diff --git a/src/constants.rs b/src/constants.rs index 636616c7..5db55def 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -227,159 +227,193 @@ pub static NORD_LIGHT_COLOUR_PALETTE: Lazy = Lazy::new(|| ConfigC // Help text pub const HELP_CONTENTS_TEXT: [&str; 8] = [ - "Press the corresponding numbers to jump to the section, or scroll:", - "1 - General", - "2 - CPU widget", - "3 - Process widget", - "4 - Process search widget", - "5 - Process sort widget", - "6 - Battery widget", - "7 - Basic memory widget", + "Press the corresponding numbers to jump to that section, or just scroll down:", + "[1] General", + "[2] CPU widget", + "[3] Process widget", + "[4] Process search widget", + "[5] Process sort widget", + "[6] Battery widget", + "[7] Basic memory widget", ]; -// TODO [Help]: Search in help? -// TODO [Help]: Move to using tables for easier formatting? -pub const GENERAL_HELP_TEXT: [&str; 30] = [ - "1 - General", - "q, Ctrl-c Quit", - "Esc 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-Left, ", - "Shift-Left, Move widget selection left", - "H, A ", - "Ctrl-Right, ", - "Shift-Right, Move widget selection right", - "L, D ", - "Ctrl-Up, ", - "Shift-Up, Move widget selection up", - "K, W ", - "Ctrl-Down, ", - "Shift-Down, Move widget selection down", - "J, S ", - "Left, h Move left within widget", - "Down, j Move down within widget", - "Up, k Move up within widget", - "Right, l Move right within widget", - "? Open help menu", - "gg Jump to the first entry", - "G Jump to the last entry", - "e Toggle expanding the currently selected widget", - "+ Zoom in on chart (decrease time range)", - "- Zoom out on chart (increase time range)", - "= 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", +pub const GENERAL_HELP_TITLE: &str = "General"; +pub const GENERAL_HELP_TEXT: [[&str; 2]; 21] = [ + ["q, Ctrl-c", "Quit"], + [ + "Esc", + "Close dialog windows, search, widgets, or exit expanded mode", + ], + ["Ctrl-r", "Resets any collected data"], + ["f", "Toggles freezing, stopping new data from being shown"], + ["Ctrl-Left\nShift-Left\nH, A", "Move widget selection left"], + [ + "Ctrl-Right\nShift-Right\nL, D", + "Move widget selection right", + ], + ["Ctrl-Up\nShift-Up\nK, W", "Move widget selection up"], + ["Ctrl-Down\nShift-Dow\nJ, S", "Move widget selection down"], + ["Left, h", "Move left within widget"], + ["Down, j", "Move down within widget"], + ["Up, k", "Move up within widget"], + ["Right, l", "Move right within widget"], + ["?", "Open help menu"], + ["gg", "Jump to the first entry"], + ["G", "Jump to the last entry"], + ["e", "Toggle expanding the currently selected widget"], + ["+", "Zoom in on chart (decrease time range)"], + ["-", "Zoom out on chart (increase time range)"], + ["=", "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", + ], ]; -pub const CPU_HELP_TEXT: [&str; 2] = [ - "2 - CPU widget\n", - "Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart", +pub const CPU_HELP_TITLE: &str = "CPU Widget"; +pub const CPU_HELP_TEXT: [[&str; 2]; 1] = [[ + "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] = [ - "3 - Process widget", - "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 SEARCH_TEXT_HELP_TITLE: &str = "Process Search"; +pub const SEARCH_HELP_TEXT: [[&str; 2]; 48] = [ + ["Tab", "Toggle between searching for PID and name"], + ["Esc", "Close the search widget (retains the filter)"], + ["Ctrl-a", "Skip to the start of the search query"], + ["Ctrl-e", "Skip to the end of the search query"], + ["Ctrl-u", "Clear the current search query"], + ["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"], + ["\n", "\n"], + ["Supported search types:", ""], + ["", "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, &&, ", ": 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] = [ - "4 - Process search widget", - "Tab Toggle between searching for PID and name", - "Esc Close the search widget (retains the filter)", - "Ctrl-a Skip to the start of the search query", - "Ctrl-e Skip to the end of the search query", - "Ctrl-u Clear the current search query", - "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:", - " 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, &&, 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 PROCESS_SORT_HELP_TITLE: &str = "Process Sort"; +pub const PROCESS_SORT_HELP_TEXT: [[&str; 2]; 5] = [ + ["Down, 'j'", "Scroll down in list"], + ["Up, 'k'", "Scroll up in list"], + ["Mouse scroll", "Scroll through sort widget"], + ["Esc", "Close the sort widget"], + ["Enter", "Sort by current selected column"], ]; -pub const SORT_HELP_TEXT: [&str; 6] = [ - "5 - Sort widget\n", - "Down, 'j' Scroll down in list", - "Up, 'k' Scroll up in list", - "Mouse scroll Scroll through sort widget", - "Esc Close the sort widget", - "Enter Sort by current selected column", +pub const BATTERY_HELP_TITLE: &str = "Battery Widget"; +pub const BATTERY_HELP_TEXT: [[&str; 2]; 2] = [ + ["Left", "Go to previous battery"], + ["Right", "Go to next battery"], ]; -pub const BATTERY_HELP_TEXT: [&str; 3] = [ - "6 - Battery widget", - "Left Go to previous battery", - "Right Go to next battery", -]; +pub const BASIC_MEM_HELP_TITLE: &str = "Basic Memory Widget"; +pub const BASIC_MEM_HELP_TEXT: [[&str; 2]; 1] = [[ + "%", + "Toggle between values and percentages for memory usage", +]]; -pub const BASIC_MEM_HELP_TEXT: [&str; 2] = [ - "7 - Basic memory widget", - "% Toggle between values and percentages for memory usage", -]; - -pub static HELP_TEXT: Lazy>> = Lazy::new(|| { - vec![ - HELP_CONTENTS_TEXT.to_vec(), +pub static HELP_TEXT: Lazy<[Vec<[&'static str; 2]>; 7]> = Lazy::new(|| { + [ GENERAL_HELP_TEXT.to_vec(), CPU_HELP_TEXT.to_vec(), PROCESS_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(), 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 pub const DEFAULT_LAYOUT: &str = r##" [[row]] diff --git a/src/lib.rs b/src/lib.rs index 05718df6..cf963426 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ pub fn handle_key_event( KeyCode::Left => app.on_left_key(), KeyCode::Right => app.on_right_key(), 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::Tab => app.on_tab(), KeyCode::Backspace => app.on_backspace(),