bug: fix possible gaps with widget layout spacing (#916)

Fixes an occasional gap appearing due to how rect spacing was previously calculated.
This commit is contained in:
Clement Tsang 2022-11-26 05:00:38 -05:00 committed by GitHub
parent 541867e5af
commit 8338a30752
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 190 additions and 71 deletions

View file

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#717](https://github.com/ClementTsang/bottom/pull/717): Fix clicking on empty space in tables selecting the very last entry of a list in some cases.
- [#805](https://github.com/ClementTsang/bottom/pull/805): Fix bottom keeping devices awake in certain scenarios.
- [#825](https://github.com/ClementTsang/bottom/pull/825): Use alternative method of getting parent PID in some cases on macOS devices to avoid needing root access.
- [#916](https://github.com/ClementTsang/bottom/pull/916): Fix possible gaps with widget layout spacing.
## Changes

View file

@ -66,14 +66,21 @@ pub struct Painter {
is_mac_os: bool, // TODO: This feels out of place...
// TODO: Redo this entire thing.
row_constraints: Vec<Constraint>,
col_constraints: Vec<Vec<Constraint>>,
col_row_constraints: Vec<Vec<Vec<Constraint>>>,
layout_constraints: Vec<Vec<Vec<Vec<Constraint>>>>,
row_constraints: Vec<LayoutConstraint>,
col_constraints: Vec<Vec<LayoutConstraint>>,
col_row_constraints: Vec<Vec<Vec<LayoutConstraint>>>,
layout_constraints: Vec<Vec<Vec<Vec<LayoutConstraint>>>>,
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
widget_layout: BottomLayout,
}
// Part of a temporary fix for https://github.com/ClementTsang/bottom/issues/896
enum LayoutConstraint {
CanvasHandled,
Grow,
Ratio(u32, u32),
}
impl Painter {
pub fn init(widget_layout: BottomLayout, colours: CanvasColours) -> anyhow::Result<Self> {
// Now for modularity; we have to also initialize the base layouts!
@ -87,9 +94,9 @@ impl Painter {
widget_layout.rows.iter().for_each(|row| {
if row.canvas_handle_height {
row_constraints.push(Constraint::Length(0));
row_constraints.push(LayoutConstraint::CanvasHandled);
} else {
row_constraints.push(Constraint::Ratio(
row_constraints.push(LayoutConstraint::Ratio(
row.row_height_ratio,
widget_layout.total_row_height_ratio,
));
@ -100,21 +107,23 @@ impl Painter {
let mut new_col_row_constraints = Vec::new();
row.children.iter().for_each(|col| {
if col.canvas_handle_width {
new_col_constraints.push(Constraint::Length(0));
new_col_constraints.push(LayoutConstraint::CanvasHandled);
} else {
new_col_constraints
.push(Constraint::Ratio(col.col_width_ratio, row.total_col_ratio));
new_col_constraints.push(LayoutConstraint::Ratio(
col.col_width_ratio,
row.total_col_ratio,
));
}
let mut new_new_col_row_constraints = Vec::new();
let mut new_new_widget_constraints = Vec::new();
col.children.iter().for_each(|col_row| {
if col_row.canvas_handle_height {
new_new_col_row_constraints.push(Constraint::Length(0));
new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled);
} else if col_row.flex_grow {
new_new_col_row_constraints.push(Constraint::Min(0));
new_new_col_row_constraints.push(LayoutConstraint::Grow);
} else {
new_new_col_row_constraints.push(Constraint::Ratio(
new_new_col_row_constraints.push(LayoutConstraint::Ratio(
col_row.col_row_height_ratio,
col.total_col_row_ratio,
));
@ -123,11 +132,11 @@ impl Painter {
let mut new_new_new_widget_constraints = Vec::new();
col_row.children.iter().for_each(|widget| {
if widget.canvas_handle_width {
new_new_new_widget_constraints.push(Constraint::Length(0));
new_new_new_widget_constraints.push(LayoutConstraint::CanvasHandled);
} else if widget.flex_grow {
new_new_new_widget_constraints.push(Constraint::Min(0));
new_new_new_widget_constraints.push(LayoutConstraint::Grow);
} else {
new_new_new_widget_constraints.push(Constraint::Ratio(
new_new_new_widget_constraints.push(LayoutConstraint::Ratio(
widget.width_ratio,
col_row.total_widget_ratio,
));
@ -292,12 +301,6 @@ impl Painter {
self.draw_help_dialog(f, app_state, middle_dialog_chunk[1]);
} else if app_state.delete_dialog_state.is_showing_dd {
// TODO: This needs the paragraph wrap feature from tui-rs to be pushed to complete... but for now it's pretty close!
// The main problem right now is that I cannot properly calculate the height offset since
// line-wrapping is NOT the same as taking the width of the text and dividing by width.
// So, I need the height AFTER wrapping.
// See: https://github.com/fdehau/tui-rs/pull/349. Land this after this pushes to release.
let dd_text = self.get_dd_spans(app_state);
let text_width = if terminal_width < 100 {
@ -314,37 +317,6 @@ impl Painter {
22
};
// let (text_width, text_height) = if let Some(dd_text) = &dd_text {
// let width = if current_width < 100 {
// current_width * 90 / 100
// } else {
// let min_possible_width = (current_width * 50 / 100) as usize;
// let mut width = dd_text.width();
// // This should theoretically never allow width to be 0... we can be safe and do an extra check though.
// while width > (current_width as usize) && width / 2 > min_possible_width {
// width /= 2;
// }
// std::cmp::max(width, min_possible_width) as u16
// };
// (
// width,
// (dd_text.height() + 2 + (dd_text.width() / width as usize)) as u16,
// )
// } else {
// // AFAIK this shouldn't happen, unless something went wrong...
// (
// if current_width < 100 {
// current_width * 90 / 100
// } else {
// current_width * 50 / 100
// },
// 7,
// )
// };
let vertical_bordering = terminal_height.saturating_sub(text_height) / 2;
let vertical_dialog_chunk = Layout::default()
.direction(Direction::Vertical)
@ -550,11 +522,160 @@ impl Painter {
}
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
let draw_locs = Layout::default()
.margin(0)
.constraints(self.row_constraints.as_slice())
.direction(Direction::Vertical)
.split(terminal_size);
fn get_constraints(
direction: Direction, constraints: &[LayoutConstraint], area: Rect,
) -> Vec<Rect> {
// Order of operations:
// - Ratios first + canvas-handled (which is just zero)
// - Then any flex-grows to take up remaining space; divide amongst remaining
// hand out any remaining space
#[derive(Debug, Default, Clone, Copy)]
struct Size {
width: u16,
height: u16,
}
impl Size {
fn shrink_width(&mut self, amount: u16) {
self.width -= amount;
}
fn shrink_height(&mut self, amount: u16) {
self.height -= amount;
}
}
let mut bounds = Size {
width: area.width,
height: area.height,
};
let mut sizes = vec![Size::default(); constraints.len()];
let mut grow = vec![];
let mut num_non_ch = 0;
for (itx, (constraint, size)) in
constraints.iter().zip(sizes.iter_mut()).enumerate()
{
match constraint {
LayoutConstraint::Ratio(a, b) => {
match direction {
Direction::Horizontal => {
let amount =
(((area.width as u32) * (*a)) / (*b)) as u16;
bounds.shrink_width(amount);
size.width = amount;
size.height = area.height;
}
Direction::Vertical => {
let amount =
(((area.height as u32) * (*a)) / (*b)) as u16;
bounds.shrink_height(amount);
size.width = area.width;
size.height = amount;
}
}
num_non_ch += 1;
}
LayoutConstraint::Grow => {
// Mark it as grow in the vector and handle in second pass.
grow.push(itx);
num_non_ch += 1;
}
LayoutConstraint::CanvasHandled => {
// Do nothing in this case. It's already 0.
}
}
}
if !grow.is_empty() {
match direction {
Direction::Horizontal => {
let width = bounds.width / grow.len() as u16;
bounds.shrink_width(width * grow.len() as u16);
for g in grow {
sizes[g] = Size {
width,
height: area.height,
};
}
}
Direction::Vertical => {
let height = bounds.height / grow.len() as u16;
bounds.shrink_height(height * grow.len() as u16);
for g in grow {
sizes[g] = Size {
width: area.width,
height,
};
}
}
}
}
if num_non_ch > 0 {
match direction {
Direction::Horizontal => {
let per_item = bounds.width / num_non_ch;
let mut remaining_width = bounds.width % num_non_ch;
for (size, constraint) in sizes.iter_mut().zip(constraints) {
match constraint {
LayoutConstraint::CanvasHandled => {}
LayoutConstraint::Grow
| LayoutConstraint::Ratio(_, _) => {
if remaining_width > 0 {
size.width += per_item + 1;
remaining_width -= 1;
} else {
size.width += per_item;
}
}
}
}
}
Direction::Vertical => {
let per_item = bounds.height / num_non_ch;
let mut remaining_height = bounds.height % num_non_ch;
for (size, constraint) in sizes.iter_mut().zip(constraints) {
match constraint {
LayoutConstraint::CanvasHandled => {}
LayoutConstraint::Grow
| LayoutConstraint::Ratio(_, _) => {
if remaining_height > 0 {
size.height += per_item + 1;
remaining_height -= 1;
} else {
size.height += per_item;
}
}
}
}
}
}
}
let mut curr_x = area.x;
let mut curr_y = area.y;
sizes
.into_iter()
.map(|size| {
let rect = Rect::new(curr_x, curr_y, size.width, size.height);
match direction {
Direction::Horizontal => {
curr_x += size.width;
}
Direction::Vertical => {
curr_y += size.height;
}
}
rect
})
.collect()
}
let draw_locs =
get_constraints(Direction::Vertical, &self.row_constraints, terminal_size);
self.derived_widget_draw_locs = izip!(
draw_locs,
@ -572,31 +693,28 @@ impl Painter {
cols,
)| {
izip!(
Layout::default()
.constraints(col_constraint.as_slice())
.direction(Direction::Horizontal)
.split(draw_loc)
.into_iter(),
get_constraints(Direction::Horizontal, col_constraint, draw_loc),
col_row_constraint,
row_constraint_vec,
&cols.children
)
.map(|(split_loc, constraint, col_constraint_vec, col_rows)| {
izip!(
Layout::default()
.constraints(constraint.as_slice())
.direction(Direction::Vertical)
.split(split_loc)
.into_iter(),
get_constraints(
Direction::Vertical,
constraint.as_slice(),
split_loc
),
col_constraint_vec,
&col_rows.children
)
.map(|(draw_loc, col_row_constraint_vec, widgets)| {
// Note that col_row_constraint_vec CONTAINS the widget constraints
let widget_draw_locs = Layout::default()
.constraints(col_row_constraint_vec.as_slice())
.direction(Direction::Horizontal)
.split(draw_loc);
let widget_draw_locs = get_constraints(
Direction::Horizontal,
col_row_constraint_vec.as_slice(),
draw_loc,
);
// Side effect, draw here.
self.draw_widgets_with_constraints(