From c64d754f88337f6d157326d5f7363a455f2c867e Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Sun, 8 Aug 2021 19:24:27 +0200 Subject: [PATCH] feat(layout): add support for multiple weighted constraints by chunks --- examples/barchart.rs | 12 +- examples/block.rs | 17 ++- examples/canvas.rs | 7 +- examples/chart.rs | 15 +-- examples/demo/ui.rs | 77 +++++++------ examples/gauge.rs | 17 ++- examples/layout.rs | 15 +-- examples/list.rs | 7 +- examples/paragraph.rs | 11 +- examples/popup.rs | 30 ++--- examples/sparkline.rs | 17 ++- examples/table.rs | 27 +++-- examples/tabs.rs | 7 +- examples/user_input.rs | 15 +-- src/layout.rs | 245 +++++++++++++++++++++++++++-------------- src/lib.rs | 15 +-- src/widgets/chart.rs | 20 ++-- src/widgets/table.rs | 91 +++++++-------- tests/widgets_gauge.rs | 12 +- tests/widgets_table.rs | 156 +++++++++++--------------- 20 files changed, 444 insertions(+), 369 deletions(-) diff --git a/examples/barchart.rs b/examples/barchart.rs index 8179cb7..3c753b8 100644 --- a/examples/barchart.rs +++ b/examples/barchart.rs @@ -6,7 +6,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, widgets::{BarChart, Block, Borders}, Terminal, @@ -73,7 +73,10 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); let barchart = BarChart::default() .block(Block::default().title("Data1").borders(Borders::ALL)) @@ -85,7 +88,10 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(chunks[1]); let barchart = BarChart::default() diff --git a/examples/block.rs b/examples/block.rs index d7c84f1..5fe6fde 100644 --- a/examples/block.rs +++ b/examples/block.rs @@ -6,7 +6,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Alignment, Constraint, Direction, Layout}, + layout::{Alignment, Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::Span, widgets::{Block, BorderType, Borders}, @@ -42,13 +42,19 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(4) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); // Top two inner blocks let top_chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(chunks[0]); // Top left inner block with green background @@ -75,7 +81,10 @@ fn main() -> Result<(), Box> { // Bottom two inner blocks let bottom_chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(chunks[1]); // Bottom left block with all default borders diff --git a/examples/canvas.rs b/examples/canvas.rs index 6f5ef59..51d7be4 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -6,7 +6,7 @@ use std::{error::Error, io, time::Duration}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Constraint, Direction, Layout, Rect, Unit}, style::Color, widgets::{ canvas::{Canvas, Map, MapResolution, Rectangle}, @@ -94,7 +94,10 @@ fn main() -> Result<(), Box> { terminal.draw(|f| { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); let canvas = Canvas::default() .block(Block::default().borders(Borders::ALL).title("World")) diff --git a/examples/chart.rs b/examples/chart.rs index 58d7ee4..8d49855 100644 --- a/examples/chart.rs +++ b/examples/chart.rs @@ -9,7 +9,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, symbols, text::Span, @@ -83,14 +83,11 @@ fn main() -> Result<(), Box> { let size = f.size(); let chunks = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), + ]) .split(size); let x_labels = vec![ Span::styled( diff --git a/examples/demo/ui.rs b/examples/demo/ui.rs index e5664c0..9fa4f8e 100644 --- a/examples/demo/ui.rs +++ b/examples/demo/ui.rs @@ -1,7 +1,8 @@ use crate::demo::App; +use std::iter::FromIterator; use tui::{ backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Constraint, Constraints, Direction, Layout, Rect, Unit}, style::{Color, Modifier, Style}, symbols, text::{Span, Spans}, @@ -15,7 +16,13 @@ use tui::{ pub fn draw(f: &mut Frame, app: &mut App) { let chunks = Layout::default() - .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) + .constraints([ + Constraint::eq(3).weight(10).into(), + Constraints::from_iter([ + Constraint::eq(Unit::Percentage(100)), + Constraint::gte(10).weight(10), + ]), + ]) .split(f.size()); let titles = app .tabs @@ -41,14 +48,14 @@ where B: Backend, { let chunks = Layout::default() - .constraints( - [ - Constraint::Length(9), - Constraint::Min(8), - Constraint::Length(7), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(9).weight(10).into(), + Constraints::from_iter([ + Constraint::eq(Unit::Percentage(100)), + Constraint::gte(8).weight(10), + ]), + Constraint::eq(7).weight(10).into(), + ]) .split(area); draw_gauges(f, app, chunks[0]); draw_charts(f, app, chunks[1]); @@ -60,14 +67,7 @@ where B: Backend, { let chunks = Layout::default() - .constraints( - [ - Constraint::Length(2), - Constraint::Length(3), - Constraint::Length(1), - ] - .as_ref(), - ) + .constraints([Constraint::eq(2), Constraint::eq(3), Constraint::eq(1)]) .margin(1) .split(area); let block = Block::default().borders(Borders::ALL).title("Graphs"); @@ -114,9 +114,12 @@ where B: Backend, { let constraints = if app.show_chart { - vec![Constraint::Percentage(50), Constraint::Percentage(50)] + vec![ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ] } else { - vec![Constraint::Percentage(100)] + vec![Constraint::eq(Unit::Percentage(100))] }; let chunks = Layout::default() .constraints(constraints) @@ -124,11 +127,17 @@ where .split(area); { let chunks = Layout::default() - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(chunks[0]); { let chunks = Layout::default() - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .direction(Direction::Horizontal) .split(chunks[0]); @@ -302,7 +311,10 @@ where B: Backend, { let chunks = Layout::default() - .constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(30)), + Constraint::eq(Unit::Percentage(70)), + ]) .direction(Direction::Horizontal) .split(area); let up_style = Style::default().fg(Color::Green); @@ -324,11 +336,7 @@ where .bottom_margin(1), ) .block(Block::default().title("Servers").borders(Borders::ALL)) - .widths(&[ - Constraint::Length(15), - Constraint::Length(15), - Constraint::Length(10), - ]); + .widths([Constraint::eq(15), Constraint::eq(15), Constraint::eq(10)]); f.render_widget(table, chunks[0]); let map = Canvas::default() @@ -382,7 +390,10 @@ where { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .constraints([ + Constraint::eq(Unit::Ratio(1, 2)), + Constraint::eq(Unit::Ratio(1, 2)), + ]) .split(area); let colors = [ Color::Reset, @@ -416,10 +427,10 @@ where .collect(); let table = Table::new(items) .block(Block::default().title("Colors").borders(Borders::ALL)) - .widths(&[ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), + .widths([ + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), ]); f.render_widget(table, chunks[0]); } diff --git a/examples/gauge.rs b/examples/gauge.rs index 9cb097b..8aad8f2 100644 --- a/examples/gauge.rs +++ b/examples/gauge.rs @@ -6,7 +6,7 @@ use std::{error::Error, io, time::Duration}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::Span, widgets::{Block, Borders, Gauge}, @@ -69,15 +69,12 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints( - [ - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), + ]) .split(f.size()); let gauge = Gauge::default() diff --git a/examples/layout.rs b/examples/layout.rs index b740186..093f469 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -6,7 +6,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, widgets::{Block, Borders}, Terminal, }; @@ -25,14 +25,11 @@ fn main() -> Result<(), Box> { terminal.draw(|f| { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(10), - Constraint::Percentage(80), - Constraint::Percentage(10), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(Unit::Percentage(10)), + Constraint::eq(Unit::Percentage(80)), + Constraint::eq(Unit::Percentage(10)), + ]) .split(f.size()); let block = Block::default().title("Block").borders(Borders::ALL); diff --git a/examples/list.rs b/examples/list.rs index 086b1ba..a0f4c01 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -9,7 +9,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Corner, Direction, Layout}, + layout::{Constraint, Corner, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, List, ListItem}, @@ -114,7 +114,10 @@ fn main() -> Result<(), Box> { // Create two chunks with equal horizontal screen space let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); // Iterate through all elements in the `items` app and append some debug text to it. diff --git a/examples/paragraph.rs b/examples/paragraph.rs index 587046c..2b6f682 100644 --- a/examples/paragraph.rs +++ b/examples/paragraph.rs @@ -6,7 +6,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Alignment, Constraint, Direction, Layout}, + layout::{Alignment, Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, Paragraph, Wrap}, @@ -42,12 +42,11 @@ fn main() -> Result<(), Box> { .margin(5) .constraints( [ - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), - Constraint::Percentage(25), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(25)), ] - .as_ref(), ) .split(size); diff --git a/examples/popup.rs b/examples/popup.rs index ca58bc5..4b675f3 100644 --- a/examples/popup.rs +++ b/examples/popup.rs @@ -6,7 +6,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Alignment, Constraint, Direction, Layout, Rect}, + layout::{Alignment, Constraint, Direction, Layout, Rect, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, Clear, Paragraph, Wrap}, @@ -18,26 +18,20 @@ use tui::{ fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { let popup_layout = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ] - .as_ref(), - ) + .constraints(vec![ + Constraint::eq(Unit::Percentage((100 - percent_y) / 2)), + Constraint::eq(Unit::Percentage(percent_y)), + Constraint::eq(Unit::Percentage((100 - percent_y) / 2)), + ]) .split(r); Layout::default() .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ] - .as_ref(), - ) + .constraints(vec![ + Constraint::eq(Unit::Percentage((100 - percent_x) / 2)), + Constraint::eq(Unit::Percentage(percent_x)), + Constraint::eq(Unit::Percentage((100 - percent_x) / 2)), + ]) .split(popup_layout[1])[1] } @@ -57,7 +51,7 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([Constraint::eq(Unit::Percentage(50)), Constraint::eq(Unit::Percentage(50))]) .split(size); let s = "Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. "; diff --git a/examples/sparkline.rs b/examples/sparkline.rs index eb0d3f4..a6d0404 100644 --- a/examples/sparkline.rs +++ b/examples/sparkline.rs @@ -9,7 +9,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Style}, widgets::{Block, Borders, Sparkline}, Terminal, @@ -68,15 +68,12 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints( - [ - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(7), - Constraint::Min(0), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(3).weight(10), + Constraint::eq(3).weight(10), + Constraint::eq(7).weight(10), + Constraint::eq(Unit::Percentage(100)), + ]) .split(f.size()); let sparkline = Sparkline::default() .block( diff --git a/examples/table.rs b/examples/table.rs index 846a55c..3b4f060 100644 --- a/examples/table.rs +++ b/examples/table.rs @@ -2,11 +2,11 @@ mod util; use crate::util::event::{Event, Events}; -use std::{error::Error, io}; +use std::{error::Error, io, iter::FromIterator}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Layout}, + layout::{Constraint, Constraints, Layout, Unit}, style::{Color, Modifier, Style}, widgets::{Block, Borders, Cell, Row, Table, TableState}, Terminal, @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { loop { terminal.draw(|f| { let rects = Layout::default() - .constraints([Constraint::Percentage(100)].as_ref()) + .constraints(vec![Constraint::eq(Unit::Percentage(100))]) .margin(5) .split(f.size()); @@ -109,18 +109,27 @@ fn main() -> Result<(), Box> { .max() .unwrap_or(0) + 1; - let cells = item.iter().map(|c| Cell::from(*c)); - Row::new(cells).height(height as u16).bottom_margin(1) + let cells = item + .iter() + .map(|c| Cell::from(*c).style(Style::default().bg(Color::Yellow))); + Row::new(cells).height(height as u16) }); + let t = Table::new(rows) .header(header) .block(Block::default().borders(Borders::ALL).title("Table")) .highlight_style(selected_style) .highlight_symbol(">> ") - .widths(&[ - Constraint::Percentage(50), - Constraint::Length(30), - Constraint::Max(10), + .widths([ + Constraint::gte(20).weight(30).into(), + Constraints::from_iter([ + Constraint::eq(20).weight(10), + Constraint::gte(10).weight(30), + ]), + Constraints::from_iter([ + Constraint::eq(Unit::Percentage(100)), + Constraint::gte(30).weight(20), + ]), ]); f.render_stateful_widget(t, rects[0], &mut table.state); })?; diff --git a/examples/tabs.rs b/examples/tabs.rs index b7a9e95..99496f6 100644 --- a/examples/tabs.rs +++ b/examples/tabs.rs @@ -9,7 +9,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, Tabs}, @@ -42,7 +42,10 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(5) - .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) + .constraints([ + Constraint::eq(3).weight(10), + Constraint::eq(Unit::Percentage(100)), + ]) .split(size); let block = Block::default().style(Style::default().bg(Color::White).fg(Color::Black)); diff --git a/examples/user_input.rs b/examples/user_input.rs index dddea7f..779ed5a 100644 --- a/examples/user_input.rs +++ b/examples/user_input.rs @@ -18,7 +18,7 @@ use std::{error::Error, io}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use tui::{ backend::TermionBackend, - layout::{Constraint, Direction, Layout}, + layout::{Constraint, Direction, Layout, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans, Text}, widgets::{Block, Borders, List, ListItem, Paragraph}, @@ -71,14 +71,11 @@ fn main() -> Result<(), Box> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints( - [ - Constraint::Length(1), - Constraint::Length(3), - Constraint::Min(1), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(1).weight(10), + Constraint::eq(3).weight(10), + Constraint::eq(Unit::Percentage(100)), + ]) .split(f.size()); let (msg, style) = match app.input_mode { diff --git a/src/layout.rs b/src/layout.rs index b1df157..15080b7 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -21,28 +21,128 @@ pub enum Direction { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Constraint { - // TODO: enforce range 0 - 100 +pub enum Unit { + Length(u16), Percentage(u16), Ratio(u32, u32), - Length(u16), - Max(u16), - Min(u16), } -impl Constraint { - pub fn apply(&self, length: u16) -> u16 { +impl From for Unit { + fn from(v: u16) -> Unit { + Unit::Length(v) + } +} + +impl Unit { + pub(crate) fn apply(&self, length: u16) -> u16 { match *self { - Constraint::Percentage(p) => length * p / 100, - Constraint::Ratio(num, den) => { + Unit::Percentage(p) => length * p / 100, + Unit::Ratio(num, den) => { let r = num * u32::from(length) / den; r as u16 } - Constraint::Length(l) => length.min(l), - Constraint::Max(m) => length.min(m), - Constraint::Min(m) => length.max(m), + Unit::Length(l) => length.min(l), + } + } + + fn check(&self) { + match *self { + Unit::Percentage(p) => { + assert!( + p <= 100, + "Percentages should be between 0 and 100 inclusively." + ); + } + Unit::Ratio(num, den) => { + assert!( + num <= den, + "Ratio numerator should be less than or equalt to denominator." + ); + } + _ => {} + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum Operator { + Equal, + GreaterThanOrEqual, + LessThanOrEqual, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Constraint { + operator: Operator, + unit: Unit, + weight: u8, +} + +impl Constraint { + pub fn gte(u: T) -> Constraint + where + T: Into, + { + let u = u.into(); + u.check(); + Constraint { + operator: Operator::GreaterThanOrEqual, + unit: u, + weight: 0, + } + } + + pub fn lte(u: T) -> Constraint + where + T: Into, + { + let u = u.into(); + u.check(); + Constraint { + operator: Operator::LessThanOrEqual, + unit: u, + weight: 0, } } + + pub fn eq(u: T) -> Constraint + where + T: Into, + { + let u = u.into(); + u.check(); + Constraint { + operator: Operator::Equal, + unit: u, + weight: 0, + } + } + + pub fn weight(mut self, weight: u8) -> Constraint { + self.weight = weight; + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Constraints(Vec); + +impl From for Constraints { + fn from(c: Constraint) -> Constraints { + Constraints(vec![c]) + } +} + +impl std::iter::FromIterator for Constraints +where + T: Into, +{ + fn from_iter(iter: I) -> Constraints + where + I: IntoIterator, + { + Constraints(iter.into_iter().map(|c| c.into()).collect()) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -62,10 +162,7 @@ pub enum Alignment { pub struct Layout { direction: Direction, margin: Margin, - constraints: Vec, - /// Whether the last chunk of the computed layout should be expanded to fill the available - /// space. - expand_to_fill: bool, + constraints: Vec, } thread_local! { @@ -81,17 +178,17 @@ impl Default for Layout { vertical: 0, }, constraints: Vec::new(), - expand_to_fill: true, } } } impl Layout { - pub fn constraints(mut self, constraints: C) -> Layout + pub fn constraints(mut self, constraints: I) -> Layout where - C: Into>, + I: IntoIterator, + I::Item: Into, { - self.constraints = constraints.into(); + self.constraints = constraints.into_iter().map(|c| c.into()).collect(); self } @@ -118,20 +215,18 @@ impl Layout { self } - pub(crate) fn expand_to_fill(mut self, expand_to_fill: bool) -> Layout { - self.expand_to_fill = expand_to_fill; - self - } - /// Wrapper function around the cassowary-rs solver to be able to split a given /// area into smaller ones based on the preferred widths or heights and the direction. /// /// # Examples /// ``` - /// # use tui::layout::{Rect, Constraint, Direction, Layout}; + /// # use tui::layout::{Rect, Constraint, Direction, Layout, Unit}; /// let chunks = Layout::default() /// .direction(Direction::Vertical) - /// .constraints([Constraint::Length(5), Constraint::Min(0)].as_ref()) + /// .constraints([ + /// Constraint::eq(5).weight(10), + /// Constraint::eq(Unit::Percentage(100)).weight(5) + /// ]) /// .split(Rect { /// x: 2, /// y: 2, @@ -158,7 +253,7 @@ impl Layout { /// /// let chunks = Layout::default() /// .direction(Direction::Horizontal) - /// .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)].as_ref()) + /// .constraints([Constraint::eq(Unit::Ratio(1, 3)), Constraint::eq(Unit::Ratio(2, 3))]) /// .split(Rect { /// x: 0, /// y: 0, @@ -231,57 +326,25 @@ fn split(area: Rect, layout: &Layout) -> Vec { Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()), }); } - if layout.expand_to_fill { - if let Some(last) = elements.last() { - ccs.push(match layout.direction { - Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()), - Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()), - }); - } - } match layout.direction { Direction::Horizontal => { for pair in elements.windows(2) { ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x); } - for (i, size) in layout.constraints.iter().enumerate() { + for (i, c) in layout.constraints.iter().enumerate() { ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y)); ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height)); - ccs.push(match *size { - Constraint::Length(v) => elements[i].width | EQ(WEAK) | f64::from(v), - Constraint::Percentage(v) => { - elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0) - } - Constraint::Ratio(n, d) => { - elements[i].width - | EQ(WEAK) - | (f64::from(dest_area.width) * f64::from(n) / f64::from(d)) - } - Constraint::Min(v) => elements[i].width | GE(WEAK) | f64::from(v), - Constraint::Max(v) => elements[i].width | LE(WEAK) | f64::from(v), - }); + apply_constraints(&mut ccs, c, elements[i].width, dest_area.width); } } Direction::Vertical => { for pair in elements.windows(2) { ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y); } - for (i, size) in layout.constraints.iter().enumerate() { + for (i, c) in layout.constraints.iter().enumerate() { ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x)); ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width)); - ccs.push(match *size { - Constraint::Length(v) => elements[i].height | EQ(WEAK) | f64::from(v), - Constraint::Percentage(v) => { - elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0) - } - Constraint::Ratio(n, d) => { - elements[i].height - | EQ(WEAK) - | (f64::from(dest_area.height) * f64::from(n) / f64::from(d)) - } - Constraint::Min(v) => elements[i].height | GE(WEAK) | f64::from(v), - Constraint::Max(v) => elements[i].height | LE(WEAK) | f64::from(v), - }); + apply_constraints(&mut ccs, c, elements[i].height, dest_area.height); } } } @@ -309,21 +372,29 @@ fn split(area: Rect, layout: &Layout) -> Vec { _ => {} } } + results +} - if layout.expand_to_fill { - // Fix imprecision by extending the last item a bit if necessary - if let Some(last) = results.last_mut() { - match layout.direction { - Direction::Vertical => { - last.height = dest_area.bottom() - last.y; - } - Direction::Horizontal => { - last.width = dest_area.right() - last.x; - } - } - } +fn apply_constraints( + ccs: &mut Vec, + constraints: &Constraints, + var: Variable, + total_length: u16, +) { + for c in &constraints.0 { + let weight = WEAK + f64::from(c.weight); + let value = match c.unit { + Unit::Length(v) => f64::from(v), + Unit::Percentage(v) => f64::from(v * total_length) / 100.0, + Unit::Ratio(n, d) => f64::from(total_length) * f64::from(n) / f64::from(d), + }; + let operator = match c.operator { + Operator::GreaterThanOrEqual => GE(weight), + Operator::Equal => EQ(weight), + Operator::LessThanOrEqual => LE(weight), + }; + ccs.push(var | operator | value); } - results } /// A container used by the solver inside split @@ -476,6 +547,12 @@ impl Rect { mod tests { use super::*; + #[test] + #[should_panic] + fn constraint_invalid_percentages() { + Constraint::eq(Unit::Percentage(110)); + } + #[test] fn test_vertical_split_by_height() { let target = Rect { @@ -487,14 +564,12 @@ mod tests { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(10), - Constraint::Max(5), - Constraint::Min(1), - ] - .as_ref(), - ) + .constraints([ + Constraint::eq(10), + Constraint::eq(10), + Constraint::lte(5), + Constraint::gte(1), + ]) .split(target); assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::()); diff --git a/src/lib.rs b/src/lib.rs index 0c228ce..76c7dc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,7 +111,7 @@ //! use tui::Terminal; //! use tui::backend::TermionBackend; //! use tui::widgets::{Widget, Block, Borders}; -//! use tui::layout::{Layout, Constraint, Direction}; +//! use tui::layout::{Layout, Constraint, Direction, Unit}; //! //! fn main() -> Result<(), io::Error> { //! let stdout = io::stdout().into_raw_mode()?; @@ -123,10 +123,10 @@ //! .margin(1) //! .constraints( //! [ -//! Constraint::Percentage(10), -//! Constraint::Percentage(80), -//! Constraint::Percentage(10) -//! ].as_ref() +//! Constraint::eq(Unit::Percentage(10)), +//! Constraint::eq(Unit::Percentage(80)), +//! Constraint::eq(Unit::Percentage(10)) +//! ] //! ) //! .split(f.size()); //! let block = Block::default() @@ -142,10 +142,7 @@ //! } //! ``` //! -//! This let you describe responsive terminal UI by nesting layouts. You should note that by -//! default the computed layout tries to fill the available space completely. So if for any reason -//! you might need a blank space somewhere, try to pass an additional constraint and don't use the -//! corresponding area. +//! This let you describe responsive terminal UI by nesting layouts. pub mod backend; pub mod buffer; diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index e74c19f..e10ead8 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -1,6 +1,6 @@ use crate::{ buffer::Buffer, - layout::{Constraint, Rect}, + layout::{Rect, Unit}, style::{Color, Style}, symbols, text::{Span, Spans}, @@ -225,7 +225,7 @@ pub struct Chart<'a> { /// The widget base style style: Style, /// Constraints used to determine whether the legend should be shown or not - hidden_legend_constraints: (Constraint, Constraint), + hidden_legend_constraints: (Unit, Unit), } impl<'a> Chart<'a> { @@ -236,7 +236,7 @@ impl<'a> Chart<'a> { y_axis: Axis::default(), style: Default::default(), datasets, - hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)), + hidden_legend_constraints: (Unit::Ratio(1, 4), Unit::Ratio(1, 4)), } } @@ -266,17 +266,17 @@ impl<'a> Chart<'a> { /// /// ``` /// # use tui::widgets::Chart; - /// # use tui::layout::Constraint; + /// # use tui::layout::Unit; /// let constraints = ( - /// Constraint::Ratio(1, 3), - /// Constraint::Ratio(1, 4) + /// Unit::Ratio(1, 3), + /// Unit::Ratio(1, 4) /// ); /// // Hide the legend when either its width is greater than 33% of the total widget width /// // or if its height is greater than 25% of the total widget height. /// let _chart: Chart = Chart::new(vec![]) /// .hidden_legend_constraints(constraints); /// ``` - pub fn hidden_legend_constraints(mut self, constraints: (Constraint, Constraint)) -> Chart<'a> { + pub fn hidden_legend_constraints(mut self, constraints: (Unit, Unit)) -> Chart<'a> { self.hidden_legend_constraints = constraints; self } @@ -562,7 +562,7 @@ mod tests { struct LegendTestCase { chart_area: Rect, - hidden_legend_constraints: (Constraint, Constraint), + hidden_legend_constraints: (Unit, Unit), legend_area: Option, } @@ -572,12 +572,12 @@ mod tests { let cases = [ LegendTestCase { chart_area: Rect::new(0, 0, 100, 100), - hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)), + hidden_legend_constraints: (Unit::Ratio(1, 4), Unit::Ratio(1, 4)), legend_area: Some(Rect::new(88, 0, 12, 12)), }, LegendTestCase { chart_area: Rect::new(0, 0, 100, 100), - hidden_legend_constraints: (Constraint::Ratio(1, 10), Constraint::Ratio(1, 4)), + hidden_legend_constraints: (Unit::Ratio(1, 10), Unit::Ratio(1, 4)), legend_area: None, }, ]; diff --git a/src/widgets/table.rs b/src/widgets/table.rs index fa75e77..1826ac3 100644 --- a/src/widgets/table.rs +++ b/src/widgets/table.rs @@ -1,6 +1,6 @@ use crate::{ buffer::Buffer, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Constraint, Constraints, Direction, Layout, Rect, Unit}, style::Style, text::Text, widgets::{Block, StatefulWidget, Widget}, @@ -178,7 +178,7 @@ impl<'a> Row<'a> { /// // As any other widget, a Table can be wrapped in a Block. /// .block(Block::default().title("Table")) /// // Columns widths are constrained in the same way as Layout... -/// .widths(&[Constraint::Length(5), Constraint::Length(5), Constraint::Length(10)]) +/// .widths([Constraint::eq(5), Constraint::eq(5), Constraint::eq(10)]) /// // ...and they can be separated by a fixed spacing. /// .column_spacing(1) /// // If you wish to highlight a row in any specific way when it is selected... @@ -193,7 +193,7 @@ pub struct Table<'a> { /// Base style for the widget style: Style, /// Width constraints for each column - widths: &'a [Constraint], + widths: Vec, /// Space between each column column_spacing: u16, /// Style used to render the selected row @@ -214,7 +214,7 @@ impl<'a> Table<'a> { Self { block: None, style: Style::default(), - widths: &[], + widths: Vec::new(), column_spacing: 1, highlight_style: Style::default(), highlight_symbol: None, @@ -233,16 +233,12 @@ impl<'a> Table<'a> { self } - pub fn widths(mut self, widths: &'a [Constraint]) -> Self { - let between_0_and_100 = |&w| match w { - Constraint::Percentage(p) => p <= 100, - _ => true, - }; - assert!( - widths.iter().all(between_0_and_100), - "Percentages should be between 0 and 100 inclusively." - ); - self.widths = widths; + pub fn widths(mut self, widths: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + self.widths = widths.into_iter().map(|w| w.into()).collect(); self } @@ -266,16 +262,24 @@ impl<'a> Table<'a> { self } - fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec { - let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1); + fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> (u16, Vec) { + let mut constraints: Vec = Vec::with_capacity(self.widths.len() * 2 + 1); if has_selection { let highlight_symbol_width = self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0); - constraints.push(Constraint::Length(highlight_symbol_width)); + constraints.push( + Constraint::eq(Unit::Length(highlight_symbol_width)) + .weight(u8::MAX) + .into(), + ); } - for constraint in self.widths { - constraints.push(*constraint); - constraints.push(Constraint::Length(self.column_spacing)); + for constraint in &self.widths { + constraints.push(constraint.clone()); + constraints.push( + Constraint::eq(Unit::Length(self.column_spacing)) + .weight(u8::MAX) + .into(), + ); } if !self.widths.is_empty() { constraints.pop(); @@ -283,17 +287,18 @@ impl<'a> Table<'a> { let mut chunks = Layout::default() .direction(Direction::Horizontal) .constraints(constraints) - .expand_to_fill(false) .split(Rect { x: 0, y: 0, width: max_width, height: 1, }); - if has_selection { - chunks.remove(0); - } - chunks.iter().step_by(2).map(|c| c.width).collect() + let first_column = if has_selection { + chunks.remove(0).width + } else { + 0 + }; + (first_column, chunks.iter().map(|c| c.width).collect()) } fn get_row_bounds( @@ -381,7 +386,8 @@ impl<'a> StatefulWidget for Table<'a> { }; let has_selection = state.selected.is_some(); - let columns_widths = self.get_columns_widths(table_area.width, has_selection); + let (first_column, columns_widths) = + self.get_columns_widths(table_area.width, has_selection); let highlight_symbol = self.highlight_symbol.unwrap_or(""); let blank_symbol = " ".repeat(highlight_symbol.width()); let mut current_height = 0; @@ -401,9 +407,14 @@ impl<'a> StatefulWidget for Table<'a> { ); let mut col = table_area.left(); if has_selection { - col += (highlight_symbol.width() as u16).min(table_area.width); + col += first_column } - for (width, cell) in columns_widths.iter().zip(header.cells.iter()) { + for (i, (width, cell)) in columns_widths + .iter() + .step_by(2) + .zip(header.cells.iter()) + .enumerate() + { render_cell( buf, cell, @@ -414,7 +425,7 @@ impl<'a> StatefulWidget for Table<'a> { height: max_header_height, }, ); - col += *width + self.column_spacing; + col += *width + columns_widths.get(i * 2 + 1).unwrap_or(&0); } current_height += max_header_height; rows_height = rows_height.saturating_sub(max_header_height); @@ -450,13 +461,18 @@ impl<'a> StatefulWidget for Table<'a> { &blank_symbol }; let (col, _) = - buf.set_stringn(col, row, symbol, table_area.width as usize, table_row.style); + buf.set_stringn(col, row, symbol, first_column as usize, table_row.style); col } else { col }; let mut col = table_row_start_col; - for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) { + for (i, (width, cell)) in columns_widths + .iter() + .step_by(2) + .zip(table_row.cells.iter()) + .enumerate() + { render_cell( buf, cell, @@ -467,7 +483,7 @@ impl<'a> StatefulWidget for Table<'a> { height: table_row.height, }, ); - col += *width + self.column_spacing; + col += *width + columns_widths.get(i * 2 + 1).unwrap_or(&0); } if is_selected { buf.set_style(table_row_area, self.highlight_style); @@ -492,14 +508,3 @@ impl<'a> Widget for Table<'a> { StatefulWidget::render(self, area, buf, &mut state); } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic] - fn table_invalid_percentages() { - Table::new(vec![]).widths(&[Constraint::Percentage(110)]); - } -} diff --git a/tests/widgets_gauge.rs b/tests/widgets_gauge.rs index ad1f5f6..ba4cb62 100644 --- a/tests/widgets_gauge.rs +++ b/tests/widgets_gauge.rs @@ -1,7 +1,7 @@ use tui::{ backend::TestBackend, buffer::Buffer, - layout::{Constraint, Direction, Layout, Rect}, + layout::{Constraint, Direction, Layout, Rect, Unit}, style::{Color, Modifier, Style}, symbols, text::Span, @@ -18,7 +18,10 @@ fn widgets_gauge_renders() { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); let gauge = Gauge::default() @@ -87,7 +90,10 @@ fn widgets_gauge_renders_no_unicode() { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .constraints([ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ]) .split(f.size()); let gauge = Gauge::default() diff --git a/tests/widgets_table.rs b/tests/widgets_table.rs index 8f292d9..69a9b33 100644 --- a/tests/widgets_table.rs +++ b/tests/widgets_table.rs @@ -1,7 +1,7 @@ use tui::{ backend::TestBackend, buffer::Buffer, - layout::Constraint, + layout::{Constraint, Unit}, style::{Color, Modifier, Style}, text::{Span, Spans}, widgets::{Block, Borders, Cell, Row, Table, TableState}, @@ -25,11 +25,7 @@ fn widgets_table_column_spacing_can_be_changed() { ]) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::default().borders(Borders::ALL)) - .widths(&[ - Constraint::Length(5), - Constraint::Length(5), - Constraint::Length(5), - ]) + .widths([Constraint::eq(5), Constraint::eq(5), Constraint::eq(5)]) .column_spacing(column_spacing); f.render_widget(table, size); }) @@ -132,11 +128,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() { // columns of zero width show nothing test_case( - &[ - Constraint::Length(0), - Constraint::Length(0), - Constraint::Length(0), - ], + vec![Constraint::eq(0), Constraint::eq(0), Constraint::eq(0)], Buffer::with_lines(vec![ "┌────────────────────────────┐", "│ │", @@ -153,11 +145,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() { // columns of 1 width trim test_case( - &[ - Constraint::Length(1), - Constraint::Length(1), - Constraint::Length(1), - ], + vec![Constraint::eq(1), Constraint::eq(1), Constraint::eq(1)], Buffer::with_lines(vec![ "┌────────────────────────────┐", "│H H H │", @@ -174,11 +162,7 @@ fn widgets_table_columns_widths_can_use_fixed_length_constraints() { // columns of large width just before pushing a column off test_case( - &[ - Constraint::Length(8), - Constraint::Length(8), - Constraint::Length(8), - ], + vec![Constraint::eq(8), Constraint::eq(8), Constraint::eq(8)], Buffer::with_lines(vec![ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", @@ -221,10 +205,10 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() { // columns of zero width show nothing test_case( - &[ - Constraint::Percentage(0), - Constraint::Percentage(0), - Constraint::Percentage(0), + vec![ + Constraint::eq(Unit::Percentage(0)), + Constraint::eq(Unit::Percentage(0)), + Constraint::eq(Unit::Percentage(0)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -242,10 +226,10 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() { // columns of not enough width trims the data test_case( - &[ - Constraint::Percentage(11), - Constraint::Percentage(11), - Constraint::Percentage(11), + vec![ + Constraint::eq(Unit::Percentage(11)), + Constraint::eq(Unit::Percentage(11)), + Constraint::eq(Unit::Percentage(11)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -263,10 +247,10 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() { // columns of large width just before pushing a column off test_case( - &[ - Constraint::Percentage(33), - Constraint::Percentage(33), - Constraint::Percentage(33), + vec![ + Constraint::eq(Unit::Percentage(33)), + Constraint::eq(Unit::Percentage(33)), + Constraint::eq(Unit::Percentage(33)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -284,7 +268,10 @@ fn widgets_table_columns_widths_can_use_percentage_constraints() { // percentages summing to 100 should give equal widths test_case( - &[Constraint::Percentage(50), Constraint::Percentage(50)], + vec![ + Constraint::eq(Unit::Percentage(50)), + Constraint::eq(Unit::Percentage(50)), + ], Buffer::with_lines(vec![ "┌────────────────────────────┐", "│Head1 Head2 │", @@ -326,10 +313,10 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() { // columns of zero width show nothing test_case( - &[ - Constraint::Percentage(0), - Constraint::Length(0), - Constraint::Percentage(0), + vec![ + Constraint::eq(Unit::Percentage(0)), + Constraint::eq(0), + Constraint::eq(Unit::Percentage(0)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -347,10 +334,10 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() { // columns of not enough width trims the data test_case( - &[ - Constraint::Percentage(11), - Constraint::Length(20), - Constraint::Percentage(11), + vec![ + Constraint::eq(Unit::Percentage(11)), + Constraint::eq(20), + Constraint::eq(Unit::Percentage(11)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -368,10 +355,10 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() { // columns of large width just before pushing a column off test_case( - &[ - Constraint::Percentage(33), - Constraint::Length(10), - Constraint::Percentage(33), + vec![ + Constraint::eq(Unit::Percentage(33)), + Constraint::eq(10), + Constraint::eq(Unit::Percentage(33)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -389,10 +376,10 @@ fn widgets_table_columns_widths_can_use_mixed_constraints() { // columns of large size (>100% total) hide the last column test_case( - &[ - Constraint::Percentage(60), - Constraint::Length(10), - Constraint::Percentage(60), + vec![ + Constraint::eq(Unit::Percentage(60)), + Constraint::eq(10), + Constraint::eq(Unit::Percentage(60)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -436,10 +423,10 @@ fn widgets_table_columns_widths_can_use_ratio_constraints() { // columns of zero width show nothing test_case( - &[ - Constraint::Ratio(0, 1), - Constraint::Ratio(0, 1), - Constraint::Ratio(0, 1), + vec![ + Constraint::eq(Unit::Ratio(0, 1)), + Constraint::eq(Unit::Ratio(0, 1)), + Constraint::eq(Unit::Ratio(0, 1)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -457,10 +444,10 @@ fn widgets_table_columns_widths_can_use_ratio_constraints() { // columns of not enough width trims the data test_case( - &[ - Constraint::Ratio(1, 9), - Constraint::Ratio(1, 9), - Constraint::Ratio(1, 9), + vec![ + Constraint::eq(Unit::Ratio(1, 9)), + Constraint::eq(Unit::Ratio(1, 9)), + Constraint::eq(Unit::Ratio(1, 9)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -478,10 +465,10 @@ fn widgets_table_columns_widths_can_use_ratio_constraints() { // columns of large width just before pushing a column off test_case( - &[ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), + vec![ + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), + Constraint::eq(Unit::Ratio(1, 3)), ], Buffer::with_lines(vec![ "┌────────────────────────────┐", @@ -499,7 +486,10 @@ fn widgets_table_columns_widths_can_use_ratio_constraints() { // percentages summing to 100 should give equal widths test_case( - &[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], + vec![ + Constraint::eq(Unit::Ratio(1, 2)), + Constraint::eq(Unit::Ratio(1, 2)), + ], Buffer::with_lines(vec![ "┌────────────────────────────┐", "│Head1 Head2 │", @@ -532,11 +522,7 @@ fn widgets_table_can_have_rows_with_multi_lines() { .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::default().borders(Borders::ALL)) .highlight_symbol(">> ") - .widths(&[ - Constraint::Length(5), - Constraint::Length(5), - Constraint::Length(5), - ]) + .widths([Constraint::eq(5), Constraint::eq(5), Constraint::eq(5)]) .column_spacing(1); f.render_stateful_widget(table, size, state); }) @@ -635,11 +621,7 @@ fn widgets_table_can_have_elements_styled_individually() { .block(Block::default().borders(Borders::LEFT | Borders::RIGHT)) .highlight_symbol(">> ") .highlight_style(Style::default().add_modifier(Modifier::BOLD)) - .widths(&[ - Constraint::Length(6), - Constraint::Length(6), - Constraint::Length(6), - ]) + .widths([Constraint::eq(6), Constraint::eq(6), Constraint::eq(6)]) .column_spacing(1); f.render_stateful_widget(table, size, &mut state); }) @@ -696,11 +678,7 @@ fn widgets_table_should_render_even_if_empty() { let table = Table::new(vec![]) .header(Row::new(vec!["Head1", "Head2", "Head3"])) .block(Block::default().borders(Borders::LEFT | Borders::RIGHT)) - .widths(&[ - Constraint::Length(6), - Constraint::Length(6), - Constraint::Length(6), - ]) + .widths([Constraint::eq(6), Constraint::eq(6), Constraint::eq(6)]) .column_spacing(1); f.render_widget(table, size); }) @@ -736,11 +714,11 @@ fn widgets_table_columns_dont_panic() { .block(Block::default().borders(Borders::ALL)) .highlight_symbol(">> ") .column_spacing(1) - .widths(&[ - Constraint::Percentage(15), - Constraint::Percentage(15), - Constraint::Percentage(25), - Constraint::Percentage(45), + .widths([ + Constraint::eq(Unit::Percentage(15)), + Constraint::eq(Unit::Percentage(15)), + Constraint::eq(Unit::Percentage(25)), + Constraint::eq(Unit::Percentage(45)), ]); let mut state = TableState::default(); @@ -771,11 +749,7 @@ fn widgets_table_should_clamp_offset_if_rows_are_removed() { ]) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::default().borders(Borders::ALL)) - .widths(&[ - Constraint::Length(5), - Constraint::Length(5), - Constraint::Length(5), - ]) + .widths([Constraint::eq(5), Constraint::eq(5), Constraint::eq(5)]) .column_spacing(1); f.render_stateful_widget(table, size, &mut state); }) @@ -800,11 +774,7 @@ fn widgets_table_should_clamp_offset_if_rows_are_removed() { let table = Table::new(vec![Row::new(vec!["Row31", "Row32", "Row33"])]) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::default().borders(Borders::ALL)) - .widths(&[ - Constraint::Length(5), - Constraint::Length(5), - Constraint::Length(5), - ]) + .widths([Constraint::eq(5), Constraint::eq(5), Constraint::eq(5)]) .column_spacing(1); f.render_stateful_widget(table, size, &mut state); })