Add sparkline widget and fix warnings

pull/3/head
Florian Dehau 8 years ago
parent 5b5d37ee69
commit d11dedd864

@ -8,4 +8,7 @@ termion = "1.1.1"
bitflags = "0.7" bitflags = "0.7"
cassowary = "0.2.0" cassowary = "0.2.0"
log = "0.3" log = "0.3"
[dev-dependencies]
log4rs = "*" log4rs = "*"
rand = "0.3"

@ -3,23 +3,23 @@ extern crate tui;
extern crate log; extern crate log;
extern crate log4rs; extern crate log4rs;
extern crate termion; extern crate termion;
extern crate rand;
use std::thread; use std::thread;
use std::time; use std::time;
use std::sync::mpsc; use std::sync::mpsc;
use std::io::{Write, stdin}; use std::io::stdin;
use termion::event; use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use log::LogLevelFilter; use log::LogLevelFilter;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender; use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder; use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Logger, Root}; use log4rs::config::{Appender, Config, Logger, Root};
use tui::Terminal; 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}; use tui::layout::{Group, Direction, Alignment, Size};
struct App { struct App {
@ -29,6 +29,7 @@ struct App {
selected: usize, selected: usize,
show_episodes: bool, show_episodes: bool,
progress: u16, progress: u16,
data: Vec<u64>,
} }
enum Event { enum Event {
@ -58,6 +59,7 @@ fn main() {
selected: 0, selected: 0,
show_episodes: false, show_episodes: false,
progress: 0, progress: 0,
data: (0..100).map(|_| rand::random::<u8>() as u64).collect(),
}; };
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let input_tx = tx.clone(); let input_tx = tx.clone();
@ -115,6 +117,8 @@ fn main() {
if app.progress > 100 { if app.progress > 100 {
app.progress = 0; app.progress = 0;
} }
app.data.insert(0, rand::random::<u8>() as u64);
app.data.pop();
} }
} }
} }
@ -128,6 +132,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
.alignment(Alignment::Left) .alignment(Alignment::Left)
.chunks(&[Size::Fixed(5), Size::Percent(80), Size::Fixed(10)]) .chunks(&[Size::Fixed(5), Size::Percent(80), Size::Fixed(10)])
.render(&terminal.area(), |chunks, tree| { .render(&terminal.area(), |chunks, tree| {
info!("{:?}", terminal.area());
tree.add(Block::default().borders(border::ALL).title("Gauges").render(&chunks[0])); tree.add(Block::default().borders(border::ALL).title("Gauges").render(&chunks[0]));
tree.add(Group::default() tree.add(Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@ -138,14 +143,14 @@ fn draw(terminal: &mut Terminal, app: &App) {
tree.add(Gauge::new() tree.add(Gauge::new()
.percent(app.progress) .percent(app.progress)
.render(&chunks[0])); .render(&chunks[0]));
tree.add(Gauge::new() tree.add(Sparkline::new()
.percent(app.progress) .data(&app.data)
.render(&chunks[2])); .render(&chunks[2]));
})); }));
let sizes = if app.show_episodes { let sizes = if app.show_episodes {
vec![Size::Percent(50), Size::Percent(50)] vec![Size::Percent(50), Size::Percent(50)]
} else { } else {
vec![Size::Percent(50), Size::Percent(50)] vec![Size::Percent(100)]
}; };
tree.add(Group::default() tree.add(Group::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
@ -153,9 +158,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
.chunks(&sizes) .chunks(&sizes)
.render(&chunks[1], |chunks, tree| { .render(&chunks[1], |chunks, tree| {
tree.add(List::default() tree.add(List::default()
.block(|b| { .block(*Block::default().borders(border::ALL).title("Podcasts"))
b.borders(border::ALL).title("Podcasts");
})
.items(&app.items) .items(&app.items)
.select(app.selected) .select(app.selected)
.formatter(|i, s| { .formatter(|i, s| {

@ -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() {}
}

@ -121,6 +121,7 @@ pub enum Size {
/// } /// }
/// ///
/// ``` /// ```
#[allow(unused_variables)]
pub fn split(area: &Rect, pub fn split(area: &Rect,
dir: &Direction, dir: &Direction,
align: &Alignment, align: &Alignment,

@ -8,3 +8,14 @@ pub mod block {
pub const ONE_QUATER: char = '▎'; pub const ONE_QUATER: char = '▎';
pub const ONE_EIGHTH: 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 = '▁';
}

@ -59,7 +59,7 @@ impl Terminal {
} }
previous.insert((node.widget_type, area), node.hash); 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)); buffers.insert(0, Buffer::empty(a));
debug!("Erased {:?} at {:?}", t, a); debug!("Erased {:?} at {:?}", t, a);
} }

@ -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; use layout::Rect;
pub fn hash<T: Hash>(t: &T, area: &Rect) -> u64 { pub fn hash<T: Hash>(t: &T, area: &Rect) -> u64 {
let mut s = SipHasher::new(); let state = RandomState::new();
t.hash(&mut s); let mut hasher = state.build_hasher();
area.hash(&mut s); t.hash(&mut hasher);
s.finish() area.hash(&mut hasher);
hasher.finish()
} }

@ -36,7 +36,7 @@ impl<'a> Block<'a> {
} }
impl<'a> Widget for 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); let mut buf = Buffer::empty(*area);

@ -47,9 +47,9 @@ impl<'a> Gauge<'a> {
} }
impl<'a> Widget for 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 { 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), None => (Buffer::empty(*area), *area),
}; };
if gauge_area.height < 1 { if gauge_area.height < 1 {

@ -7,7 +7,7 @@ use widgets::{Widget, WidgetType, Block};
use layout::Rect; use layout::Rect;
pub struct List<'a, T> { pub struct List<'a, T> {
block: Block<'a>, block: Option<Block<'a>>,
selected: usize, selected: usize,
formatter: Box<Fn(&T, bool) -> String>, formatter: Box<Fn(&T, bool) -> String>,
items: Vec<T>, items: Vec<T>,
@ -26,7 +26,7 @@ impl<'a, T> Hash for List<'a, T>
impl<'a, T> Default for List<'a, T> { impl<'a, T> Default for List<'a, T> {
fn default() -> List<'a, T> { fn default() -> List<'a, T> {
List { List {
block: Block::default(), block: None,
selected: 0, selected: 0,
formatter: Box::new(|_, _| String::from("")), formatter: Box::new(|_, _| String::from("")),
items: Vec::new(), items: Vec::new(),
@ -37,10 +37,8 @@ impl<'a, T> Default for List<'a, T> {
impl<'a, T> List<'a, T> impl<'a, T> List<'a, T>
where T: Clone where T: Clone
{ {
pub fn block<F>(&'a mut self, f: F) -> &mut List<'a, T> pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> {
where F: Fn(&mut Block) self.block = Some(block);
{
f(&mut self.block);
self self
} }
@ -65,14 +63,14 @@ impl<'a, T> List<'a, T>
impl<'a, T> Widget for List<'a, T> impl<'a, T> Widget for List<'a, T>
where T: Display + Hash where T: Display + Hash
{ {
fn _buffer(&self, area: &Rect) -> Buffer { fn buffer(&self, area: &Rect) -> Buffer {
let mut buf = self.block.buffer(area);
if area.area() == 0 { let (mut buf, list_area) = match self.block {
return buf; Some(ref b) => (b.buffer(area), area.inner(1)),
} None => (Buffer::empty(*area), *area),
};
let list_length = self.items.len(); let list_length = self.items.len();
let list_area = area.inner(1);
let list_height = list_area.height as usize; let list_height = list_area.height as usize;
let bound = min(list_height, list_length); let bound = min(list_height, list_length);
let offset = if self.selected > list_height { let offset = if self.selected > list_height {

@ -1,10 +1,12 @@
mod block; mod block;
mod list; mod list;
mod gauge; mod gauge;
mod sparkline;
pub use self::block::Block; pub use self::block::Block;
pub use self::list::List; pub use self::list::List;
pub use self::gauge::Gauge; pub use self::gauge::Gauge;
pub use self::sparkline::Sparkline;
use std::hash::Hash; use std::hash::Hash;
@ -13,6 +15,7 @@ use buffer::{Buffer, Cell};
use layout::{Rect, Tree, Leaf}; use layout::{Rect, Tree, Leaf};
use style::Color; use style::Color;
#[allow(dead_code)]
enum Line { enum Line {
Horizontal, Horizontal,
Vertical, Vertical,
@ -88,16 +91,11 @@ pub enum WidgetType {
Block, Block,
List, List,
Gauge, Gauge,
Sparkline,
} }
pub trait Widget: Hash { pub trait Widget: Hash {
fn _buffer(&self, area: &Rect) -> Buffer; fn buffer(&self, area: &Rect) -> Buffer;
fn buffer(&self, area: &Rect) -> Buffer {
match area.area() {
0 => Buffer::empty(*area),
_ => self._buffer(area),
}
}
fn widget_type(&self) -> WidgetType; fn widget_type(&self) -> WidgetType;
fn render(&self, area: &Rect) -> Tree { fn render(&self, area: &Rect) -> Tree {
let widget_type = self.widget_type(); let widget_type = self.widget_type();

@ -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<Block<'a>>,
color: Color,
data: Vec<u64>,
}
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::<String>();
buf.set_string(margin, margin, &line);
}
None => {}
}
}
buf
}
fn widget_type(&self) -> WidgetType {
WidgetType::Sparkline
}
}
Loading…
Cancel
Save