diff --git a/src/app.rs b/src/app.rs index 939b316c..864ec490 100644 --- a/src/app.rs +++ b/src/app.rs @@ -353,11 +353,11 @@ impl AppState { } } else { for (id, widget) in self.widget_lookup_map.iter_mut() { - if widget.does_intersect_mouse(&event) { - let is_id_selected = self.selected_widget == *id; + if widget.does_border_intersect_mouse(&event) { + let was_id_already_selected = self.selected_widget == *id; self.selected_widget = *id; - if is_id_selected { + if was_id_already_selected { let result = widget.handle_mouse_event(event); return self.convert_widget_event_result(result); } else { diff --git a/src/app/event.rs b/src/app/event.rs index a776a493..a6afb2d8 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -4,6 +4,7 @@ const MAX_TIMEOUT: Duration = Duration::from_millis(400); /// These are "signals" that are sent along with an [`WidgetEventResult`] to signify a potential additional action /// that the caller must do, along with the "core" result of either drawing or redrawing. +#[derive(Debug)] pub enum ReturnSignal { /// A signal returned when some process widget was told to try to kill a process (or group of processes). /// @@ -18,6 +19,7 @@ pub enum ReturnSignal { } /// The results of handling an event by the [`AppState`]. +#[derive(Debug)] pub enum EventResult { /// Kill the program. Quit, @@ -29,6 +31,7 @@ pub enum EventResult { /// The results of a widget handling some event, like a mouse or key event, /// signifying what the program should then do next. +#[derive(Debug)] pub enum WidgetEventResult { /// Kill the program. Quit, diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index d2927a35..7105fa73 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1001,13 +1001,16 @@ pub struct ColLayout { } /// A [`LayoutNode`] represents a single node in the overall widget hierarchy. Each node is one of: -/// - [`LayoutNode::Row`] (a a non-leaf that distributes its children horizontally) +/// - [`LayoutNode::Row`] (a non-leaf that distributes its children horizontally) /// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically) /// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with) #[derive(PartialEq, Eq)] pub enum LayoutNode { + /// A non-leaf that distributes its children horizontally Row(RowLayout), + /// A non-leaf node that distributes its children vertically Col(ColLayout), + /// A leaf node that contains the ID of the widget it is associated with Widget, } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 751157c7..104e6ac4 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -6,7 +6,7 @@ use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame}; use crate::{ app::{ - event::{WidgetEventResult, SelectionAction}, + event::{SelectionAction, WidgetEventResult}, layout_manager::BottomWidgetType, }, canvas::Painter, @@ -63,15 +63,36 @@ pub trait Component { /// coordinates. fn bounds(&self) -> Rect; - /// Updates a [`Component`]s bounding box to `new_bounds`. + /// Updates a [`Component`]'s bounding box to `new_bounds`. fn set_bounds(&mut self, new_bounds: Rect); - /// Returns whether a [`MouseEvent`] intersects a [`Component`]. - fn does_intersect_mouse(&self, event: &MouseEvent) -> bool { + /// Returns a [`Component`]'s bounding box, *including the border*. Defaults to just returning the normal bounds. + /// Note that these are defined in *global*, *absolute* coordinates. + fn border_bounds(&self) -> Rect { + self.bounds() + } + + /// Updates a [`Component`]'s bounding box to `new_bounds`. Defaults to just setting the normal bounds. + fn set_border_bounds(&mut self, new_bounds: Rect) { + self.set_bounds(new_bounds); + } + + /// Returns whether a [`MouseEvent`] intersects a [`Component`]'s bounds. + fn does_bounds_intersect_mouse(&self, event: &MouseEvent) -> bool { let x = event.column; let y = event.row; - let rect = self.bounds(); - x >= rect.left() && x <= rect.right() && y >= rect.top() && y <= rect.bottom() + let bounds = self.bounds(); + + x >= bounds.left() && x < bounds.right() && y >= bounds.top() && y < bounds.bottom() + } + + /// Returns whether a [`MouseEvent`] intersects a [`Component`]'s bounds, including any borders, if there are. + fn does_border_intersect_mouse(&self, event: &MouseEvent) -> bool { + let x = event.column; + let y = event.row; + let bounds = self.border_bounds(); + + x >= bounds.left() && x < bounds.right() && y >= bounds.top() && y < bounds.bottom() } } diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs index 811d7017..54000a06 100644 --- a/src/app/widgets/base/scrollable.rs +++ b/src/app/widgets/base/scrollable.rs @@ -224,7 +224,7 @@ impl Component for Scrollable { fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { match event.kind { MouseEventKind::Down(MouseButton::Left) => { - if self.does_intersect_mouse(&event) { + if self.does_bounds_intersect_mouse(&event) { // This requires a bit of fancy calculation. The main trick is remembering that // we are using a *visual* index here - not what is the actual index! Luckily, we keep track of that // inside our linked copy of TableState! diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs index 23f46250..cc95f2c0 100644 --- a/src/app/widgets/base/sort_text_table.rs +++ b/src/app/widgets/base/sort_text_table.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}; -use tui::{backend::Backend, layout::Rect, widgets::Block}; +use tui::{backend::Backend, layout::Rect, widgets::Block, Frame}; use crate::{ app::{ @@ -60,6 +60,9 @@ pub enum SortStatus { /// A trait for sortable columns. pub trait SortableColumn { + /// Returns the original name of the column. + fn original_name(&self) -> &Cow<'static, str>; + /// Returns the shortcut for the column, if it exists. fn shortcut(&self) -> &Option<(KeyEvent, String)>; @@ -73,12 +76,18 @@ pub trait SortableColumn { /// Sets the sorting status. fn set_sorting_status(&mut self, sorting_status: SortStatus); + // ----- The following are required since SortableColumn implements TableColumn ----- + + /// Returns the displayed name on the column when drawing. fn display_name(&self) -> Cow<'static, str>; + /// Returns the desired width of the column when drawing. fn get_desired_width(&self) -> &DesiredColumnWidth; + /// Returns the x bounds of a column. The y is assumed to be 0, relative to the table.. fn get_x_bounds(&self) -> Option<(u16, u16)>; + /// Sets the x bounds of a column. fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>); } @@ -106,8 +115,11 @@ where /// A [`SimpleSortableColumn`] represents some column in a [`SortableTextTable`]. #[derive(Debug)] pub struct SimpleSortableColumn { + original_name: Cow<'static, str>, pub shortcut: Option<(KeyEvent, String)>, pub default_descending: bool, + x_bounds: Option<(u16, u16)>, + pub internal: SimpleColumn, /// Whether this column is currently selected for sorting, and which direction. @@ -117,12 +129,15 @@ pub struct SimpleSortableColumn { impl SimpleSortableColumn { /// Creates a new [`SimpleSortableColumn`]. fn new( - full_name: Cow<'static, str>, shortcut: Option<(KeyEvent, String)>, - default_descending: bool, desired_width: DesiredColumnWidth, + original_name: Cow<'static, str>, full_name: Cow<'static, str>, + shortcut: Option<(KeyEvent, String)>, default_descending: bool, + desired_width: DesiredColumnWidth, ) -> Self { Self { + original_name, shortcut, default_descending, + x_bounds: None, internal: SimpleColumn::new(full_name, desired_width), sorting_status: SortStatus::NotSorting, } @@ -141,11 +156,12 @@ impl SimpleSortableColumn { Some((shortcut, shortcut_name)), ) } else { - (name, None) + (name.clone(), None) }; let full_name_len = full_name.len(); SimpleSortableColumn::new( + name, full_name, shortcut, default_descending, @@ -165,11 +181,12 @@ impl SimpleSortableColumn { Some((shortcut, shortcut_name)), ) } else { - (name, None) + (name.clone(), None) }; let full_name_len = full_name.len(); SimpleSortableColumn::new( + name, full_name, shortcut, default_descending, @@ -182,6 +199,10 @@ impl SimpleSortableColumn { } impl SortableColumn for SimpleSortableColumn { + fn original_name(&self) -> &Cow<'static, str> { + &self.original_name + } + fn shortcut(&self) -> &Option<(KeyEvent, String)> { &self.shortcut } @@ -236,6 +257,9 @@ where /// The underlying [`TextTable`]. pub table: TextTable, + + /// A corresponding "sort" menu. + pub sort_menu: TextTable, } impl SortableTextTable @@ -244,9 +268,15 @@ where { /// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty. pub fn new(columns: Vec) -> Self { + let sort_menu_columns = columns + .iter() + .map(|column| SimpleColumn::new_hard(column.original_name().clone(), None)) + .collect::>(); + let mut st = Self { sort_index: 0, table: TextTable::new(columns), + sort_menu: TextTable::new(sort_menu_columns), }; st.set_sort_index(0); st @@ -317,15 +347,21 @@ where /// Draws a [`tui::widgets::Table`] on screen. /// - /// Note if the number of columns don't match in the [`TextTable`] and data, + /// Note if the number of columns don't match in the [`SortableTextTable`] and data, /// it will only create as many columns as it can grab data from both sources from. pub fn draw_tui_table( - &mut self, painter: &Painter, f: &mut tui::Frame<'_, B>, data: &TextTableData, - block: Block<'_>, block_area: Rect, show_selected_entry: bool, + &mut self, painter: &Painter, f: &mut Frame<'_, B>, data: &TextTableData, block: Block<'_>, + block_area: Rect, show_selected_entry: bool, ) { self.table .draw_tui_table(painter, f, data, block, block_area, show_selected_entry); } + + /// Draws a [`tui::widgets::Table`] on screen corresponding to the sort columns of this [`SortableTextTable`]. + pub fn draw_sort_table( + &mut self, painter: &Painter, f: &mut Frame<'_, B>, block: Block<'_>, block_area: Rect, + ) { + } } impl Component for SortableTextTable @@ -347,7 +383,7 @@ where fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { if let MouseEventKind::Down(MouseButton::Left) = event.kind { - if !self.does_intersect_mouse(&event) { + if !self.does_bounds_intersect_mouse(&event) { return WidgetEventResult::NoRedraw; } @@ -373,10 +409,18 @@ where } fn bounds(&self) -> Rect { - self.table.bounds + self.table.bounds() } fn set_bounds(&mut self, new_bounds: Rect) { - self.table.bounds = new_bounds; + self.table.set_bounds(new_bounds) + } + + fn border_bounds(&self) -> Rect { + self.table.border_bounds() + } + + fn set_border_bounds(&mut self, new_bounds: Rect) { + self.table.set_border_bounds(new_bounds) } } diff --git a/src/app/widgets/base/text_input.rs b/src/app/widgets/base/text_input.rs index dfe776de..597dff6e 100644 --- a/src/app/widgets/base/text_input.rs +++ b/src/app/widgets/base/text_input.rs @@ -12,6 +12,7 @@ pub struct TextInput { text: String, cursor_index: usize, bounds: Rect, + border_bounds: Rect, } impl TextInput { @@ -92,6 +93,14 @@ impl Component for TextInput { self.bounds = new_bounds; } + fn border_bounds(&self) -> Rect { + self.border_bounds + } + + fn set_border_bounds(&mut self, new_bounds: Rect) { + self.border_bounds = new_bounds; + } + fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult { if event.modifiers.is_empty() { match event.code { diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs index 20d78b8e..87f3f69d 100644 --- a/src/app/widgets/base/text_table.rs +++ b/src/app/widgets/base/text_table.rs @@ -131,6 +131,9 @@ where /// The bounding box of the [`TextTable`]. pub bounds: Rect, // TODO: Consider moving bounds to something else??? + /// The bounds including the border, if there is one. + pub border_bounds: Rect, + /// Whether we draw columns from left-to-right. pub left_to_right: bool, @@ -149,6 +152,7 @@ where cached_column_widths: CachedColumnWidths::Uncached, show_gap: true, bounds: Rect::default(), + border_bounds: Rect::default(), left_to_right: true, selectable: true, } @@ -342,7 +346,7 @@ where } } - /// Draws a [`Table`] on screen.. + /// Draws a [`Table`] on screen corresponding to the [`TextTable`]. /// /// Note if the number of columns don't match in the [`TextTable`] and data, /// it will only create as many columns as it can grab data from both sources from. @@ -353,7 +357,6 @@ where use tui::widgets::Row; let inner_area = block.inner(block_area); - let table_gap = if !self.show_gap || inner_area.height < TABLE_GAP_HEIGHT_LIMIT { 0 } else { @@ -361,6 +364,7 @@ where }; self.set_num_items(data.len()); + self.set_border_bounds(block_area); self.set_bounds(inner_area); let table_extras = 1 + table_gap; let scrollable_height = inner_area.height.saturating_sub(table_extras); @@ -466,4 +470,12 @@ where fn set_bounds(&mut self, new_bounds: Rect) { self.bounds = new_bounds; } + + fn border_bounds(&self) -> Rect { + self.border_bounds + } + + fn set_border_bounds(&mut self, new_bounds: Rect) { + self.border_bounds = new_bounds; + } } diff --git a/src/app/widgets/base/time_graph.rs b/src/app/widgets/base/time_graph.rs index 1f8399ff..1bfd4a8b 100644 --- a/src/app/widgets/base/time_graph.rs +++ b/src/app/widgets/base/time_graph.rs @@ -111,6 +111,7 @@ pub struct TimeGraph { time_interval: u64, bounds: Rect, + border_bounds: Rect, use_dot: bool, } @@ -129,6 +130,7 @@ impl TimeGraph { max_duration, time_interval, bounds: Rect::default(), + border_bounds: Rect::default(), use_dot, } } @@ -236,6 +238,7 @@ impl TimeGraph { ) { let inner_area = block.inner(block_area); + self.set_border_bounds(block_area); self.set_bounds(inner_area); let time_start = -(self.current_display_time as f64); @@ -324,4 +327,12 @@ impl Component for TimeGraph { fn set_bounds(&mut self, new_bounds: Rect) { self.bounds = new_bounds; } + + fn border_bounds(&self) -> Rect { + self.border_bounds + } + + fn set_border_bounds(&mut self, new_bounds: Rect) { + self.border_bounds = new_bounds; + } } diff --git a/src/app/widgets/cpu.rs b/src/app/widgets/cpu.rs index 566ce4cc..12647a47 100644 --- a/src/app/widgets/cpu.rs +++ b/src/app/widgets/cpu.rs @@ -16,9 +16,7 @@ use crate::{ data_conversion::{convert_cpu_data_points, ConvertedCpuData}, }; -use super::{ - AppScrollWidgetState, CanvasTableWidthState, Component, SortableTextTable, TimeGraph, Widget, -}; +use super::{AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, TimeGraph, Widget}; pub struct CpuWidgetState { pub current_display_time: u64, @@ -77,10 +75,10 @@ pub enum CpuGraphLegendPosition { Right, } -/// A widget designed to show CPU usage via a graph, along with a side legend implemented as a [`TextTable`]. +/// A widget designed to show CPU usage via a graph, along with a side legend in a table. pub struct CpuGraph { graph: TimeGraph, - legend: SortableTextTable, + legend: TextTable, legend_position: CpuGraphLegendPosition, showing_avg: bool, @@ -95,7 +93,7 @@ impl CpuGraph { /// Creates a new [`CpuGraph`] from a config. pub fn from_config(app_config_fields: &AppConfigFields) -> Self { let graph = TimeGraph::from_config(app_config_fields); - let legend = SortableTextTable::new(vec![ + let legend = TextTable::new(vec![ SimpleSortableColumn::new_flex("CPU".into(), None, false, 0.5), SimpleSortableColumn::new_flex("Use%".into(), None, false, 0.5), ]); @@ -129,7 +127,7 @@ impl Component for CpuGraph { } fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { - if self.graph.does_intersect_mouse(&event) { + if self.graph.does_border_intersect_mouse(&event) { if let CpuGraphSelection::Graph = self.selected { self.graph.handle_mouse_event(event) } else { @@ -137,7 +135,7 @@ impl Component for CpuGraph { self.graph.handle_mouse_event(event); WidgetEventResult::Redraw } - } else if self.legend.does_intersect_mouse(&event) { + } else if self.legend.does_border_intersect_mouse(&event) { if let CpuGraphSelection::Legend = self.selected { self.legend.handle_mouse_event(event) } else { @@ -176,11 +174,16 @@ impl Widget for CpuGraph { } }; + // debug!("Area: {:?}", area); + let split_area = Layout::default() + .margin(0) .direction(Direction::Horizontal) .constraints(constraints) .split(area); + // debug!("Split area: {:?}", split_area); + const Y_BOUNDS: [f64; 2] = [0.0, 100.5]; let y_bound_labels: [Cow<'static, str>; 2] = ["0%".into(), "100%".into()]; diff --git a/src/app/widgets/disk.rs b/src/app/widgets/disk.rs index 92340e67..a9a35e19 100644 --- a/src/app/widgets/disk.rs +++ b/src/app/widgets/disk.rs @@ -9,14 +9,17 @@ use tui::{ }; use crate::{ - app::{data_farmer::DataCollection, event::WidgetEventResult, sort_text_table::SimpleSortableColumn}, + app::{ + data_farmer::DataCollection, event::WidgetEventResult, + sort_text_table::SimpleSortableColumn, + }, canvas::Painter, data_conversion::convert_disk_row, }; use super::{ - text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, - SortableTextTable, Widget, + text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, + Widget, }; pub struct DiskWidgetState { @@ -52,9 +55,9 @@ impl DiskState { } } -/// A table displaying disk data. Essentially a wrapper around a [`TextTable`]. +/// A table displaying disk data. pub struct DiskTable { - table: SortableTextTable, + table: TextTable, bounds: Rect, display_data: TextTableData, @@ -62,7 +65,7 @@ pub struct DiskTable { impl Default for DiskTable { fn default() -> Self { - let table = SortableTextTable::new(vec![ + let table = TextTable::new(vec![ SimpleSortableColumn::new_flex("Disk".into(), None, false, 0.2), SimpleSortableColumn::new_flex("Mount".into(), None, false, 0.2), SimpleSortableColumn::new_hard("Used".into(), None, false, Some(5)), @@ -115,7 +118,6 @@ impl Widget for DiskTable { .borders(Borders::ALL); self.table - .table .draw_tui_table(painter, f, &self.display_data, block, area, selected); } diff --git a/src/app/widgets/mem.rs b/src/app/widgets/mem.rs index 997c9850..437410e5 100644 --- a/src/app/widgets/mem.rs +++ b/src/app/widgets/mem.rs @@ -59,6 +59,7 @@ pub struct MemGraph { swap_labels: Option<(String, String)>, mem_data: Vec<(f64, f64)>, swap_data: Vec<(f64, f64)>, + bounds: Rect, } impl MemGraph { @@ -70,6 +71,7 @@ impl MemGraph { swap_labels: Default::default(), mem_data: Default::default(), swap_data: Default::default(), + bounds: Rect::default(), } } } @@ -84,11 +86,11 @@ impl Component for MemGraph { } fn bounds(&self) -> Rect { - self.graph.bounds() + self.bounds } fn set_bounds(&mut self, new_bounds: Rect) { - self.graph.set_bounds(new_bounds); + self.bounds = new_bounds; } } diff --git a/src/app/widgets/net.rs b/src/app/widgets/net.rs index 84d0d3cc..10a676e3 100644 --- a/src/app/widgets/net.rs +++ b/src/app/widgets/net.rs @@ -434,6 +434,8 @@ pub struct NetGraph { pub use_binary_prefix: bool, hide_legend: bool, + + bounds: Rect, } impl NetGraph { @@ -454,6 +456,7 @@ impl NetGraph { unit_type: app_config_fields.network_unit_type.clone(), use_binary_prefix: app_config_fields.network_use_binary_prefix, hide_legend: false, + bounds: Rect::default(), } } @@ -514,11 +517,11 @@ impl NetGraph { impl Component for NetGraph { fn bounds(&self) -> Rect { - self.graph.bounds() + self.bounds } fn set_bounds(&mut self, new_bounds: Rect) { - self.graph.set_bounds(new_bounds); + self.bounds = new_bounds; } fn handle_key_event( diff --git a/src/app/widgets/process.rs b/src/app/widgets/process.rs index 3ece0532..735616e1 100644 --- a/src/app/widgets/process.rs +++ b/src/app/widgets/process.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}; use float_ord::FloatOrd; @@ -799,6 +799,10 @@ impl ProcessSortColumn { } impl SortableColumn for ProcessSortColumn { + fn original_name(&self) -> &Cow<'static, str> { + self.sortable_column.original_name() + } + fn shortcut(&self) -> &Option<(KeyEvent, String)> { self.sortable_column.shortcut() } @@ -815,7 +819,7 @@ impl SortableColumn for ProcessSortColumn { self.sortable_column.set_sorting_status(sorting_status) } - fn display_name(&self) -> std::borrow::Cow<'static, str> { + fn display_name(&self) -> Cow<'static, str> { self.sortable_column.display_name() } @@ -1024,7 +1028,7 @@ impl Component for ProcessManager { fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult { match &event.kind { MouseEventKind::Down(MouseButton::Left) => { - if self.process_table.does_intersect_mouse(&event) { + if self.process_table.does_border_intersect_mouse(&event) { if let ProcessManagerSelection::Processes = self.selected { self.process_table.handle_mouse_event(event) } else { @@ -1037,7 +1041,7 @@ impl Component for ProcessManager { WidgetEventResult::Signal(s) => WidgetEventResult::Signal(s), } } - } else if self.sort_table.does_intersect_mouse(&event) { + } else if self.sort_table.does_border_intersect_mouse(&event) { if let ProcessManagerSelection::Sort = self.selected { self.sort_table.handle_mouse_event(event) } else { @@ -1045,7 +1049,7 @@ impl Component for ProcessManager { self.sort_table.handle_mouse_event(event); WidgetEventResult::Redraw } - } else if self.search_input.does_intersect_mouse(&event) { + } else if self.search_input.does_border_intersect_mouse(&event) { if let ProcessManagerSelection::Search = self.selected { self.search_input.handle_mouse_event(event) } else { diff --git a/src/app/widgets/temp.rs b/src/app/widgets/temp.rs index 6c07570d..3d648da8 100644 --- a/src/app/widgets/temp.rs +++ b/src/app/widgets/temp.rs @@ -18,8 +18,8 @@ use crate::{ }; use super::{ - text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, - SortableTextTable, Widget, + text_table::TextTableData, AppScrollWidgetState, CanvasTableWidthState, Component, TextTable, + Widget, }; pub struct TempWidgetState { @@ -55,9 +55,9 @@ impl TempState { } } -/// A table displaying disk data. Essentially a wrapper around a [`TextTable`]. +/// A table displaying disk data.. pub struct TempTable { - table: SortableTextTable, + table: TextTable, bounds: Rect, display_data: TextTableData, temp_type: TemperatureType, @@ -65,7 +65,7 @@ pub struct TempTable { impl Default for TempTable { fn default() -> Self { - let table = SortableTextTable::new(vec![ + let table = TextTable::new(vec![ SimpleSortableColumn::new_flex("Sensor".into(), None, false, 0.8), SimpleSortableColumn::new_hard("Temp".into(), None, false, Some(5)), ]) @@ -123,7 +123,6 @@ impl Widget for TempTable { .borders(Borders::ALL); // TODO: Also do the scrolling indicator! self.table - .table .draw_tui_table(painter, f, &self.display_data, block, area, selected); } diff --git a/src/bin/main.rs b/src/bin/main.rs index dcb3b63f..9b1edfbd 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -116,8 +116,11 @@ fn main() -> Result<()> { ist_clone.store(true, Ordering::SeqCst); })?; + // Paint once first. + try_drawing(&mut terminal, &mut app, &mut painter)?; + while !is_terminated.load(Ordering::SeqCst) { - if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) { + if let Ok(recv) = receiver.recv() { match app.handle_event(recv) { EventResult::Quit => { break; @@ -129,6 +132,8 @@ fn main() -> Result<()> { continue; } } + } else { + break; } } diff --git a/src/canvas.rs b/src/canvas.rs index 726ecf70..1725d82d 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -360,10 +360,16 @@ impl Painter { match layout_node { LayoutNode::Row(row) => { let split_area = Layout::default() + .margin(0) .direction(Direction::Horizontal) .constraints(row.constraints.clone()) .split(area); + // debug!( + // "Row - constraints: {:#?}, split_area: {:#?}", + // row.constraints, split_area + // ); + for (child, child_area) in node.children(arena).zip(split_area) { traverse_and_draw_tree( child, @@ -379,10 +385,16 @@ impl Painter { } LayoutNode::Col(col) => { let split_area = Layout::default() + .margin(0) .direction(Direction::Vertical) .constraints(col.constraints.clone()) .split(area); + // debug!( + // "Col - constraints: {:#?}, split_area: {:#?}", + // col.constraints, split_area + // ); + for (child, child_area) in node.children(arena).zip(split_area) { traverse_and_draw_tree( child, @@ -397,6 +409,8 @@ impl Painter { } } LayoutNode::Widget => { + // debug!("Widget - area: {:#?}", area); + if let Some(widget) = lookup_map.get_mut(&node) { widget.set_bounds(area); widget.draw(painter, f, area, selected_id == node); diff --git a/src/lib.rs b/src/lib.rs index 87db72f0..247db4d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ use crossterm::{ use app::{ data_harvester::{self, processes::ProcessSorting}, - event::WidgetEventResult, + event::EventResult, layout_manager::WidgetDirection, AppState, UsedWidgets, }; @@ -76,27 +76,27 @@ pub enum ThreadControlEvent { UpdateUpdateTime(u64), } -pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> WidgetEventResult { +pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> EventResult { match event.kind { MouseEventKind::Down(MouseButton::Left) => { app.on_left_mouse_up(event.column, event.row); - WidgetEventResult::Redraw + EventResult::Redraw } MouseEventKind::ScrollUp => { app.handle_scroll_up(); - WidgetEventResult::Redraw + EventResult::Redraw } MouseEventKind::ScrollDown => { app.handle_scroll_down(); - WidgetEventResult::Redraw + EventResult::Redraw } - _ => WidgetEventResult::NoRedraw, + _ => EventResult::NoRedraw, } } pub fn handle_key_event( event: KeyEvent, app: &mut AppState, reset_sender: &std::sync::mpsc::Sender, -) -> WidgetEventResult { +) -> EventResult { // debug!("KeyEvent: {:?}", event); // TODO: [PASTE] Note that this does NOT support some emojis like flags. This is due to us @@ -107,7 +107,7 @@ pub fn handle_key_event( if event.modifiers.is_empty() { // Required catch for searching - otherwise you couldn't search with q. if event.code == KeyCode::Char('q') && !app.is_in_search_widget() { - return WidgetEventResult::Quit; + return EventResult::Quit; } match event.code { KeyCode::End => app.skip_to_last(), @@ -129,7 +129,7 @@ pub fn handle_key_event( KeyCode::F(6) => app.toggle_sort(), KeyCode::F(9) => app.start_killing_process(), _ => { - return WidgetEventResult::NoRedraw; + return EventResult::NoRedraw; } } } else { @@ -145,7 +145,7 @@ pub fn handle_key_event( } } else if let KeyModifiers::CONTROL = event.modifiers { if event.code == KeyCode::Char('c') { - return WidgetEventResult::Quit; + return EventResult::Quit; } match event.code { @@ -172,7 +172,7 @@ pub fn handle_key_event( // are hard to iter while truncating last (eloquently). // KeyCode::Backspace => app.skip_word_backspace(), _ => { - return WidgetEventResult::NoRedraw; + return EventResult::NoRedraw; } } } else if let KeyModifiers::SHIFT = event.modifiers { @@ -183,13 +183,13 @@ pub fn handle_key_event( KeyCode::Down => app.move_widget_selection(&WidgetDirection::Down), KeyCode::Char(caught_char) => app.on_char_key(caught_char), _ => { - return WidgetEventResult::NoRedraw; + return EventResult::NoRedraw; } } } } - WidgetEventResult::Redraw + EventResult::Redraw } pub fn read_config(config_location: Option<&str>) -> error::Result> { @@ -474,7 +474,7 @@ fn update_final_process_list(_app: &mut AppState, _widget_id: u64) { // } } -fn sort_process_data( +fn _sort_process_data( to_sort_vec: &mut Vec, proc_widget_state: &app::ProcWidgetState, ) { to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());