diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 87a9830e..ce14d02d 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1,21 +1,40 @@ use std::collections::BTreeMap; +use tui::layout::Constraint; + use crate::{constants::DEFAULT_WIDGET_ID, options::OptionError}; +// Represents a start and end coordinate in some dimension. +type LineSegment = (u16, u16); + +type WidgetMappings = (u16, BTreeMap); +type ColumnRowMappings = (u16, BTreeMap); +type ColumnMappings = (u16, BTreeMap); + /// Represents a more usable representation of the layout, derived from the /// config. +/// +/// FIXME: This is kinda gross. Ideally optimize out the hard-coded stuff. #[derive(Clone, Debug)] pub struct BottomLayout { pub rows: Vec, - pub total_row_height_ratio: u32, + pub total_row_height_ratio: u16, } -// Represents a start and end coordinate in some dimension. -type LineSegment = (u32, u32); +trait Ratio { + fn ratio(&self) -> u16; +} -type WidgetMappings = (u32, BTreeMap); -type ColumnRowMappings = (u32, BTreeMap); -type ColumnMappings = (u32, BTreeMap); +impl Ratio for Constraint { + fn ratio(&self) -> u16 { + match self { + Constraint::Min(min) => std::cmp::max(*min, 1), + Constraint::Length(_) => 1, + Constraint::Fill(scaling) => *scaling, + _ => unreachable!("if this gets hit then you're refactoring layouts"), + } + } +} impl BottomLayout { pub fn get_movement_mappings(&mut self) { @@ -27,7 +46,7 @@ impl BottomLayout { || a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1 } - fn get_distance(target: LineSegment, candidate: LineSegment) -> u32 { + fn get_distance(target: LineSegment, candidate: LineSegment) -> u16 { if candidate.0 < target.0 { candidate.1 - target.0 } else if candidate.1 < target.1 { @@ -39,7 +58,6 @@ impl BottomLayout { // Now we need to create the correct mapping for moving from a specific // widget to another - let mut layout_mapping: BTreeMap = BTreeMap::new(); let mut total_height = 0; for row in &self.rows { @@ -56,6 +74,10 @@ impl BottomLayout { let mut col_row_mapping: BTreeMap = BTreeMap::new(); let mut is_valid_col_row = false; for widget in &col_row.children { + let widget_ratio = widget + .ratio_override + .unwrap_or_else(|| widget.constraint.ratio()); + match widget.widget_type { BottomWidgetType::Empty => {} _ => { @@ -63,14 +85,14 @@ impl BottomLayout { col_row_mapping.insert( ( widget_width * 100 / col_row.total_widget_ratio, - (widget_width + widget.constraint.ratio()) * 100 + (widget_width + widget_ratio) * 100 / col_row.total_widget_ratio, ), widget.widget_id, ); } } - widget_width += widget.constraint.ratio(); + widget_width += widget_ratio; } if is_valid_col_row { col_mapping.insert( @@ -141,11 +163,14 @@ impl BottomLayout { continue; } + let widget_ratio = widget + .ratio_override + .unwrap_or_else(|| widget.constraint.ratio()); + let widget_width_percentage_start = widget_cursor * 100 / col_row.total_widget_ratio; let widget_width_percentage_end = - (widget_cursor + widget.constraint.ratio()) * 100 - / col_row.total_widget_ratio; + (widget_cursor + widget_ratio) * 100 / col_row.total_widget_ratio; if let Some(current_row) = layout_mapping .get(&(row_height_percentage_start, row_height_percentage_end)) @@ -521,7 +546,7 @@ impl BottomLayout { } } } - widget_cursor += widget.constraint.ratio(); + widget_cursor += widget_ratio; } col_row_cursor += col_row.constraint.ratio(); } @@ -684,41 +709,12 @@ impl BottomLayout { } } -#[derive(Clone, Debug)] -pub enum IntermediaryConstraint { - PartialRatio(u32), - CanvasHandled { ratio: Option }, - Grow { minimum: Option }, -} - -impl Default for IntermediaryConstraint { - fn default() -> Self { - IntermediaryConstraint::PartialRatio(1) - } -} - -impl IntermediaryConstraint { - pub fn ratio(&self) -> u32 { - match self { - IntermediaryConstraint::PartialRatio(val) => *val, - IntermediaryConstraint::Grow { minimum } => match minimum { - Some(val) => *val, - None => 1, - }, - IntermediaryConstraint::CanvasHandled { ratio } => match ratio { - Some(val) => *val, - None => 1, - }, - } - } -} - /// Represents a single row in the layout. #[derive(Clone, Debug)] pub struct BottomRow { pub children: Vec, - pub total_col_ratio: u32, - pub constraint: IntermediaryConstraint, + pub total_col_ratio: u16, + pub constraint: Constraint, } impl BottomRow { @@ -726,22 +722,22 @@ impl BottomRow { Self { children, total_col_ratio: 1, - constraint: IntermediaryConstraint::default(), + constraint: Constraint::Fill(1), } } - pub fn total_col_ratio(mut self, total_col_ratio: u32) -> Self { + pub fn total_col_ratio(mut self, total_col_ratio: u16) -> Self { self.total_col_ratio = total_col_ratio; self } - pub fn ratio(mut self, row_height_ratio: u32) -> Self { - self.constraint = IntermediaryConstraint::PartialRatio(row_height_ratio); + pub fn ratio(mut self, value: u16) -> Self { + self.constraint = Constraint::Fill(value); self } pub fn canvas_handled(mut self) -> Self { - self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; + self.constraint = Constraint::Length(0); self } } @@ -752,8 +748,8 @@ impl BottomRow { #[derive(Clone, Debug)] pub struct BottomCol { pub children: Vec, - pub total_col_row_ratio: u32, - pub constraint: IntermediaryConstraint, + pub total_col_row_ratio: u16, + pub constraint: Constraint, } impl BottomCol { @@ -761,22 +757,22 @@ impl BottomCol { Self { children, total_col_row_ratio: 1, - constraint: IntermediaryConstraint::default(), + constraint: Constraint::Fill(1), } } - pub fn total_col_row_ratio(mut self, total_col_row_ratio: u32) -> Self { + pub fn total_col_row_ratio(mut self, total_col_row_ratio: u16) -> Self { self.total_col_row_ratio = total_col_row_ratio; self } - pub fn ratio(mut self, col_width_ratio: u32) -> Self { - self.constraint = IntermediaryConstraint::PartialRatio(col_width_ratio); + pub fn ratio(mut self, value: u16) -> Self { + self.constraint = Constraint::Fill(value); self } pub fn canvas_handled(mut self) -> Self { - self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; + self.constraint = Constraint::Length(0); self } } @@ -784,8 +780,8 @@ impl BottomCol { #[derive(Clone, Default, Debug)] pub struct BottomColRow { pub children: Vec, - pub total_widget_ratio: u32, - pub constraint: IntermediaryConstraint, + pub total_widget_ratio: u16, + pub constraint: Constraint, } impl BottomColRow { @@ -793,27 +789,27 @@ impl BottomColRow { Self { children, total_widget_ratio: 1, - constraint: IntermediaryConstraint::default(), + constraint: Constraint::Fill(1), } } - pub(crate) fn total_widget_ratio(mut self, total_widget_ratio: u32) -> Self { + pub(crate) fn total_widget_ratio(mut self, total_widget_ratio: u16) -> Self { self.total_widget_ratio = total_widget_ratio; self } - pub fn ratio(mut self, col_row_height_ratio: u32) -> Self { - self.constraint = IntermediaryConstraint::PartialRatio(col_row_height_ratio); + pub fn ratio(mut self, value: u16) -> Self { + self.constraint = Constraint::Fill(value); self } pub fn canvas_handled(mut self) -> Self { - self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; + self.constraint = Constraint::Length(0); self } - pub fn grow(mut self, minimum: Option) -> Self { - self.constraint = IntermediaryConstraint::Grow { minimum }; + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = Constraint::Min(minimum.unwrap_or(0)); self } } @@ -844,7 +840,7 @@ impl WidgetDirection { pub struct BottomWidget { pub widget_type: BottomWidgetType, pub widget_id: u64, - pub constraint: IntermediaryConstraint, + pub constraint: Constraint, pub left_neighbour: Option, pub right_neighbour: Option, pub up_neighbour: Option, @@ -854,10 +850,16 @@ pub struct BottomWidget { pub parent_reflector: Option<(WidgetDirection, u64)>, /// Top left corner when drawn, for mouse click detection. (x, y) + /// + /// TODO: Replace this with just an Option for top + bottom. pub top_left_corner: Option<(u16, u16)>, /// Bottom right corner when drawn, for mouse click detection. (x, y) pub bottom_right_corner: Option<(u16, u16)>, + + /// TODO: REMOVE THIS LATER. This is temporary code to bridge the + /// old layout system with a newer system later. + ratio_override: Option, } impl BottomWidget { @@ -865,7 +867,7 @@ impl BottomWidget { Self { widget_type, widget_id, - constraint: IntermediaryConstraint::default(), + constraint: Constraint::Fill(1), left_neighbour: None, right_neighbour: None, up_neighbour: None, @@ -873,6 +875,7 @@ impl BottomWidget { parent_reflector: None, top_left_corner: None, bottom_right_corner: None, + ratio_override: None, } } @@ -896,23 +899,25 @@ impl BottomWidget { self } - pub(crate) fn ratio(mut self, width_ratio: u32) -> Self { - self.constraint = IntermediaryConstraint::PartialRatio(width_ratio); + pub(crate) fn ratio(mut self, value: u16) -> Self { + self.constraint = Constraint::Fill(value); self } pub fn canvas_handled(mut self) -> Self { - self.constraint = IntermediaryConstraint::CanvasHandled { ratio: None }; + self.constraint = Constraint::Length(0); self } - pub fn canvas_with_ratio(mut self, ratio: u32) -> Self { - self.constraint = IntermediaryConstraint::CanvasHandled { ratio: Some(ratio) }; + pub fn grow(mut self, minimum: Option) -> Self { + self.constraint = Constraint::Min(minimum.unwrap_or(0)); self } - pub fn grow(mut self, minimum: Option) -> Self { - self.constraint = IntermediaryConstraint::Grow { minimum }; + /// TODO: REMOVE THIS LATER. This is temporary code to bridge the + /// old layout system with a newer system later. + pub fn with_ratio_override(mut self, ratio_override: u16) -> Self { + self.ratio_override = Some(ratio_override); self } diff --git a/src/canvas.rs b/src/canvas.rs index 61139ca9..463f37ec 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -8,7 +8,6 @@ pub mod dialogs; mod drawing_utils; mod widgets; -use itertools::izip; use tui::{ Frame, Terminal, backend::Backend, @@ -20,7 +19,7 @@ use tui::{ use crate::{ app::{ App, - layout_manager::{BottomColRow, BottomLayout, BottomWidgetType, IntermediaryConstraint}, + layout_manager::{BottomColRow, BottomLayout, BottomWidgetType}, }, constants::*, options::config::style::Styles, @@ -29,122 +28,24 @@ use crate::{ /// Handles the canvas' state. pub struct Painter { pub styles: Styles, + + /// Used to know whether to invalidate things. previous_height: u16, + + /// Used to know whether to invalidate things. previous_width: u16, - // TODO: Redo this entire thing. - row_constraints: Vec, - col_constraints: Vec>, - col_row_constraints: Vec>>, - layout_constraints: Vec>>>, - derived_widget_draw_locs: Vec>>>, - widget_layout: BottomLayout, -} - -/// The constraints of a widget relative to its parent. -/// -/// This is used over ratatui's internal representation due to -/// . -pub enum LayoutConstraint { - CanvasHandled, - Grow, - Ratio(u32, u32), + /// The layout. + layout: BottomLayout, } impl Painter { pub fn init(layout: BottomLayout, styling: Styles) -> anyhow::Result { - // Now for modularity; we have to also initialize the base layouts! - // We want to do this ONCE and reuse; after this we can just construct - // based on the console size. - - let mut row_constraints = Vec::new(); - let mut col_constraints = Vec::new(); - let mut col_row_constraints = Vec::new(); - let mut layout_constraints = Vec::new(); - - layout.rows.iter().for_each(|row| { - match row.constraint { - IntermediaryConstraint::PartialRatio(val) => { - row_constraints - .push(LayoutConstraint::Ratio(val, layout.total_row_height_ratio)); - } - IntermediaryConstraint::CanvasHandled { .. } => { - row_constraints.push(LayoutConstraint::CanvasHandled); - } - IntermediaryConstraint::Grow { .. } => { - row_constraints.push(LayoutConstraint::Grow); - } - } - - let mut new_col_constraints = Vec::new(); - let mut new_widget_constraints = Vec::new(); - let mut new_col_row_constraints = Vec::new(); - row.children.iter().for_each(|col| { - match col.constraint { - IntermediaryConstraint::PartialRatio(val) => { - new_col_constraints.push(LayoutConstraint::Ratio(val, row.total_col_ratio)); - } - IntermediaryConstraint::CanvasHandled { .. } => { - new_col_constraints.push(LayoutConstraint::CanvasHandled); - } - IntermediaryConstraint::Grow { .. } => { - new_col_constraints.push(LayoutConstraint::Grow); - } - } - - let mut new_new_col_row_constraints = Vec::new(); - let mut new_new_widget_constraints = Vec::new(); - col.children.iter().for_each(|col_row| { - match col_row.constraint { - IntermediaryConstraint::PartialRatio(val) => { - new_new_col_row_constraints - .push(LayoutConstraint::Ratio(val, col.total_col_row_ratio)); - } - IntermediaryConstraint::CanvasHandled { .. } => { - new_new_col_row_constraints.push(LayoutConstraint::CanvasHandled); - } - IntermediaryConstraint::Grow { .. } => { - new_new_col_row_constraints.push(LayoutConstraint::Grow); - } - } - - let mut new_new_new_widget_constraints = Vec::new(); - col_row - .children - .iter() - .for_each(|widget| match widget.constraint { - IntermediaryConstraint::PartialRatio(val) => { - new_new_new_widget_constraints - .push(LayoutConstraint::Ratio(val, col_row.total_widget_ratio)); - } - IntermediaryConstraint::CanvasHandled { .. } => { - new_new_new_widget_constraints - .push(LayoutConstraint::CanvasHandled); - } - IntermediaryConstraint::Grow { .. } => { - new_new_new_widget_constraints.push(LayoutConstraint::Grow); - } - }); - new_new_widget_constraints.push(new_new_new_widget_constraints); - }); - new_col_row_constraints.push(new_new_col_row_constraints); - new_widget_constraints.push(new_new_widget_constraints); - }); - col_row_constraints.push(new_col_row_constraints); - layout_constraints.push(new_widget_constraints); - col_constraints.push(new_col_constraints); - }); - let painter = Painter { styles: styling, previous_height: 0, previous_width: 0, - row_constraints, - col_constraints, - col_row_constraints, - layout_constraints, - widget_layout: layout, - derived_widget_draw_locs: Vec::default(), + layout, }; Ok(painter) @@ -455,235 +356,34 @@ impl Painter { self.draw_frozen_indicator(f, frozen_draw_loc); } - if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw { - // TODO: Can I remove this? Does ratatui's layout constraints work properly for - // fixing https://github.com/ClementTsang/bottom/issues/896 now? - fn get_constraints( - direction: Direction, constraints: &[LayoutConstraint], area: Rect, - ) -> Vec { - // 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 + // A two-pass algorithm - get layouts using constraints (first pass), + // then pass each layout to the corresponding widget (second pass). + // Note that layouts are already cached in ratatui, so we don't need + // to do it manually! + let base = Layout::vertical(self.layout.rows.iter().map(|r| r.constraint)) + .split(terminal_size); - #[derive(Debug, Default, Clone, Copy)] - struct Size { - width: u16, - height: u16, - } + for (br, base) in self.layout.rows.iter().zip(base.iter()) { + let base = + Layout::horizontal(br.children.iter().map(|bc| bc.constraint)).split(*base); - impl Size { - fn shrink_width(&mut self, amount: u16) { - self.width -= amount; - } + for (bc, base) in br.children.iter().zip(base.iter()) { + let base = Layout::vertical(bc.children.iter().map(|bcr| bcr.constraint)) + .split(*base); - fn shrink_height(&mut self, amount: u16) { - self.height -= amount; - } - } + for (widgets, base) in bc.children.iter().zip(base.iter()) { + let widget_draw_locs = + Layout::horizontal(widgets.children.iter().map(|bw| bw.constraint)) + .split(*base); - 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, - &self.col_constraints, - &self.col_row_constraints, - &self.layout_constraints, - &self.widget_layout.rows - ) - .map( - |( - draw_loc, - col_constraint, - col_row_constraint, - row_constraint_vec, - cols, - )| { - izip!( - 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!( - 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 = get_constraints( - Direction::Horizontal, - col_row_constraint_vec.as_slice(), - draw_loc, - ); - - // Side effect, draw here. - self.draw_widgets_with_constraints( - f, - app_state, - widgets, - &widget_draw_locs, - ); - - widget_draw_locs - }) - .collect() - }) - .collect() - }, - ) - .collect(); - } else { - self.widget_layout - .rows - .iter() - .flat_map(|row| &row.children) - .flat_map(|col| &col.children) - .zip(self.derived_widget_draw_locs.iter().flatten().flatten()) - .for_each(|(widgets, widget_draw_locs)| { self.draw_widgets_with_constraints( f, app_state, widgets, - widget_draw_locs, + &widget_draw_locs, ); - }); + } + } } } })?; diff --git a/src/constants.rs b/src/constants.rs index 4554b952..f1d609ef 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -223,10 +223,10 @@ pub(crate) const DEFAULT_BATTERY_LAYOUT: &str = r#" ratio=30 [[row.child]] ratio=2 - type="cpu" + type="cpu" [[row.child]] ratio=1 - type="battery" + type="battery" [[row]] ratio=40 [[row.child]] diff --git a/src/options/config/layout.rs b/src/options/config/layout.rs index dbf64206..99f24581 100644 --- a/src/options/config/layout.rs +++ b/src/options/config/layout.rs @@ -9,7 +9,7 @@ use crate::{app::layout_manager::*, options::OptionResult}; #[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))] #[serde(rename = "row")] pub struct Row { - pub ratio: Option, + pub ratio: Option, pub child: Option>, } @@ -21,7 +21,8 @@ fn new_cpu(cpu_left_legend: bool, iter_id: &mut u64) -> BottomColRow { if cpu_left_legend { BottomColRow::new(vec![ BottomWidget::new(BottomWidgetType::CpuLegend, legend_id) - .canvas_with_ratio(3) + .canvas_handled() + .with_ratio_override(3) .parent_reflector(Some((WidgetDirection::Right, 1))), BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)), ]) @@ -29,7 +30,8 @@ fn new_cpu(cpu_left_legend: bool, iter_id: &mut u64) -> BottomColRow { BottomColRow::new(vec![ BottomWidget::new(BottomWidgetType::Cpu, cpu_id).grow(Some(17)), BottomWidget::new(BottomWidgetType::CpuLegend, legend_id) - .canvas_with_ratio(3) + .canvas_handled() + .with_ratio_override(3) .parent_reflector(Some((WidgetDirection::Left, 1))), ]) } @@ -53,7 +55,7 @@ fn new_proc_search(search_id: u64) -> BottomWidget { impl Row { pub fn convert_row_to_bottom_row( - &self, iter_id: &mut u64, total_height_ratio: &mut u32, default_widget_id: &mut u64, + &self, iter_id: &mut u64, total_height_ratio: &mut u16, default_widget_id: &mut u64, default_widget_type: &Option, default_widget_count: &mut u64, cpu_left_legend: bool, ) -> OptionResult { @@ -222,7 +224,7 @@ impl Row { pub enum RowChildren { Widget(FinalWidget), Col { - ratio: Option, + ratio: Option, child: Vec, }, } @@ -232,7 +234,7 @@ pub enum RowChildren { #[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))] #[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))] pub struct FinalWidget { - pub ratio: Option, + pub ratio: Option, #[serde(rename = "type")] pub widget_type: String, pub default: Option,