summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2022-11-26 05:00:38 -0500
committerGitHub <noreply@github.com>2022-11-26 05:00:38 -0500
commit8338a307523681c603efdcdda9cf9fe4866bd0b5 (patch)
tree2c8e6d532bd302398e0a03937966eb4698fbc69a /src
parent541867e5af1e39a0e3734f2bafef8721fbd24b96 (diff)
bug: fix possible gaps with widget layout spacing (#916)
Fixes an occasional gap appearing due to how rect spacing was previously calculated.
Diffstat (limited to 'src')
-rw-r--r--src/canvas.rs260
1 files changed, 189 insertions, 71 deletions
diff --git a/src/canvas.rs b/src/canvas.rs
index c7508802..55534e48 100644
--- a/src/canvas.rs
+++ b/src/canvas.rs
@@ -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(