mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-29 22:11:34 +00:00
Add sparkline widget and fix warnings
This commit is contained in:
parent
5b5d37ee69
commit
d11dedd864
@ -8,4 +8,7 @@ termion = "1.1.1"
|
||||
bitflags = "0.7"
|
||||
cassowary = "0.2.0"
|
||||
log = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
log4rs = "*"
|
||||
rand = "0.3"
|
||||
|
@ -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<u64>,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
@ -58,6 +59,7 @@ fn main() {
|
||||
selected: 0,
|
||||
show_episodes: false,
|
||||
progress: 0,
|
||||
data: (0..100).map(|_| rand::random::<u8>() 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::<u8>() 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| {
|
||||
|
@ -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,
|
||||
dir: &Direction,
|
||||
align: &Alignment,
|
||||
|
@ -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 = '▁';
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
12
src/util.rs
12
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: 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()
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -7,7 +7,7 @@ use widgets::{Widget, WidgetType, Block};
|
||||
use layout::Rect;
|
||||
|
||||
pub struct List<'a, T> {
|
||||
block: Block<'a>,
|
||||
block: Option<Block<'a>>,
|
||||
selected: usize,
|
||||
formatter: Box<Fn(&T, bool) -> String>,
|
||||
items: Vec<T>,
|
||||
@ -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<F>(&'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 {
|
||||
|
@ -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();
|
||||
|
84
src/widgets/sparkline.rs
Normal file
84
src/widgets/sparkline.rs
Normal file
@ -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…
x
Reference in New Issue
Block a user