Add gauge, fix rendering and cleanup code

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

@ -5,6 +5,7 @@ extern crate log4rs;
extern crate termion; extern crate termion;
use std::thread; use std::thread;
use std::time;
use std::sync::mpsc; use std::sync::mpsc;
use std::io::{Write, stdin}; use std::io::{Write, stdin};
@ -18,7 +19,7 @@ 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, Border}; use tui::widgets::{Widget, Block, List, Gauge, border};
use tui::layout::{Group, Direction, Alignment, Size}; use tui::layout::{Group, Direction, Alignment, Size};
struct App { struct App {
@ -27,26 +28,24 @@ struct App {
items: Vec<String>, items: Vec<String>,
selected: usize, selected: usize,
show_episodes: bool, show_episodes: bool,
progress: u16,
} }
enum Event { enum Event {
Input(event::Key), Input(event::Key),
Tick,
} }
fn main() { fn main() {
let log = FileAppender::builder() let log = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}"))) .encoder(Box::new(PatternEncoder::new("{l} / {d(%H:%M:%S)} / {M}:{L}{n}{m}{n}{n}")))
.build("prototype.log") .build("prototype.log")
.unwrap(); .unwrap();
let config = Config::builder() let config = Config::builder()
.appender(Appender::builder().build("log", Box::new(log))) .appender(Appender::builder().build("log", Box::new(log)))
.logger(Logger::builder() .build(Root::builder().appender("log").build(LogLevelFilter::Debug))
.appender("log")
.additive(false)
.build("log", LogLevelFilter::Info))
.build(Root::builder().appender("log").build(LogLevelFilter::Info))
.unwrap(); .unwrap();
let handle = log4rs::init_config(config).unwrap(); let handle = log4rs::init_config(config).unwrap();
@ -58,21 +57,30 @@ fn main() {
items: ["1", "2", "3"].into_iter().map(|e| String::from(*e)).collect(), items: ["1", "2", "3"].into_iter().map(|e| String::from(*e)).collect(),
selected: 0, selected: 0,
show_episodes: false, show_episodes: false,
progress: 0,
}; };
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let input_tx = tx.clone();
thread::spawn(move || { thread::spawn(move || {
let tx = tx.clone();
let stdin = stdin(); let stdin = stdin();
for c in stdin.keys() { for c in stdin.keys() {
let evt = c.unwrap(); let evt = c.unwrap();
tx.send(Event::Input(evt)).unwrap(); input_tx.send(Event::Input(evt)).unwrap();
if evt == event::Key::Char('q') { if evt == event::Key::Char('q') {
break; break;
} }
} }
}); });
thread::spawn(move || {
let tx = tx.clone();
loop {
tx.send(Event::Tick).unwrap();
thread::sleep(time::Duration::from_millis(1000));
}
});
let mut terminal = Terminal::new().unwrap(); let mut terminal = Terminal::new().unwrap();
terminal.clear(); terminal.clear();
terminal.hide_cursor(); terminal.hide_cursor();
@ -102,6 +110,12 @@ fn main() {
_ => {} _ => {}
} }
} }
Event::Tick => {
app.progress += 5;
if app.progress > 100 {
app.progress = 0;
}
}
} }
} }
terminal.show_cursor(); terminal.show_cursor();
@ -112,12 +126,22 @@ fn draw(terminal: &mut Terminal, app: &App) {
let ui = Group::default() let ui = Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.alignment(Alignment::Left) .alignment(Alignment::Left)
.chunks(&[Size::Fixed(3), Size::Percent(100), Size::Fixed(3)]) .chunks(&[Size::Fixed(5), Size::Percent(80), Size::Fixed(10)])
.render(&terminal.area(), |chunks, tree| { .render(&terminal.area(), |chunks, tree| {
tree.add(Block::default() tree.add(Block::default().borders(border::ALL).title("Gauges").render(&chunks[0]));
.borders(Border::ALL) tree.add(Group::default()
.title("Header") .direction(Direction::Vertical)
.render(&chunks[0])); .alignment(Alignment::Left)
.margin(1)
.chunks(&[Size::Fixed(1), Size::Fixed(1), Size::Fixed(1)])
.render(&chunks[0], |chunks, tree| {
tree.add(Gauge::new()
.percent(app.progress)
.render(&chunks[0]));
tree.add(Gauge::new()
.percent(app.progress)
.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 {
@ -130,7 +154,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
.render(&chunks[1], |chunks, tree| { .render(&chunks[1], |chunks, tree| {
tree.add(List::default() tree.add(List::default()
.block(|b| { .block(|b| {
b.borders(Border::ALL).title("Podcasts"); b.borders(border::ALL).title("Podcasts");
}) })
.items(&app.items) .items(&app.items)
.select(app.selected) .select(app.selected)
@ -141,12 +165,12 @@ fn draw(terminal: &mut Terminal, app: &App) {
.render(&chunks[0])); .render(&chunks[0]));
if app.show_episodes { if app.show_episodes {
tree.add(Block::default() tree.add(Block::default()
.borders(Border::ALL) .borders(border::ALL)
.title("Episodes") .title("Episodes")
.render(&chunks[1])); .render(&chunks[1]));
} }
})); }));
tree.add(Block::default().borders(Border::ALL).title("Footer").render(&chunks[2])); tree.add(Block::default().borders(border::ALL).title("Footer").render(&chunks[2]));
}); });
terminal.render(ui); terminal.render(ui);
} }

@ -0,0 +1,22 @@
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() {}
}

@ -94,6 +94,15 @@ impl Buffer {
self.content[i].symbol = symbol; self.content[i].symbol = symbol;
} }
pub fn set_fg(&mut self, x: u16, y: u16, color: Color) {
let i = self.index_of(x, y);
self.content[i].fg = color;
}
pub fn set_bg(&mut self, x: u16, y: u16, color: Color) {
let i = self.index_of(x, y);
self.content[i].bg = color;
}
pub fn set_string(&mut self, x: u16, y: u16, string: &str) { pub fn set_string(&mut self, x: u16, y: u16, string: &str) {
let mut cursor = (x, y); let mut cursor = (x, y);
for c in string.chars() { for c in string.chars() {

@ -3,9 +3,8 @@ use std::collections::HashMap;
use cassowary::{Solver, Variable, Constraint}; use cassowary::{Solver, Variable, Constraint};
use cassowary::WeightedRelation::*; use cassowary::WeightedRelation::*;
use cassowary::strength::{WEAK, MEDIUM, STRONG, REQUIRED}; use cassowary::strength::{WEAK, MEDIUM, REQUIRED};
use util::hash;
use buffer::Buffer; use buffer::Buffer;
use widgets::WidgetType; use widgets::WidgetType;
@ -24,7 +23,7 @@ pub enum Direction {
Vertical, Vertical,
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Hash, Debug, Clone, Copy, Eq, PartialEq)]
pub struct Rect { pub struct Rect {
pub x: u16, pub x: u16,
pub y: u16, pub y: u16,
@ -46,10 +45,10 @@ impl Default for Rect {
impl Rect { impl Rect {
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect { pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
Rect { Rect {
x: 0, x: x,
y: 0, y: y,
width: 0, width: width,
height: 0, height: height,
} }
} }
@ -57,15 +56,15 @@ impl Rect {
self.width * self.height self.width * self.height
} }
pub fn inner(&self, spacing: u16) -> Rect { pub fn inner(&self, margin: u16) -> Rect {
if self.width - spacing < 0 || self.height - spacing < 0 { if self.width < 2 * margin || self.height < 2 * margin {
Rect::default() Rect::default()
} else { } else {
Rect { Rect {
x: self.x + spacing, x: self.x + margin,
y: self.y + spacing, y: self.y + margin,
width: self.width - 2 * spacing, width: self.width - 2 * margin,
height: self.height - 2 * spacing, height: self.height - 2 * margin,
} }
} }
} }
@ -114,16 +113,25 @@ pub enum Size {
/// use tui::layout::{Rect, Size, Alignment, Direction, split}; /// use tui::layout::{Rect, Size, Alignment, Direction, split};
/// ///
/// fn main() { /// fn main() {
/// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10}, Direction::Vertical, /// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
/// Alignment::Left, &[Size::Fixed(5.0), Size::Percent(80.0)]); /// &Direction::Vertical,
/// &Alignment::Left,
/// 0,
/// &[Size::Fixed(5), Size::Percent(80)]);
/// } /// }
/// ///
/// ``` /// ```
pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) -> Vec<Rect> { pub fn split(area: &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(|e| Element::new()).collect::<Vec<Element>>(); let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>();
let mut results = sizes.iter().map(|e| Rect::default()).collect::<Vec<Rect>>(); let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
let dest_area = area.inner(margin);
for (i, e) in elements.iter().enumerate() { for (i, e) in elements.iter().enumerate() {
vars.insert(e.x, (i, 0)); vars.insert(e.x, (i, 0));
vars.insert(e.y, (i, 1)); vars.insert(e.y, (i, 1));
@ -131,20 +139,19 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
vars.insert(e.height, (i, 3)); vars.insert(e.height, (i, 3));
} }
let mut constraints: Vec<Constraint> = Vec::new(); let mut constraints: Vec<Constraint> = Vec::new();
if let Some(size) = sizes.first() { if let Some(first) = elements.first() {
constraints.push(match *dir { constraints.push(match *dir {
Direction::Horizontal => elements[0].x | EQ(REQUIRED) | area.x as f64, Direction::Horizontal => first.x | EQ(REQUIRED) | dest_area.x as f64,
Direction::Vertical => elements[0].y | EQ(REQUIRED) | area.y as f64, Direction::Vertical => first.y | EQ(REQUIRED) | dest_area.y as f64,
}) })
} }
if let Some(size) = sizes.last() { if let Some(last) = elements.last() {
let last = elements.last().unwrap();
constraints.push(match *dir { constraints.push(match *dir {
Direction::Horizontal => { Direction::Horizontal => {
last.x + last.width | EQ(REQUIRED) | (area.x + 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(REQUIRED) | (area.y + area.height) as f64 last.y + last.height | EQ(REQUIRED) | (dest_area.y + dest_area.height) as f64
} }
}) })
} }
@ -154,12 +161,13 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
constraints.push(pair[0].x + pair[0].width | LE(REQUIRED) | pair[1].x); constraints.push(pair[0].x + pair[0].width | LE(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) | area.y as f64, let cs = [elements[i].y | EQ(REQUIRED) | dest_area.y as f64,
elements[i].height | EQ(REQUIRED) | area.height as f64, elements[i].height | EQ(REQUIRED) | dest_area.height as f64,
match *size { match *size {
Size::Fixed(f) => elements[i].width | EQ(REQUIRED) | f as f64, Size::Fixed(f) => elements[i].width | EQ(MEDIUM) | f as f64,
Size::Percent(p) => { Size::Percent(p) => {
elements[i].width | EQ(WEAK) | (area.width * p) as f64 / 100.0 elements[i].width | EQ(WEAK) |
(dest_area.width * p) as f64 / 100.0
} }
}]; }];
constraints.extend_from_slice(&cs); constraints.extend_from_slice(&cs);
@ -170,12 +178,13 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
constraints.push(pair[0].y + pair[0].height | LE(REQUIRED) | pair[1].y); constraints.push(pair[0].y + pair[0].height | LE(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) | area.x as f64, let cs = [elements[i].x | EQ(REQUIRED) | dest_area.x as f64,
elements[i].width | EQ(REQUIRED) | area.width as f64, elements[i].width | EQ(REQUIRED) | dest_area.width as f64,
match *size { match *size {
Size::Fixed(f) => elements[i].height | EQ(REQUIRED) | f as f64, Size::Fixed(f) => elements[i].height | EQ(REQUIRED) | f as f64,
Size::Percent(p) => { Size::Percent(p) => {
elements[i].height | EQ(WEAK) | (area.height * p) as f64 / 100.0 elements[i].height | EQ(WEAK) |
(dest_area.height * p) as f64 / 100.0
} }
}]; }];
constraints.extend_from_slice(&cs); constraints.extend_from_slice(&cs);
@ -286,6 +295,7 @@ pub struct Leaf {
pub struct Group { pub struct Group {
direction: Direction, direction: Direction,
alignment: Alignment, alignment: Alignment,
margin: u16,
chunks: Vec<Size>, chunks: Vec<Size>,
} }
@ -294,6 +304,7 @@ impl Default for Group {
Group { Group {
direction: Direction::Horizontal, direction: Direction::Horizontal,
alignment: Alignment::Left, alignment: Alignment::Left,
margin: 0,
chunks: Vec::new(), chunks: Vec::new(),
} }
} }
@ -310,6 +321,11 @@ impl Group {
self self
} }
pub fn margin(&mut self, margin: u16) -> &mut Group {
self.margin = margin;
self
}
pub fn chunks(&mut self, chunks: &[Size]) -> &mut Group { pub fn chunks(&mut self, chunks: &[Size]) -> &mut Group {
self.chunks = Vec::from(chunks); self.chunks = Vec::from(chunks);
self self
@ -317,9 +333,13 @@ impl Group {
pub fn render<F>(&self, area: &Rect, f: F) -> Tree pub fn render<F>(&self, area: &Rect, f: F) -> Tree
where F: Fn(&[Rect], &mut Node) where F: Fn(&[Rect], &mut Node)
{ {
let chunks = split(area, &self.direction, &self.alignment, &self.chunks); let chunks = split(area,
&self.direction,
&self.alignment,
self.margin,
&self.chunks);
let mut node = Node { children: Vec::new() }; let mut node = Node { children: Vec::new() };
let results = f(&chunks, &mut node); f(&chunks, &mut node);
Tree::Node(node) Tree::Node(node)
} }
} }

@ -7,6 +7,7 @@ extern crate cassowary;
mod buffer; mod buffer;
mod util; mod util;
pub mod symbols;
pub mod terminal; pub mod terminal;
pub mod widgets; pub mod widgets;
pub mod style; pub mod style;

@ -0,0 +1,10 @@
pub mod block {
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 = '▏';
}

@ -1,4 +1,3 @@
use std::iter;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::collections::HashMap; use std::collections::HashMap;
@ -8,13 +7,13 @@ use termion::raw::{IntoRawMode, RawTerminal};
use buffer::Buffer; use buffer::Buffer;
use widgets::WidgetType; use widgets::WidgetType;
use layout::{Rect, Tree, Node, Leaf}; use layout::{Rect, Tree};
pub struct Terminal { pub struct Terminal {
width: u16, width: u16,
height: u16, height: u16,
stdout: RawTerminal<io::Stdout>, stdout: RawTerminal<io::Stdout>,
previous: HashMap<(WidgetType, u64), Rect>, previous: HashMap<(WidgetType, Rect), u64>,
} }
impl Terminal { impl Terminal {
@ -39,29 +38,33 @@ impl Terminal {
} }
pub fn render(&mut self, ui: Tree) { pub fn render(&mut self, ui: Tree) {
info!("Render"); debug!("Render Pass");
let mut buffers: Vec<Buffer> = Vec::new(); let mut buffers: Vec<Buffer> = Vec::new();
let mut previous: HashMap<(WidgetType, u64), Rect> = HashMap::new(); let mut previous: HashMap<(WidgetType, Rect), u64> = HashMap::new();
for node in ui.into_iter() { for node in ui.into_iter() {
let area = *node.buffer.area(); let area = *node.buffer.area();
match self.previous.remove(&(node.widget_type, node.hash)) { match self.previous.remove(&(node.widget_type, area)) {
Some(r) => { Some(h) => {
if r != area { if h == node.hash {
debug!("Skip {:?} at {:?}", node.widget_type, area);
} else {
debug!("Update {:?} at {:?}", node.widget_type, area);
buffers.push(node.buffer); buffers.push(node.buffer);
} }
} }
None => { None => {
buffers.push(node.buffer); buffers.push(node.buffer);
debug!("Render {:?} at {:?}", node.widget_type, area);
} }
} }
previous.insert((node.widget_type, node.hash), area); previous.insert((node.widget_type, area), node.hash);
} }
for (_, area) in &self.previous { for (&(t, a), h) in &self.previous {
buffers.insert(0, Buffer::empty(*area)); buffers.insert(0, Buffer::empty(a));
debug!("Erased {:?} at {:?}", t, a);
} }
for buf in buffers { for buf in buffers {
self.render_buffer(&buf); self.render_buffer(&buf);
info!("{:?}", buf.area());
} }
self.previous = previous; self.previous = previous;
} }

@ -1,7 +1,10 @@
use std::hash::{Hash, SipHasher, Hasher}; use std::hash::{Hash, SipHasher, Hasher};
pub fn hash<T: Hash>(t: &T) -> u64 { use layout::Rect;
pub fn hash<T: Hash>(t: &T, area: &Rect) -> u64 {
let mut s = SipHasher::new(); let mut s = SipHasher::new();
t.hash(&mut s); t.hash(&mut s);
area.hash(&mut s);
s.finish() s.finish()
} }

@ -2,12 +2,12 @@
use buffer::Buffer; use buffer::Buffer;
use layout::Rect; use layout::Rect;
use style::Color; use style::Color;
use widgets::{Widget, WidgetType, Border, Line, vline, hline}; use widgets::{Widget, WidgetType, border, Line, vline, hline};
#[derive(Hash)] #[derive(Hash, Clone, Copy)]
pub struct Block<'a> { pub struct Block<'a> {
title: Option<&'a str>, title: Option<&'a str>,
borders: Border::Flags, borders: border::Flags,
border_fg: Color, border_fg: Color,
border_bg: Color, border_bg: Color,
} }
@ -16,7 +16,7 @@ impl<'a> Default for Block<'a> {
fn default() -> Block<'a> { fn default() -> Block<'a> {
Block { Block {
title: None, title: None,
borders: Border::NONE, borders: border::NONE,
border_fg: Color::White, border_fg: Color::White,
border_bg: Color::Black, border_bg: Color::Black,
} }
@ -29,35 +29,31 @@ impl<'a> Block<'a> {
self self
} }
pub fn borders(&mut self, flag: Border::Flags) -> &mut Block<'a> { pub fn borders(&mut self, flag: border::Flags) -> &mut Block<'a> {
self.borders = flag; self.borders = flag;
self self
} }
} }
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);
if area.area() == 0 { if self.borders == border::NONE {
return buf;
}
if self.borders == Border::NONE {
return buf; return buf;
} }
// Sides // Sides
if self.borders.intersects(Border::LEFT) { if self.borders.intersects(border::LEFT) {
let line = vline(area.x, area.y, area.height, self.border_fg, self.border_bg); let line = vline(area.x, area.y, area.height, self.border_fg, self.border_bg);
buf.merge(&line); buf.merge(&line);
} }
if self.borders.intersects(Border::TOP) { if self.borders.intersects(border::TOP) {
let line = hline(area.x, area.y, area.width, self.border_fg, self.border_bg); let line = hline(area.x, area.y, area.width, self.border_fg, self.border_bg);
buf.merge(&line); buf.merge(&line);
} }
if self.borders.intersects(Border::RIGHT) { if self.borders.intersects(border::RIGHT) {
let line = vline(area.x + area.width - 1, let line = vline(area.x + area.width - 1,
area.y, area.y,
area.height, area.height,
@ -65,7 +61,7 @@ impl<'a> Widget for Block<'a> {
self.border_bg); self.border_bg);
buf.merge(&line); buf.merge(&line);
} }
if self.borders.intersects(Border::BOTTOM) { if self.borders.intersects(border::BOTTOM) {
let line = hline(area.x, let line = hline(area.x,
area.y + area.height - 1, area.y + area.height - 1,
area.width, area.width,
@ -75,16 +71,16 @@ impl<'a> Widget for Block<'a> {
} }
// Corners // Corners
if self.borders.contains(Border::LEFT | Border::TOP) { if self.borders.contains(border::LEFT | border::TOP) {
buf.set_symbol(0, 0, Line::TopLeft.get()); buf.set_symbol(0, 0, Line::TopLeft.get());
} }
if self.borders.contains(Border::RIGHT | Border::TOP) { if self.borders.contains(border::RIGHT | border::TOP) {
buf.set_symbol(area.width - 1, 0, Line::TopRight.get()); buf.set_symbol(area.width - 1, 0, Line::TopRight.get());
} }
if self.borders.contains(Border::BOTTOM | Border::LEFT) { if self.borders.contains(border::BOTTOM | border::LEFT) {
buf.set_symbol(0, area.height - 1, Line::BottomLeft.get()); buf.set_symbol(0, area.height - 1, Line::BottomLeft.get());
} }
if self.borders.contains(Border::BOTTOM | Border::RIGHT) { if self.borders.contains(border::BOTTOM | border::RIGHT) {
buf.set_symbol(area.width - 1, area.height - 1, Line::BottomRight.get()); buf.set_symbol(area.width - 1, area.height - 1, Line::BottomRight.get());
} }
if let Some(ref title) = self.title { if let Some(ref title) = self.title {

@ -0,0 +1,75 @@
use widgets::{Widget, WidgetType, Block};
use buffer::Buffer;
use style::Color;
use layout::Rect;
/// Progress bar widget
///
/// # Examples:
///
/// ```
/// extern crate tui;
/// use tui::widgets::{Widget, Gauge, Block, border};
///
/// fn main() {
/// Gauge::new()
/// .block(*Block::default().borders(border::ALL).title("Progress"))
/// .percent(20);
/// }
/// ```
#[derive(Hash)]
pub struct Gauge<'a> {
block: Option<Block<'a>>,
percent: u16,
fg: Color,
bg: Color,
}
impl<'a> Gauge<'a> {
pub fn new() -> Gauge<'a> {
Gauge {
block: None,
percent: 0,
bg: Color::White,
fg: Color::Black,
}
}
pub fn block(&'a mut self, block: Block<'a>) -> &mut Gauge<'a> {
self.block = Some(block);
self
}
pub fn percent(&mut self, percent: u16) -> &mut Gauge<'a> {
self.percent = percent;
self
}
}
impl<'a> Widget for Gauge<'a> {
fn _buffer(&self, area: &Rect) -> Buffer {
let (mut buf, gauge_area) = match self.block {
Some(ref b) => (b._buffer(area), area.inner(1)),
None => (Buffer::empty(*area), *area),
};
if gauge_area.height < 1 {
return buf;
} else {
let margin = gauge_area.x - area.x;
let width = (gauge_area.width * self.percent) / 100;
for i in 0..width {
buf.set_bg(margin + i, margin, self.bg);
buf.set_fg(margin + i, margin, self.fg);
}
let percent_string = format!("{}%", self.percent);
let len = percent_string.len() as u16;
let middle = gauge_area.width / 2 - len / 2;
buf.set_string(middle, margin, &percent_string);
}
buf
}
fn widget_type(&self) -> WidgetType {
WidgetType::Gauge
}
}

@ -1,10 +1,9 @@
use std::cmp::{min, max}; use std::cmp::min;
use std::fmt::Display; use std::fmt::Display;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use buffer::Buffer; use buffer::Buffer;
use widgets::{Widget, WidgetType, Block}; use widgets::{Widget, WidgetType, Block};
use style::Color;
use layout::Rect; use layout::Rect;
pub struct List<'a, T> { pub struct List<'a, T> {
@ -29,7 +28,7 @@ impl<'a, T> Default for List<'a, T> {
List { List {
block: Block::default(), block: Block::default(),
selected: 0, selected: 0,
formatter: Box::new(|e: &T, selected: bool| String::from("")), formatter: Box::new(|_, _| String::from("")),
items: Vec::new(), items: Vec::new(),
} }
} }
@ -66,7 +65,7 @@ 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); let mut buf = self.block.buffer(area);
if area.area() == 0 { if area.area() == 0 {
return buf; return buf;

@ -1,9 +1,12 @@
mod block; mod block;
mod list; mod list;
mod gauge;
pub use self::block::Block; pub use self::block::Block;
pub use self::list::List; pub use self::list::List;
use std::hash::{Hash, SipHasher, Hasher}; pub use self::gauge::Gauge;
use std::hash::Hash;
use util::hash; use util::hash;
use buffer::{Buffer, Cell}; use buffer::{Buffer, Cell};
@ -23,7 +26,7 @@ enum Line {
HorizontalUp, HorizontalUp,
} }
pub mod Border { pub mod border {
bitflags! { bitflags! {
pub flags Flags: u32 { pub flags Flags: u32 {
const NONE = 0b00000001, const NONE = 0b00000001,
@ -53,7 +56,6 @@ impl Line {
} }
} }
fn hline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer { fn hline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
Buffer::filled(Rect { Buffer::filled(Rect {
x: x, x: x,
@ -85,14 +87,21 @@ fn vline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
pub enum WidgetType { pub enum WidgetType {
Block, Block,
List, List,
Gauge,
} }
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();
let hash = hash(&self); let hash = hash(&self, area);
let buffer = self.buffer(area); let buffer = self.buffer(area);
Tree::Leaf(Leaf { Tree::Leaf(Leaf {
widget_type: widget_type, widget_type: widget_type,

Loading…
Cancel
Save