mirror of
https://github.com/ClementTsang/bottom
synced 2025-02-16 21:28:26 +00:00
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:
parent
541867e5af
commit
8338a30752
2 changed files with 190 additions and 71 deletions
|
@ -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.
|
- [#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.
|
- [#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.
|
- [#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
|
## Changes
|
||||||
|
|
||||||
|
|
260
src/canvas.rs
260
src/canvas.rs
|
@ -66,14 +66,21 @@ pub struct Painter {
|
||||||
is_mac_os: bool, // TODO: This feels out of place...
|
is_mac_os: bool, // TODO: This feels out of place...
|
||||||
|
|
||||||
// TODO: Redo this entire thing.
|
// TODO: Redo this entire thing.
|
||||||
row_constraints: Vec<Constraint>,
|
row_constraints: Vec<LayoutConstraint>,
|
||||||
col_constraints: Vec<Vec<Constraint>>,
|
col_constraints: Vec<Vec<LayoutConstraint>>,
|
||||||
col_row_constraints: Vec<Vec<Vec<Constraint>>>,
|
col_row_constraints: Vec<Vec<Vec<LayoutConstraint>>>,
|
||||||
layout_constraints: Vec<Vec<Vec<Vec<Constraint>>>>,
|
layout_constraints: Vec<Vec<Vec<Vec<LayoutConstraint>>>>,
|
||||||
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
|
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
|
||||||
widget_layout: BottomLayout,
|
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 {
|
impl Painter {
|
||||||
pub fn init(widget_layout: BottomLayout, colours: CanvasColours) -> anyhow::Result<Self> {
|
pub fn init(widget_layout: BottomLayout, colours: CanvasColours) -> anyhow::Result<Self> {
|
||||||
// Now for modularity; we have to also initialize the base layouts!
|
// Now for modularity; we have to also initialize the base layouts!
|
||||||
|
@ -87,9 +94,9 @@ impl Painter {
|
||||||
|
|
||||||
widget_layout.rows.iter().for_each(|row| {
|
widget_layout.rows.iter().for_each(|row| {
|
||||||
if row.canvas_handle_height {
|
if row.canvas_handle_height {
|
||||||
row_constraints.push(Constraint::Length(0));
|
row_constraints.push(LayoutConstraint::CanvasHandled);
|
||||||
} else {
|
} else {
|
||||||
row_constraints.push(Constraint::Ratio(
|
row_constraints.push(LayoutConstraint::Ratio(
|
||||||
row.row_height_ratio,
|
row.row_height_ratio,
|
||||||
widget_layout.total_row_height_ratio,
|
widget_layout.total_row_height_ratio,
|
||||||
));
|
));
|
||||||
|
@ -100,21 +107,23 @@ impl Painter {
|
||||||
let mut new_col_row_constraints = Vec::new();
|
let mut new_col_row_constraints = Vec::new();
|
||||||
row.children.iter().for_each(|col| {
|
row.children.iter().for_each(|col| {
|
||||||
if col.canvas_handle_width {
|
if col.canvas_handle_width {
|
||||||
new_col_constraints.push(Constraint::Length(0));
|
new_col_constraints.push(LayoutConstraint::CanvasHandled);
|
||||||
} else {
|
} else {
|
||||||
new_col_constraints
|
new_col_constraints.push(LayoutConstraint::Ratio(
|
||||||
.push(Constraint::Ratio(col.col_width_ratio, row.total_col_ratio));
|
col.col_width_ratio,
|
||||||
|
row.total_col_ratio,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_new_col_row_constraints = Vec::new();
|
let mut new_new_col_row_constraints = Vec::new();
|
||||||
let mut new_new_widget_constraints = Vec::new();
|
let mut new_new_widget_constraints = Vec::new();
|
||||||
col.children.iter().for_each(|col_row| {
|
col.children.iter().for_each(|col_row| {
|
||||||
if col_row.canvas_handle_height {
|
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 {
|
} else if col_row.flex_grow {
|
||||||
new_new_col_row_constraints.push(Constraint::Min(0));
|
new_new_col_row_constraints.push(LayoutConstraint::Grow);
|
||||||
} else {
|
} else {
|
||||||
new_new_col_row_constraints.push(Constraint::Ratio(
|
new_new_col_row_constraints.push(LayoutConstraint::Ratio(
|
||||||
col_row.col_row_height_ratio,
|
col_row.col_row_height_ratio,
|
||||||
col.total_col_row_ratio,
|
col.total_col_row_ratio,
|
||||||
));
|
));
|
||||||
|
@ -123,11 +132,11 @@ impl Painter {
|
||||||
let mut new_new_new_widget_constraints = Vec::new();
|
let mut new_new_new_widget_constraints = Vec::new();
|
||||||
col_row.children.iter().for_each(|widget| {
|
col_row.children.iter().for_each(|widget| {
|
||||||
if widget.canvas_handle_width {
|
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 {
|
} else if widget.flex_grow {
|
||||||
new_new_new_widget_constraints.push(Constraint::Min(0));
|
new_new_new_widget_constraints.push(LayoutConstraint::Grow);
|
||||||
} else {
|
} else {
|
||||||
new_new_new_widget_constraints.push(Constraint::Ratio(
|
new_new_new_widget_constraints.push(LayoutConstraint::Ratio(
|
||||||
widget.width_ratio,
|
widget.width_ratio,
|
||||||
col_row.total_widget_ratio,
|
col_row.total_widget_ratio,
|
||||||
));
|
));
|
||||||
|
@ -292,12 +301,6 @@ impl Painter {
|
||||||
|
|
||||||
self.draw_help_dialog(f, app_state, middle_dialog_chunk[1]);
|
self.draw_help_dialog(f, app_state, middle_dialog_chunk[1]);
|
||||||
} else if app_state.delete_dialog_state.is_showing_dd {
|
} 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 dd_text = self.get_dd_spans(app_state);
|
||||||
|
|
||||||
let text_width = if terminal_width < 100 {
|
let text_width = if terminal_width < 100 {
|
||||||
|
@ -314,37 +317,6 @@ impl Painter {
|
||||||
22
|
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_bordering = terminal_height.saturating_sub(text_height) / 2;
|
||||||
let vertical_dialog_chunk = Layout::default()
|
let vertical_dialog_chunk = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
|
@ -550,11 +522,160 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
|
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
|
||||||
let draw_locs = Layout::default()
|
fn get_constraints(
|
||||||
.margin(0)
|
direction: Direction, constraints: &[LayoutConstraint], area: Rect,
|
||||||
.constraints(self.row_constraints.as_slice())
|
) -> Vec<Rect> {
|
||||||
.direction(Direction::Vertical)
|
// Order of operations:
|
||||||
.split(terminal_size);
|
// - 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!(
|
self.derived_widget_draw_locs = izip!(
|
||||||
draw_locs,
|
draw_locs,
|
||||||
|
@ -572,31 +693,28 @@ impl Painter {
|
||||||
cols,
|
cols,
|
||||||
)| {
|
)| {
|
||||||
izip!(
|
izip!(
|
||||||
Layout::default()
|
get_constraints(Direction::Horizontal, col_constraint, draw_loc),
|
||||||
.constraints(col_constraint.as_slice())
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.split(draw_loc)
|
|
||||||
.into_iter(),
|
|
||||||
col_row_constraint,
|
col_row_constraint,
|
||||||
row_constraint_vec,
|
row_constraint_vec,
|
||||||
&cols.children
|
&cols.children
|
||||||
)
|
)
|
||||||
.map(|(split_loc, constraint, col_constraint_vec, col_rows)| {
|
.map(|(split_loc, constraint, col_constraint_vec, col_rows)| {
|
||||||
izip!(
|
izip!(
|
||||||
Layout::default()
|
get_constraints(
|
||||||
.constraints(constraint.as_slice())
|
Direction::Vertical,
|
||||||
.direction(Direction::Vertical)
|
constraint.as_slice(),
|
||||||
.split(split_loc)
|
split_loc
|
||||||
.into_iter(),
|
),
|
||||||
col_constraint_vec,
|
col_constraint_vec,
|
||||||
&col_rows.children
|
&col_rows.children
|
||||||
)
|
)
|
||||||
.map(|(draw_loc, col_row_constraint_vec, widgets)| {
|
.map(|(draw_loc, col_row_constraint_vec, widgets)| {
|
||||||
// Note that col_row_constraint_vec CONTAINS the widget constraints
|
// Note that col_row_constraint_vec CONTAINS the widget constraints
|
||||||
let widget_draw_locs = Layout::default()
|
let widget_draw_locs = get_constraints(
|
||||||
.constraints(col_row_constraint_vec.as_slice())
|
Direction::Horizontal,
|
||||||
.direction(Direction::Horizontal)
|
col_row_constraint_vec.as_slice(),
|
||||||
.split(draw_loc);
|
draw_loc,
|
||||||
|
);
|
||||||
|
|
||||||
// Side effect, draw here.
|
// Side effect, draw here.
|
||||||
self.draw_widgets_with_constraints(
|
self.draw_widgets_with_constraints(
|
||||||
|
|
Loading…
Add table
Reference in a new issue