From 9089231bc46e9732aac522be89877dcd53a06c6d Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sun, 26 Sep 2021 01:18:03 -0400 Subject: refactor: delete more stuff Mostly previously re-added files during the merge conflict resolution, and a lot of unused code. Still more to delete after I finish rewriting the process kill dialog. --- src/app.rs | 450 +++++-------- src/app/data_farmer.rs | 2 +- src/app/data_harvester.rs | 2 +- src/app/data_harvester/network/heim.rs | 2 +- src/app/data_harvester/processes.rs | 1 + src/app/data_harvester/processes/linux.rs | 2 - src/app/layout_manager.rs | 5 +- src/app/query.rs | 2 +- src/app/states.rs | 940 ---------------------------- src/app/widgets.rs | 102 +-- src/app/widgets/base/scrollable.rs | 2 +- src/app/widgets/base/sort_text_table.rs | 2 +- src/app/widgets/base/text_input.rs | 4 +- src/app/widgets/base/text_table.rs | 4 +- src/app/widgets/base/time_graph.rs | 2 +- src/app/widgets/bottom_widgets/basic_mem.rs | 2 +- src/app/widgets/bottom_widgets/battery.rs | 22 +- src/app/widgets/bottom_widgets/cpu.rs | 32 +- src/app/widgets/bottom_widgets/disk.rs | 30 +- src/app/widgets/bottom_widgets/mem.rs | 13 +- src/app/widgets/bottom_widgets/net.rs | 15 +- src/app/widgets/bottom_widgets/process.rs | 551 +--------------- src/app/widgets/bottom_widgets/temp.rs | 40 +- src/app/widgets/dialogs/help.rs | 2 +- src/bin/main.rs | 6 +- src/canvas.rs | 5 +- src/canvas/dialogs/dd_dialog.rs | 52 +- src/canvas/widgets/mem_graph.rs | 249 -------- src/canvas/widgets/network_graph.rs | 770 ----------------------- src/canvas/widgets/process_table.rs | 911 --------------------------- src/clap.rs | 7 +- src/constants.rs | 2 +- src/data_conversion.rs | 161 ++--- src/lib.rs | 4 +- src/options.rs | 2 +- src/utils/error.rs | 1 - tests/layout_management_tests.rs | 4 +- tests/layout_movement_tests.rs | 4 +- 38 files changed, 309 insertions(+), 4098 deletions(-) delete mode 100644 src/app/states.rs delete mode 100644 src/canvas/widgets/mem_graph.rs delete mode 100644 src/canvas/widgets/network_graph.rs delete mode 100644 src/canvas/widgets/process_table.rs diff --git a/src/app.rs b/src/app.rs index ec577661..4b58b014 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,24 +7,20 @@ mod process_killer; pub mod query; pub mod widgets; -use std::{collections::HashMap, time::Instant}; +use std::time::Instant; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; use fxhash::FxHashMap; use indextree::{Arena, NodeId}; -use unicode_width::UnicodeWidthStr; pub use data_farmer::*; -use data_harvester::{processes, temperature}; +use data_harvester::temperature; pub use filter::*; use layout_manager::*; pub use widgets::*; use crate::{ - canvas, constants, - units::data_units::DataUnit, - utils::error::{BottomError, Result}, - BottomEvent, Pid, + canvas, constants, units::data_units::DataUnit, utils::error::Result, BottomEvent, Pid, }; use self::event::{ComponentEventResult, EventResult, ReturnSignal}; @@ -91,7 +87,7 @@ pub struct AppConfigFields { pub hide_time: bool, pub autohide_time: bool, pub use_old_network_legend: bool, - pub table_gap: u16, // TODO: Just make this a bool... + pub table_gap: u16, // TODO: [Config, Refactor] Just make this a bool... pub disable_click: bool, pub no_write: bool, pub show_table_scroll_position: bool, @@ -129,23 +125,8 @@ pub struct AppState { pub filters: DataFilters, pub app_config_fields: AppConfigFields, - // --- Eventually delete/rewrite --- + // --- FIXME: TO DELETE/REWRITE --- pub delete_dialog_state: AppDeleteDialogState, - - // --- TO DELETE --- - pub cpu_state: CpuState, - pub mem_state: MemState, - pub net_state: NetState, - pub proc_state: ProcState, - pub temp_state: TempState, - pub disk_state: DiskState, - pub battery_state: BatteryState, - pub basic_table_widget_state: Option, - pub widget_map: HashMap, - pub current_widget: BottomWidget, - - pub basic_mode_use_percent: bool, - pub is_force_redraw: bool, pub is_determining_widget_boundary: bool, @@ -190,17 +171,6 @@ impl AppState { data_collection: Default::default(), is_expanded: Default::default(), delete_dialog_state: Default::default(), - cpu_state: Default::default(), - mem_state: Default::default(), - net_state: Default::default(), - proc_state: Default::default(), - temp_state: Default::default(), - disk_state: Default::default(), - battery_state: Default::default(), - basic_table_widget_state: Default::default(), - widget_map: Default::default(), - current_widget: Default::default(), - basic_mode_use_percent: Default::default(), is_force_redraw: Default::default(), is_determining_widget_boundary: Default::default(), frozen_state: Default::default(), @@ -484,7 +454,7 @@ impl AppState { BottomEvent::Update(new_data) => { self.data_collection.eat_data(new_data); - // TODO: Optimization for dialogs; don't redraw here. + // TODO: [Optimization] Optimization for dialogs - don't redraw on an update! if !self.is_frozen() { let data_collection = &self.data_collection; @@ -509,104 +479,6 @@ impl AppState { } } - pub fn is_in_search_widget(&self) -> bool { - matches!( - self.current_widget.widget_type, - BottomWidgetType::ProcSearch - ) - } - - fn is_in_dialog(&self) -> bool { - self.delete_dialog_state.is_showing_dd - } - - fn ignore_normal_keybinds(&self) -> bool { - self.is_in_dialog() - } - - pub fn on_tab(&mut self) { - // Allow usage whilst only in processes - - if !self.ignore_normal_keybinds() { - match self.current_widget.widget_type { - BottomWidgetType::Cpu => { - if let Some(cpu_widget_state) = self - .cpu_state - .get_mut_widget_state(self.current_widget.widget_id) - { - cpu_widget_state.is_multi_graph_mode = - !cpu_widget_state.is_multi_graph_mode; - } - } - BottomWidgetType::Proc => { - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id) - { - // Do NOT allow when in tree mode! - if !proc_widget_state.is_tree_mode { - // Toggles process widget grouping state - proc_widget_state.is_grouped = !(proc_widget_state.is_grouped); - - // Forcefully switch off column if we were on it... - if (proc_widget_state.is_grouped - && (proc_widget_state.process_sorting_type - == processes::ProcessSorting::Pid - || proc_widget_state.process_sorting_type - == processes::ProcessSorting::User - || proc_widget_state.process_sorting_type - == processes::ProcessSorting::State)) - || (!proc_widget_state.is_grouped - && proc_widget_state.process_sorting_type - == processes::ProcessSorting::Count) - { - proc_widget_state.process_sorting_type = - processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group - proc_widget_state.is_process_sort_descending = true; - } - - proc_widget_state.columns.set_to_sorted_index_from_type( - &proc_widget_state.process_sorting_type, - ); - - proc_widget_state.columns.try_set( - &processes::ProcessSorting::State, - !(proc_widget_state.is_grouped), - ); - - #[cfg(target_family = "unix")] - proc_widget_state.columns.try_set( - &processes::ProcessSorting::User, - !(proc_widget_state.is_grouped), - ); - - proc_widget_state - .columns - .toggle(&processes::ProcessSorting::Count); - proc_widget_state - .columns - .toggle(&processes::ProcessSorting::Pid); - - proc_widget_state.requires_redraw = true; - self.proc_state.force_update = Some(self.current_widget.widget_id); - } - } - } - _ => {} - } - } - } - - /// I don't like this, but removing it causes a bunch of breakage. - /// Use ``proc_widget_state.is_grouped`` if possible! - pub fn is_grouped(&self, widget_id: u64) -> bool { - if let Some(proc_widget_state) = self.proc_state.widget_states.get(&widget_id) { - proc_widget_state.is_grouped - } else { - false - } - } - #[cfg(target_family = "unix")] pub fn on_number(&mut self, number_char: char) { if self.delete_dialog_state.is_showing_dd { @@ -642,149 +514,122 @@ impl AppState { } pub fn on_left_key(&mut self) { - if !self.is_in_dialog() { - match self.current_widget.widget_type { - BottomWidgetType::ProcSearch => { - let is_in_search_widget = self.is_in_search_widget(); - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 1) - { - if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state - .search_walk_back(proc_widget_state.get_search_cursor_position()); - if proc_widget_state.get_search_cursor_position() < prev_cursor { - let str_slice = &proc_widget_state - .process_search_state - .search_state - .current_search_query - [proc_widget_state.get_search_cursor_position()..prev_cursor]; - proc_widget_state - .process_search_state - .search_state - .char_cursor_position -= UnicodeWidthStr::width(str_slice); - proc_widget_state - .process_search_state - .search_state - .cursor_direction = CursorDirection::Left; - } - } - } - } - BottomWidgetType::Battery => { - if !self.canvas_data.battery_data.is_empty() { - if let Some(battery_widget_state) = self - .battery_state - .get_mut_widget_state(self.current_widget.widget_id) - { - if battery_widget_state.currently_selected_battery_index > 0 { - battery_widget_state.currently_selected_battery_index -= 1; - } - } - } - } - _ => {} - } - } else if self.delete_dialog_state.is_showing_dd { - #[cfg(target_family = "unix")] - { - if self.app_config_fields.is_advanced_kill { - match self.delete_dialog_state.selected_signal { - KillSignal::Kill(prev_signal) => { - self.delete_dialog_state.selected_signal = match prev_signal - 1 { - 0 => KillSignal::Cancel, - // 32+33 are skipped - 33 => KillSignal::Kill(31), - signal => KillSignal::Kill(signal), - }; - } - KillSignal::Cancel => {} - }; - } else { - self.delete_dialog_state.selected_signal = KillSignal::default(); - } - } - #[cfg(target_os = "windows")] - { - self.delete_dialog_state.selected_signal = KillSignal::Kill(1); - } - } + // if !self.is_in_dialog() { + // match self.current_widget.widget_type { + // BottomWidgetType::ProcSearch => { + // let is_in_search_widget = self.is_in_search_widget(); + // if let Some(proc_widget_state) = self + // .proc_state + // .get_mut_widget_state(self.current_widget.widget_id - 1) + // { + // if is_in_search_widget { + // let prev_cursor = proc_widget_state.get_search_cursor_position(); + // proc_widget_state + // .search_walk_back(proc_widget_state.get_search_cursor_position()); + // if proc_widget_state.get_search_cursor_position() < prev_cursor { + // let str_slice = &proc_widget_state + // .process_search_state + // .search_state + // .current_search_query + // [proc_widget_state.get_search_cursor_position()..prev_cursor]; + // proc_widget_state + // .process_search_state + // .search_state + // .char_cursor_position -= UnicodeWidthStr::width(str_slice); + // proc_widget_state + // .process_search_state + // .search_state + // .cursor_direction = CursorDirection::Left; + // } + // } + // } + // } + // _ => {} + // } + // } else if self.delete_dialog_state.is_showing_dd { + // #[cfg(target_family = "unix")] + // { + // if self.app_config_fields.is_advanced_kill { + // match self.delete_dialog_state.selected_signal { + // KillSignal::Kill(prev_signal) => { + // self.delete_dialog_state.selected_signal = match prev_signal - 1 { + // 0 => KillSignal::Cancel, + // // 32+33 are skipped + // 33 => KillSignal::Kill(31), + // signal => KillSignal::Kill(signal), + // }; + // } + // KillSignal::Cancel => {} + // }; + // } else { + // self.delete_dialog_state.selected_signal = KillSignal::default(); + // } + // } + // #[cfg(target_os = "windows")] + // { + // self.delete_dialog_state.selected_signal = KillSignal::Kill(1); + // } + // } } pub fn on_right_key(&mut self) { - if !self.is_in_dialog() { - match self.current_widget.widget_type { - BottomWidgetType::ProcSearch => { - let is_in_search_widget = self.is_in_search_widget(); - if let Some(proc_widget_state) = self - .proc_state - .get_mut_widget_state(self.current_widget.widget_id - 1) - { - if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state.search_walk_forward( - proc_widget_state.get_search_cursor_position(), - ); - if proc_widget_state.get_search_cursor_position() > prev_cursor { - let str_slice = &proc_widget_state - .process_search_state - .search_state - .current_search_query - [prev_cursor..proc_widget_state.get_search_cursor_position()]; - proc_widget_state - .process_search_state - .search_state - .char_cursor_position += UnicodeWidthStr::width(str_slice); - proc_widget_state - .process_search_state - .search_state - .cursor_direction = CursorDirection::Right; - } - } - } - } - BottomWidgetType::Battery => { - if !self.canvas_data.battery_data.is_empty() { - let battery_count = self.canvas_data.battery_data.len(); - if let Some(battery_widget_state) = self - .battery_state - .get_mut_widget_state(self.current_widget.widget_id) - { - if battery_widget_state.currently_selected_battery_index - < battery_count - 1 - { - battery_widget_state.currently_selected_battery_index += 1; - } - } - } - } - _ => {} - } - } else if self.delete_dialog_state.is_showing_dd { - #[cfg(target_family = "unix")] - { - if self.app_config_fields.is_advanced_kill { - let new_signal = match self.delete_dialog_state.selected_signal { - KillSignal::Cancel => 1, - // 32+33 are skipped - #[cfg(target_os = "linux")] - KillSignal::Kill(31) => 34, - #[cfg(target_os = "macos")] - KillSignal::Kill(31) => 31, - KillSignal::Kill(64) => 64, - KillSignal::Kill(signal) => signal + 1, - }; - self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal); - } else { - self.delete_dialog_state.selected_signal = KillSignal::Cancel; - } - } - #[cfg(target_os = "windows")] - { - self.delete_dialog_state.selected_signal = KillSignal::Cancel; - } - } + // if !self.is_in_dialog() { + // match self.current_widget.widget_type { + // BottomWidgetType::ProcSearch => { + // let is_in_search_widget = self.is_in_search_widget(); + // if let Some(proc_widget_state) = self + // .proc_state + // .get_mut_widget_state(self.current_widget.widget_id - 1) + // { + // if is_in_search_widget { + // let prev_cursor = proc_widget_state.get_search_cursor_position(); + // proc_widget_state.search_walk_forward( + // proc_widget_state.get_search_cursor_position(), + // ); + // if proc_widget_state.get_search_cursor_position() > prev_cursor { + // let str_slice = &proc_widget_state + // .process_search_state + // .search_state + // .current_search_query + // [prev_cursor..proc_widget_state.get_search_cursor_position()]; + // proc_widget_state + // .process_search_state + // .search_state + // .char_cursor_position += UnicodeWidthStr::width(str_slice); + // proc_widget_state + // .process_search_state + // .search_state + // .cursor_direction = CursorDirection::Right; + // } + // } + // } + // } + // _ => {} + // } + // } else if self.delete_dialog_state.is_showing_dd { + // #[cfg(target_family = "unix")] + // { + // if self.app_config_fields.is_advanced_kill { + // let new_signal = match self.delete_dialog_state.selected_signal { + // KillSignal::Cancel => 1, + // // 32+33 are skipped + // #[cfg(target_os = "linux")] + // KillSignal::Kill(31) => 34, + // #[cfg(target_os = "macos")] + // KillSignal::Kill(31) => 31, + // KillSignal::Kill(64) => 64, + // KillSignal::Kill(signal) => signal + 1, + // }; + // self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal); + // } else { + // self.delete_dialog_state.selected_signal = KillSignal::Cancel; + // } + // } + // #[cfg(target_os = "windows")] + // { + // self.delete_dialog_state.selected_signal = KillSignal::Cancel; + // } + // } } pub fn start_killing_process(&mut self) { @@ -828,35 +673,38 @@ impl AppState { } pub fn kill_highlighted_process(&mut self) -> Result<()> { - if let BottomWidgetType::Proc = self.current_widget.widget_type { - if let Some(current_selected_processes) = &self.to_delete_process_list { - #[cfg(target_family = "unix")] - let signal = match self.delete_dialog_state.selected_signal { - KillSignal::Kill(sig) => sig, - KillSignal::Cancel => 15, // should never happen, so just TERM - }; - for pid in ¤t_selected_processes.1 { - #[cfg(target_family = "unix")] - { - process_killer::kill_process_given_pid(*pid, signal)?; - } - #[cfg(target_os = "windows")] - { - process_killer::kill_process_given_pid(*pid)?; - } - } - } - self.to_delete_process_list = None; - Ok(()) - } else { - Err(BottomError::GenericError( - "Cannot kill processes if the current widget is not the Process widget!" - .to_string(), - )) - } + // if let BottomWidgetType::Proc = self.current_widget.widget_type { + // if let Some(current_selected_processes) = &self.to_delete_process_list { + // #[cfg(target_family = "unix")] + // let signal = match self.delete_dialog_state.selected_signal { + // KillSignal::Kill(sig) => sig, + // KillSignal::Cancel => 15, // should never happen, so just TERM + // }; + // for pid in ¤t_selected_processes.1 { + // #[cfg(target_family = "unix")] + // { + // process_killer::kill_process_given_pid(*pid, signal)?; + // } + // #[cfg(target_os = "windows")] + // { + // process_killer::kill_process_given_pid(*pid)?; + // } + // } + // } + // self.to_delete_process_list = None; + // Ok(()) + // } else { + // Err(BottomError::GenericError( + // "Cannot kill processes if the current widget is not the Process widget!" + // .to_string(), + // )) + // } + + Ok(()) } pub fn get_to_delete_processes(&self) -> Option<(String, Vec)> { - self.to_delete_process_list.clone() + // self.to_delete_process_list.clone() + todo!() } } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 7badfe57..6b850f8b 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -319,7 +319,7 @@ impl DataCollection { } fn eat_proc(&mut self, list_of_processes: Vec) { - // TODO: Probably more efficient to do this in the data collection step, but it's fine for now. + // TODO: [Optimization] Probably more efficient to do this in the data collection step, but it's fine for now. self.process_name_pid_map.clear(); self.process_cmd_pid_map.clear(); list_of_processes.iter().for_each(|process_harvest| { diff --git a/src/app/data_harvester.rs b/src/app/data_harvester.rs index 2f5613cc..2672022d 100644 --- a/src/app/data_harvester.rs +++ b/src/app/data_harvester.rs @@ -149,7 +149,7 @@ impl DataCollector { self.sys.refresh_memory(); self.mem_total_kb = self.sys.get_total_memory(); - // TODO: Would be good to get this and network list running on a timer instead...? + // TODO: [Data Collection] Would be good to get this and network list running on a timer instead...? // Refresh components list once... if self.widgets_to_harvest.use_temp { self.sys.refresh_components_list(); diff --git a/src/app/data_harvester/network/heim.rs b/src/app/data_harvester/network/heim.rs index 3c12fd73..6d05cc60 100644 --- a/src/app/data_harvester/network/heim.rs +++ b/src/app/data_harvester/network/heim.rs @@ -39,7 +39,7 @@ pub async fn get_network_data( }; if to_keep { - // TODO: Use bytes as the default instead, perhaps? + // TODO: [Optimization] Optimization (Potential)Use bytes as the default instead, perhaps? // Since you might have to do a double conversion (bytes -> bits -> bytes) in some cases; // but if you stick to bytes, then in the bytes, case, you do no conversion, and in the bits case, // you only do one conversion... diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index 151d3a55..4b09a078 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -27,6 +27,7 @@ use std::borrow::Cow; use crate::Pid; +// FIXME: [URGENT] Delete this. #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum ProcessSorting { CpuPercent, diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index c1b7f4e8..aad67adf 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -229,8 +229,6 @@ pub fn get_process_data( pid_mapping: &mut FxHashMap, use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64, user_table: &mut UserTable, ) -> crate::utils::error::Result> { - // TODO: [PROC THREADS] Add threads - if let Ok((cpu_usage, cpu_fraction)) = cpu_usage_calculation(prev_idle, prev_non_idle) { let mut pids_to_clear: FxHashSet = pid_mapping.keys().cloned().collect(); diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 48e97774..bde3febb 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -332,7 +332,7 @@ pub struct LayoutCreationOutput { /// Creates a new [`Arena`] from the given config and returns it, along with the [`NodeId`] representing /// the root of the newly created [`Arena`], a mapping from [`NodeId`]s to [`BottomWidget`]s, and optionally, a default /// selected [`NodeId`]. -// FIXME: This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this! +// FIXME: [AFTER REFACTOR] This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this! pub fn create_layout_tree( rows: &[Row], process_defaults: ProcessDefaults, app_config_fields: &AppConfigFields, ) -> Result { @@ -924,6 +924,7 @@ pub fn move_widget_selection( if let Some(proposed_widget) = widget_lookup_map.get_mut(&proposed_id) { match proposed_widget.selectable_type() { SelectableType::Unselectable => { + // FIXME: [URGENT] Test this; make sure this cannot recurse infinitely! Maybe through a unit test too. // Try to move again recursively. move_widget_selection( layout_tree, @@ -960,7 +961,7 @@ pub fn generate_layout( root: NodeId, arena: &mut Arena, area: Rect, lookup_map: &FxHashMap, ) { - // TODO: [Layout] Add some caching/dirty mechanisms to reduce calls. + // TODO: [Optimization, Layout] Add some caching/dirty mechanisms to reduce calls. /// A [`Size`] is a set of widths and heights that a node in our layout wants to be. #[derive(Default, Clone, Copy, Debug)] diff --git a/src/app/query.rs b/src/app/query.rs index b7522485..b32a23df 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -253,7 +253,7 @@ pub fn parse_query( if content == "=" { // Check next string if possible if let Some(queue_next) = query.pop_front() { - // TODO: Need to consider the following cases: + // TODO: [Query, ???] Need to consider the following cases: // - (test) // - (test // - test) diff --git a/src/app/states.rs b/src/app/states.rs deleted file mode 100644 index bf0b87af..00000000 --- a/src/app/states.rs +++ /dev/null @@ -1,940 +0,0 @@ -use std::{collections::HashMap, time::Instant}; - -use unicode_segmentation::GraphemeCursor; - -use tui::widgets::TableState; - -use crate::{ - app::{layout_manager::BottomWidgetType, query::*}, - constants, - data_harvester::processes::{self, ProcessSorting}, -}; -use ProcessSorting::*; - -#[derive(Debug)] -pub enum ScrollDirection { - // UP means scrolling up --- this usually DECREMENTS - Up, - // DOWN means scrolling down --- this usually INCREMENTS - Down, -} - -impl Default for ScrollDirection { - fn default() -> Self { - ScrollDirection::Down - } -} - -#[derive(Debug)] -pub enum CursorDirection { - Left, - Right, -} - -/// AppScrollWidgetState deals with fields for a scrollable app's current state. -#[derive(Default)] -pub struct AppScrollWidgetState { - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub scroll_direction: ScrollDirection, - pub table_state: TableState, -} - -#[derive(PartialEq)] -pub enum KillSignal { - Cancel, - Kill(usize), -} - -impl Default for KillSignal { - #[cfg(target_family = "unix")] - fn default() -> Self { - KillSignal::Kill(15) - } - #[cfg(target_os = "windows")] - fn default() -> Self { - KillSignal::Kill(1) - } -} - -#[derive(Default)] -pub struct AppDeleteDialogState { - pub is_showing_dd: bool, - pub selected_signal: KillSignal, - /// tl x, tl y, br x, br y, index/signal - pub button_positions: Vec<(u16, u16, u16, u16, usize)>, - pub keyboard_signal_select: usize, - pub last_number_press: Option, - pub scroll_pos: usize, -} - -pub struct AppHelpDialogState { - pub is_showing_help: bool, - pub scroll_state: ParagraphScrollState, - pub index_shortcuts: Vec, -} - -impl Default for AppHelpDialogState { - fn default() -> Self { - AppHelpDialogState { - is_showing_help: false, - scroll_state: ParagraphScrollState::default(), - index_shortcuts: vec![0; constants::HELP_TEXT.len()], - } - } -} - -/// AppSearchState deals with generic searching (I might do this in the future). -pub struct AppSearchState { - pub is_enabled: bool, - pub current_search_query: String, - pub is_blank_search: bool, - pub is_invalid_search: bool, - pub grapheme_cursor: GraphemeCursor, - pub cursor_direction: CursorDirection, - pub cursor_bar: usize, - /// This represents the position in terms of CHARACTERS, not graphemes - pub char_cursor_position: usize, - /// The query - pub query: Option, - pub error_message: Option, -} - -impl Default for AppSearchState { - fn default() -> Self { - AppSearchState { - is_enabled: false, - current_search_query: String::default(), - is_invalid_search: false, - is_blank_search: true, - grapheme_cursor: GraphemeCursor::new(0, 0, true), - cursor_direction: CursorDirection::Right, - cursor_bar: 0, - char_cursor_position: 0, - query: None, - error_message: None, - } - } -} - -impl AppSearchState { - /// Returns a reset but still enabled app search state - pub fn reset(&mut self) { - *self = AppSearchState { - is_enabled: self.is_enabled, - ..AppSearchState::default() - } - } - - pub fn is_invalid_or_blank_search(&self) -> bool { - self.is_blank_search || self.is_invalid_search - } -} - -/// Meant for canvas operations involving table column widths. -#[derive(Default)] -pub struct CanvasTableWidthState { - pub desired_column_widths: Vec, - pub calculated_column_widths: Vec, -} - -/// ProcessSearchState only deals with process' search's current settings and state. -pub struct ProcessSearchState { - pub search_state: AppSearchState, - pub is_ignoring_case: bool, - pub is_searching_whole_word: bool, - pub is_searching_with_regex: bool, -} - -impl Default for ProcessSearchState { - fn default() -> Self { - ProcessSearchState { - search_state: AppSearchState::default(), - is_ignoring_case: true, - is_searching_whole_word: false, - is_searching_with_regex: false, - } - } -} - -impl ProcessSearchState { - pub fn search_toggle_ignore_case(&mut self) { - self.is_ignoring_case = !self.is_ignoring_case; - } - - pub fn search_toggle_whole_word(&mut self) { - self.is_searching_whole_word = !self.is_searching_whole_word; - } - - pub fn search_toggle_regex(&mut self) { - self.is_searching_with_regex = !self.is_searching_with_regex; - } -} - -pub struct ColumnInfo { - pub enabled: bool, - pub shortcut: Option<&'static str>, - // FIXME: Move column width logic here! - // pub hard_width: Option, - // pub max_soft_width: Option, -} - -pub struct ProcColumn { - pub ordered_columns: Vec, - /// The y location of headers. Since they're all aligned, it's just one value. - pub column_header_y_loc: Option, - /// The x start and end bounds for each header. - pub column_header_x_locs: Option>, - pub column_mapping: HashMap, - pub longest_header_len: u16, - pub column_state: TableState, - pub scroll_direction: ScrollDirection, - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub backup_prev_scroll_position: usize, -} - -impl Default for ProcColumn { - fn default() -> Self { - let ordered_columns = vec![ - Count, - Pid, - ProcessName, - Command, - CpuPercent, - Mem, - MemPercent, - ReadPerSecond, - WritePerSecond, - TotalRead, - TotalWrite, - User, - State, - ]; - - let mut column_mapping = HashMap::new(); - let mut longest_header_len = 0; - for column in ordered_columns.clone() { - longest_header_len = std::cmp::max(longest_header_len, column.to_string().len()); - match column { - CpuPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("c"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - MemPercent => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Mem => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("m"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - ProcessName => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Command => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: Some("n"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Pid => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: Some("p"), - // hard_width: None, - // max_soft_width: None, - }, - ); - } - Count => { - column_mapping.insert( - column, - ColumnInfo { - enabled: false, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - User => { - column_mapping.insert( - column, - ColumnInfo { - enabled: cfg!(target_family = "unix"), - shortcut: None, - }, - ); - } - _ => { - column_mapping.insert( - column, - ColumnInfo { - enabled: true, - shortcut: None, - // hard_width: None, - // max_soft_width: None, - }, - ); - } - } - } - let longest_header_len = longest_header_len as u16; - - ProcColumn { - ordered_columns, - column_mapping, - longest_header_len, - column_state: TableState::default(), - scroll_direction: ScrollDirection::default(), - current_scroll_position: 0, - previous_scroll_position: 0, - backup_prev_scroll_position: 0, - column_header_y_loc: None, - column_header_x_locs: None, - } - } -} - -impl ProcColumn { - /// Returns its new status. - pub fn toggle(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = !(mapping.enabled); - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_set(&mut self, column: &ProcessSorting, setting: bool) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = setting; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_enable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = true; - Some(mapping.enabled) - } else { - None - } - } - - pub fn try_disable(&mut self, column: &ProcessSorting) -> Option { - if let Some(mapping) = self.column_mapping.get_mut(column) { - mapping.enabled = false; - Some(mapping.enabled) - } else { - None - } - } - - pub fn is_enabled(&self, column: &ProcessSorting) -> bool { - if let Some(mapping) = self.column_mapping.get(column) { - mapping.enabled - } else { - false - } - } - - pub fn get_enabled_columns_len(&self) -> usize { - self.ordered_columns - .iter() - .filter_map(|column_type| { - if let Some(col_map) = self.column_mapping.get(column_type) { - if col_map.enabled { - Some(1) - } else { - None - } - } else { - None - } - }) - .sum() - } - - /// NOTE: ALWAYS call this when opening the sorted window. - pub fn set_to_sorted_index_from_type(&mut self, proc_sorting_type: &ProcessSorting) { - // TODO [Custom Columns]: If we add custom columns, this may be needed! Since column indices will change, this runs the risk of OOB. So, when you change columns, CALL THIS AND ADAPT! - let mut true_index = 0; - for column in &self.ordered_columns { - if *column == *proc_sorting_type { - break; - } - if self.column_mapping.get(column).unwrap().enabled { - true_index += 1; - } - } - - self.current_scroll_position = true_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - /// This function sets the scroll position based on the index. - pub fn set_to_sorted_index_from_visual_index(&mut self, visual_index: usize) { - self.current_scroll_position = visual_index; - self.backup_prev_scroll_position = self.previous_scroll_position; - } - - pub fn get_column_headers( - &self, proc_sorting_type: &ProcessSorting, sort_reverse: bool, - ) -> Vec { - const DOWN_ARROW: char = '▼'; - const UP_ARROW: char = '▲'; - - // TODO: Gonna have to figure out how to do left/right GUI notation if we add it. - self.ordered_columns - .iter() - .filter_map(|column_type| { - let mapping = self.column_mapping.get(column_type).unwrap(); - let mut command_str = String::default(); - if let Some(command) = mapping.shortcut { - command_str = format!("({})", command); - } - - if mapping.enabled { - Some(format!( - "{}{}{}", - column_type.to_string(), - command_str.as_str(), - if proc_sorting_type == column_type { - if sort_reverse { - DOWN_ARROW - } else { - UP_ARROW - } - } else { - ' ' - } - )) - } else { - None - } - }) - .collect() - } -} - -pub struct ProcWidgetState { - pub process_search_state: ProcessSearchState, - pub is_grouped: bool, - pub scroll_state: AppScrollWidgetState, - pub process_sorting_type: processes::ProcessSorting, - pub is_process_sort_descending: bool, - pub is_using_command: bool, - pub current_column_index: usize, - pub is_sort_open: bool, - pub columns: ProcColumn, - pub is_tree_mode: bool, - pub table_width_state: CanvasTableWidthState, - pub requires_redraw: bool, -} - -impl ProcWidgetState { - pub fn init( - is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool, - show_memory_as_values: bool, is_tree_mode: bool, is_using_command: bool, - ) -> Self { - let mut process_search_state = ProcessSearchState::default(); - - if is_case_sensitive { - // By default it's off - process_search_state.search_toggle_ignore_case(); - } - if is_match_whole_word { - process_search_state.search_toggle_whole_word(); - } - if is_use_regex { - process_search_state.search_toggle_regex(); - } - - let (process_sorting_type, is_process_sort_descending) = if is_tree_mode { - (processes::ProcessSorting::Pid, false) - } else { - (processes::ProcessSorting::CpuPercent, true) - }; - - // TODO: If we add customizable columns, this should pull from config - let mut columns = ProcColumn::default(); - columns.set_to_sorted_index_from_type(&process_sorting_type); - if is_grouped { - // Normally defaults to showing by PID, toggle count on instead. - columns.toggle(&ProcessSorting::Count); - columns.toggle(&ProcessSorting::Pid); - } - if show_memory_as_values { - // Normally defaults to showing by percent, toggle value on instead. - columns.toggle(&ProcessSorting::Mem); - columns.toggle(&ProcessSorting::MemPercent); - } - - ProcWidgetState { - process_search_state, - is_grouped, - scroll_state: AppScrollWidgetState::default(), - process_sorting_type, - is_process_sort_descending, - is_using_command, - current_column_index: 0, - is_sort_open: false, - columns, - is_tree_mode, - table_width_state: CanvasTableWidthState::default(), - requires_redraw: false, - } - } - - /// Updates sorting when using the column list. - /// ...this really should be part of the ProcColumn struct (along with the sorting fields), - /// but I'm too lazy. - /// - /// Sorry, future me, you're gonna have to refactor this later. Too busy getting - /// the feature to work in the first place! :) - pub fn update_sorting_with_columns(&mut self) { - let mut true_index = 0; - let mut enabled_index = 0; - let target_itx = self.columns.current_scroll_position; - for column in &self.columns.ordered_columns { - let enabled = self.columns.column_mapping.get(column).unwrap().enabled; - if enabled_index == target_itx && enabled { - break; - } - if enabled { - enabled_index += 1; - } - true_index += 1; - } - - if let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) { - if *new_sort_type == self.process_sorting_type { - // Just reverse the search if we're reselecting! - self.is_process_sort_descending = !(self.is_process_sort_descending); - } else { - self.process_sorting_type = new_sort_type.clone(); - match self.process_sorting_type { - ProcessSorting::State - | ProcessSorting::Pid - | ProcessSorting::ProcessName - | ProcessSorting::Command => { - // Also invert anything that uses alphabetical sorting by default. - self.is_process_sort_descending = false; - } - _ => { - self.is_process_sort_descending = true; - } - } - } - } - } - - pub fn toggle_command_and_name(&mut self, is_using_command: bool) { - if let Some(pn) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::ProcessName) - { - pn.enabled = !is_using_command; - } - if let Some(c) = self - .columns - .column_mapping - .get_mut(&ProcessSorting::Command) - { - c.enabled = is_using_command; - } - } - - pub fn get_search_cursor_position(&self) -> usize { - self.process_search_state - .search_state - .grapheme_cursor - .cur_cursor() - } - - pub fn get_char_cursor_position(&self) -> usize { - self.process_search_state.search_state.char_cursor_position - } - - pub fn is_search_enabled(&self) -> bool { - self.process_search_state.search_state.is_enabled - } - - pub fn get_current_search_query(&self) -> &String { - &self.process_search_state.search_state.current_search_query - } - - pub fn update_query(&mut self) { - if self - .process_search_state - .search_state - .current_search_query - .is_empty() - { - self.process_search_state.search_state.is_blank_search = true; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else { - let parsed_query = self.parse_query(); - // debug!("Parsed query: {:#?}", parsed_query); - - if let Ok(parsed_query) = parsed_query { - self.process_search_state.search_state.query = Some(parsed_query); - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = false; - self.process_search_state.search_state.error_message = None; - } else if let Err(err) = parsed_query { - self.process_search_state.search_state.is_blank_search = false; - self.process_search_state.search_state.is_invalid_search = true; - self.process_search_state.search_state.error_message = Some(err.to_string()); - } - } - self.scroll_state.previous_scroll_position = 0; - self.scroll_state.current_scroll_position = 0; - } - - pub fn clear_search(&mut self) { - self.process_search_state.search_state.reset(); - } - - pub fn search_walk_forward(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .next_boundary( - &self.process_search_state.search_state.current_search_query[start_position..], - start_position, - ) - .unwrap(); - } - - pub fn search_walk_back(&mut self, start_position: usize) { - self.process_search_state - .search_state - .grapheme_cursor - .prev_boundary( - &self.process_search_state.search_state.current_search_query[..start_position], - 0, - ) - .unwrap(); - } -} - -pub struct ProcState { - pub widget_states: HashMap, - pub force_update: Option, - pub force_update_all: bool, -} - -impl ProcState { - pub fn init(widget_states: HashMap) -> Self { - ProcState { - widget_states, - force_update: None, - force_update_all: false, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&ProcWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct NetWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, - // pub draw_max_range_cache: f64, - // pub draw_labels_cache: Vec, - // pub draw_time_start_cache: f64, - // TODO: Re-enable these when we move net details state-side! - // pub unit_type: DataUnitTypes, - // pub scale_type: AxisScaling, -} - -impl NetWidgetState { - pub fn init( - current_display_time: u64, - autohide_timer: Option, - // unit_type: DataUnitTypes, - // scale_type: AxisScaling, - ) -> Self { - NetWidgetState { - current_display_time, - autohide_timer, - // draw_max_range_cache: 0.0, - // draw_labels_cache: vec![], - // draw_time_start_cache: 0.0, - // unit_type, - // scale_type, - } - } -} - -pub struct NetState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl NetState { - pub fn init(widget_states: HashMap) -> Self { - NetState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct CpuWidgetState { - pub current_display_time: u64, - pub is_legend_hidden: bool, - pub autohide_timer: Option, - pub scroll_state: AppScrollWidgetState, - pub is_multi_graph_mode: bool, - pub table_width_state: CanvasTableWidthState, -} - -impl CpuWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - CpuWidgetState { - current_display_time, - is_legend_hidden: false, - autohide_timer, - scroll_state: AppScrollWidgetState::default(), - is_multi_graph_mode: false, - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct CpuState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl CpuState { - pub fn init(widget_states: HashMap) -> Self { - CpuState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&CpuWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct MemWidgetState { - pub current_display_time: u64, - pub autohide_timer: Option, -} - -impl MemWidgetState { - pub fn init(current_display_time: u64, autohide_timer: Option) -> Self { - MemWidgetState { - current_display_time, - autohide_timer, - } - } -} -pub struct MemState { - pub force_update: Option, - pub widget_states: HashMap, -} - -impl MemState { - pub fn init(widget_states: HashMap) -> Self { - MemState { - force_update: None, - widget_states, - } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct TempWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -impl TempWidgetState { - pub fn init() -> Self { - TempWidgetState { - scroll_state: AppScrollWidgetState::default(), - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct TempState { - pub widget_states: HashMap, -} - -impl TempState { - pub fn init(widget_states: HashMap) -> Self { - TempState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut TempWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&TempWidgetState> { - self.widget_states.get(&widget_id) - } -} - -pub struct DiskWidgetState { - pub scroll_state: AppScrollWidgetState, - pub table_width_state: CanvasTableWidthState, -} - -impl DiskWidgetState { - pub fn init() -> Self { - DiskWidgetState { - scroll_state: AppScrollWidgetState::default(), - table_width_state: CanvasTableWidthState::default(), - } - } -} - -pub struct DiskState { - pub widget_states: HashMap, -} - -impl DiskState { - pub fn init(widget_states: HashMap) -> Self { - DiskState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut DiskWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&DiskWidgetState> { - self.widget_states.get(&widget_id) - } -} -pub struct BasicTableWidgetState { - // Since this is intended (currently) to only be used for ONE widget, that's - // how it's going to be written. If we want to allow for multiple of these, - // then we can expand outwards with a normal BasicTableState and a hashmap - pub currently_displayed_widget_type: BottomWidgetType, - pub currently_displayed_widget_id: u64, - pub widget_id: i64, - pub left_tlc: Option<(u16, u16)>, - pub left_brc: Option<(u16, u16)>, - pub right_tlc: Option<(u16, u16)>, - pub right_brc: Option<(u16, u16)>, -} - -#[derive(Default)] -pub struct BatteryWidgetState { - pub currently_selected_battery_index: usize, - pub tab_click_locs: Option>, -} - -pub struct BatteryState { - pub widget_states: HashMap, -} - -impl BatteryState { - pub fn init(widget_states: HashMap) -> Self { - BatteryState { widget_states } - } - - pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> { - self.widget_states.get_mut(&widget_id) - } - - pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> { - self.widget_states.get(&widget_id) - } -} - -#[derive(Default)] -pub struct ParagraphScrollState { - pub current_scroll_index: u16, - pub max_scroll_index: u16, -} - -#[derive(Default)] -pub struct ConfigState { - pub current_category_index: usize, - pub category_list: Vec, -} - -#[derive(Default)] -pub struct ConfigCategory { - pub category_name: &'static str, - pub options_list: Vec, -} - -pub struct ConfigOption { - pub set_function: Box anyhow::Result<()>>, -} diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 3e31b966..7ef2f4dd 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -2,13 +2,10 @@ use std::{fmt::Debug, time::Instant}; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; -use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; +use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ - app::{ - event::{ComponentEventResult, SelectionAction}, - layout_manager::BottomWidgetType, - }, + app::event::{ComponentEventResult, SelectionAction}, canvas::Painter, options::layout_options::LayoutRule, }; @@ -26,7 +23,7 @@ pub use bottom_widgets::*; use self::tui_stuff::BlockBuilder; -use super::{data_farmer::DataCollection, event::EventResult}; +use super::data_farmer::DataCollection; /// A trait for things that are drawn with state. #[enum_dispatch] @@ -235,36 +232,7 @@ where } } -// ----- Old stuff below ----- - -#[derive(Debug)] -pub enum ScrollDirection { - // UP means scrolling up --- this usually DECREMENTS - Up, - // DOWN means scrolling down --- this usually INCREMENTS - Down, -} - -impl Default for ScrollDirection { - fn default() -> Self { - ScrollDirection::Down - } -} - -#[derive(Debug)] -pub enum CursorDirection { - Left, - Right, -} - -/// AppScrollWidgetState deals with fields for a scrollable app's current state. -#[derive(Default)] -pub struct AppScrollWidgetState { - pub current_scroll_position: usize, - pub previous_scroll_position: usize, - pub scroll_direction: ScrollDirection, - pub table_state: TableState, -} +// ----- FIXME: Delete the old stuff below ----- #[derive(PartialEq)] pub enum KillSignal { @@ -293,65 +261,3 @@ pub struct AppDeleteDialogState { pub last_number_press: Option, pub scroll_pos: usize, } - -pub struct AppHe