diff --git a/Cargo.toml b/Cargo.toml index da15ec4..19fcd18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,7 @@ termion = "1.1.1" bitflags = "0.7" cassowary = "0.2.0" log = "0.3" + +[dev-dependencies] log4rs = "*" +rand = "0.3" diff --git a/examples/prototype.rs b/examples/prototype.rs index 2243060..7b2d8c5 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -3,23 +3,23 @@ extern crate tui; extern crate log; extern crate log4rs; extern crate termion; +extern crate rand; use std::thread; use std::time; use std::sync::mpsc; -use std::io::{Write, stdin}; +use std::io::stdin; use termion::event; use termion::input::TermRead; use log::LogLevelFilter; -use log4rs::append::console::ConsoleAppender; use log4rs::append::file::FileAppender; use log4rs::encode::pattern::PatternEncoder; use log4rs::config::{Appender, Config, Logger, Root}; use tui::Terminal; -use tui::widgets::{Widget, Block, List, Gauge, border}; +use tui::widgets::{Widget, Block, List, Gauge, Sparkline, border}; use tui::layout::{Group, Direction, Alignment, Size}; struct App { @@ -29,6 +29,7 @@ struct App { selected: usize, show_episodes: bool, progress: u16, + data: Vec, } enum Event { @@ -58,6 +59,7 @@ fn main() { selected: 0, show_episodes: false, progress: 0, + data: (0..100).map(|_| rand::random::() as u64).collect(), }; let (tx, rx) = mpsc::channel(); let input_tx = tx.clone(); @@ -115,6 +117,8 @@ fn main() { if app.progress > 100 { app.progress = 0; } + app.data.insert(0, rand::random::() as u64); + app.data.pop(); } } } @@ -128,6 +132,7 @@ fn draw(terminal: &mut Terminal, app: &App) { .alignment(Alignment::Left) .chunks(&[Size::Fixed(5), Size::Percent(80), Size::Fixed(10)]) .render(&terminal.area(), |chunks, tree| { + info!("{:?}", terminal.area()); tree.add(Block::default().borders(border::ALL).title("Gauges").render(&chunks[0])); tree.add(Group::default() .direction(Direction::Vertical) @@ -138,14 +143,14 @@ fn draw(terminal: &mut Terminal, app: &App) { tree.add(Gauge::new() .percent(app.progress) .render(&chunks[0])); - tree.add(Gauge::new() - .percent(app.progress) + tree.add(Sparkline::new() + .data(&app.data) .render(&chunks[2])); })); let sizes = if app.show_episodes { vec![Size::Percent(50), Size::Percent(50)] } else { - vec![Size::Percent(50), Size::Percent(50)] + vec![Size::Percent(100)] }; tree.add(Group::default() .direction(Direction::Horizontal) @@ -153,9 +158,7 @@ fn draw(terminal: &mut Terminal, app: &App) { .chunks(&sizes) .render(&chunks[1], |chunks, tree| { tree.add(List::default() - .block(|b| { - b.borders(border::ALL).title("Podcasts"); - }) + .block(*Block::default().borders(border::ALL).title("Podcasts")) .items(&app.items) .select(app.selected) .formatter(|i, s| { diff --git a/src/.lib.rs.rustfmt b/src/.lib.rs.rustfmt deleted file mode 100644 index d7e4735..0000000 --- a/src/.lib.rs.rustfmt +++ /dev/null @@ -1,22 +0,0 @@ -extern crate termion; -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate log; -extern crate cassowary; - -mod buffer; -mod util; -pub mod symbols; -pub mod terminal; -pub mod widgets; -pub mod style; -pub mod layout; - -pub use self::terminal::Terminal; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() {} -} diff --git a/src/layout.rs b/src/layout.rs index 5cb48bd..4537395 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -121,6 +121,7 @@ pub enum Size { /// } /// /// ``` +#[allow(unused_variables)] pub fn split(area: &Rect, dir: &Direction, align: &Alignment, diff --git a/src/symbols.rs b/src/symbols.rs index 1dda230..aea230c 100644 --- a/src/symbols.rs +++ b/src/symbols.rs @@ -8,3 +8,14 @@ pub mod block { pub const ONE_QUATER: char = '▎'; pub const ONE_EIGHTH: char = '▏'; } + +pub mod bar { + pub const FULL: char = '█'; + pub const SEVEN_EIGHTHS: char = '▇'; + pub const THREE_QUATERS: char = '▆'; + pub const FIVE_EIGHTHS: char = '▅'; + pub const HALF: char = '▄'; + pub const THREE_EIGHTHS: char = '▃'; + pub const ONE_QUATER: char = '▂'; + pub const ONE_EIGHTH: char = '▁'; +} diff --git a/src/terminal.rs b/src/terminal.rs index 80e9b8b..e2c0b1e 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -59,7 +59,7 @@ impl Terminal { } previous.insert((node.widget_type, area), node.hash); } - for (&(t, a), h) in &self.previous { + for (&(t, a), _h) in &self.previous { buffers.insert(0, Buffer::empty(a)); debug!("Erased {:?} at {:?}", t, a); } diff --git a/src/util.rs b/src/util.rs index 7b2c686..cbf0e81 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,10 +1,12 @@ -use std::hash::{Hash, SipHasher, Hasher}; +use std::collections::hash_map::RandomState; +use std::hash::{Hash, Hasher, BuildHasher}; use layout::Rect; pub fn hash(t: &T, area: &Rect) -> u64 { - let mut s = SipHasher::new(); - t.hash(&mut s); - area.hash(&mut s); - s.finish() + let state = RandomState::new(); + let mut hasher = state.build_hasher(); + t.hash(&mut hasher); + area.hash(&mut hasher); + hasher.finish() } diff --git a/src/widgets/block.rs b/src/widgets/block.rs index ffc2f81..129e2b7 100644 --- a/src/widgets/block.rs +++ b/src/widgets/block.rs @@ -36,7 +36,7 @@ impl<'a> Block<'a> { } impl<'a> Widget for Block<'a> { - fn _buffer(&self, area: &Rect) -> Buffer { + fn buffer(&self, area: &Rect) -> Buffer { let mut buf = Buffer::empty(*area); diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index 512d401..b66590a 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -47,9 +47,9 @@ impl<'a> Gauge<'a> { } impl<'a> Widget for Gauge<'a> { - fn _buffer(&self, area: &Rect) -> Buffer { + fn buffer(&self, area: &Rect) -> Buffer { let (mut buf, gauge_area) = match self.block { - Some(ref b) => (b._buffer(area), area.inner(1)), + Some(ref b) => (b.buffer(area), area.inner(1)), None => (Buffer::empty(*area), *area), }; if gauge_area.height < 1 { diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 391db7a..6a3bbeb 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -7,7 +7,7 @@ use widgets::{Widget, WidgetType, Block}; use layout::Rect; pub struct List<'a, T> { - block: Block<'a>, + block: Option>, selected: usize, formatter: Box String>, items: Vec, @@ -26,7 +26,7 @@ impl<'a, T> Hash for List<'a, T> impl<'a, T> Default for List<'a, T> { fn default() -> List<'a, T> { List { - block: Block::default(), + block: None, selected: 0, formatter: Box::new(|_, _| String::from("")), items: Vec::new(), @@ -37,10 +37,8 @@ impl<'a, T> Default for List<'a, T> { impl<'a, T> List<'a, T> where T: Clone { - pub fn block(&'a mut self, f: F) -> &mut List<'a, T> - where F: Fn(&mut Block) - { - f(&mut self.block); + pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> { + self.block = Some(block); self } @@ -65,14 +63,14 @@ impl<'a, T> List<'a, T> impl<'a, T> Widget for List<'a, T> where T: Display + Hash { - fn _buffer(&self, area: &Rect) -> Buffer { - let mut buf = self.block.buffer(area); - if area.area() == 0 { - return buf; - } + fn buffer(&self, area: &Rect) -> Buffer { + + let (mut buf, list_area) = match self.block { + Some(ref b) => (b.buffer(area), area.inner(1)), + None => (Buffer::empty(*area), *area), + }; let list_length = self.items.len(); - let list_area = area.inner(1); let list_height = list_area.height as usize; let bound = min(list_height, list_length); let offset = if self.selected > list_height { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 29fc616..2cb2831 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,10 +1,12 @@ mod block; mod list; mod gauge; +mod sparkline; pub use self::block::Block; pub use self::list::List; pub use self::gauge::Gauge; +pub use self::sparkline::Sparkline; use std::hash::Hash; @@ -13,6 +15,7 @@ use buffer::{Buffer, Cell}; use layout::{Rect, Tree, Leaf}; use style::Color; +#[allow(dead_code)] enum Line { Horizontal, Vertical, @@ -88,16 +91,11 @@ pub enum WidgetType { Block, List, Gauge, + Sparkline, } pub trait Widget: Hash { - fn _buffer(&self, area: &Rect) -> Buffer; - fn buffer(&self, area: &Rect) -> Buffer { - match area.area() { - 0 => Buffer::empty(*area), - _ => self._buffer(area), - } - } + fn buffer(&self, area: &Rect) -> Buffer; fn widget_type(&self) -> WidgetType; fn render(&self, area: &Rect) -> Tree { let widget_type = self.widget_type(); diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs new file mode 100644 index 0000000..247eac1 --- /dev/null +++ b/src/widgets/sparkline.rs @@ -0,0 +1,84 @@ +use std::cmp::min; + +use layout::Rect; +use buffer::Buffer; +use widgets::{Widget, WidgetType, Block}; +use style::Color; +use symbols::bar; + +#[derive(Hash)] +pub struct Sparkline<'a> { + block: Option>, + color: Color, + data: Vec, +} + +impl<'a> Sparkline<'a> { + pub fn new() -> Sparkline<'a> { + Sparkline { + block: None, + color: Color::White, + data: Vec::new(), + } + } + + pub fn block(&mut self, block: Block<'a>) -> &mut Sparkline<'a> { + self.block = Some(block); + self + } + + pub fn color(&mut self, color: Color) -> &mut Sparkline<'a> { + self.color = color; + self + } + + pub fn data(&mut self, data: &[u64]) -> &mut Sparkline<'a> { + self.data = data.to_vec(); + self + } +} + +impl<'a> Widget for Sparkline<'a> { + fn buffer(&self, area: &Rect) -> Buffer { + let (mut buf, spark_area) = match self.block { + Some(ref b) => (b.buffer(area), area.inner(1)), + None => (Buffer::empty(*area), *area), + }; + if spark_area.height < 1 { + return buf; + } else { + let margin = spark_area.x - area.x; + match self.data.iter().max() { + Some(max_value) => { + let max_index = min(spark_area.width as usize, self.data.len()); + let line = self.data + .iter() + .take(max_index) + .filter_map(|e| { + let value = e * 8 / max_value; + match value { + 0 => Some(' '), + 1 => Some(bar::ONE_EIGHTH), + 2 => Some(bar::ONE_QUATER), + 3 => Some(bar::THREE_EIGHTHS), + 4 => Some(bar::HALF), + 5 => Some(bar::FIVE_EIGHTHS), + 6 => Some(bar::THREE_EIGHTHS), + 7 => Some(bar::THREE_QUATERS), + 8 => Some(bar::FULL), + _ => None, + } + }) + .collect::(); + buf.set_string(margin, margin, &line); + } + None => {} + } + } + buf + } + + fn widget_type(&self) -> WidgetType { + WidgetType::Sparkline + } +}