summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-26 16:49:20 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-08-28 04:16:12 -0400
commit0afc371eaa9a608861ff79f5a2164e578ac0014f (patch)
treeff38b34439369283ff59f2d1e05248452e3083a1
parentdd7e183ec8a152d5fda84c49a7f79141171e1448 (diff)
refactor: start moving over drawing system
In particular, moving over table-style widgets
-rw-r--r--clippy.toml3
-rw-r--r--src/app.rs139
-rw-r--r--src/app/layout_manager.rs177
-rw-r--r--src/app/widgets.rs20
-rw-r--r--src/app/widgets/base/scrollable.rs8
-rw-r--r--src/app/widgets/base/text_table.rs405
-rw-r--r--src/app/widgets/battery.rs17
-rw-r--r--src/app/widgets/cpu.rs1
-rw-r--r--src/app/widgets/disk.rs35
-rw-r--r--src/app/widgets/mem.rs1
-rw-r--r--src/app/widgets/net.rs50
-rw-r--r--src/app/widgets/process.rs27
-rw-r--r--src/app/widgets/temp.rs31
-rw-r--r--src/bin/main.rs6
-rw-r--r--src/canvas.rs474
-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.rs16
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
diff --git a/src/app.rs b/src/app.rs
index fe4e44da..5c1e9b90 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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(),