bug: fix bug causing click bounds to fail

There were three bugs:

1. The click bounds calculation was incorrect. I did the silly mistake
   of checking for <= bounds for the bottom and right sections of a
   Rect when checking if the mouse intersected - this is WRONG.

   For example, let's say you want to calculate if an x value of 5 falls
   between something that starts at 0 and is 5 long.  It shouldn't,
   right?  Because it draws from 0 to 4?  But if you just did <=
   Rect.right(), you would get a hit - because it just does (start +
   width), so you get 5, and 5 <= 5!

   So, easy fix, change all far bounds checks to <.

2. The second bug is a mistake where I accidentally did not include
   bounds sets for my memory and net widgets. Instead, they set their
   bounds to the underlying graph representation, which is WRONG, since
   that bound gets updated on draw, and gets set to a slightly smaller
   rect due to borders!

3. A slightly sneakier one. This broke my bounds checks for the CPU
   widget - and it would have broken my process widget too.

   The problem lies in the concept of widgets that handle multiple
   "sub"-blocks internally, and how I was doing click detection
   internally - I would check if the bounds of the internal Components
   were hit.  Say, the CPU, I would check if the internal graph was hit,
   then if the internal table was hit.

   But wait! I said in point 2 that a graph gets its borders updated on
   draw to something slightly smaller, due to borders!  And there's the
   problem - it affected tables too.  I was setting the bounds of
   components to that of the *internal* representation - without borders
   - but my click detection *needed* borders included!

   Solution?  Add another trait function to check bordered bounds, and
   make the default implementation just check the existing bounds. For
   cases like internal Components that may need it, I add a separate
   implementation.

   I also switched over all border bounds checks for Widgets to that,
   since it's a bit more consistent.
This commit is contained in:
ClementTsang 2021-08-29 20:05:17 -04:00
parent 48c572dbaf
commit 3fa50605b3
18 changed files with 204 additions and 69 deletions

View File

@ -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 {

View File

@ -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,

View File

@ -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,
}

View File

@ -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()
}
}

View File

@ -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!

View File

@ -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<S>,
/// A corresponding "sort" menu.
pub sort_menu: TextTable,
}
impl<S> SortableTextTable<S>
@ -244,9 +268,15 @@ where
{
/// Creates a new [`SortableTextTable`]. Note that `columns` cannot be empty.
pub fn new(columns: Vec<S>) -> Self {
let sort_menu_columns = columns
.iter()
.map(|column| SimpleColumn::new_hard(column.original_name().clone(), None))
.collect::<Vec<_>>();
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<B: Backend>(
&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<B: Backend>(
&mut self, painter: &Painter, f: &mut Frame<'_, B>, block: Block<'_>, block_area: Rect,
) {
}
}
impl<S> Component for SortableTextTable<S>
@ -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)
}
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<SimpleSortableColumn>,
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()];

View File

@ -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<SimpleSortableColumn>,
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);
}

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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 {

View File

@ -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<SimpleSortableColumn>,
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);
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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<ThreadControlEvent>,
) -> 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<Option<PathBuf>> {
@ -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<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
) {
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());