Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rewrite column algorithm #227

Merged
merged 12 commits into from
Sep 10, 2020
Prev Previous commit
Next Next commit
Switch to yet another system...
  • Loading branch information
ClementTsang committed Sep 9, 2020
commit 2158ad1ba24b62593e4c255ba9e8d888dcb1d1f2
1 change: 1 addition & 0 deletions src/app/data_farmer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ impl DataCollection {
}

fn eat_network(&mut self, network: &network::NetworkHarvest, new_entry: &mut TimedData) {
// FIXME [NETWORKING; CONFIG]: The ability to config this?
// FIXME [NETWORKING]: Support bits, support switching between decimal and binary units (move the log part to conversion and switch on the fly)
// RX
new_entry.rx_data = if network.rx > 0 {
Expand Down
118 changes: 66 additions & 52 deletions src/canvas/drawing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,91 @@ use crate::app;
use std::cmp::min;

/// Return a (hard)-width vector for column widths.
/// * `total_width` is how much width we have to work with overall.
/// * `desired_widths` is the width that is *desired*, but may not be reached.
/// * `max_widths` is the maximal percentage we allow a column to take in the table. If it is
/// negative, we assume it can take whatever size it wants.
/// * `min_widths` is the minimal hard width we allow a column to take in the table. If it is
/// smaller than this, we just don't use it.
/// * `column_bias` is how we determine which columns are more important. A higher value on a
/// column means it is more important.
/// * `spare_bias` is how we determine which column gets spare space first. Again, higher value
/// means it is more important.
///
/// * `total_width` is the, well, total width available. **NOTE:** This function automatically
/// takes away 2 from the width as part of the left/right
/// bounds.
/// * `hard_widths` is inflexible column widths. Use a `None` to represent a soft width.
/// * `soft_widths_min` is the lower limit for a soft width. Use `None` if a hard width goes there.
/// * `soft_widths_max` is the upper limit for a soft width, in percentage of the total width. Use
/// `None` if a hard width goes there.
/// * `soft_widths_desired` is the desired soft width. Use `None` if a hard width goes there.
/// * `spacing_priority` is the order we should prioritize columns in terms of the index. Indices
/// that come earlier in this list go first.
///
/// **NOTE:** This function ASSUMES THAT ALL PASSED SLICES ARE OF THE SAME SIZE.
///
/// **NOTE:** The returned vector may not be the same size as the slices, this is because including
/// 0-constraints breaks tui-rs.
///
/// **NOTE:** This function automatically takes away 2 from the width as part of the left/right
/// bounds.
pub fn get_column_widths(
total_width: u16, desired_widths: &[u16], max_widths: Option<&[f64]>,
min_widths: Option<&[u16]>, column_bias: &[usize], spare_bias: &[usize],
total_width: u16, hard_widths: &[Option<u16>], soft_widths_min: &[Option<u16>],
soft_widths_max: &[Option<f64>], soft_widths_desired: &[Option<u16>],
spacing_priority: &[usize],
) -> Vec<u16> {
let mut total_width_left = total_width.saturating_sub(desired_widths.len() as u16) + 1 - 2;
let mut column_widths: Vec<u16> = vec![0; desired_widths.len()];

// Let's sort out our bias into a sorted list (reverse to get descending order).
let mut bias_list = column_bias.iter().enumerate().collect::<Vec<_>>();
bias_list.sort_by(|a, b| a.1.cmp(b.1));
bias_list.reverse();
let initial_width = total_width.saturating_sub(hard_widths.len() as u16) + 1 - 2;
let mut total_width_left = initial_width;
let mut column_widths: Vec<u16> = vec![0; hard_widths.len()];

// Now, let's do a first pass.
for itx in column_bias {
let itx = *itx;
let desired_width = if let Some(width_thresholds) = max_widths {
if width_thresholds[itx].is_sign_negative() {
desired_widths[itx]
} else {
min(
desired_widths[itx],
(width_thresholds[itx] * total_width as f64).ceil() as u16,
)
for itx in spacing_priority {
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
// Hard width...
let space_taken = min(total_width_left, *hard_width);
if space_taken >= *hard_width {
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
}
} else {
desired_widths[itx]
};
let remaining_width = min(total_width_left, desired_width);
if let Some(min_widths) = min_widths {
if remaining_width >= min_widths[itx] {
column_widths[itx] = remaining_width;
total_width_left -= remaining_width;
} 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 = if soft_width_max.is_sign_negative() {
*soft_width_desired
} else {
(*soft_width_max * initial_width as f64).ceil() as u16
};
let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left);
if space_taken >= *soft_width_min {
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
}
} else {
column_widths[itx] = remaining_width;
total_width_left -= remaining_width;
}
}

// Second pass to fill in gaps and spaces
// Redistribute remaining; this goes between flex evenly.
while total_width_left > 0 {
for itx in spare_bias {
if column_widths[*itx] > 0 {
column_widths[*itx] += 1;
total_width_left -= 1;
if total_width_left == 0 {
break;
for itx in spacing_priority {
if let Some(col) = hard_widths.get(*itx) {
if col.is_none() && column_widths[*itx] > 0 {
column_widths[*itx] += 1;
total_width_left -= 1;
if total_width_left == 0 {
break;
}
}
}
}
}

column_widths.into_iter().filter(|x| *x > 0).collect()
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
}

pub fn get_search_start_position(
Expand Down
8 changes: 4 additions & 4 deletions src/canvas/widgets/cpu_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,10 @@ impl CpuGraphWidget for Painter {
if recalculate_column_widths {
cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&[6, 4],
Some(&[0.5, 0.5]),
None,
&[1, 0],
&[None, None],
&[Some(3), Some(4)],
&[Some(0.5), Some(0.5)],
&[Some(6), Some(4)],
&[1, 0],
);
}
Expand Down
18 changes: 13 additions & 5 deletions src/canvas/widgets/disk_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,19 @@ impl DiskTableWidget for Painter {
};
disk_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&disk_widget_state.table_width_state.desired_column_widths,
Some(&[0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13]),
Some(&[4, 5, 3, 4, 2, 2, 2]),
&[4, 3, 2, 1, 0, 5, 6],
&[4, 3, 2, 1, 0, 5, 6],
&[None, None, Some(4), Some(6), Some(6), Some(7), Some(7)],
&(DISK_HEADERS_LENS
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
&[Some(0.2), Some(0.2), None, None, None, None, None],
&(disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
&[0, 1, 2, 3, 4, 5, 6],
);
}

Expand Down
13 changes: 8 additions & 5 deletions src/canvas/widgets/network_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,14 @@ impl NetworkGraphWidget for Painter {
// Calculate widths
let intrinsic_widths = get_column_widths(
draw_loc.width,
&NETWORK_HEADERS_LENS,
Some(&[0.25, 0.25, 0.25, 0.25]),
None,
&[3, 2, 1, 0],
&[3, 2, 1, 0],
&[None, None, None, None],
&[Some(6); 4],
&[Some(0.25); 4],
&(NETWORK_HEADERS_LENS
.iter()
.map(|s| Some(*s))
.collect::<Vec<_>>()),
&[0, 1, 2, 3],
);

// Draw
Expand Down
65 changes: 49 additions & 16 deletions src/canvas/widgets/process_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ impl ProcessTableWidget for Painter {
.iter()
.map(|entry| std::cmp::max(entry.len(), 5) as u16)
.collect::<Vec<_>>();
let min_width_ratios = column_widths.clone();

proc_widget_state.table_width_state.desired_column_widths = {
for (row, _disabled) in processed_sliced_vec.clone() {
Expand All @@ -245,16 +244,41 @@ impl ProcessTableWidget for Painter {
column_widths
};

let max_width_ratios = if proc_widget_state.is_grouped {
let soft_widths_max = if proc_widget_state.is_grouped {
if proc_widget_state.is_using_command {
vec![0.25, 0.6, 0.25, 0.25, 0.2, 0.2, 0.2, 0.2]
vec![None, Some(0.6), None, None, None, None, None, None]
} else {
vec![0.25, 0.4, 0.25, 0.25, 0.2, 0.2, 0.2, 0.2]
vec![None, Some(0.4), None, None, None, None, None, None]
}
} else if proc_widget_state.is_using_command {
vec![0.25, 0.6, 0.25, 0.25, 0.2, 0.2, 0.2, 0.2, 0.2]
vec![
None,
Some(0.6),
None,
None,
None,
None,
None,
None,
Some(0.2),
]
} else {
vec![0.25, 0.4, 0.25, 0.25, 0.2, 0.2, 0.2, 0.2, 0.2]
vec![
None,
Some(0.4),
None,
None,
None,
None,
None,
None,
Some(0.2),
]
};
let soft_widths_min = if proc_widget_state.is_grouped {
vec![None, Some(8), None, None, None, None, None, None]
} else {
vec![None, Some(8), None, None, None, None, None, None, Some(5)]
};

let column_bias = if proc_widget_state.is_grouped {
Expand All @@ -263,20 +287,29 @@ impl ProcessTableWidget for Painter {
vec![0, 2, 3, 1, 4, 5, 6, 7, 8]
};

let space_bias = if proc_widget_state.is_grouped {
vec![5, 4, 7, 6, 1, 3, 2, 0]
} else {
vec![5, 4, 8, 7, 6, 1, 3, 2, 0]
};

proc_widget_state.table_width_state.calculated_column_widths =
get_column_widths(
draw_loc.width,
&proc_widget_state.table_width_state.desired_column_widths,
Some(&max_width_ratios),
Some(&min_width_ratios),
&[
Some(7),
None,
Some(8),
Some(8),
Some(8),
Some(8),
Some(7),
Some(8),
None,
],
&soft_widths_min,
&soft_widths_max,
&(proc_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&column_bias,
&space_bias,
);
}

Expand Down
16 changes: 12 additions & 4 deletions src/canvas/widgets/temp_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,19 @@ impl TempTableWidget for Painter {
};
temp_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&temp_widget_state.table_width_state.desired_column_widths,
Some(&[0.80, -1.0]),
None,
&[None, None],
&(TEMP_HEADERS_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.80), Some(-1.0)],
&temp_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>(),
&[1, 0],
&[0, 1],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/data_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ pub enum ProcessNamingType {
pub fn convert_process_data(
current_data: &data_farmer::DataCollection,
) -> Vec<ConvertedProcessData> {
// FIXME: Thread highlighting and hiding support
// TODO [THREAD]: Thread highlighting and hiding support
// For macOS see https://github.com/hishamhm/htop/pull/848/files

current_data
Expand Down