diff options
author | ClementTsang <34804052+ClementTsang@users.noreply.github.com> | 2023-06-04 14:48:16 -0400 |
---|---|---|
committer | ClementTsang <34804052+ClementTsang@users.noreply.github.com> | 2023-06-04 14:48:16 -0400 |
commit | a68a7346c3ecc0e58158556fd62ef6725b4baede (patch) | |
tree | e7fd9a9321bb4216f5ea34b4e455d6922fbb908b | |
parent | 6555d2d7514077fdff37fa55214382afb81ebe7e (diff) |
remove code dedup
-rw-r--r-- | src/app/layout_manager.rs | 494 | ||||
-rw-r--r-- | src/canvas.rs | 253 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 12 | ||||
-rw-r--r-- | src/options.rs | 48 | ||||
-rw-r--r-- | tests/layout_management_tests.rs | 2 |
5 files changed, 402 insertions, 407 deletions
diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 4ac882a0..27c0bbe3 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -1,15 +1,12 @@ use std::collections::BTreeMap; +use tui::layout::Direction; + +use crate::canvas::LayoutConstraint; use crate::constants::DEFAULT_WIDGET_ID; use crate::error::{BottomError, Result}; - -/// Represents a more usable representation of the layout, derived from the -/// config. -#[derive(Clone, Debug)] -pub struct BottomLayout { - pub rows: Vec<BottomRow>, - pub total_row_height_ratio: u32, -} +use crate::options::layout_options::Row; +use crate::utils::error; /// Represents a start and end coordinate in some dimension. type LineSegment = (u32, u32); @@ -18,8 +15,276 @@ type WidgetMappings = (u32, BTreeMap<LineSegment, u64>); type ColumnRowMappings = (u32, BTreeMap<LineSegment, WidgetMappings>); type ColumnMappings = (u32, BTreeMap<LineSegment, ColumnRowMappings>); +#[derive(Clone, Debug)] +pub enum Node { + /// A container type, containing more [`Node`] children. + Container(Container), + + /// A leaf node, containing a [`BottomWidget`]. + Widget(BottomWidget), +} + +impl Node { + pub fn constraint(&self) -> LayoutConstraint { + match self { + Node::Container(c) => c.constraint, + Node::Widget(w) => w.constraint, + } + } +} + +/// A "container" that contains more [`Node`]s. +#[derive(Debug, Clone)] +pub(crate) struct Container { + /// The children elements. + pub(crate) children: Vec<usize>, + + /// How the container should be sized. + pub(crate) constraint: LayoutConstraint, + + /// The direction. + direction: ContainerDirection, +} + +impl Container { + pub(crate) fn row(children: Vec<usize>, sizing: LayoutConstraint) -> Self { + Self { + children, + constraint: sizing, + direction: ContainerDirection::Row, + } + } + + pub(crate) fn col(children: Vec<usize>, constraint: LayoutConstraint) -> Self { + Self { + children, + constraint, + direction: ContainerDirection::Col, + } + } + + /// Returns the direction of the container. + pub fn direction(&self) -> ContainerDirection { + self.direction + } +} + +/// The direction in which children in a [`BottomContainer`] will be laid out. +#[derive(Debug, Clone, Copy)] +pub(crate) enum ContainerDirection { + /// Lay out all children horizontally. + Row, + + /// Lay out all children vertically. + Col, +} + +impl From<ContainerDirection> for Direction { + fn from(value: ContainerDirection) -> Self { + match value { + ContainerDirection::Row => Direction::Horizontal, + ContainerDirection::Col => Direction::Vertical, + } + } +} + +/// Represents a more usable representation of the layout, derived from the +/// config. +/// +/// Internally represented by an arena-backed tree. +#[derive(Clone, Debug, Default)] +pub struct BottomLayout { + arena: Vec<Node>, +} + impl BottomLayout { - pub fn get_movement_mappings(&mut self) { + /// Add a node to the layout arena. The ID is returned. + pub fn add_node(&mut self, node: Node) -> usize { + let id = self.arena.len(); + self.arena.push(node); + + id + } + + /// Get the node with the corresponding ID. + pub fn get_node(&self, id: usize) -> Option<&Node> { + self.arena.get(id) + } + + /// Returns the number of elements in the layout. + pub fn len(&self) -> usize { + self.arena.len() + } + + /// Creates a new [`BottomLayout`] given a slice of [`Row`]s. + pub fn from_rows(rows: &[Row]) -> error::Result<Self> { + let mut num_widgets = 0; + + // TODO: Create the thing; use num_widgets to count how many widgets were inserted. + + if num_widgets > 0 { + todo!() + } else { + Err(error::BottomError::ConfigError( + "please have at least one widget under the '[[row]]' section.".to_string(), + )) + } + } + + /// Creates a new [`BottomLayout`] following the basic layout. + pub fn new_basic(use_battery: bool) -> Self { + let table_widgets = if use_battery { + let disk_widget = BottomWidget::new_handled(BottomWidgetType::Disk, 4) + .up_neighbour(Some(100)) + .left_neighbour(Some(8)) + .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); + + let proc_sort = + BottomWidget::new_handled(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) + .up_neighbour(Some(100)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .left_neighbour(Some(4)) + .right_neighbour(Some(DEFAULT_WIDGET_ID)) + .parent_reflector(Some((WidgetDirection::Right, 2))); + + let proc = BottomWidget::new_handled(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) + .up_neighbour(Some(100)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) + .right_neighbour(Some(7)); + + let proc_search = + BottomWidget::new_handled(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) + .up_neighbour(Some(DEFAULT_WIDGET_ID)) + .left_neighbour(Some(4)) + .right_neighbour(Some(7)) + .parent_reflector(Some((WidgetDirection::Up, 1))); + + let temp = BottomWidget::new_handled(BottomWidgetType::Temp, 7) + .up_neighbour(Some(100)) + .left_neighbour(Some(DEFAULT_WIDGET_ID)) + .right_neighbour(Some(8)); + + let battery = BottomWidget::new_handled(BottomWidgetType::Battery, 8) + .up_neighbour(Some(100)) + .left_neighbour(Some(7)) + .right_neighbour(Some(4)); + + vec![ + BottomCol::new(vec![ + BottomColRow::new(vec![disk_widget]).canvas_handle_height(true) + ]) + .canvas_handle_width(true), + BottomCol::new(vec![ + BottomColRow::new(vec![proc_sort, proc]) + .canvas_handle_height(true) + .total_widget_ratio(3), + BottomColRow::new(vec![proc_search]).canvas_handle_height(true), + ]) + .canvas_handle_width(true), + BottomCol::new(vec![ + BottomColRow::new(vec![temp]).canvas_handle_height(true) + ]) + .canvas_handle_width(true), + BottomCol::new(vec![ + BottomColRow::new(vec![battery]).canvas_handle_height(true) + ]) + .canvas_handle_width(true), + ] + } else { + let disk = BottomWidget::new_handled(BottomWidgetType::Disk, 4) + .up_neighbour(Some(100)) + .left_neighbour(Some(7)) + .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); + + let proc_sort = + BottomWidget::new_handled(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) + .up_neighbour(Some(100)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .left_neighbour(Some(4)) + .right_neighbour(Some(DEFAULT_WIDGET_ID)) + .parent_reflector(Some((WidgetDirection::Right, 2))); + + let proc = BottomWidget::new_handled(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) + .up_neighbour(Some(100)) + .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) + .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) + .right_neighbour(Some(7)); + + let proc_search = + BottomWidget::new_handled(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) + .up_neighbour(Some(DEFAULT_WIDGET_ID)) + .left_neighbour(Some(4)) + .right_neighbour(Some(7)) + .parent_reflector(Some((WidgetDirection::Up, 1))); + + let temp = BottomWidget::new_handled(BottomWidgetType::Temp, 7) + .up_neighbour(Some(100)) + .left_neighbour(Some(DEFAULT_WIDGET_ID)) + .right_neighbour(Some(4)); + + vec![ + BottomCol::new(vec![ + BottomColRow::new(vec![disk]).canvas_handle_height(true) + ]) + .canvas_handle_width(true), + BottomCol::new(vec![ + BottomColRow::new(vec![proc_sort, proc]).canvas_handle_height(true), + BottomColRow::new(vec![proc_search]).canvas_handle_height(true), + ]) + .canvas_handle_width(true), + BottomCol::new(vec![ + BottomColRow::new(vec![temp]).canvas_handle_height(true) + ]) + .canvas_handle_width(true), + ] + }; + + let cpu = BottomWidget::new_handled(BottomWidgetType::BasicCpu, 1).down_neighbour(Some(2)); + + let mem = BottomWidget::new_handled(BottomWidgetType::BasicMem, 2) + .up_neighbour(Some(1)) + .down_neighbour(Some(100)) + .right_neighbour(Some(3)); + + let net = BottomWidget::new_handled(BottomWidgetType::BasicNet, 3) + .up_neighbour(Some(1)) + .down_neighbour(Some(100)) + .left_neighbour(Some(2)); + + let table = + BottomWidget::new_handled(BottomWidgetType::BasicTables, 100).up_neighbour(Some(2)); + + let mut layout = BottomLayout::default(); + // TODO: Add nodes; should we instead back with a hashmap? + + // BottomLayout { + // total_row_height_ratio: 3, + // rows: vec![ + // BottomRow::new(vec![BottomCol::new(vec![ + // BottomColRow::new(vec![cpu]).canvas_handle_height(true) + // ]) + // .canvas_handle_width(true)]) + // .canvas_handle_height(true), + // BottomRow::new(vec![BottomCol::new(vec![BottomColRow::new(vec![ + // mem, net, + // ]) + // .canvas_handle_height(true)]) + // .canvas_handle_width(true)]) + // .canvas_handle_height(true), + // BottomRow::new(vec![BottomCol::new(vec![ + // BottomColRow::new(vec![table]).canvas_handle_height(true) + // ]) + // .canvas_handle_width(true)]) + // .canvas_handle_height(true), + // BottomRow::new(table_widgets).canvas_handle_height(true), + // ], + // } + + layout + } + + fn get_movement_mappings(&mut self) { #[allow(clippy::suspicious_operation_groupings)] // Have to enable this, clippy really doesn't like me doing this with tuples... fn is_intersecting(a: LineSegment, b: LineSegment) -> bool { a.0 >= b.0 && a.1 <= b.1 @@ -39,7 +304,7 @@ impl BottomLayout { } // Now we need to create the correct mapping for moving from a specific - // widget to another + // widget to another. let mut layout_mapping: BTreeMap<LineSegment, ColumnMappings> = BTreeMap::new(); let mut total_height = 0; for row in &self.rows { @@ -529,201 +794,6 @@ impl BottomLayout { height_cursor += row.row_height_ratio; } } - - pub fn init_basic_default(use_battery: bool) -> Self { - let table_widgets = if use_battery { - let disk_widget = BottomWidget::new_handled(BottomWidgetType::Disk, 4) - .up_neighbour(Some(100)) - .left_neighbour(Some(8)) - .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); - - let proc_sort = - BottomWidget::new_handled(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(4)) - .right_neighbour(Some(DEFAULT_WIDGET_ID)) - .parent_reflector(Some((WidgetDirection::Right, 2))); - - let proc = BottomWidget::new_handled(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .right_neighbour(Some(7)); - - let proc_search = - BottomWidget::new_handled(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) - .up_neighbour(Some(DEFAULT_WIDGET_ID)) - .left_neighbour(Some(4)) - .right_neighbour(Some(7)) - .parent_reflector(Some((WidgetDirection::Up, 1))); - - let temp = BottomWidget::new_handled(BottomWidgetType::Temp, 7) - .up_neighbour(Some(100)) - .left_neighbour(Some(DEFAULT_WIDGET_ID)) - .right_neighbour(Some(8)); - - let battery = BottomWidget::new_handled(BottomWidgetType::Battery, 8) - .up_neighbour(Some(100)) - .left_neighbour(Some(7)) - .right_neighbour(Some(4)); - - vec![ - BottomCol::new(vec![ - BottomColRow::new(vec![disk_widget]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![proc_sort, proc]) - .canvas_handle_height(true) - .total_widget_ratio(3), - BottomColRow::new(vec![proc_search]).canvas_handle_height(true), - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![temp]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![battery]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - ] - } else { - let disk = BottomWidget::new_handled(BottomWidgetType::Disk, 4) - .up_neighbour(Some(100)) - .left_neighbour(Some(7)) - .right_neighbour(Some(DEFAULT_WIDGET_ID + 2)); - - let proc_sort = - BottomWidget::new_handled(BottomWidgetType::ProcSort, DEFAULT_WIDGET_ID + 2) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(4)) - .right_neighbour(Some(DEFAULT_WIDGET_ID)) - .parent_reflector(Some((WidgetDirection::Right, 2))); - - let proc = BottomWidget::new_handled(BottomWidgetType::Proc, DEFAULT_WIDGET_ID) - .up_neighbour(Some(100)) - .down_neighbour(Some(DEFAULT_WIDGET_ID + 1)) - .left_neighbour(Some(DEFAULT_WIDGET_ID + 2)) - .right_neighbour(Some(7)); - - let proc_search = - BottomWidget::new_handled(BottomWidgetType::ProcSearch, DEFAULT_WIDGET_ID + 1) - .up_neighbour(Some(DEFAULT_WIDGET_ID)) - .left_neighbour(Some(4)) - .right_neighbour(Some(7)) - .parent_reflector(Some((WidgetDirection::Up, 1))); - - let temp = BottomWidget::new_handled(BottomWidgetType::Temp, 7) - .up_neighbour(Some(100)) - .left_neighbour(Some(DEFAULT_WIDGET_ID)) - .right_neighbour(Some(4)); - - vec![ - BottomCol::new(vec![ - BottomColRow::new(vec![disk]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![proc_sort, proc]).canvas_handle_height(true), - BottomColRow::new(vec![proc_search]).canvas_handle_height(true), - ]) - .canvas_handle_width(true), - BottomCol::new(vec![ - BottomColRow::new(vec![temp]).canvas_handle_height(true) - ]) - .canvas_handle_width(true), - ] - }; - - let cpu = BottomWidget::new_handled(BottomWidgetType::BasicCpu, 1).down_neighbour(Some(2)); - - let mem = BottomWidget::new_handled(BottomWidgetType::BasicMem, 2) - .up_neighbour(Some(1)) - .down_neighbour(Some(100)) - .right_neighbour(Some(3)); - - let net = BottomWidget::new_handled(BottomWidgetType::BasicNet, 3) - .up_neighbour(Some(1)) - .down_neighbour(Some(100)) - .left_neighbour(Some(2)); - - let table = - BottomWidget::new_handled(BottomWidgetType::BasicTables, 100).up_neighbour(Some(2)); - - BottomLayout { - total_row_height_ratio: 3, - rows: vec![ - BottomRow::new(vec![BottomCol::new(vec![ - BottomColRow::new(vec![cpu]).canvas_handle_height(true) - ]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), - BottomRow::new(vec![BottomCol::new(vec![BottomColRow::new(vec![ - mem, net, - ]) - .canvas_handle_height(true)]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), - BottomRow::new(vec![BottomCol::new(vec![ - BottomColRow::new(vec![table]).canvas_handle_height(true) - ]) - .canvas_handle_width(true)]) - .canvas_handle_height(true), - BottomRow::new(table_widgets).canvas_handle_height(true), - ], - } - } -} - -#[derive(Debug, Clone)] -pub(crate) enum BottomLayoutNode { - /// A container type, containing more [`BottomLayoutNode`] children. - Container(BottomContainer), - - /// A leaf node, containing a [`BottomWidget`]. - Widget(BottomWidget), -} - -/// A "container" that contains more [`BottomLayoutNode`]s. -#[derive(Debug, Clone)] -pub(crate) struct BottomContainer { - /// The children elements. - pub(crate) children: Vec<BottomLayoutNode>, - - /// How the container should be sized. - pub(crate) sizing: ElementSizing, -} - -/// The direction in which children in a [`BottomContainer`] will be laid out. -#[derive(Debug, Clone, Copy)] -pub(crate) enum BottomContainerType { - /// Lay out all children horizontally. - Row, - - /// Lay out all children vertically. - Col, -} - -/// How the element sizing should be determined. -/// -/// FIXME: This should honestly be matched tighter to the canvas system; we currently have two very similar ways of doing things! -#[derive(Debug, Clone, Copy)] -pub(crate) enum ElementSizing { - /// Denotes that the canvas should follow the given ratio of `lhs:rhs` to determine spacing for the element. - Ratio { lhs: u32, rhs: u32 }, - - /// Denotes that the canvas should let this element grow to take up whatever remaining space is left after - /// sizing the other sibling elements. - FlexGrow, - - /// Denotes that the canvas can do whatever it likes to determine spacing for the element. - CanvasHandled, - - /// Denotes that the element should take up 100% of the space. - Fill, } /// Represents a single row in the layout. @@ -884,7 +954,7 @@ pub struct BottomWidget { pub widget_id: u64, /// How the widget should be sized by the canvas. - pub sizing: ElementSizing, + pub constraint: LayoutConstraint, /// The widget ID to go to when moving to the left. pub left_neighbour: Option<u64>, @@ -910,12 +980,12 @@ pub struct BottomWidget { impl BottomWidget { pub(crate) fn new( - widget_type: BottomWidgetType, widget_id: u64, sizing: ElementSizing, + widget_type: BottomWidgetType, widget_id: u64, constraint: LayoutConstraint, ) -> Self { Self { widget_type, widget_id, - sizing, + constraint, left_neighbour: None, right_neighbour: None, up_neighbour: None, @@ -927,11 +997,15 @@ impl BottomWidget { } pub(crate) fn new_fill(widget_type: BottomWidgetType, widget_id: u64) -> Self { - Self::new(widget_type, widget_id, ElementSizing::Fill) + Self::new( + widget_type, + widget_id, + LayoutConstraint::Ratio { lhs: 1, rhs: 1 }, + ) } pub(crate) fn new_handled(widget_type: BottomWidgetType, widget_id: u64) -> Self { - Self::new(widget_type, widget_id, ElementSizing::CanvasHandled) + Self::new(widget_type, widget_id, LayoutConstraint::CanvasHandled) } pub(crate) fn left_neighbour(mut self, left_neighbour: Option<u64>) -> Self { diff --git a/src/canvas.rs b/src/canvas.rs index 896418a5..31bff6a7 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use canvas_styling::*; -use itertools::izip; +use hashbrown::HashMap; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, @@ -13,7 +13,7 @@ use tui::{ use crate::{ app::{ self, - layout_manager::{BottomColRow, BottomLayout, BottomWidgetType, ElementSizing}, + layout_manager::{BottomColRow, BottomLayout, BottomWidget, BottomWidgetType, Node}, App, }, constants::*, @@ -63,22 +63,23 @@ pub struct Painter { height: u16, width: u16, styled_help_text: Vec<Line<'static>>, - is_mac_os: bool, // TODO: This feels out of place... - - // TODO: Redo this entire thing. - 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>>>>, + + derived_widget_draw_locs: HashMap<usize, Rect>, widget_layout: BottomLayout, } // Part of a temporary fix for https://github.com/ClementTsang/bottom/issues/896 -enum LayoutConstraint { +#[derive(Debug, Clone, Copy)] +pub enum LayoutConstraint { + /// Denotes that the canvas should follow the given ratio of `lhs:rhs` to determine spacing for the element. + Ratio { lhs: u32, rhs: u32 }, + + /// Denotes that the canvas should let this element grow to take up whatever remaining space is left after + /// sizing the other sibling elements. + FlexGrow, + + /// Denotes that the canvas can do whatever it likes to determine spacing for the element. CanvasHandled, - Grow, - Ratio(u32, u32), } impl Painter { @@ -87,77 +88,11 @@ impl Painter { // 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(); - - widget_layout.rows.iter().for_each(|row| { - if row.canvas_handle_height { - row_constraints.push(LayoutConstraint::CanvasHandled); - } else { - row_constraints.push(LayoutConstraint::Ratio( - row.row_height_ratio, - widget_layout.total_row_height_ratio, - )); - } - - 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| { - if col.canvas_handle_width { - new_col_constraints.push(LayoutConstraint::CanvasHandled); - } else { - 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(LayoutConstraint::CanvasHandled); - } else if col_row.flex_grow { - new_new_col_row_constraints.push(LayoutConstraint::Grow); - } else { - new_new_col_row_constraints.push(LayoutConstraint::Ratio( - col_row.col_row_height_ratio, - col.total_col_row_ratio, - )); - } - - let mut new_new_new_widget_constraints = Vec::new(); - col_row.children.iter().for_each(|widget| { - new_new_new_widget_constraints.push(match widget.sizing { - ElementSizing::Ratio { lhs, rhs } => LayoutConstraint::Ratio(lhs, rhs), - ElementSizing::FlexGrow => LayoutConstraint::Grow, - ElementSizing::CanvasHandled => LayoutConstraint::CanvasHandled, - ElementSizing::Fill => LayoutConstraint::Ratio(1, 1), - }); - }); - 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 mut painter = Painter { colours: styling, height: 0, width: 0, styled_help_text: Vec::default(), - is_mac_os: cfg!(target_os = "macos"), - row_constraints, - col_constraints, - col_row_constraints, - layout_constraints, widget_layout, derived_widget_draw_locs: Vec::default(), }; @@ -525,8 +460,8 @@ impl Painter { } if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw { - fn get_constraints( - direction: Direction, constraints: &[LayoutConstraint], area: Rect, + fn get_rects<I: ExactSizeIterator<Item = LayoutConstraint>>( + direction: Direction, constraints: I, area: Rect, ) -> Vec<Rect> { // Order of operations: // - Ratios first + canvas-handled (which is just zero) @@ -558,21 +493,20 @@ impl Painter { let mut num_non_ch = 0; for (itx, (constraint, size)) in - constraints.iter().zip(sizes.iter_mut()).enumerate() + constraints.zip(sizes.iter_mut()).enumerate() { match constraint { - LayoutConstraint::Ratio(a, b) => { + LayoutConstraint::Ratio { lhs, rhs } => { match direction { Direction::Horizontal => { - let amount = - (((area.width as u32) * (*a)) / (*b)) as u16; + let amount = (((area.width as u32) * lhs) / rhs) 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; + (((area.height as u32) * lhs) / rhs) as u16; bounds.shrink_height(amount); size.width = area.width; size.height = amount; @@ -580,7 +514,7 @@ impl Painter { } num_non_ch += 1; } - LayoutConstraint::Grow => { + LayoutConstraint::FlexGrow => { // Mark it as grow in the vector and handle in second pass. grow.push(itx); num_non_ch += 1; @@ -624,8 +558,8 @@ impl Painter { for (size, constraint) in sizes.iter_mut().zip(constraints) { match constraint { LayoutConstraint::CanvasHandled => {} - LayoutConstraint::Grow - | LayoutConstraint::Ratio(_, _) => { + LayoutConstraint::FlexGrow + | LayoutConstraint::Ratio { .. } => { if remaining_width > 0 { size.width += per_item + 1; remaining_width -= 1; @@ -642,8 +576,8 @@ impl Painter { for (size, constraint) in sizes.iter_mut().zip(constraints) { match constraint { LayoutConstraint::CanvasHandled => {} - LayoutConstraint::Grow - | LayoutConstraint::Ratio(_, _) => { + LayoutConstraint::FlexGrow + | LayoutConstraint::Ratio { .. } => { if remaining_height > 0 { size.height += per_item + 1; remaining_height -= 1; @@ -677,79 +611,53 @@ impl Painter { .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(); + // Do a preorder traversal through the tree in, and calculate the draw [`Rect`]s for each widget. + let root_id = self.widget_layout.len().saturating_sub(1); + let mut queue = vec![(root_id, terminal_size)]; + while let Some((current_id, rect)) = queue.pop() { + if let Some(widget) = self.widget_layout.get_node(current_id) { + match widget { + Node::Container(container) => { + let constraints = container.children.iter().map(|child| { + if let Some(node) = self.widget_layout.get_node(*child) { + node.constraint() + } else { + LayoutConstraint::FlexGrow + } + }); + + let rects = + get_rects(container.direction().into(), constraints, rect); + + // If it's a container, push in reverse order to the stack. + for child in container + .children + .iter() + .cloned() + .zip(rects.into_iter()) + .rev() + { + queue.push(child); + } + } + Node::Widget(widget) => { + // If we're instead on a widget, we can instead assign the rect to the widget. + self.derived_widget_draw_locs.insert(current_id, rect); + } + } + } + } } 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, - |