diff --git a/examples/prototype.rs b/examples/prototype.rs index 31d760b..2547435 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -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")) diff --git a/src/layout.rs b/src/layout.rs index a0a48bd..25317aa 100644 --- a/src/layout.rs +++ b/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 { +pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec { let mut solver = Solver::new(); let mut vars: HashMap = HashMap::new(); let elements = sizes.iter().map(|_| Element::new()).collect::>(); @@ -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, + sizes: Vec, } 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(&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); diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 4e93501..49bf95f 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -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; diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index e6b67bc..ed549a6 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -23,8 +23,8 @@ pub struct Gauge<'a> { block: Option>, 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 diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 0b14151..8e6f1c1 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -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 } diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index f4d0caf..9e1874d 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -8,9 +8,9 @@ use symbols::bar; pub struct Sparkline<'a> { block: Option>, - fg: Color, - bg: Color, - data: Vec, + color: Color, + background_color: Color, + data: &'a [u64], max: Option, } @@ -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;