diff options
author | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-08-26 16:49:20 -0400 |
---|---|---|
committer | ClementTsang <cjhtsang@uwaterloo.ca> | 2021-08-28 04:16:12 -0400 |
commit | 0afc371eaa9a608861ff79f5a2164e578ac0014f (patch) | |
tree | ff38b34439369283ff59f2d1e05248452e3083a1 | |
parent | dd7e183ec8a152d5fda84c49a7f79141171e1448 (diff) |
refactor: start moving over drawing system
In particular, moving over table-style widgets
-rw-r--r-- | clippy.toml | 3 | ||||
-rw-r--r-- | src/app.rs | 139 | ||||
-rw-r--r-- | src/app/layout_manager.rs | 177 | ||||
-rw-r--r-- | src/app/widgets.rs | 20 | ||||
-rw-r--r-- | src/app/widgets/base/scrollable.rs | 8 | ||||
-rw-r--r-- | src/app/widgets/base/text_table.rs | 405 | ||||
-rw-r--r-- | src/app/widgets/battery.rs | 17 | ||||
-rw-r--r-- | src/app/widgets/cpu.rs | 1 | ||||
-rw-r--r-- | src/app/widgets/disk.rs | 35 | ||||
-rw-r--r-- | src/app/widgets/mem.rs | 1 | ||||
-rw-r--r-- | src/app/widgets/net.rs | 50 | ||||
-rw-r--r-- | src/app/widgets/process.rs | 27 | ||||
-rw-r--r-- | src/app/widgets/temp.rs | 31 | ||||
-rw-r--r-- | src/bin/main.rs | 6 | ||||
-rw-r--r-- | src/canvas.rs | 474 | ||||
-rw-r--r-- | src/canvas/drawing.rs (renamed from src/canvas/widgets.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/basic_table_arrows.rs (renamed from src/canvas/widgets/basic_table_arrows.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/battery_display.rs (renamed from src/canvas/widgets/battery_display.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/cpu_basic.rs (renamed from src/canvas/widgets/cpu_basic.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/cpu_graph.rs (renamed from src/canvas/widgets/cpu_graph.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/disk_table.rs (renamed from src/canvas/widgets/disk_table.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/mem_basic.rs (renamed from src/canvas/widgets/mem_basic.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/mem_graph.rs (renamed from src/canvas/widgets/mem_graph.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/network_basic.rs (renamed from src/canvas/widgets/network_basic.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/network_graph.rs (renamed from src/canvas/widgets/network_graph.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/process_table.rs (renamed from src/canvas/widgets/process_table.rs) | 0 | ||||
-rw-r--r-- | src/canvas/drawing/temp_table.rs (renamed from src/canvas/widgets/temp_table.rs) | 0 | ||||
-rw-r--r-- | src/options.rs | 16 |
28 files changed, 822 insertions, 588 deletions
diff --git a/clippy.toml b/clippy.toml index b3a62dba..e25ae33d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,3 @@ cognitive-complexity-threshold = 100 -type-complexity-threshold = 500
\ No newline at end of file +type-complexity-threshold = 500 +too-many-arguments-threshold = 8 @@ -19,8 +19,6 @@ use indextree::{Arena, NodeId}; use unicode_segmentation::GraphemeCursor; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; -use typed_builder::*; - use data_farmer::*; use data_harvester::{processes, temperature}; pub use filter::*; @@ -56,6 +54,35 @@ pub struct UsedWidgets { pub use_battery: bool, } +impl UsedWidgets { + pub fn add(&mut self, widget_type: &BottomWidgetType) { + match widget_type { + BottomWidgetType::Cpu | BottomWidgetType::BasicCpu => { + self.use_cpu = true; + } + BottomWidgetType::Mem | BottomWidgetType::BasicMem => { + self.use_mem = true; + } + BottomWidgetType::Net | BottomWidgetType::BasicNet => { + self.use_net = true; + } + BottomWidgetType::Proc => { + self.use_proc = true; + } + BottomWidgetType::Temp => { + self.use_temp = true; + } + BottomWidgetType::Disk => { + self.use_disk = true; + } + BottomWidgetType::Battery => { + self.use_battery = true; + } + _ => {} + } + } +} + /// AppConfigFields is meant to cover basic fields that would normally be set /// by config files or launch options. #[derive(Debug)] @@ -72,7 +99,7 @@ pub struct AppConfigFields { pub hide_time: bool, pub autohide_time: bool, pub use_old_network_legend: bool, - pub table_gap: u16, + pub table_gap: u16, // TODO: Just make this a bool... pub disable_click: bool, pub no_write: bool, pub show_table_scroll_position: bool, @@ -83,55 +110,32 @@ pub struct AppConfigFields { pub network_use_binary_prefix: bool, } -// FIXME: Get rid of TypedBuilder here! -#[derive(TypedBuilder)] pub struct AppState { - #[builder(default, setter(skip))] pub dd_err: Option<String>, - #[builder(default, setter(skip))] to_delete_process_list: Option<(String, Vec<Pid>)>, - #[builder(default = false, setter(skip))] pub is_frozen: bool, - #[builder(default = Instant::now(), setter(skip))] - last_key_press: Instant, - - #[builder(default, setter(skip))] pub canvas_data: canvas::DisplayableData, - #[builder(default, setter(skip))] pub data_collection: DataCollection, - #[builder(default = false, setter(skip))] pub is_expanded: bool, - #[builder(default = false, setter(skip))] - pub is_force_redraw: bool, - - #[builder(default = false, setter(skip))] - pub is_determining_widget_boundary: bool, - - #[builder(default = false, setter(skip))] - pub basic_mode_use_percent: bool, - #[cfg(target_family = "unix")] - #[builder(default, setter(skip))] pub user_table: processes::UserTable, pub used_widgets: UsedWidgets, pub filters: DataFilters, pub app_config_fields: AppConfigFields, - // --- Possibly delete? --- - #[builder(default, setter(skip))] + // --- Eventually delete/rewrite --- pub delete_dialog_state: AppDeleteDialogState, - #[builder(default, setter(skip))] pub help_dialog_state: AppHelpDialogState, - // --- TO DELETE--- + // --- TO DELETE --- pub cpu_state: CpuState, pub mem_state: MemState, pub net_state: NetState, @@ -143,12 +147,18 @@ pub struct AppState { pub widget_map: HashMap<u64, BottomWidget>, pub current_widget: BottomWidget, - #[builder(default = false, setter(skip))] + last_key_press: Instant, + awaiting_second_char: bool, - #[builder(default, setter(skip))] second_char: Option<char>, + pub basic_mode_use_percent: bool, + + pub is_force_redraw: bool, + + pub is_determining_widget_boundary: bool, + // --- NEW STUFF --- pub selected_widget: NodeId, pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>, @@ -159,10 +169,53 @@ pub struct AppState { impl AppState { /// Creates a new [`AppState`]. pub fn new( - _app_config_fields: AppConfigFields, _filters: DataFilters, - _layout_tree_output: LayoutCreationOutput, + app_config_fields: AppConfigFields, filters: DataFilters, + layout_tree_output: LayoutCreationOutput, ) -> Self { - todo!() + let LayoutCreationOutput { + layout_tree, + root: layout_tree_root, + widget_lookup_map, + selected: selected_widget, + used_widgets, + } = layout_tree_output; + + Self { + app_config_fields, + filters, + used_widgets, + selected_widget, + widget_lookup_map, + layout_tree, + layout_tree_root, + + // Use defaults. + dd_err: Default::default(), + to_delete_process_list: Default::default(), + is_frozen: Default::default(), + canvas_data: Default::default(), + data_collection: Default::default(), + is_expanded: Default::default(), + user_table: Default::default(), + delete_dialog_state: Default::default(), + help_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(), + last_key_press: Instant::now(), + awaiting_second_char: Default::default(), + second_char: Default::default(), + basic_mode_use_percent: Default::default(), + is_force_redraw: Default::default(), + is_determining_widget_boundary: Default::default(), + } } pub fn reset(&mut self) { @@ -248,12 +301,13 @@ impl AppState { for (id, widget) in self.widget_lookup_map.iter_mut() { if does_point_intersect_rect(x, y, widget.bounds()) { - if self.selected_widget == *id { - self.selected_widget = *id; + let is_id_selected = self.selected_widget == *id; + self.selected_widget = *id; + + if is_id_selected { return widget.handle_mouse_event(event); } else { // If the aren't equal, *force* a redraw. - self.selected_widget = *id; widget.handle_mouse_event(event); return EventResult::Redraw; } @@ -262,10 +316,10 @@ impl AppState { EventResult::NoRedraw } - BottomEvent::Update(new_data) => { + BottomEvent::Update(_new_data) => { if !self.is_frozen { // TODO: Update all data, and redraw. - EventResult::Redraw + todo!() } else { EventResult::NoRedraw } @@ -282,9 +336,14 @@ impl AppState { } } - /// Handles a [`ReturnSignal`], and returns + /// Handles a [`ReturnSignal`], and returns an [`EventResult`]. pub fn handle_return_signal(&mut self, return_signal: ReturnSignal) -> EventResult { - todo!() + match return_signal { + ReturnSignal::Nothing => EventResult::NoRedraw, + ReturnSignal::KillProcess => { + todo!() + } + } } pub fn on_esc(&mut self) { diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 065b89fb..a2a70a42 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1,5 +1,7 @@ use crate::{ - app::{DiskTable, MemGraph, NetGraph, OldNetGraph, ProcessManager, TempTable}, + app::{ + text_table::Column, DiskTable, MemGraph, NetGraph, OldNetGraph, ProcessManager, TempTable, + }, error::{BottomError, Result}, options::layout_options::{Row, RowChildren}, }; @@ -12,7 +14,7 @@ use typed_builder::*; use crate::app::widgets::Widget; use crate::constants::DEFAULT_WIDGET_ID; -use super::{event::SelectionAction, CpuGraph, TextTable, TimeGraph, TmpBottomWidget}; +use super::{event::SelectionAction, CpuGraph, TextTable, TimeGraph, TmpBottomWidget, UsedWidgets}; /// Represents a more usable representation of the layout, derived from the /// config. @@ -985,49 +987,17 @@ Supported widget names: // --- New stuff --- /// Represents a row in the layout tree. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Default)] pub struct RowLayout { last_selected_index: usize, - pub constraint: Constraint, -} - -impl Default for RowLayout { - fn default() -> Self { - Self { - last_selected_index: 0, - constraint: Constraint::Min(0), - } - } + pub constraints: Vec<Constraint>, } /// Represents a column in the layout tree. -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Default)] pub struct ColLayout { last_selected_index: usize, - pub constraint: Constraint, -} - -impl Default for ColLayout { - fn default() -> Self { - Self { - last_selected_index: 0, - constraint: Constraint::Min(0), - } - } -} - -/// Represents a widget in the layout tree. -#[derive(PartialEq, Eq)] -pub struct WidgetLayout { - pub constraint: Constraint, -} - -impl Default for WidgetLayout { - fn default() -> Self { - Self { - constraint: Constraint::Min(0), - } - } + pub constraints: Vec<Constraint>, } /// A [`LayoutNode`] represents a single node in the overall widget hierarchy. Each node is one of: @@ -1038,7 +1008,21 @@ impl Default for WidgetLayout { pub enum LayoutNode { Row(RowLayout), Col(ColLayout), - Widget(WidgetLayout), + Widget, +} + +impl LayoutNode { + pub fn set_constraints(&mut self, constraints: Vec<Constraint>) { + match self { + LayoutNode::Row(row) => { + row.constraints = constraints; + } + LayoutNode::Col(col) => { + col.constraints = constraints; + } + LayoutNode::Widget => {} + } + } } /// Relative movement direction from the currently selected widget. @@ -1054,7 +1038,8 @@ pub struct LayoutCreationOutput { pub layout_tree: Arena<LayoutNode>, pub root: NodeId, pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>, - pub selected: Option<NodeId>, + pub selected: NodeId, + pub used_widgets: UsedWidgets, } /// Creates a new [`Arena<LayoutNode>`] from the given config and returns it, along with the [`NodeId`] representing @@ -1066,14 +1051,17 @@ pub fn create_layout_tree( app_config_fields: &super::AppConfigFields, ) -> Result<LayoutCreationOutput> { fn add_widget_to_map( - widget_lookup_map: &mut FxHashMap<NodeId, TmpBottomWidget>, widget_type: &str, + widget_lookup_map: &mut FxHashMap<NodeId, TmpBottomWidget>, widget_type: BottomWidgetType, widget_id: NodeId, process_defaults: &crate::options::ProcessDefaults, app_config_fields: &super::AppConfigFields, ) -> Result<()> { - match widget_type.parse::<BottomWidgetType>()? { + match widget_type { BottomWidgetType::Cpu => { let graph = TimeGraph::from_config(app_config_fields); - let legend = TextTable::new(vec![("CPU", None, false), ("Use%", None, false)]); + let legend = TextTable::new(vec![ + Column::new_flex("CPU", None, false, 0.5), + Column::new_flex("Use%", None, false, 0.5), + ]); let legend_position = super::CpuGraphLegendPosition::Right; widget_lookup_map.insert( @@ -1100,20 +1088,10 @@ pub fn create_layout_tree( ); } BottomWidgetType::Temp => { - let table = TextTable::new(vec![("Sensor", None, false), ("Temp", None, false)]); - widget_lookup_map.insert(widget_id, TempTable::new(table).into()); + widget_lookup_map.insert(widget_id, TempTable::default().into()); } BottomWidgetType::Disk => { - let table = TextTable::new(vec![ - ("Disk", None, false), - ("Mount", None, false), - ("Used", None, false), - ("Free", None, false), - ("Total", None, false), - ("R/s", None, false), - ("W/s", None, false), - ]); - widget_lookup_map.insert(widget_id, DiskTable::new(table).into()); + widget_lookup_map.insert(widget_id, DiskTable::default().into()); } BottomWidgetType::Battery => {} _ => {} @@ -1125,19 +1103,20 @@ pub fn create_layout_tree( let mut layout_tree = Arena::new(); let root_id = layout_tree.new_node(LayoutNode::Col(ColLayout::default())); let mut widget_lookup_map = FxHashMap::default(); - let mut selected = None; + let mut first_selected = None; + let mut first_widget_seen = None; // Backup + let mut used_widgets = UsedWidgets::default(); let row_sum: u32 = rows.iter().map(|row| row.ratio.unwrap_or(1)).sum(); + let mut root_constraints = Vec::with_capacity(rows.len()); for row in rows { - let ratio = row.ratio.unwrap_or(1); - let layout_node = LayoutNode::Row(RowLayout { - constraint: Constraint::Ratio(ratio, row_sum), - ..Default::default() - }); + root_constraints.push(Constraint::Ratio(row.ratio.unwrap_or(1), row_sum)); + let layout_node = LayoutNode::Row(RowLayout::default()); let row_id = layout_tree.new_node(layout_node); root_id.append(row_id, &mut layout_tree); if let Some(cols) = &row.child { + let mut row_constraints = Vec::with_capacity(cols.len()); let col_sum: u32 = cols .iter() .map(|col| match col { @@ -1149,18 +1128,24 @@ pub fn create_layout_tree( for col in cols { match col { RowChildren::Widget(widget) => { - let widget_node = LayoutNode::Widget(WidgetLayout { - constraint: Constraint::Ratio(widget.ratio.unwrap_or(1), col_sum), - }); - let widget_id = layout_tree.new_node(widget_node); + row_constraints.push(Constraint::Ratio(widget.ratio.unwrap_or(1), col_sum)); + let widget_id = layout_tree.new_node(LayoutNode::Widget); row_id.append(widget_id, &mut layout_tree); if let Some(true) = widget.default { - selected = Some(widget_id); + first_selected = Some(widget_id); } + + if first_widget_seen.is_none() { + first_widget_seen = Some(widget_id); + } + + let widget_type = widget.widget_type.parse::<BottomWidgetType>()?; + used_widgets.add(&widget_type); + add_widget_to_map( &mut widget_lookup_map, - &widget.widget_type, + widget_type, widget_id, &process_defaults, app_config_fields, @@ -1170,45 +1155,73 @@ pub fn create_layout_tree( ratio, child: children, } => { - let col_node = LayoutNode::Col(ColLayout { - constraint: Constraint::Ratio(ratio.unwrap_or(1), col_sum), - ..Default::default() - }); + row_constraints.push(Constraint::Ratio(ratio.unwrap_or(1), col_sum)); + let col_node = LayoutNode::Col(ColLayout::default()); let col_id = layout_tree.new_node(col_node); row_id.append(col_id, &mut layout_tree); let child_sum: u32 = children.iter().map(|child| child.ratio.unwrap_or(1)).sum(); + let mut col_constraints = Vec::with_capacity(children.len()); for child in children { - let widget_node = LayoutNode::Widget(WidgetLayout { - constraint: Constraint::Ratio(child.ratio.unwrap_or(1), child_sum), - }); - let widget_id = layout_tree.new_node(widget_node); + col_constraints + .push(Constraint::Ratio(child.ratio.unwrap_or(1), child_sum)); + let widget_id = layout_tree.new_node(LayoutNode::Widget); col_id.append(widget_id, &mut layout_tree); if let Some(true) = child.default { - selected = Some(widget_id); + first_selected = Some(widget_id); } + + if first_widget_seen.is_none() { + first_widget_seen = Some(widget_id); + } + + let widget_type = child.widget_type.parse::<BottomWidgetType>()?; + used_widgets.add(&widget_type); + add_widget_to_map( &mut widget_lookup_map, - &child.widget_type, + widget_type, widget_id, &process_defaults, app_config_fields, )?; } + layout_tree[col_id] + .get_mut() + .set_constraints(col_constraints); } } } + layout_tree[row_id] + .get_mut() + .set_constraints(row_constraints); } } + layout_tree[root_id] + .get_mut() + .set_constraints(root_constraints); + + let selected: NodeId; + + if let Some(first_selected) = first_selected { + selected = first_selected; + } else if let Some(first_widget_seen) = first_widget_seen { + selected = first_widget_seen; + } else { + return Err(BottomError::ConfigError( + "A layout cannot contain zero widgets!".to_string(), + )); + } Ok(LayoutCreationOutput { layout_tree, root: root_id, widget_lookup_map, selected, + used_widgets, }) } @@ -1252,7 +1265,7 @@ pub fn move_widget_selection( .and_then(|(parent_id, parent_node)| match parent_node.get() { LayoutNode::Row(_) => Some((parent_id, current_id)), LayoutNode::Col(_) => find_first_row(layout_tree, parent_id), - LayoutNode::Widget(_) => None, + LayoutNode::Widget => None, }) } @@ -1273,7 +1286,7 @@ pub fn move_widget_selection( .and_then(|(parent_id, parent_node)| match parent_node.get() { LayoutNode::Row(_) => find_first_col(layout_tree, parent_id), LayoutNode::Col(_) => Some((parent_id, current_id)), - LayoutNode::Widget(_) => None, + LayoutNode::Widget => None, }) } @@ -1283,11 +1296,11 @@ pub fn move_widget_selection( match current_node.get() { LayoutNode::Row(RowLayout { last_selected_index, - constraint: _, + constraints: _, }) | LayoutNode::Col(ColLayout { last_selected_index, - constraint: _, + constraints: _, }) => { if let Some(next_child) = current_id.children(layout_tree).nth(*last_selected_index) @@ -1297,7 +1310,7 @@ pub fn move_widget_selection( current_id } } - LayoutNode::Widget(_) => { + LayoutNode::Widget => { // Halt! current_id } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index d5ba3586..a99fe7b8 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -2,13 +2,19 @@ use std::time::Instant; use crossterm::event::{KeyEvent, MouseEvent}; use enum_dispatch::enum_dispatch; -use tui::{layout::Rect, widgets::TableState}; +use tui::{ + backend::Backend, + layout::Rect, + widgets::{Block, TableState}, + Frame, +}; use crate::{ app::{ event::{EventResult, SelectionAction}, layout_manager::BottomWidgetType, }, + canvas::{DisplayableData, Painter}, constants, }; @@ -64,6 +70,7 @@ pub trait Component { /// A trait for actual fully-fledged widgets to be displayed in bottom. #[enum_dispatch] +#[allow(unused_variables)] pub trait Widget { /// Updates a [`Widget`] given some data. Defaults to doing nothing. fn update(&mut self) {} @@ -92,10 +99,21 @@ pub trait Widget { SelectionAction::NotHandled } + /// Returns a [`Widget`]'s "pretty" display name. fn get_pretty_name(&self) -> &'static str; + + /// Draws a [`Widget`]. Defaults to doing nothing. + fn draw<B: Backend>( + &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, block: Block<'_>, + data: &DisplayableData, + ) { + // TODO: Remove the default implementation in the future! + // TODO: Do another pass on ALL of the draw code - currently it's just glue, it should eventually be done properly! + } } /// The "main" widgets that are used by bottom to display information! +#[allow(clippy::large_enum_variant)] #[enum_dispatch(Component, Widget)] pub enum TmpBottomWidget { MemGraph, diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs index 4606b02b..c180192a 100644 --- a/src/app/widgets/base/scrollable.rs +++ b/src/app/widgets/base/scrollable.rs @@ -131,13 +131,17 @@ impl Scrollable { self.num_items = num_items; if num_items <= self.current_index { - self.current_index = num_items - 1; + self.current_index = num_items.saturating_sub(1); } if num_items <= self.previous_index { - self.previous_index = num_items - 1; + self.previous_index = num_items.saturating_sub(1); } } + + pub fn num_items(&self) -> usize { + self.num_items + } } impl Component for Scrollable { diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index d5ff3d2c..fef2e31f 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -1,32 +1,132 @@ -use crossterm::event::{KeyEvent, MouseEvent}; -use tui::layout::Rect; +use std::{ + borrow::Cow, + cmp::{max, min}, +}; -use crate::app::{event::EventResult, Component, Scrollable}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent}; +use tui::{ + layout::{Constraint, Rect}, + text::Text, + widgets::{Table, TableState}, +}; +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + app::{event::EventResult, Component, Scrollable}, + canvas::Painter, + constants::TABLE_GAP_HEIGHT_LIMIT, +}; + +/// Represents the desired widths a column tries to have. +#[derive(Clone, Debug)] +pub enum DesiredColumnWidth { + Hard(u16), + Flex { desired: u16, max_percentage: f64 }, +} /// A [`Column`] represents some column in a [`TextTable`]. +#[derive(Debug)] pub struct Column { pub name: &'static str, - pub shortcut: Option<KeyEvent>, + pub shortcut: Option<(KeyEvent, String)>, pub default_descending: bool, // TODO: I would remove these in the future, storing them here feels weird... - pub desired_column_width: u16, - pub calculated_column_width: u16, + pub desired_width: DesiredColumnWidth, pub x_bounds: (u16, u16), } impl Column { - /// Creates a new [`Column`], given a name and optional shortcut. - pub fn new(name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool) -> Self { + /// Creates a new [`Column`]. + pub fn new( + name: &'static str, shortcut: Option<KeyEvent>, default_descending: bool, + desired_width: DesiredColumnWidth, + ) -> Self { Self { name, - desired_column_width: 0, - calculated_column_width: 0, x_bounds: (0, 0), - shortcut, + shortcut: shortcut.map(|e| { + let modifier = if e.modifiers.is_empty() { + "" + } else if let KeyModifiers::ALT = e.modifiers { + "Alt+" + } else if let KeyModifiers::SHIFT = e.modifiers { + "Shift+" + } else if let KeyModifiers::CONTROL = e.modifiers { + "Ctrl+" + } else { + // For now, that's all we support, though combos/more could be added. + "" + }; + + let key: Cow<'static, str> = match e.code { + KeyCode::Backspace => "Backspace".into(), + KeyCode::Enter => "Enter".into(), + KeyCode::Left => "Left".into(), + KeyCode::Right => "Right".into(), + KeyCode::Up => "Up".into(), + KeyCode::Down => "Down".into(), + KeyCode::Home => "Home".into(), + KeyCode::End => "End".into(), + KeyCode::PageUp => "PgUp".into(), + KeyCode::PageDown => "PgDown".into(), + KeyCode::Tab => "Tab".into(), + KeyCode::BackTab => "BackTab".into(), + KeyCode::Delete => "Del".into(), + KeyCode::Insert => "Insert".into(), + KeyCode::F(num) => format!("F{}", num).into(), + KeyCode::Char(c) => format!("{}", c).into(), + KeyCode::Null => "Null".into(), |