mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-21 20:23:11 +00:00
feat(layout): add support for multiple weighted constraints by chunks
This commit is contained in:
parent
3797863e14
commit
c64d754f88
20 changed files with 445 additions and 370 deletions
|
@ -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<dyn Error>> {
|
|||
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<dyn Error>> {
|
|||
|
||||
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()
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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<dyn Error>> {
|
|||
// 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
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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"))
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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(
|
||||
|
|
|
@ -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<B: Backend>(f: &mut Frame<B>, 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]);
|
||||
}
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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()
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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);
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
// 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.
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
.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);
|
||||
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
|
||||
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. ";
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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(
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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<dyn Error>> {
|
|||
.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);
|
||||
})?;
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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));
|
||||
|
|
|
@ -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<dyn Error>> {
|
|||
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 {
|
||||
|
|
247
src/layout.rs
247
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<u16> 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<T>(u: T) -> Constraint
|
||||
where
|
||||
T: Into<Unit>,
|
||||
{
|
||||
let u = u.into();
|
||||
u.check();
|
||||
Constraint {
|
||||
operator: Operator::GreaterThanOrEqual,
|
||||
unit: u,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lte<T>(u: T) -> Constraint
|
||||
where
|
||||
T: Into<Unit>,
|
||||
{
|
||||
let u = u.into();
|
||||
u.check();
|
||||
Constraint {
|
||||
operator: Operator::LessThanOrEqual,
|
||||
unit: u,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq<T>(u: T) -> Constraint
|
||||
where
|
||||
T: Into<Unit>,
|
||||
{
|
||||
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<Constraint>);
|
||||
|
||||
impl From<Constraint> for Constraints {
|
||||
fn from(c: Constraint) -> Constraints {
|
||||
Constraints(vec![c])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::iter::FromIterator<T> for Constraints
|
||||
where
|
||||
T: Into<Constraint>,
|
||||
{
|
||||
fn from_iter<I>(iter: I) -> Constraints
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
{
|
||||
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<Constraint>,
|
||||
/// Whether the last chunk of the computed layout should be expanded to fill the available
|
||||
/// space.
|
||||
expand_to_fill: bool,
|
||||
constraints: Vec<Constraints>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
|
@ -81,17 +178,17 @@ impl Default for Layout {
|
|||
vertical: 0,
|
||||
},
|
||||
constraints: Vec::new(),
|
||||
expand_to_fill: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn constraints<C>(mut self, constraints: C) -> Layout
|
||||
pub fn constraints<I>(mut self, constraints: I) -> Layout
|
||||
where
|
||||
C: Into<Vec<Constraint>>,
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraints>,
|
||||
{
|
||||
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<Rect> {
|
|||
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,23 +372,31 @@ fn split(area: Rect, layout: &Layout) -> Vec<Rect> {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
fn apply_constraints(
|
||||
ccs: &mut Vec<CassowaryConstraint>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// A container used by the solver inside split
|
||||
struct Element {
|
||||
x: Variable,
|
||||
|
@ -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::<u16>());
|
||||
|
|
15
src/lib.rs
15
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;
|
||||
|
|
|
@ -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<Rect>,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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<Constraints>,
|
||||
/// 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<I>(mut self, widths: I) -> Self
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<Constraints>,
|
||||
{
|
||||
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<u16> {
|
||||
let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
|
||||
fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> (u16, Vec<u16>) {
|
||||
let mut constraints: Vec<Constraints> = 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)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue