mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +02:00
refactor: finish help menu
This commit is contained in:
parent
e7b9c72912
commit
7ee85a82f7
@ -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
|
||||
|
||||
|
29
Cargo.lock
generated
29
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above |
|
||||
|
345
src/app.rs
345
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<LayoutNode>,
|
||||
pub layout_tree_root: NodeId,
|
||||
frozen_state: FrozenState,
|
||||
|
||||
pub help_dialog: DialogState<HelpDialog>,
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -224,15 +224,12 @@ impl DataCollection {
|
||||
}
|
||||
|
||||
fn eat_temp(&mut self, temperature_sensors: Vec<temperature::TempHarvest>) {
|
||||
// TODO: [PO] To implement
|
||||
self.temp_harvest = temperature_sensors.to_vec();
|
||||
}
|
||||
|
||||
fn eat_disks(
|
||||
&mut self, disks: Vec<disks::DiskHarvest>, 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();
|
||||
|
@ -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,
|
||||
|
@ -57,7 +57,6 @@ fn is_temp_filtered(filter: &Option<Filter>, text: &str) -> bool {
|
||||
|
||||
fn temp_vec_sort(temperature_vec: &mut Vec<TempHarvest>) {
|
||||
// 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) {
|
||||
|
109
src/app/event.rs
109
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<char>,
|
||||
}
|
||||
|
||||
impl MultiKey {
|
||||
/// Creates a new [`MultiKey`] with a given pattern and timeout.
|
||||
pub fn register(pattern: Vec<char>) -> Self {
|
||||
Self {
|
||||
state: MultiKeyState::Idle,
|
||||
pattern,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to an idle state.
|
||||
fn reset(&mut self) {
|
||||
self.state = MultiKeyState::Idle;
|
||||
}
|
||||
|
||||
/// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
|
||||
/// - Accepting the char and moving to the next state
|
||||
/// - Completing the multi-key pattern
|
||||
/// - Rejecting it
|
||||
///
|
||||
/// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
|
||||
/// before trying to check the char.
|
||||
pub fn input(&mut self, c: char) -> MultiKeyResult {
|
||||
match &mut self.state {
|
||||
MultiKeyState::Idle => {
|
||||
if let Some(first) = self.pattern.first() {
|
||||
if *first == c {
|
||||
self.state = MultiKeyState::Waiting {
|
||||
trigger_instant: Instant::now(),
|
||||
checked_index: 0,
|
||||
};
|
||||
|
||||
return MultiKeyResult::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
MultiKeyState::Waiting {
|
||||
trigger_instant,
|
||||
checked_index,
|
||||
} => {
|
||||
if trigger_instant.elapsed() > MAX_TIMEOUT {
|
||||
// Just reset and recursively call (putting it into Idle).
|
||||
self.reset();
|
||||
self.input(c)
|
||||
} else if let Some(next) = self.pattern.get(*checked_index + 1) {
|
||||
if *next == c {
|
||||
*checked_index += 1;
|
||||
|
||||
if *checked_index == self.pattern.len() - 1 {
|
||||
self.reset();
|
||||
MultiKeyResult::Completed
|
||||
} else {
|
||||
MultiKeyResult::Accepted
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
101
src/app/event/multi_key.rs
Normal file
101
src/app/event/multi_key.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const MAX_TIMEOUT: Duration = Duration::from_millis(400);
|
||||
|
||||
/// The states a [`MultiKey`] can be in.
|
||||
enum MultiKeyState {
|
||||
/// Currently not waiting on any next input.
|
||||
Idle,
|
||||
/// Waiting for the next input, with a given trigger [`Instant`].
|
||||
Waiting {
|
||||
/// When it was triggered.
|
||||
trigger_instant: Instant,
|
||||
|
||||
/// What part of the pattern it is at.
|
||||
checked_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// The possible outcomes of calling [`MultiKey::input`].
|
||||
pub enum MultiKeyResult {
|
||||
/// Returned when a character was *accepted*, but has not completed the sequence required.
|
||||
Accepted,
|
||||
/// Returned when a character is accepted and completes the sequence.
|
||||
Completed,
|
||||
/// Returned if a character breaks the sequence or if it has already timed out.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
/// A struct useful for managing multi-key keybinds.
|
||||
pub struct MultiKey {
|
||||
state: MultiKeyState,
|
||||
pattern: Vec<char>,
|
||||
}
|
||||
|
||||
impl MultiKey {
|
||||
/// Creates a new [`MultiKey`] with a given pattern and timeout.
|
||||
pub fn register(pattern: Vec<char>) -> Self {
|
||||
Self {
|
||||
state: MultiKeyState::Idle,
|
||||
pattern,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets to an idle state.
|
||||
fn reset(&mut self) {
|
||||
self.state = MultiKeyState::Idle;
|
||||
}
|
||||
|
||||
/// Handles a char input and returns the current status of the [`MultiKey`] after, which is one of:
|
||||
/// - Accepting the char and moving to the next state
|
||||
/// - Completing the multi-key pattern
|
||||
/// - Rejecting it
|
||||
///
|
||||
/// Note that if a [`MultiKey`] only "times out" upon calling this - if it has timed out, it will first reset
|
||||
/// before trying to check the char.
|
||||
pub fn input(&mut self, c: char) -> MultiKeyResult {
|
||||
match &mut self.state {
|
||||
MultiKeyState::Idle => {
|
||||
if let Some(first) = self.pattern.first() {
|
||||
if *first == c {
|
||||
self.state = MultiKeyState::Waiting {
|
||||
trigger_instant: Instant::now(),
|
||||
checked_index: 0,
|
||||
};
|
||||
|
||||
return MultiKeyResult::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
MultiKeyState::Waiting {
|
||||
trigger_instant,
|
||||
checked_index,
|
||||
} => {
|
||||
if trigger_instant.elapsed() > MAX_TIMEOUT {
|
||||
// Just reset and recursively call (putting it into Idle).
|
||||
self.reset();
|
||||
self.input(c)
|
||||
} else if let Some(next) = self.pattern.get(*checked_index + 1) {
|
||||
if *next == c {
|
||||
*checked_index += 1;
|
||||
|
||||
if *checked_index == self.pattern.len() - 1 {
|
||||
self.reset();
|
||||
MultiKeyResult::Completed
|
||||
} else {
|
||||
MultiKeyResult::Accepted
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
} else {
|
||||
self.reset();
|
||||
MultiKeyResult::Rejected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1511,7 +1511,6 @@ pub fn move_widget_selection(
|
||||
}
|
||||
LayoutNode::Widget(_) => {
|
||||
// Halt!
|
||||
// TODO: How does this handle carousel?
|
||||
current_id
|
||||
}
|
||||
}
|
||||
|
@ -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<D: Default + Component> {
|
||||
Hidden,
|
||||
Shown(D),
|
||||
}
|
||||
|
||||
impl<D> Default for DialogState<D>
|
||||
where
|
||||
D: Default + Component,
|
||||
{
|
||||
fn default() -> Self {
|
||||
DialogState::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> DialogState<D>
|
||||
where
|
||||
D: Default + Component,
|
||||
{
|
||||
pub fn is_showing(&self) -> bool {
|
||||
matches!(self, DialogState::Shown(_))
|
||||
}
|
||||
|
||||
pub fn hide(&mut self) {
|
||||
*self = DialogState::Hidden;
|
||||
}
|
||||
|
||||
pub fn show(&mut self) {
|
||||
*self = DialogState::Shown(D::default());
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Old stuff below -----
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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<S> Component for SortableTextTable<S>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<C> Component for TextTable<C>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
2
src/app/widgets/dialogs.rs
Normal file
2
src/app/widgets/dialogs.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod help;
|
||||
pub use help::HelpDialog;
|
304
src/app/widgets/dialogs/help.rs
Normal file
304
src/app/widgets/dialogs/help.rs
Normal file
@ -0,0 +1,304 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
|
||||
use fxhash::FxHashMap;
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
text::{Span, Spans},
|
||||
widgets::{Borders, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
event::{ComponentEventResult, MultiKey, MultiKeyResult},
|
||||
widgets::tui_stuff::BlockBuilder,
|
||||
Component,
|
||||
},
|
||||
canvas::Painter,
|
||||
constants::*,
|
||||
};
|
||||
|
||||
pub struct HelpDialog {
|
||||
current_index: usize,
|
||||
max_index: usize,
|
||||
bounds: Rect,
|
||||
wrapped_text: Vec<Vec<Spans<'static>>>,
|
||||
left_column_width: Constraint,
|
||||
right_column_width: Constraint,
|
||||
|
||||
/// Manages the `gg` double-tap shortcut.
|
||||
gg_manager: MultiKey,
|
||||
|
||||
/// A jury-rigged solution for shortcut indices.
|
||||
/// TODO: THIS DOES NOT SCALE WELL!
|
||||
shortcut_indices: FxHashMap<u32, usize>,
|
||||
}
|
||||
|
||||
impl Default for HelpDialog {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
current_index: Default::default(),
|
||||
max_index: Default::default(),
|
||||
bounds: Default::default(),
|
||||
left_column_width: Constraint::Length(0),
|
||||
right_column_width: Constraint::Length(0),
|
||||
wrapped_text: Default::default(),
|
||||
gg_manager: MultiKey::register(vec!['g', 'g']),
|
||||
shortcut_indices: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HelpDialog {
|
||||
pub fn rebuild_wrapped_text(&mut self, painter: &Painter) {
|
||||
let left_column_width = HELP_TEXT
|
||||
.iter()
|
||||
.map(|text| {
|
||||
text.iter()
|
||||
.map(|[labels, _details]| {
|
||||
labels
|
||||
.lines()
|
||||
.map(|line| UnicodeWidthStr::width(line))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
+ 2;
|
||||
let right_column_width = (self.bounds.width as usize).saturating_sub(left_column_width);
|
||||
|
||||
self.left_column_width = Constraint::Length(left_column_width as u16);
|
||||
self.right_column_width = Constraint::Length(right_column_width as u16);
|
||||
|
||||
let mut shortcut_index = 1;
|
||||
let mut current_index = HELP_TITLES.len() + 2;
|
||||
|
||||
// let mut section_indices: Vec<usize> = Vec::with_capacity(HELP_TITLES.len());
|
||||
// let mut help_title_index = HELP_CONTENTS_TEXT.len() + 1;
|
||||
// Behold, this monstrosity of an iterator (I'm sorry).
|
||||
// Be warned, for when you stare into the iterator, the iterator stares back.
|
||||
|
||||
let wrapped_details_iter = HELP_TEXT.iter().map(|text| {
|
||||
text.iter()
|
||||
.map(|[labels, details]| {
|
||||
let labels = textwrap::fill(labels, left_column_width);
|
||||
let details = textwrap::fill(details, right_column_width);
|
||||
|
||||
labels
|
||||
.lines()
|
||||
.zip_longest(details.lines())
|
||||
.map(|z| match z {
|
||||
EitherOrBoth::Both(a, b) => vec![
|
||||
Spans::from(Span::styled(
|
||||
a.to_string(),
|
||||
painter.colours.text_style,
|
||||
)),
|
||||
Spans::from(Span::styled(
|
||||
b.to_string(),
|
||||
painter.colours.text_style,
|
||||
)),
|
||||
],
|
||||
EitherOrBoth::Left(s) => {
|
||||
vec![Spans::from(Span::styled(
|
||||
s.to_string(),
|
||||
painter.colours.text_style,
|
||||
))]
|
||||
}
|
||||
EitherOrBoth::Right(s) => vec![
|
||||
Spans::default(),
|
||||
Spans::from(Span::styled(
|
||||
s.to_string(),
|
||||
painter.colours.text_style,
|
||||
)),
|
||||
],
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
self.wrapped_text = HELP_CONTENTS_TEXT
|
||||
.iter()
|
||||
.map(|t| vec![Spans::from(Span::styled(*t, painter.colours.text_style))])
|
||||
.chain(
|
||||
HELP_TITLES
|
||||
.iter()
|
||||
.zip(wrapped_details_iter)
|
||||
.map(|(title, text)| {
|
||||
self.shortcut_indices.insert(shortcut_index, current_index);
|
||||
shortcut_index += 1;
|
||||
current_index += 2 + text.len();
|
||||
std::iter::once(vec![Spans::default()])
|
||||
.chain(std::iter::once(vec![Spans::from(Span::styled(
|
||||
*title,
|
||||
painter.colours.highlighted_border_style,
|
||||
))]))
|
||||
.chain(text)
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.collect();
|
||||
|
||||
self.max_index = self
|
||||
.wrapped_text
|
||||
.len()
|
||||
.saturating_sub(self.bounds.height as usize);
|
||||
|
||||
for value in self.shortcut_indices.values_mut() {
|
||||
*value = min(*value, self.max_index);
|
||||
}
|
||||
|
||||
if self.current_index > self.max_index {
|
||||
self.current_index = self.max_index;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_help<B: Backend>(
|
||||
&mut self, painter: &Painter, f: &mut Frame<'_, B>, block_area: Rect,
|
||||
) {
|
||||
let block = BlockBuilder::new("Help")
|
||||
.borders(Borders::all())
|
||||
.show_esc(true)
|
||||
.build(painter, block_area);
|
||||
|
||||
let inner_area = block.inner(block_area);
|
||||
if inner_area != self.bounds {
|
||||
self.bounds = inner_area;
|
||||
self.rebuild_wrapped_text(painter);
|
||||
}
|
||||
let end_index = self.current_index + inner_area.height as usize;
|
||||
|
||||
let split_area = Layout::default()
|
||||
.constraints(vec![Constraint::Length(1); inner_area.height as usize])
|
||||
.direction(tui::layout::Direction::Vertical)
|
||||
.split(inner_area);
|
||||
|
||||
self.wrapped_text[self.current_index..end_index]
|
||||
.iter()
|
||||
.zip(split_area)
|
||||
.for_each(|(row, area)| {
|
||||
if row.len() > 1 {
|
||||
let row_split_area = Layout::default()
|
||||
.constraints(vec![self.left_column_width, self.right_column_width])
|
||||
.direction(tui::layout::Direction::Horizontal)
|
||||
.split(area);
|
||||
|
||||
let left_area = row_split_area[0];
|
||||
let right_area = row_split_area[1];
|
||||
|
||||
f.render_widget(Paragraph::new(row[0].clone()), left_area);
|
||||
f.render_widget(Paragraph::new(row[1].clone()), right_area);
|
||||
} else if let Some(line) = row.get(0) {
|
||||
f.render_widget(Paragraph::new(line.clone()), area);
|
||||
}
|
||||
});
|
||||
|
||||
f.render_widget(block, block_area);
|
||||
}
|
||||
|
||||
fn move_up(&mut self, amount: usize) -> ComponentEventResult {
|
||||
let new_index = self.current_index.saturating_sub(amount);
|
||||
if self.current_index == new_index {
|
||||
ComponentEventResult::NoRedraw
|
||||
} else {
|
||||
self.current_index = new_index;
|
||||
ComponentEventResult::Redraw
|
||||
}
|
||||
}
|
||||
|
||||
fn move_down(&mut self, amount: usize) -> ComponentEventResult {
|
||||
let new_index = self.current_index + amount;
|
||||
if new_index > self.max_index || self.current_index == new_index {
|
||||
ComponentEventResult::NoRedraw
|
||||
} else {
|
||||
self.current_index = new_index;
|
||||
ComponentEventResult::Redraw
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_to_first(&mut self) -> ComponentEventResult {
|
||||
if self.current_index == 0 {
|
||||
ComponentEventResult::NoRedraw
|
||||
} else {
|
||||
self.current_index = 0;
|
||||
ComponentEventResult::Redraw
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_to_last(&mut self) -> ComponentEventResult {
|
||||
if self.current_index == self.max_index {
|
||||
ComponentEventResult::NoRedraw
|
||||
} else {
|
||||
self.current_index = self.max_index;
|
||||
ComponentEventResult::Redraw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for HelpDialog {
|
||||
fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn set_bounds(&mut self, new_bounds: Rect) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
|
||||
use crossterm::event::KeyCode::{Char, Down, Up};
|
||||
|
||||
if event.modifiers == KeyModifiers::NONE || event.modifiers == KeyModifiers::SHIFT {
|
||||
match event.code {
|
||||
Down if event.modifiers == KeyModifiers::NONE => self.move_down(1),
|
||||
Up if event.modifiers == KeyModifiers::NONE => self.move_up(1),
|
||||
Char(c) => match c {
|
||||
'j' => self.move_down(1),
|
||||
'k' => self.move_up(1),
|
||||
'g' => match self.gg_manager.input('g') {
|
||||
MultiKeyResult::Completed => self.skip_to_first(),
|
||||
MultiKeyResult::Accepted | MultiKeyResult::Rejected => {
|
||||
ComponentEventResult::NoRedraw
|
||||
}
|
||||
},
|
||||
'G' => self.skip_to_last(),
|
||||
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
|
||||
if let Some(potential_index) = c.to_digit(10) {
|
||||
if let Some(&new_index) = self.shortcut_indices.get(&potential_index) {
|
||||
if new_index != self.current_index {
|
||||
self.current_index = new_index;
|
||||
ComponentEventResult::Redraw
|
||||
} else {
|
||||
ComponentEventResult::NoRedraw
|
||||
}
|
||||
} else {
|
||||
ComponentEventResult::Unhandled
|
||||
}
|
||||
} else {
|
||||
ComponentEventResult::Unhandled
|
||||
}
|
||||
}
|
||||
_ => ComponentEventResult::Unhandled,
|
||||
},
|
||||
_ => ComponentEventResult::Unhandled,
|
||||
}
|
||||
} else {
|
||||
ComponentEventResult::Unhandled
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, event: MouseEvent) -> ComponentEventResult {
|
||||
match event.kind {
|
||||
MouseEventKind::ScrollDown => self.move_down(1),
|
||||
MouseEventKind::ScrollUp => self.move_up(1),
|
||||
_ => ComponentEventResult::Unhandled,
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String>,
|
||||
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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<Spans<'static>>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn init(config: &Config, colour_scheme: ColourScheme) -> anyhow::Result<Self> {
|
||||
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::<Vec<_>>(),
|
||||
);
|
||||
} else {
|
||||
// Not required check but it runs only a few times... so whatever ig, prevents me from
|
||||
// being dumb and leaving a help text section only one line long.
|
||||
if section.len() > 1 {
|
||||
styled_help_spans.push(Span::raw(""));
|
||||
styled_help_spans
|
||||
.push(Span::styled(section[0], self.colours.table_header_style));
|
||||
styled_help_spans.extend(
|
||||
section[1..]
|
||||
.iter()
|
||||
.map(|&text| Span::styled(text, self.colours.text_style))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.styled_help_text = styled_help_spans.into_iter().map(Spans::from).collect();
|
||||
}
|
||||
|
||||
// TODO: [CONFIG] write this, should call painter init and any changed colour functions...
|
||||
pub fn update_painter_colours(&mut self) {}
|
||||
|
||||
fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) {
|
||||
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
|
||||
|
@ -1,5 +1,2 @@
|
||||
pub mod dd_dialog;
|
||||
pub mod help_dialog;
|
||||
|
||||
pub use dd_dialog::KillDialog;
|
||||
pub use help_dialog::HelpDialog;
|
||||
|
@ -1,121 +0,0 @@
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{app::AppState, canvas::Painter, constants};
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
terminal::Frame,
|
||||
text::Span,
|
||||
text::Spans,
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
};
|
||||
|
||||
const HELP_BASE: &str = " Help ── Esc to close ";
|
||||
|
||||
pub trait HelpDialog {
|
||||
fn draw_help_dialog<B: Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: [REFACTOR] Make generic dialog boxes to build off of instead?
|
||||
impl HelpDialog for Painter {
|
||||
fn draw_help_dialog<B: Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
|
||||
) {
|
||||
let help_title = Spans::from(vec![
|
||||
Span::styled(" Help ", self.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to close ",
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(HELP_BASE.chars().count() + 2)
|
||||
)
|
||||
),
|
||||
self.colours.border_style,
|
||||
),
|
||||
]);
|
||||
|
||||
if app_state.should_get_widget_bounds() {
|
||||
// We must also recalculate how many lines are wrapping to properly get scrolling to work on
|
||||
// small terminal sizes... oh joy.
|
||||
|
||||
let mut overflow_buffer = 0;
|
||||
let paragraph_width = std::cmp::max(draw_loc.width.saturating_sub(2), 1);
|
||||
let mut prev_section_len = 0;
|
||||
|
||||
constants::HELP_TEXT
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(itx, section)| {
|
||||
let mut buffer = 0;
|
||||
|
||||
if itx == 0 {
|
||||
section.iter().for_each(|text_line| {
|
||||
buffer += UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16
|
||||
/ paragraph_width;
|
||||
});
|
||||
|
||||
app_state.help_dialog_state.index_shortcuts[itx] = 0;
|
||||
} else {
|
||||
section.iter().for_each(|text_line| {
|
||||
buffer += UnicodeWidthStr::width(*text_line).saturating_sub(1) as u16
|
||||
/ paragraph_width;
|
||||
});
|
||||
|
||||
app_state.help_dialog_state.index_shortcuts[itx] =
|
||||
app_state.help_dialog_state.index_shortcuts[itx - 1]
|
||||
+ 1
|
||||
+ prev_section_len;
|
||||
}
|
||||
prev_section_len = section.len() as u16 + buffer;
|
||||
overflow_buffer += buffer;
|
||||
});
|
||||
|
||||
app_state.help_dialog_state.scroll_state.max_scroll_index =
|
||||
(self.styled_help_text.len() as u16
|
||||
+ (constants::HELP_TEXT.len() as u16 - 5)
|
||||
+ overflow_buffer)
|
||||
.saturating_sub(draw_loc.height);
|
||||
|
||||
// Fix if over-scrolled
|
||||
if app_state
|
||||
.help_dialog_state
|
||||
.scroll_state
|
||||
.current_scroll_index
|
||||
>= app_state.help_dialog_state.scroll_state.max_scroll_index
|
||||
{
|
||||
app_state
|
||||
.help_dialog_state
|
||||
.scroll_state
|
||||
.current_scroll_index = app_state
|
||||
.help_dialog_state
|
||||
.scroll_state
|
||||
.max_scroll_index
|
||||
.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(self.styled_help_text.clone())
|
||||
.block(
|
||||
Block::default()
|
||||
.title(help_title)
|
||||
.style(self.colours.border_style)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(self.colours.border_style),
|
||||
)
|
||||
.style(self.colours.text_style)
|
||||
.alignment(Alignment::Left)
|
||||
.wrap(Wrap { trim: true })
|
||||
.scroll((
|
||||
app_state
|
||||
.help_dialog_state
|
||||
.scroll_state
|
||||
.current_scroll_index,
|
||||
0,
|
||||
)),
|
||||
draw_loc,
|
||||
);
|
||||
}
|
||||
}
|
296
src/constants.rs
296
src/constants.rs
@ -227,159 +227,193 @@ pub static NORD_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = 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:", ""],
|
||||
["<by name/cmd>", "ex: btm"],
|
||||
["pid", "ex: pid 825"],
|
||||
["cpu, cpu%", "ex: cpu > 4.2"],
|
||||
["mem, mem%", "ex: mem < 4.2"],
|
||||
["memb", "ex: memb < 100 kb"],
|
||||
["read, r/s", "ex: read >= 1 b"],
|
||||
["write, w/s", "ex: write <= 1 tb"],
|
||||
["tread, t.read", "ex: tread = 1"],
|
||||
["twrite, t.write", "ex: twrite = 1"],
|
||||
["user", "ex: user = root"],
|
||||
["state", "ex: state = running"],
|
||||
["\n", "\n"],
|
||||
["Comparison operators:", ""],
|
||||
["=", "ex: cpu = 1"],
|
||||
[">", "ex: cpu > 1"],
|
||||
["<", "ex: cpu < 1"],
|
||||
[">=", "ex: cpu >= 1"],
|
||||
["<=", "ex: cpu <= 1"],
|
||||
["\n", "\n"],
|
||||
["Logical operators:", ""],
|
||||
["and, &&, <Space> ", ": btm and cpu > 1 and mem > 1"],
|
||||
["or, ||", "ex: btm or firefox"],
|
||||
["\n", "\n"],
|
||||
["Supported units:", ""],
|
||||
["B", "ex: read > 1 b"],
|
||||
["KB", "ex: read > 1 kb"],
|
||||
["MB", "ex: read > 1 mb"],
|
||||
["TB", "ex: read > 1 tb"],
|
||||
["KiB", "ex: read > 1 kib"],
|
||||
["MiB", "ex: read > 1 mib"],
|
||||
["GiB", "ex: read > 1 gib"],
|
||||
["TiB", "ex: read > 1 tib"],
|
||||
];
|
||||
|
||||
pub const SEARCH_HELP_TEXT: [&str; 49] = [
|
||||
"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:",
|
||||
"<by name/cmd> ex: btm",
|
||||
"pid ex: pid 825",
|
||||
"cpu, cpu% ex: cpu > 4.2",
|
||||
"mem, mem% ex: mem < 4.2",
|
||||
"memb ex: memb < 100 kb",
|
||||
"read, r/s ex: read >= 1 b",
|
||||
"write, w/s ex: write <= 1 tb",
|
||||
"tread, t.read ex: tread = 1",
|
||||
"twrite, t.write ex: twrite = 1",
|
||||
"user ex: user = root",
|
||||
"state ex: state = running",
|
||||
"",
|
||||
"Comparison operators:",
|
||||
"= ex: cpu = 1",
|
||||
"> ex: cpu > 1",
|
||||
"< ex: cpu < 1",
|
||||
">= ex: cpu >= 1",
|
||||
"<= ex: cpu <= 1",
|
||||
"",
|
||||
"Logical operators:",
|
||||
"and, &&, <Space> ex: btm and cpu > 1 and mem > 1",
|
||||
"or, || ex: btm or firefox",
|
||||
"",
|
||||
"Supported units:",
|
||||
"B ex: read > 1 b",
|
||||
"KB ex: read > 1 kb",
|
||||
"MB ex: read > 1 mb",
|
||||
"TB ex: read > 1 tb",
|
||||
"KiB ex: read > 1 kib",
|
||||
"MiB ex: read > 1 mib",
|
||||
"GiB ex: read > 1 gib",
|
||||
"TiB ex: read > 1 tib",
|
||||
pub const 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<Vec<Vec<&'static str>>> = 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]]
|
||||
|
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user