Simpler layout and cleanup api

pull/3/head
Florian Dehau 8 years ago
parent b411690fdd
commit ea485b5439

@ -23,7 +23,7 @@ use log4rs::config::{Appender, Config, Root};
use tui::Terminal; use tui::Terminal;
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
BarChart}; BarChart};
use tui::layout::{Group, Direction, Alignment, Size}; use tui::layout::{Group, Direction, Size};
use tui::style::Color; use tui::style::Color;
#[derive(Clone)] #[derive(Clone)]
@ -234,44 +234,41 @@ fn draw(t: &mut Terminal, app: &App) {
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.alignment(Alignment::Left) .sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)])
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(7)])
.render(t, &size, |t, chunks| { .render(t, &size, |t, chunks| {
Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t); Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t);
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.alignment(Alignment::Left)
.margin(1) .margin(1)
.chunks(&[Size::Fixed(2), Size::Fixed(3)]) .sizes(&[Size::Fixed(2), Size::Fixed(3)])
.render(t, &chunks[0], |t, chunks| { .render(t, &chunks[0], |t, chunks| {
Gauge::default() Gauge::default()
.block(Block::default().title("Gauge:")) .block(Block::default().title("Gauge:"))
.bg(Color::Magenta) .background_color(Color::Magenta)
.percent(app.progress) .percent(app.progress)
.render(&chunks[0], t); .render(&chunks[0], t);
Sparkline::default() Sparkline::default()
.block(Block::default().title("Sparkline:")) .block(Block::default().title("Sparkline:"))
.fg(Color::Green) .color(Color::Green)
.data(&app.data) .data(&app.data)
.render(&chunks[1], t); .render(&chunks[1], t);
}); });
let sizes = if app.show_chart { let sizes = if app.show_chart {
vec![Size::Max(40), Size::Min(20)] vec![Size::Percent(50), Size::Percent(50)]
} else { } else {
vec![Size::Max(40)] vec![Size::Percent(100)]
}; };
Group::default() Group::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.alignment(Alignment::Left) .sizes(&sizes)
.chunks(&sizes)
.render(t, &chunks[1], |t, chunks| { .render(t, &chunks[1], |t, chunks| {
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.chunks(&[Size::Min(20), Size::Max(40)]) .sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| { .render(t, &chunks[0], |t, chunks| {
Group::default() Group::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.chunks(&[Size::Max(20), Size::Min(0)]) .sizes(&[Size::Percent(50), Size::Percent(50)])
.render(t, &chunks[0], |t, chunks| { .render(t, &chunks[0], |t, chunks| {
List::default() List::default()
.block(Block::default().borders(border::ALL).title("List")) .block(Block::default().borders(border::ALL).title("List"))

@ -3,21 +3,12 @@ use std::collections::HashMap;
use cassowary::{Solver, Variable, Constraint}; use cassowary::{Solver, Variable, Constraint};
use cassowary::WeightedRelation::*; use cassowary::WeightedRelation::*;
use cassowary::strength::{WEAK, REQUIRED}; use cassowary::strength::{REQUIRED, WEAK};
use terminal::Terminal; use terminal::Terminal;
use util::hash; use util::hash;
#[derive(Hash)] #[derive(Hash, PartialEq)]
pub enum Alignment {
Top,
Left,
Center,
Bottom,
Right,
}
#[derive(Hash)]
pub enum Direction { pub enum Direction {
Horizontal, Horizontal,
Vertical, Vertical,
@ -104,6 +95,7 @@ impl Rect {
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub enum Size { pub enum Size {
Fixed(u16), Fixed(u16),
Percent(u16),
Max(u16), Max(u16),
Min(u16), Min(u16),
} }
@ -123,12 +115,7 @@ pub enum Size {
/// ///
/// ``` /// ```
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn split(area: &Rect, pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
dir: &Direction,
align: &Alignment,
margin: u16,
sizes: &[Size])
-> Vec<Rect> {
let mut solver = Solver::new(); let mut solver = Solver::new();
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new(); let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>(); let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>();
@ -145,15 +132,15 @@ pub fn split(area: &Rect,
constraints.push(match *dir { constraints.push(match *dir {
Direction::Horizontal => first.x | EQ(REQUIRED) | dest_area.x as f64, Direction::Horizontal => first.x | EQ(REQUIRED) | dest_area.x as f64,
Direction::Vertical => first.y | EQ(REQUIRED) | dest_area.y as f64, Direction::Vertical => first.y | EQ(REQUIRED) | dest_area.y as f64,
}) });
} }
if let Some(last) = elements.last() { if let Some(last) = elements.last() {
constraints.push(match *dir { constraints.push(match *dir {
Direction::Horizontal => { 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 => { 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); constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
} }
for (i, size) in sizes.iter().enumerate() { for (i, size) in sizes.iter().enumerate() {
let cs = [elements[i].y | EQ(REQUIRED) | dest_area.y as f64, constraints.push(elements[i].y | EQ(REQUIRED) | dest_area.y as f64);
elements[i].height | EQ(REQUIRED) | dest_area.height as f64, constraints.push(elements[i].height | EQ(REQUIRED) | dest_area.height as f64);
match *size { constraints.push(match *size {
Size::Fixed(v) => elements[i].width | EQ(REQUIRED) | v as f64, Size::Fixed(v) => elements[i].width | EQ(WEAK) | v as f64,
Size::Min(v) => elements[i].width | GE(REQUIRED) | v as f64, Size::Percent(v) => {
Size::Max(v) => elements[i].width | LE(REQUIRED) | v as f64, elements[i].width | EQ(WEAK) | ((v * dest_area.width) as f64 / 100.0)
}]; }
constraints.extend_from_slice(&cs); Size::Min(v) => elements[i].width | GE(WEAK) | v as f64,
Size::Max(v) => elements[i].width | LE(WEAK) | v as f64,
});
} }
} }
Direction::Vertical => { Direction::Vertical => {
@ -178,14 +167,16 @@ pub fn split(area: &Rect,
constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y); constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
} }
for (i, size) in sizes.iter().enumerate() { for (i, size) in sizes.iter().enumerate() {
let cs = [elements[i].x | EQ(REQUIRED) | dest_area.x as f64, constraints.push(elements[i].x | EQ(REQUIRED) | dest_area.x as f64);
elements[i].width | EQ(REQUIRED) | dest_area.width as f64, constraints.push(elements[i].width | EQ(REQUIRED) | dest_area.width as f64);
match *size { constraints.push(match *size {
Size::Fixed(v) => elements[i].height | EQ(REQUIRED) | v as f64, Size::Fixed(v) => elements[i].height | EQ(WEAK) | v as f64,
Size::Min(v) => elements[i].height | GE(REQUIRED) | v as f64, Size::Percent(v) => {
Size::Max(v) => elements[i].height | LE(REQUIRED) | v as f64, elements[i].height | EQ(WEAK) | ((v * dest_area.height) as f64 / 100.0)
}]; }
constraints.extend_from_slice(&cs); 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 // TODO: Find a better way to handle overflow error
for &(var, value) in solver.fetch_changes() { for &(var, value) in solver.fetch_changes() {
let (index, attr) = vars[&var]; let (index, attr) = vars[&var];
let value = value as u16;
match attr { match attr {
0 => { 0 => {
results[index].x = value as u16; if value <= area.width {
results[index].x = value;
}
} }
1 => { 1 => {
results[index].y = value as u16; if value <= area.height {
results[index].y = value;
}
} }
2 => { 2 => {
let mut v = value as u16; if value <= area.width {
if v > area.width { results[index].width = value;
v = 0;
} }
results[index].width = v;
} }
3 => { 3 => {
let mut v = value as u16; if value <= area.height {
if v > area.height { results[index].height = value;
v = 0;
} }
results[index].height = v;
} }
_ => {} _ => {}
} }
} }
info!("{:?}, {:?}", results, dest_area);
results results
} }
@ -241,18 +234,16 @@ impl Element {
#[derive(Hash)] #[derive(Hash)]
pub struct Group { pub struct Group {
direction: Direction, direction: Direction,
alignment: Alignment,
margin: u16, margin: u16,
chunks: Vec<Size>, sizes: Vec<Size>,
} }
impl Default for Group { impl Default for Group {
fn default() -> Group { fn default() -> Group {
Group { Group {
direction: Direction::Horizontal, direction: Direction::Horizontal,
alignment: Alignment::Left,
margin: 0, margin: 0,
chunks: Vec::new(), sizes: Vec::new(),
} }
} }
} }
@ -263,18 +254,13 @@ impl Group {
self self
} }
pub fn alignment(&mut self, alignment: Alignment) -> &mut Group {
self.alignment = alignment;
self
}
pub fn margin(&mut self, margin: u16) -> &mut Group { pub fn margin(&mut self, margin: u16) -> &mut Group {
self.margin = margin; self.margin = margin;
self self
} }
pub fn chunks(&mut self, chunks: &[Size]) -> &mut Group { pub fn sizes(&mut self, sizes: &[Size]) -> &mut Group {
self.chunks = Vec::from(chunks); self.sizes = Vec::from(sizes);
self self
} }
pub fn render<F>(&self, t: &mut Terminal, area: &Rect, mut f: F) 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 hash = hash(self, area);
let (cache_update, chunks) = match t.get_layout(hash) { let (cache_update, chunks) = match t.get_layout(hash) {
Some(chs) => (false, chs.to_vec()), Some(chs) => (false, chs.to_vec()),
None => { None => (true, split(area, &self.direction, self.margin, &self.sizes)),
(true,
split(area,
&self.direction,
&self.alignment,
self.margin,
&self.chunks))
}
}; };
f(t, &chunks); f(t, &chunks);

@ -160,8 +160,11 @@ impl<'a> Chart<'a> {
fn layout(&self, inner: &Rect, outer: &Rect) -> ChartLayout { fn layout(&self, inner: &Rect, outer: &Rect) -> ChartLayout {
let mut layout = ChartLayout::default(); let mut layout = ChartLayout::default();
if inner.height == 0 || inner.width == 0 {
return layout;
}
let mut x = inner.x - outer.x; 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 { if self.x_axis.labels.is_some() && y > 1 {
layout.label_x = Some(y); layout.label_x = Some(y);
@ -215,6 +218,9 @@ impl<'a> Widget<'a> for Chart<'a> {
}; };
let layout = self.layout(&chart_area, area); 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 width = layout.graph_area.width;
let height = layout.graph_area.height; let height = layout.graph_area.height;
let margin_x = layout.graph_area.x - area.x; let margin_x = layout.graph_area.x - area.x;

@ -23,8 +23,8 @@ pub struct Gauge<'a> {
block: Option<Block<'a>>, block: Option<Block<'a>>,
percent: u16, percent: u16,
percent_string: String, percent_string: String,
fg: Color, color: Color,
bg: Color, background_color: Color,
} }
impl<'a> Default for Gauge<'a> { impl<'a> Default for Gauge<'a> {
@ -33,8 +33,8 @@ impl<'a> Default for Gauge<'a> {
block: None, block: None,
percent: 0, percent: 0,
percent_string: String::from("0%"), percent_string: String::from("0%"),
bg: Color::Reset, color: Color::Reset,
fg: Color::Reset, background_color: Color::Reset,
} }
} }
} }
@ -51,13 +51,13 @@ impl<'a> Gauge<'a> {
self self
} }
pub fn bg(&mut self, bg: Color) -> &mut Gauge<'a> { pub fn color(&mut self, color: Color) -> &mut Gauge<'a> {
self.bg = bg; self.color = color;
self self
} }
pub fn fg(&mut self, fg: Color) -> &mut Gauge<'a> { pub fn background_color(&mut self, color: Color) -> &mut Gauge<'a> {
self.fg = fg; self.background_color = color;
self self
} }
} }
@ -76,15 +76,23 @@ impl<'a> Widget<'a> for Gauge<'a> {
// Gauge // Gauge
let width = (gauge_area.width * self.percent) / 100; let width = (gauge_area.width * self.percent) / 100;
for i in 0..width { 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 // Label
let len = self.percent_string.len() as u16; let len = self.percent_string.len() as u16;
let middle = gauge_area.width / 2 - len / 2; 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)); let bound = max(middle, min(middle + len, width));
for i in middle..bound { 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 buf

@ -12,8 +12,8 @@ pub struct List<'a> {
selected: usize, selected: usize,
selection_symbol: Option<&'a str>, selection_symbol: Option<&'a str>,
selection_color: Color, selection_color: Color,
text_color: Color, color: Color,
bg: Color, background_color: Color,
items: &'a [&'a str], items: &'a [&'a str],
} }
@ -24,8 +24,8 @@ impl<'a> Default for List<'a> {
selected: 0, selected: 0,
selection_symbol: None, selection_symbol: None,
selection_color: Color::Reset, selection_color: Color::Reset,
text_color: Color::Reset, color: Color::Reset,
bg: Color::Reset, background_color: Color::Reset,
items: &[], items: &[],
} }
} }
@ -42,13 +42,13 @@ impl<'a> List<'a> {
self self
} }
pub fn text_color(&'a mut self, text_color: Color) -> &mut List<'a> { pub fn color(&'a mut self, color: Color) -> &mut List<'a> {
self.text_color = text_color; self.color = color;
self self
} }
pub fn bg(&'a mut self, bg: Color) -> &mut List<'a> { pub fn background_color(&'a mut self, color: Color) -> &mut List<'a> {
self.bg = bg; self.background_color = color;
self self
} }
@ -93,16 +93,16 @@ impl<'a> Widget<'a> for List<'a> {
let color = if index == self.selected { let color = if index == self.selected {
self.selection_color self.selection_color
} else { } 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 { if let Some(s) = self.selection_symbol {
buf.set_string(1, buf.set_string(1,
(1 + self.selected - offset) as u16, (1 + self.selected - offset) as u16,
s, s,
self.selection_color, self.selection_color,
self.bg); self.background_color);
} }
buf buf
} }

@ -8,9 +8,9 @@ use symbols::bar;
pub struct Sparkline<'a> { pub struct Sparkline<'a> {
block: Option<Block<'a>>, block: Option<Block<'a>>,
fg: Color, color: Color,
bg: Color, background_color: Color,
data: Vec<u64>, data: &'a [u64],
max: Option<u64>, max: Option<u64>,
} }
@ -18,9 +18,9 @@ impl<'a> Default for Sparkline<'a> {
fn default() -> Sparkline<'a> { fn default() -> Sparkline<'a> {
Sparkline { Sparkline {
block: None, block: None,
fg: Color::Reset, color: Color::Reset,
bg: Color::Reset, background_color: Color::Reset,
data: Vec::new(), data: &[],
max: None, max: None,
} }
} }
@ -32,19 +32,19 @@ impl<'a> Sparkline<'a> {
self self
} }
pub fn fg(&mut self, fg: Color) -> &mut Sparkline<'a> { pub fn color(&mut self, color: Color) -> &mut Sparkline<'a> {
self.fg = fg; self.color = color;
self self
} }
pub fn bg(&mut self, bg: Color) -> &mut Sparkline<'a> { pub fn background_color(&mut self, color: Color) -> &mut Sparkline<'a> {
self.bg = bg; self.background_color = color;
self self
} }
pub fn data(&mut self, data: &[u64]) -> &mut Sparkline<'a> { pub fn data(&mut self, data: &'a [u64]) -> &mut Sparkline<'a> {
self.data = data.to_vec(); self.data = data;
self self
} }
@ -88,7 +88,11 @@ impl<'a> Widget<'a> for Sparkline<'a> {
7 => bar::SEVEN_EIGHTHS, 7 => bar::SEVEN_EIGHTHS,
_ => bar::FULL, _ => 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 { if *d > 8 {
*d -= 8; *d -= 8;

Loading…
Cancel
Save