mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-28 13:31:14 +00:00
Simpler layout and cleanup api
This commit is contained in:
parent
b411690fdd
commit
ea485b5439
@ -23,7 +23,7 @@ use log4rs::config::{Appender, Config, Root};
|
||||
use tui::Terminal;
|
||||
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
|
||||
BarChart};
|
||||
use tui::layout::{Group, Direction, Alignment, Size};
|
||||
use tui::layout::{Group, Direction, Size};
|
||||
use tui::style::Color;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -234,44 +234,41 @@ fn draw(t: &mut Terminal, app: &App) {
|
||||
|
||||
Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
.alignment(Alignment::Left)
|
||||
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(7)])
|
||||
.sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)])
|
||||
.render(t, &size, |t, chunks| {
|
||||
Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t);
|
||||
Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
.alignment(Alignment::Left)
|
||||
.margin(1)
|
||||
.chunks(&[Size::Fixed(2), Size::Fixed(3)])
|
||||
.sizes(&[Size::Fixed(2), Size::Fixed(3)])
|
||||
.render(t, &chunks[0], |t, chunks| {
|
||||
Gauge::default()
|
||||
.block(Block::default().title("Gauge:"))
|
||||
.bg(Color::Magenta)
|
||||
.background_color(Color::Magenta)
|
||||
.percent(app.progress)
|
||||
.render(&chunks[0], t);
|
||||
Sparkline::default()
|
||||
.block(Block::default().title("Sparkline:"))
|
||||
.fg(Color::Green)
|
||||
.color(Color::Green)
|
||||
.data(&app.data)
|
||||
.render(&chunks[1], t);
|
||||
});
|
||||
let sizes = if app.show_chart {
|
||||
vec![Size::Max(40), Size::Min(20)]
|
||||
vec![Size::Percent(50), Size::Percent(50)]
|
||||
} else {
|
||||
vec![Size::Max(40)]
|
||||
vec![Size::Percent(100)]
|
||||
};
|
||||
Group::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.alignment(Alignment::Left)
|
||||
.chunks(&sizes)
|
||||
.sizes(&sizes)
|
||||
.render(t, &chunks[1], |t, chunks| {
|
||||
Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
.chunks(&[Size::Min(20), Size::Max(40)])
|
||||
.sizes(&[Size::Percent(50), Size::Percent(50)])
|
||||
.render(t, &chunks[0], |t, chunks| {
|
||||
Group::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.chunks(&[Size::Max(20), Size::Min(0)])
|
||||
.sizes(&[Size::Percent(50), Size::Percent(50)])
|
||||
.render(t, &chunks[0], |t, chunks| {
|
||||
List::default()
|
||||
.block(Block::default().borders(border::ALL).title("List"))
|
||||
|
109
src/layout.rs
109
src/layout.rs
@ -3,21 +3,12 @@ use std::collections::HashMap;
|
||||
|
||||
use cassowary::{Solver, Variable, Constraint};
|
||||
use cassowary::WeightedRelation::*;
|
||||
use cassowary::strength::{WEAK, REQUIRED};
|
||||
use cassowary::strength::{REQUIRED, WEAK};
|
||||
|
||||
use terminal::Terminal;
|
||||
use util::hash;
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum Alignment {
|
||||
Top,
|
||||
Left,
|
||||
Center,
|
||||
Bottom,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Hash)]
|
||||
#[derive(Hash, PartialEq)]
|
||||
pub enum Direction {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
@ -104,6 +95,7 @@ impl Rect {
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum Size {
|
||||
Fixed(u16),
|
||||
Percent(u16),
|
||||
Max(u16),
|
||||
Min(u16),
|
||||
}
|
||||
@ -123,12 +115,7 @@ pub enum Size {
|
||||
///
|
||||
/// ```
|
||||
#[allow(unused_variables)]
|
||||
pub fn split(area: &Rect,
|
||||
dir: &Direction,
|
||||
align: &Alignment,
|
||||
margin: u16,
|
||||
sizes: &[Size])
|
||||
-> Vec<Rect> {
|
||||
pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
|
||||
let mut solver = Solver::new();
|
||||
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
|
||||
let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>();
|
||||
@ -145,15 +132,15 @@ pub fn split(area: &Rect,
|
||||
constraints.push(match *dir {
|
||||
Direction::Horizontal => first.x | EQ(REQUIRED) | dest_area.x as f64,
|
||||
Direction::Vertical => first.y | EQ(REQUIRED) | dest_area.y as f64,
|
||||
})
|
||||
});
|
||||
}
|
||||
if let Some(last) = elements.last() {
|
||||
constraints.push(match *dir {
|
||||
Direction::Horizontal => {
|
||||
(last.x + last.width) | EQ(WEAK) | (dest_area.x + dest_area.width) as f64
|
||||
(last.x + last.width) | EQ(REQUIRED) | (dest_area.x + dest_area.width) as f64
|
||||
}
|
||||
Direction::Vertical => {
|
||||
(last.y + last.height) | EQ(WEAK) | (dest_area.y + dest_area.height) as f64
|
||||
(last.y + last.height) | EQ(REQUIRED) | (dest_area.y + dest_area.height) as f64
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -163,14 +150,16 @@ pub fn split(area: &Rect,
|
||||
constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].y | EQ(REQUIRED) | dest_area.y as f64,
|
||||
elements[i].height | EQ(REQUIRED) | dest_area.height as f64,
|
||||
match *size {
|
||||
Size::Fixed(v) => elements[i].width | EQ(REQUIRED) | v as f64,
|
||||
Size::Min(v) => elements[i].width | GE(REQUIRED) | v as f64,
|
||||
Size::Max(v) => elements[i].width | LE(REQUIRED) | v as f64,
|
||||
}];
|
||||
constraints.extend_from_slice(&cs);
|
||||
constraints.push(elements[i].y | EQ(REQUIRED) | dest_area.y as f64);
|
||||
constraints.push(elements[i].height | EQ(REQUIRED) | dest_area.height as f64);
|
||||
constraints.push(match *size {
|
||||
Size::Fixed(v) => elements[i].width | EQ(WEAK) | v as f64,
|
||||
Size::Percent(v) => {
|
||||
elements[i].width | EQ(WEAK) | ((v * dest_area.width) as f64 / 100.0)
|
||||
}
|
||||
Size::Min(v) => elements[i].width | GE(WEAK) | v as f64,
|
||||
Size::Max(v) => elements[i].width | LE(WEAK) | v as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
Direction::Vertical => {
|
||||
@ -178,14 +167,16 @@ pub fn split(area: &Rect,
|
||||
constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].x | EQ(REQUIRED) | dest_area.x as f64,
|
||||
elements[i].width | EQ(REQUIRED) | dest_area.width as f64,
|
||||
match *size {
|
||||
Size::Fixed(v) => elements[i].height | EQ(REQUIRED) | v as f64,
|
||||
Size::Min(v) => elements[i].height | GE(REQUIRED) | v as f64,
|
||||
Size::Max(v) => elements[i].height | LE(REQUIRED) | v as f64,
|
||||
}];
|
||||
constraints.extend_from_slice(&cs);
|
||||
constraints.push(elements[i].x | EQ(REQUIRED) | dest_area.x as f64);
|
||||
constraints.push(elements[i].width | EQ(REQUIRED) | dest_area.width as f64);
|
||||
constraints.push(match *size {
|
||||
Size::Fixed(v) => elements[i].height | EQ(WEAK) | v as f64,
|
||||
Size::Percent(v) => {
|
||||
elements[i].height | EQ(WEAK) | ((v * dest_area.height) as f64 / 100.0)
|
||||
}
|
||||
Size::Min(v) => elements[i].height | GE(WEAK) | v as f64,
|
||||
Size::Max(v) => elements[i].height | LE(WEAK) | v as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,30 +184,32 @@ pub fn split(area: &Rect,
|
||||
// TODO: Find a better way to handle overflow error
|
||||
for &(var, value) in solver.fetch_changes() {
|
||||
let (index, attr) = vars[&var];
|
||||
let value = value as u16;
|
||||
match attr {
|
||||
0 => {
|
||||
results[index].x = value as u16;
|
||||
if value <= area.width {
|
||||
results[index].x = value;
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
results[index].y = value as u16;
|
||||
if value <= area.height {
|
||||
results[index].y = value;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let mut v = value as u16;
|
||||
if v > area.width {
|
||||
v = 0;
|
||||
if value <= area.width {
|
||||
results[index].width = value;
|
||||
}
|
||||
results[index].width = v;
|
||||
}
|
||||
3 => {
|
||||
let mut v = value as u16;
|
||||
if v > area.height {
|
||||
v = 0;
|
||||
if value <= area.height {
|
||||
results[index].height = value;
|
||||
}
|
||||
results[index].height = v;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
info!("{:?}, {:?}", results, dest_area);
|
||||
results
|
||||
}
|
||||
|
||||
@ -241,18 +234,16 @@ impl Element {
|
||||
#[derive(Hash)]
|
||||
pub struct Group {
|
||||
direction: Direction,
|
||||
alignment: Alignment,
|
||||
margin: u16,
|
||||
chunks: Vec<Size>,
|
||||
sizes: Vec<Size>,
|
||||
}
|
||||
|
||||
impl Default for Group {
|
||||
fn default() -> Group {
|
||||
Group {
|
||||
direction: Direction::Horizontal,
|
||||
alignment: Alignment::Left,
|
||||
margin: 0,
|
||||
chunks: Vec::new(),
|
||||
sizes: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,18 +254,13 @@ impl Group {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(&mut self, alignment: Alignment) -> &mut Group {
|
||||
self.alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn margin(&mut self, margin: u16) -> &mut Group {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn chunks(&mut self, chunks: &[Size]) -> &mut Group {
|
||||
self.chunks = Vec::from(chunks);
|
||||
pub fn sizes(&mut self, sizes: &[Size]) -> &mut Group {
|
||||
self.sizes = Vec::from(sizes);
|
||||
self
|
||||
}
|
||||
pub fn render<F>(&self, t: &mut Terminal, area: &Rect, mut f: F)
|
||||
@ -283,14 +269,7 @@ impl Group {
|
||||
let hash = hash(self, area);
|
||||
let (cache_update, chunks) = match t.get_layout(hash) {
|
||||
Some(chs) => (false, chs.to_vec()),
|
||||
None => {
|
||||
(true,
|
||||
split(area,
|
||||
&self.direction,
|
||||
&self.alignment,
|
||||
self.margin,
|
||||
&self.chunks))
|
||||
}
|
||||
None => (true, split(area, &self.direction, self.margin, &self.sizes)),
|
||||
};
|
||||
|
||||
f(t, &chunks);
|
||||
|
@ -160,8 +160,11 @@ impl<'a> Chart<'a> {
|
||||
|
||||
fn layout(&self, inner: &Rect, outer: &Rect) -> ChartLayout {
|
||||
let mut layout = ChartLayout::default();
|
||||
if inner.height == 0 || inner.width == 0 {
|
||||
return layout;
|
||||
}
|
||||
let mut x = inner.x - outer.x;
|
||||
let mut y = inner.height - 1 + (inner.y - outer.y);
|
||||
let mut y = inner.height + (inner.y - outer.y) - 1;
|
||||
|
||||
if self.x_axis.labels.is_some() && y > 1 {
|
||||
layout.label_x = Some(y);
|
||||
@ -215,6 +218,9 @@ impl<'a> Widget<'a> for Chart<'a> {
|
||||
};
|
||||
|
||||
let layout = self.layout(&chart_area, area);
|
||||
if layout.graph_area.width == 0 || layout.graph_area.height == 0 {
|
||||
return buf;
|
||||
}
|
||||
let width = layout.graph_area.width;
|
||||
let height = layout.graph_area.height;
|
||||
let margin_x = layout.graph_area.x - area.x;
|
||||
|
@ -23,8 +23,8 @@ pub struct Gauge<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
percent: u16,
|
||||
percent_string: String,
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
color: Color,
|
||||
background_color: Color,
|
||||
}
|
||||
|
||||
impl<'a> Default for Gauge<'a> {
|
||||
@ -33,8 +33,8 @@ impl<'a> Default for Gauge<'a> {
|
||||
block: None,
|
||||
percent: 0,
|
||||
percent_string: String::from("0%"),
|
||||
bg: Color::Reset,
|
||||
fg: Color::Reset,
|
||||
color: Color::Reset,
|
||||
background_color: Color::Reset,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,13 +51,13 @@ impl<'a> Gauge<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&mut self, bg: Color) -> &mut Gauge<'a> {
|
||||
self.bg = bg;
|
||||
pub fn color(&mut self, color: Color) -> &mut Gauge<'a> {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(&mut self, fg: Color) -> &mut Gauge<'a> {
|
||||
self.fg = fg;
|
||||
pub fn background_color(&mut self, color: Color) -> &mut Gauge<'a> {
|
||||
self.background_color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -76,15 +76,23 @@ impl<'a> Widget<'a> for Gauge<'a> {
|
||||
// Gauge
|
||||
let width = (gauge_area.width * self.percent) / 100;
|
||||
for i in 0..width {
|
||||
buf.update_cell(margin_x + i, margin_y, " ", self.fg, self.bg);
|
||||
buf.update_cell(margin_x + i,
|
||||
margin_y,
|
||||
" ",
|
||||
self.color,
|
||||
self.background_color);
|
||||
}
|
||||
// Label
|
||||
let len = self.percent_string.len() as u16;
|
||||
let middle = gauge_area.width / 2 - len / 2;
|
||||
buf.set_string(middle, margin_y, &self.percent_string, self.bg, self.fg);
|
||||
buf.set_string(middle,
|
||||
margin_y,
|
||||
&self.percent_string,
|
||||
self.background_color,
|
||||
self.color);
|
||||
let bound = max(middle, min(middle + len, width));
|
||||
for i in middle..bound {
|
||||
buf.update_colors(margin_x + i, margin_y, self.fg, self.bg);
|
||||
buf.update_colors(margin_x + i, margin_y, self.color, self.background_color);
|
||||
}
|
||||
}
|
||||
buf
|
||||
|
@ -12,8 +12,8 @@ pub struct List<'a> {
|
||||
selected: usize,
|
||||
selection_symbol: Option<&'a str>,
|
||||
selection_color: Color,
|
||||
text_color: Color,
|
||||
bg: Color,
|
||||
color: Color,
|
||||
background_color: Color,
|
||||
items: &'a [&'a str],
|
||||
}
|
||||
|
||||
@ -24,8 +24,8 @@ impl<'a> Default for List<'a> {
|
||||
selected: 0,
|
||||
selection_symbol: None,
|
||||
selection_color: Color::Reset,
|
||||
text_color: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
color: Color::Reset,
|
||||
background_color: Color::Reset,
|
||||
items: &[],
|
||||
}
|
||||
}
|
||||
@ -42,13 +42,13 @@ impl<'a> List<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_color(&'a mut self, text_color: Color) -> &mut List<'a> {
|
||||
self.text_color = text_color;
|
||||
pub fn color(&'a mut self, color: Color) -> &mut List<'a> {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&'a mut self, bg: Color) -> &mut List<'a> {
|
||||
self.bg = bg;
|
||||
pub fn background_color(&'a mut self, color: Color) -> &mut List<'a> {
|
||||
self.background_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
@ -93,16 +93,16 @@ impl<'a> Widget<'a> for List<'a> {
|
||||
let color = if index == self.selected {
|
||||
self.selection_color
|
||||
} else {
|
||||
self.text_color
|
||||
self.color
|
||||
};
|
||||
buf.set_string(x, 1 + i as u16, item, color, self.bg);
|
||||
buf.set_string(x, 1 + i as u16, item, color, self.background_color);
|
||||
}
|
||||
if let Some(s) = self.selection_symbol {
|
||||
buf.set_string(1,
|
||||
(1 + self.selected - offset) as u16,
|
||||
s,
|
||||
self.selection_color,
|
||||
self.bg);
|
||||
self.background_color);
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ use symbols::bar;
|
||||
|
||||
pub struct Sparkline<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
data: Vec<u64>,
|
||||
color: Color,
|
||||
background_color: Color,
|
||||
data: &'a [u64],
|
||||
max: Option<u64>,
|
||||
}
|
||||
|
||||
@ -18,9 +18,9 @@ impl<'a> Default for Sparkline<'a> {
|
||||
fn default() -> Sparkline<'a> {
|
||||
Sparkline {
|
||||
block: None,
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
data: Vec::new(),
|
||||
color: Color::Reset,
|
||||
background_color: Color::Reset,
|
||||
data: &[],
|
||||
max: None,
|
||||
}
|
||||
}
|
||||
@ -32,19 +32,19 @@ impl<'a> Sparkline<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(&mut self, fg: Color) -> &mut Sparkline<'a> {
|
||||
self.fg = fg;
|
||||
pub fn color(&mut self, color: Color) -> &mut Sparkline<'a> {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&mut self, bg: Color) -> &mut Sparkline<'a> {
|
||||
self.bg = bg;
|
||||
pub fn background_color(&mut self, color: Color) -> &mut Sparkline<'a> {
|
||||
self.background_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn data(&mut self, data: &[u64]) -> &mut Sparkline<'a> {
|
||||
self.data = data.to_vec();
|
||||
pub fn data(&mut self, data: &'a [u64]) -> &mut Sparkline<'a> {
|
||||
self.data = data;
|
||||
self
|
||||
}
|
||||
|
||||
@ -88,7 +88,11 @@ impl<'a> Widget<'a> for Sparkline<'a> {
|
||||
7 => bar::SEVEN_EIGHTHS,
|
||||
_ => bar::FULL,
|
||||
};
|
||||
buf.update_cell(margin_x + i as u16, margin_y + j, symbol, self.fg, self.bg);
|
||||
buf.update_cell(margin_x + i as u16,
|
||||
margin_y + j,
|
||||
symbol,
|
||||
self.color,
|
||||
self.background_color);
|
||||
|
||||
if *d > 8 {
|
||||
*d -= 8;
|
||||
|
Loading…
x
Reference in New Issue
Block a user