bug: fix issues caused by having a width that is too small (#665)

Due to a missing check, you could resize the window to a width that was too small, and it would trigger an endless while-loop for any table while trying to redistribute remaining space. This has been rectified with an explicit check, as well as a smarter method of redistributing remaining space borrowed from the rewrite.

This also adds explicit width checks for widgets that have borders; if the width is <2, before, it would panic.

Note that the rewrite I have kinda fixes all these issues already, so I don't want to invest too hard into this, but this should be fine as a patch for now.

Also note that minimal heights don't seem to be causing any issues, it just seems to be minimal widths.
This commit is contained in:
Clement Tsang 2022-01-27 16:16:27 -08:00 committed by GitHub
parent 255b69c15f
commit 6c989785fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 136 deletions

View File

@ -19,7 +19,7 @@ path = "src/bin/main.rs"
doc = false
[lib]
test = false
test = true
doctest = false
doc = false

View File

@ -282,9 +282,6 @@ impl Painter {
self.styled_help_text = styled_help_spans.into_iter().map(Spans::from).collect();
}
// FIXME: [CONFIG] write this, should call painter init and any changed colour functions...
pub fn update_painter_colours(&mut self) {}
fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) {
f.render_widget(
Paragraph::new(Span::styled(
@ -559,44 +556,62 @@ impl Painter {
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(vertical_chunks[1]);
self.draw_basic_cpu(f, app_state, vertical_chunks[0], 1);
self.draw_basic_memory(f, app_state, middle_chunks[0], 2);
self.draw_basic_network(f, app_state, middle_chunks[1], 3);
if vertical_chunks[0].width >= 2 {
self.draw_basic_cpu(f, app_state, vertical_chunks[0], 1);
}
if middle_chunks[0].width >= 2 {
self.draw_basic_memory(f, app_state, middle_chunks[0], 2);
}
if middle_chunks[1].width >= 2 {
self.draw_basic_network(f, app_state, middle_chunks[1], 3);
}
let mut later_widget_id: Option<u64> = None;
if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
let widget_id = basic_table_widget_state.currently_displayed_widget_id;
later_widget_id = Some(widget_id);
match basic_table_widget_state.currently_displayed_widget_type {
Disk => {
self.draw_disk_table(f, app_state, vertical_chunks[3], false, widget_id)
}
Proc | ProcSort => {
let wid = widget_id
- match basic_table_widget_state.currently_displayed_widget_type {
ProcSearch => 1,
ProcSort => 2,
_ => 0,
};
self.draw_process_features(
if vertical_chunks[3].width >= 2 {
match basic_table_widget_state.currently_displayed_widget_type {
Disk => self.draw_disk_table(
f,
app_state,
vertical_chunks[3],
false,
wid,
);
widget_id,
),
Proc | ProcSort => {
let wid = widget_id
- match basic_table_widget_state.currently_displayed_widget_type
{
ProcSearch => 1,
ProcSort => 2,
_ => 0,
};
self.draw_process_features(
f,
app_state,
vertical_chunks[3],
false,
wid,
);
}
Temp => self.draw_temp_table(
f,
app_state,
vertical_chunks[3],
false,
widget_id,
),
Battery => self.draw_battery_display(
f,
app_state,
vertical_chunks[3],
false,
widget_id,
),
_ => {}
}
Temp => {
self.draw_temp_table(f, app_state, vertical_chunks[3], false, widget_id)
}
Battery => self.draw_battery_display(
f,
app_state,
vertical_chunks[3],
false,
widget_id,
),
_ => {}
}
}
@ -712,32 +727,34 @@ impl Painter {
) {
use BottomWidgetType::*;
for (widget, widget_draw_loc) in widgets.children.iter().zip(widget_draw_locs) {
match &widget.widget_type {
Empty => {}
Cpu => self.draw_cpu(f, app_state, *widget_draw_loc, widget.widget_id),
Mem => self.draw_memory_graph(f, app_state, *widget_draw_loc, widget.widget_id),
Net => self.draw_network(f, app_state, *widget_draw_loc, widget.widget_id),
Temp => {
self.draw_temp_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
if widget_draw_loc.width >= 2 && widget_draw_loc.height >= 2 {
match &widget.widget_type {
Empty => {}
Cpu => self.draw_cpu(f, app_state, *widget_draw_loc, widget.widget_id),
Mem => self.draw_memory_graph(f, app_state, *widget_draw_loc, widget.widget_id),
Net => self.draw_network(f, app_state, *widget_draw_loc, widget.widget_id),
Temp => {
self.draw_temp_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Disk => {
self.draw_disk_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Proc => self.draw_process_features(
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
Battery => self.draw_battery_display(
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
_ => {}
}
Disk => {
self.draw_disk_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Proc => self.draw_process_features(
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
Battery => self.draw_battery_display(
f,
app_state,
*widget_draw_loc,
true,
widget.widget_id,
),
_ => {}
}
}
}

View File

@ -35,94 +35,83 @@ pub fn get_column_widths(
"soft width max length != soft width desired length!"
);
let initial_width = total_width - 2;
let mut total_width_left = initial_width;
let mut column_widths: Vec<u16> = vec![0; hard_widths.len()];
let range: Vec<usize> = if left_to_right {
(0..hard_widths.len()).collect()
} else {
(0..hard_widths.len()).rev().collect()
};
if total_width > 2 {
let initial_width = total_width - 2;
let mut total_width_left = initial_width;
let mut column_widths: Vec<u16> = vec![0; hard_widths.len()];
let range: Vec<usize> = if left_to_right {
(0..hard_widths.len()).collect()
} else {
(0..hard_widths.len()).rev().collect()
};
for itx in &range {
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
// Hard width...
let space_taken = min(*hard_width, total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *hard_width > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
} else if let (
Some(Some(soft_width_max)),
Some(Some(soft_width_min)),
Some(Some(soft_width_desired)),
) = (
soft_widths_max.get(*itx),
soft_widths_min.get(*itx),
soft_widths_desired.get(*itx),
) {
// Soft width...
let soft_limit = max(
if soft_width_max.is_sign_negative() {
*soft_width_desired
} else {
(*soft_width_max * initial_width as f64).ceil() as u16
},
*soft_width_min,
);
let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *soft_width_min > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
}
}
// Redistribute remaining.
while total_width_left > 0 {
for itx in &range {
if column_widths[*itx] > 0 {
column_widths[*itx] += 1;
total_width_left -= 1;
if total_width_left == 0 {
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
// Hard width...
let space_taken = min(*hard_width, total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *hard_width > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
} else if let (
Some(Some(soft_width_max)),
Some(Some(soft_width_min)),
Some(Some(soft_width_desired)),
) = (
soft_widths_max.get(*itx),
soft_widths_min.get(*itx),
soft_widths_desired.get(*itx),
) {
// Soft width...
let soft_limit = max(
if soft_width_max.is_sign_negative() {
*soft_width_desired
} else {
(*soft_width_max * initial_width as f64).ceil() as u16
},
*soft_width_min,
);
let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *soft_width_min > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
}
}
while let Some(0) = column_widths.last() {
column_widths.pop();
}
if !column_widths.is_empty() {
// Redistribute remaining.
let amount_per_slot = total_width_left / column_widths.len() as u16;
total_width_left %= column_widths.len() as u16;
for (index, width) in column_widths.iter_mut().enumerate() {
if (index as u16) < total_width_left {
*width += amount_per_slot + 1;
} else {
*width += amount_per_slot;
}
}
}
column_widths
} else {
vec![]
}
let mut filtered_column_widths: Vec<u16> = vec![];
let mut still_seeing_zeros = true;
column_widths.iter().rev().for_each(|width| {
if still_seeing_zeros {
if *width != 0 {
still_seeing_zeros = false;
filtered_column_widths.push(*width);
}
} else {
filtered_column_widths.push(*width);
}
});
filtered_column_widths.reverse();
filtered_column_widths
}
/// FIXME: [command move] This is a greedy method of determining column widths. This is reserved for columns where we are okay with
/// shoving information as far right as required.
// pub fn greedy_get_column_widths() -> Vec<u16> {
// vec![]
// }
pub fn get_search_start_position(
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
current_cursor_position: usize, is_force_redraw: bool,
@ -216,3 +205,56 @@ pub fn interpolate_points(point_one: &(f64, f64), point_two: &(f64, f64), time:
(point_one.1 + (time - point_one.0) * slope).max(0.0)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_zero_width() {
assert_eq!(
get_column_widths(
0,
&[Some(1), None, None],
&[None, Some(1), Some(2)],
&[None, Some(0.125), Some(0.5)],
&[None, Some(10), Some(10)],
true
),
vec![],
"vector should be empty"
);
}
#[test]
fn test_two_width() {
assert_eq!(
get_column_widths(
2,
&[Some(1), None, None],
&[None, Some(1), Some(2)],
&[None, Some(0.125), Some(0.5)],
&[None, Some(10), Some(10)],
true
),
vec![],
"vector should be empty"
);
}
#[test]
fn test_non_zero_width() {
assert_eq!(
get_column_widths(
16,
&[Some(1), None, None],
&[None, Some(1), Some(2)],
&[None, Some(0.125), Some(0.5)],
&[None, Some(10), Some(10)],
true
),
vec![2, 2, 7],
"vector should not be empty"
);
}
}