diff --git a/Cargo.toml b/Cargo.toml index 19fcd18..5d6405c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ termion = "1.1.1" bitflags = "0.7" cassowary = "0.2.0" log = "0.3" +unicode-segmentation = "0.1.2" [dev-dependencies] log4rs = "*" diff --git a/examples/block.rs b/examples/block.rs new file mode 100644 index 0000000..22d4e50 --- /dev/null +++ b/examples/block.rs @@ -0,0 +1,43 @@ +extern crate tui; +extern crate termion; + +use std::io; +use termion::event; +use termion::input::TermRead; + +use tui::Terminal; +use tui::widgets::{Widget, Block}; +use tui::layout::{Group, Direction, Alignment, Size}; + +fn main() { + let mut terminal = Terminal::new().unwrap(); + let stdin = io::stdin(); + terminal.clear(); + terminal.hide_cursor(); + for c in stdin.keys() { + let evt = c.unwrap(); + if evt == event::Key::Char('q') { + break; + } + draw(&mut terminal); + } + terminal.show_cursor(); +} + +fn draw(t: &mut Terminal) { + + Group::default() + .direction(Direction::Vertical) + .alignment(Alignment::Left) + .chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) + .render(&Terminal::size().unwrap(), |chunks| { + t.render(chunks[0], Block::default().title("Block")); + Group::default() + .direction(Direction::Vertical) + .alignment(Alignment::Left) + .chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) + .render(&chunks[1], |chunks| { + t.render(chunks[0], Block::default().title("Block")); + }); + }); +} diff --git a/examples/prototype.rs b/examples/prototype.rs index 811cffb..dafc8b3 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -148,6 +148,7 @@ fn main() { terminal.hide_cursor(); loop { + terminal.clear(); draw(&mut terminal, &app); let evt = rx.recv().unwrap(); match evt { @@ -195,70 +196,59 @@ fn main() { terminal.show_cursor(); } -fn draw(terminal: &mut Terminal, app: &App) { +fn draw(t: &mut Terminal, app: &App) { - let ui = Group::default() + Group::default() .direction(Direction::Vertical) .alignment(Alignment::Left) .chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) - .render(&Terminal::size().unwrap(), |chunks, tree| { - tree.add(Block::default().borders(border::ALL).title("Graphs").render(&chunks[0])); - tree.add(Group::default() + .render(&Terminal::size().unwrap(), |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)]) - .render(&chunks[0], |chunks, tree| { - tree.add(Gauge::default() + .render(&chunks[0], |chunks| { + Gauge::default() .block(*Block::default().title("Gauge:")) .bg(Color::Yellow) .percent(app.progress) - .render(&chunks[0])); - tree.add(Sparkline::default() + .render(&chunks[0], t); + Sparkline::default() .block(*Block::default().title("Sparkline:")) .fg(Color::Green) .data(&app.data) - .render(&chunks[1])); - })); + .render(&chunks[1], t); + }); let sizes = if app.show_chart { vec![Size::Max(40), Size::Min(20)] } else { vec![Size::Max(40)] }; - tree.add(Group::default() + Group::default() .direction(Direction::Horizontal) .alignment(Alignment::Left) .chunks(&sizes) - .render(&chunks[1], |chunks, tree| { - tree.add(List::default() + .render(&chunks[1], |chunks| { + List::default() .block(*Block::default().borders(border::ALL).title("List")) - .items(&app.items) - .select(app.selected) - .formatter(|i, s| { - let (prefix, fg) = if s { - (">", Color::Cyan) - } else { - ("*", Color::White) - }; - (format!("{} {}", prefix, i), fg, Color::Black) - }) - .render(&chunks[0])); + .render(&chunks[0], t); if app.show_chart { - tree.add(Chart::default() + Chart::default() .block(*Block::default() .borders(border::ALL) .title("Chart")) .fg(Color::Cyan) .axis([0, 40]) .data(&app.data2) - .render(&chunks[1])); + .render(&chunks[1], t); } - })); - tree.add(Text::default() + }); + Text::default() .block(*Block::default().borders(border::ALL).title("Footer")) .fg(app.colors[app.color_index]) - .text("This is a footer") - .render(&chunks[2])); + .text("This żółw is a footer") + .render(&chunks[2], t); }); - terminal.render(ui); } diff --git a/src/buffer.rs b/src/buffer.rs index 06dc5b8..408ca89 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,17 +1,19 @@ +use unicode_segmentation::UnicodeSegmentation; + use layout::Rect; use style::Color; #[derive(Debug, Clone)] -pub struct Cell { - pub symbol: char, +pub struct Cell<'a> { + pub symbol: &'a str, pub fg: Color, pub bg: Color, } -impl Default for Cell { - fn default() -> Cell { +impl<'a> Default for Cell<'a> { + fn default() -> Cell<'a> { Cell { - symbol: ' ', + symbol: "", fg: Color::White, bg: Color::Black, } @@ -19,13 +21,13 @@ impl Default for Cell { } #[derive(Debug, Clone)] -pub struct Buffer { +pub struct Buffer<'a> { area: Rect, - content: Vec, + content: Vec>, } -impl Default for Buffer { - fn default() -> Buffer { +impl<'a> Default for Buffer<'a> { + fn default() -> Buffer<'a> { Buffer { area: Default::default(), content: Vec::new(), @@ -33,13 +35,13 @@ impl Default for Buffer { } } -impl Buffer { - pub fn empty(area: Rect) -> Buffer { +impl<'a> Buffer<'a> { + pub fn empty(area: Rect) -> Buffer<'a> { let cell: Cell = Default::default(); Buffer::filled(area, cell) } - pub fn filled(area: Rect, cell: Cell) -> Buffer { + pub fn filled(area: Rect, cell: Cell<'a>) -> Buffer<'a> { let size = area.area() as usize; let mut content = Vec::with_capacity(size); for _ in 0..size { @@ -51,7 +53,7 @@ impl Buffer { } } - pub fn content(&self) -> &[Cell] { + pub fn content(&'a self) -> &'a [Cell<'a>] { &self.content } @@ -83,12 +85,12 @@ impl Buffer { } } - pub fn set(&mut self, x: u16, y: u16, cell: Cell) { + pub fn set(&mut self, x: u16, y: u16, cell: Cell<'a>) { let i = self.index_of(x, y); self.content[i] = cell; } - pub fn set_symbol(&mut self, x: u16, y: u16, symbol: char) { + pub fn set_symbol(&mut self, x: u16, y: u16, symbol: &'a str) { let i = self.index_of(x, y); self.content[i].symbol = symbol; } @@ -102,11 +104,12 @@ impl Buffer { self.content[i].bg = color; } - pub fn set_string(&mut self, x: u16, y: u16, string: &str, fg: Color, bg: Color) { + pub fn set_string(&mut self, x: u16, y: u16, string: &'a str, fg: Color, bg: Color) { let mut cursor = (x, y); - for c in string.chars() { + for s in UnicodeSegmentation::graphemes(string, true).collect::>() { + info!("{}", s); let index = self.index_of(cursor.0, cursor.1); - self.content[index].symbol = c; + self.content[index].symbol = s; self.content[index].fg = fg; self.content[index].bg = bg; match self.next_pos(cursor.0, cursor.1) { @@ -127,12 +130,7 @@ impl Buffer { f(&mut self.content[i]); } - pub fn get(&self, x: u16, y: u16) -> &Cell { - let i = self.index_of(x, y); - &self.content[i] - } - - pub fn merge(&mut self, other: &Buffer) { + pub fn merge(&'a mut self, other: Buffer<'a>) { let area = self.area.union(&other.area); let cell: Cell = Default::default(); self.content.resize(area.area() as usize, cell.clone()); diff --git a/src/layout.rs b/src/layout.rs index d6cf172..ff6d6e3 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -6,9 +6,7 @@ use cassowary::WeightedRelation::*; use cassowary::strength::{WEAK, REQUIRED}; use buffer::Buffer; -use widgets::WidgetType; -#[derive(Hash)] pub enum Alignment { Top, Left, @@ -17,13 +15,12 @@ pub enum Alignment { Right, } -#[derive(Hash)] pub enum Direction { Horizontal, Vertical, } -#[derive(Hash, Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy)] pub struct Rect { pub x: u16, pub y: u16, @@ -238,67 +235,6 @@ impl Element { } } -pub enum Tree { - Node(Node), - Leaf(Leaf), -} - -impl IntoIterator for Tree { - type Item = Leaf; - type IntoIter = WidgetIterator; - - fn into_iter(self) -> WidgetIterator { - WidgetIterator::new(self) - } -} - -pub struct WidgetIterator { - stack: Vec, -} - -impl WidgetIterator { - fn new(tree: Tree) -> WidgetIterator { - WidgetIterator { stack: vec![tree] } - } -} - -impl Iterator for WidgetIterator { - type Item = Leaf; - fn next(&mut self) -> Option { - match self.stack.pop() { - Some(t) => { - match t { - Tree::Node(n) => { - let index = self.stack.len(); - for c in n.children { - self.stack.insert(index, c); - } - self.next() - } - Tree::Leaf(l) => Some(l), - } - } - None => None, - } - } -} - -pub struct Node { - pub children: Vec, -} - -impl Node { - pub fn add(&mut self, node: Tree) { - self.children.push(node); - } -} - -pub struct Leaf { - pub widget_type: WidgetType, - pub hash: u64, - pub buffer: Buffer, -} - pub struct Group { direction: Direction, alignment: Alignment, @@ -337,16 +273,14 @@ impl Group { self.chunks = Vec::from(chunks); self } - pub fn render(&self, area: &Rect, f: F) -> Tree - where F: Fn(&[Rect], &mut Node) + pub fn render(&self, area: &Rect, mut f: F) + where F: FnMut(&[Rect]) { let chunks = split(area, &self.direction, &self.alignment, self.margin, &self.chunks); - let mut node = Node { children: Vec::with_capacity(chunks.len()) }; - f(&chunks, &mut node); - Tree::Node(node) + f(&chunks); } } diff --git a/src/lib.rs b/src/lib.rs index d7e4735..f056979 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate bitflags; #[macro_use] extern crate log; extern crate cassowary; +extern crate unicode_segmentation; mod buffer; mod util; diff --git a/src/symbols.rs b/src/symbols.rs index fba8b28..efaac87 100644 --- a/src/symbols.rs +++ b/src/symbols.rs @@ -1,23 +1,36 @@ 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 = '▏'; + pub const FULL: &'static str = "█"; + pub const SEVEN_EIGHTHS: &'static str = "▉"; + pub const THREE_QUATERS: &'static str = "▊"; + pub const FIVE_EIGHTHS: &'static str = "▋"; + pub const HALF: &'static str = "▌"; + pub const THREE_EIGHTHS: &'static str = "▍"; + pub const ONE_QUATER: &'static str = "▎"; + pub const ONE_EIGHTH: &'static str = "▏"; } 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 = '▁'; + pub const FULL: &'static str = "█"; + pub const SEVEN_EIGHTHS: &'static str = "▇"; + pub const THREE_QUATERS: &'static str = "▆"; + pub const FIVE_EIGHTHS: &'static str = "▅"; + pub const HALF: &'static str = "▄"; + pub const THREE_EIGHTHS: &'static str = "▃"; + pub const ONE_QUATER: &'static str = "▂"; + pub const ONE_EIGHTH: &'static str = "▁"; } -pub const DOT: char = '•'; +pub mod line { + pub const TOP_RIGHT: &'static str = "┐"; + pub const VERTICAL: &'static str = "│"; + pub const HORIZONTAL: &'static str = "─"; + pub const TOP_LEFT: &'static str = "┌"; + pub const BOTTOM_RIGHT: &'static str = "┘"; + pub const BOTTOM_LEFT: &'static str = "└"; + pub const VERTICAL_LEFT: &'static str = "┤"; + pub const VERTICAL_RIGHT: &'static str = "├"; + pub const HORIZONTAL_DOWN: &'static str = "┬"; + pub const HORIZONTAL_UP: &'static str = "┴"; +} + +pub const DOT: &'static str = "•"; diff --git a/src/terminal.rs b/src/terminal.rs index 54f8db9..24476f3 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -6,21 +6,18 @@ use termion; use termion::raw::{IntoRawMode, RawTerminal}; use buffer::Buffer; -use widgets::WidgetType; -use layout::{Rect, Tree}; +use widgets::Widget; +use layout::Rect; +use util::hash; pub struct Terminal { stdout: RawTerminal, - cache: HashMap<(WidgetType, Rect), u64>, } impl Terminal { pub fn new() -> Result { let stdout = try!(io::stdout().into_raw_mode()); - Ok(Terminal { - stdout: stdout, - cache: HashMap::new(), - }) + Ok(Terminal { stdout: stdout }) } pub fn size() -> Result { @@ -33,49 +30,19 @@ impl Terminal { }) } - pub fn render(&mut self, ui: Tree) { - debug!("Render Pass"); - let mut buffers: Vec = Vec::new(); - let mut cache: HashMap<(WidgetType, Rect), u64> = HashMap::new(); - for node in ui { - let area = *node.buffer.area(); - match self.cache.remove(&(node.widget_type, area)) { - Some(h) => { - if h == node.hash { - debug!("Skip {:?} at {:?}", node.widget_type, area); - } else { - debug!("Update {:?} at {:?}", node.widget_type, area); - buffers.push(node.buffer); - } - } - None => { - buffers.push(node.buffer); - debug!("Render {:?} at {:?}", node.widget_type, area); - } - } - cache.insert((node.widget_type, area), node.hash); - } - for &(t, a) in self.cache.keys() { - buffers.insert(0, Buffer::empty(a)); - debug!("Erased {:?} at {:?}", t, a); - } - for buf in buffers { - self.render_buffer(&buf); - } - self.cache = cache; - } - - pub fn render_buffer(&mut self, buffer: &Buffer) { + pub fn render_buffer(&mut self, buffer: Buffer) { for (i, cell) in buffer.content().iter().enumerate() { let (lx, ly) = buffer.pos_of(i); let (x, y) = (lx + buffer.area().x, ly + buffer.area().y); - write!(self.stdout, - "{}{}{}{}", - termion::cursor::Goto(x + 1, y + 1), - cell.fg.fg(), - cell.bg.bg(), - cell.symbol) - .unwrap(); + if cell.symbol != "" { + write!(self.stdout, + "{}{}{}{}", + termion::cursor::Goto(x + 1, y + 1), + cell.fg.fg(), + cell.bg.bg(), + cell.symbol) + .unwrap(); + } } self.stdout.flush().unwrap(); } diff --git a/src/util.rs b/src/util.rs index cbf0e81..5ab4da3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,10 +3,9 @@ use std::hash::{Hash, Hasher, BuildHasher}; use layout::Rect; -pub fn hash(t: &T, area: &Rect) -> u64 { +pub fn hash(t: &T) -> u64 { 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 25c88ad..4116975 100644 --- a/src/widgets/block.rs +++ b/src/widgets/block.rs @@ -2,9 +2,10 @@ use buffer::Buffer; use layout::Rect; use style::Color; -use widgets::{Widget, WidgetType, border, Line, vline, hline}; +use widgets::{Widget, border}; +use symbols::line; -#[derive(Hash, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Block<'a> { title: Option<&'a str>, title_fg: Color, @@ -82,8 +83,8 @@ impl<'a> Block<'a> { } } -impl<'a> Widget for Block<'a> { - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for Block<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let mut buf = Buffer::empty(*area); @@ -93,55 +94,65 @@ impl<'a> Widget for Block<'a> { // Sides if self.borders.intersects(border::LEFT) { - let line = vline(area.x, area.y, area.height, self.border_fg, self.border_bg); - buf.merge(&line); + for y in 0..area.height { + let c = buf.update_cell(0, y, |c| { + c.symbol = line::VERTICAL; + c.fg = self.border_fg; + c.bg = self.border_bg; + }); + } } if self.borders.intersects(border::TOP) { - let line = hline(area.x, area.y, area.width, self.border_fg, self.border_bg); - buf.merge(&line); + for x in 0..area.width { + let c = buf.update_cell(x, 0, |c| { + c.symbol = line::HORIZONTAL; + c.fg = self.border_fg; + c.bg = self.border_bg; + }); + } } if self.borders.intersects(border::RIGHT) { - let line = vline(area.x + area.width - 1, - area.y, - area.height, - self.border_fg, - self.border_bg); - buf.merge(&line); + let x = area.width - 1; + for y in 0..area.height { + buf.update_cell(x, y, |c| { + c.symbol = line::VERTICAL; + c.fg = self.border_fg; + c.bg = self.border_bg; + }); + } } if self.borders.intersects(border::BOTTOM) { - let line = hline(area.x, - area.y + area.height - 1, - area.width, - self.border_fg, - self.border_bg); - buf.merge(&line); + let y = area.height - 1; + for x in 0..area.width { + buf.update_cell(x, y, |c| { + c.symbol = line::HORIZONTAL; + c.fg = self.border_fg; + c.bg = self.border_bg; + }); + } } // Corners if self.borders.contains(border::LEFT | border::TOP) { - buf.set_symbol(0, 0, Line::TopLeft.get()); + buf.set_symbol(0, 0, line::TOP_LEFT); } 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::TOP_RIGHT); } 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::BOTTOM_LEFT); } 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::BOTTOM_RIGHT); } if let Some(title) = self.title { - let (margin_x, string) = if self.borders.intersects(border::LEFT) { - (1, format!(" {} ", title)) + let margin_x = if self.borders.intersects(border::LEFT) { + 1 } else { - (0, String::from(title)) + 0 }; - buf.set_string(margin_x, 0, &string, self.title_fg, self.title_bg); + buf.set_string(margin_x, 0, &title, self.title_fg, self.title_bg); } buf } - - fn widget_type(&self) -> WidgetType { - WidgetType::Block - } } diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 8eb67b5..844a393 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -1,12 +1,12 @@ use std::cmp::min; -use widgets::{Widget, WidgetType, Block}; +use widgets::{Widget, Block}; use buffer::Buffer; use layout::Rect; use style::Color; +use util::hash; use symbols; -#[derive(Hash)] pub struct Chart<'a> { block: Option>, fg: Color, @@ -55,8 +55,8 @@ impl<'a> Chart<'a> { } } -impl<'a> Widget for Chart<'a> { - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for Chart<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let (mut buf, chart_area) = match self.block { Some(ref b) => (b.buffer(area), b.inner(*area)), None => (Buffer::empty(*area), *area), @@ -82,7 +82,4 @@ impl<'a> Widget for Chart<'a> { } buf } - fn widget_type(&self) -> WidgetType { - WidgetType::Chart - } } diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index 57c3418..3a42d09 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -1,7 +1,8 @@ -use widgets::{Widget, WidgetType, Block}; +use widgets::{Widget, Block}; use buffer::Buffer; use style::Color; use layout::Rect; +use util::hash; /// Progress bar widget /// @@ -17,10 +18,10 @@ use layout::Rect; /// .percent(20); /// } /// ``` -#[derive(Hash)] pub struct Gauge<'a> { block: Option>, percent: u16, + percent_string: String, fg: Color, bg: Color, } @@ -30,6 +31,7 @@ impl<'a> Default for Gauge<'a> { Gauge { block: None, percent: 0, + percent_string: String::from("0%"), bg: Color::White, fg: Color::Black, } @@ -44,6 +46,7 @@ impl<'a> Gauge<'a> { pub fn percent(&mut self, percent: u16) -> &mut Gauge<'a> { self.percent = percent; + self.percent_string = format!("{}%", percent); self } @@ -58,8 +61,8 @@ impl<'a> Gauge<'a> { } } -impl<'a> Widget for Gauge<'a> { - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for Gauge<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let (mut buf, gauge_area) = match self.block { Some(ref b) => (b.buffer(area), b.inner(*area)), None => (Buffer::empty(*area), *area), @@ -69,22 +72,23 @@ impl<'a> Widget for Gauge<'a> { } else { let margin_x = gauge_area.x - area.x; let margin_y = gauge_area.y - area.y; - // Label - 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_y, &percent_string, self.bg, self.fg); // Gauge let width = (gauge_area.width * self.percent) / 100; + info!("{}", width); + // 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); for i in 0..width { - buf.set_bg(margin_x + i, margin_y, self.bg); - buf.set_fg(margin_x + i, margin_y, self.fg); + buf.update_cell(margin_x + i, margin_y, |c| { + if c.symbol == "" { + c.symbol = " " + }; + c.fg = self.fg; + c.bg = self.bg; + }) } } buf } - - fn widget_type(&self) -> WidgetType { - WidgetType::Gauge - } } diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 9ba576d..bd9f70d 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -2,71 +2,44 @@ use std::cmp::min; use std::hash::{Hash, Hasher}; use buffer::Buffer; -use widgets::{Widget, WidgetType, Block}; +use widgets::{Widget, Block}; use layout::Rect; use style::Color; +use util::hash; -pub struct List<'a, T> { +pub struct List<'a> { block: Option>, selected: usize, - formatter: Box (String, Color, Color)>, - items: Vec, + items: Vec<(&'a str, Color, Color)>, } -impl<'a, T> Hash for List<'a, T> - where T: Hash -{ - fn hash(&self, state: &mut H) { - self.block.hash(state); - self.selected.hash(state); - self.items.hash(state); - } -} - -impl<'a, T> Default for List<'a, T> { - fn default() -> List<'a, T> { +impl<'a> Default for List<'a> { + fn default() -> List<'a> { List { block: None, selected: 0, - formatter: Box::new(|_, _| (String::from(""), Color::White, Color::Black)), items: Vec::new(), } } } -impl<'a, T> List<'a, T> - where T: Clone -{ - pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> { +impl<'a> List<'a> { + pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> { self.block = Some(block); self } - pub fn formatter(&'a mut self, f: F) -> &mut List<'a, T> - where F: 'static + Fn(&T, bool) -> (String, Color, Color) - { - self.formatter = Box::new(f); - self - } - - pub fn items(&'a mut self, items: &'a [T]) -> &mut List<'a, T> { + pub fn items(&'a mut self, items: &[(&'a str, Color, Color)]) -> &mut List<'a> { self.items = items.to_vec(); self } - - pub fn select(&'a mut self, index: usize) -> &mut List<'a, T> { - self.selected = index; - self - } } -impl<'a, T> Widget for List<'a, T> - where T: Hash -{ - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for List<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let (mut buf, list_area) = match self.block { - Some(ref b) => (b.buffer(area), area.inner(1)), + Some(ref b) => (b.buffer(area), b.inner(*area)), None => (Buffer::empty(*area), *area), }; @@ -80,16 +53,9 @@ impl<'a, T> Widget for List<'a, T> }; for i in 0..bound { let index = i + offset; - let item = &self.items[index]; - let formatter = &self.formatter; - let (mut string, fg, bg) = formatter(item, self.selected == index); - string.truncate(list_area.width as usize); - buf.set_string(1, 1 + i as u16, &string, fg, bg); + let (item, fg, bg) = self.items[index]; + buf.set_string(1, 1 + i as u16, item, fg, bg); } buf } - - fn widget_type(&self) -> WidgetType { - WidgetType::List - } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 8c6ad95..1c621e0 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -16,22 +16,9 @@ use std::hash::Hash; use util::hash; use buffer::{Buffer, Cell}; -use layout::{Rect, Tree, Leaf}; +use layout::Rect; use style::Color; - -#[allow(dead_code)] -enum Line { - Horizontal, - Vertical, - TopLeft, - TopRight, - BottomLeft, - BottomRight, - VerticalLeft, - VerticalRight, - HorizontalDown, - HorizontalUp, -} +use terminal::Terminal; pub mod border { bitflags! { @@ -46,71 +33,9 @@ pub mod border { } } -impl Line { - fn get(&self) -> char { - match *self { - Line::TopRight => '┐', - Line::Vertical => '│', - Line::Horizontal => '─', - Line::TopLeft => '┌', - Line::BottomRight => '┘', - Line::BottomLeft => '└', - Line::VerticalLeft => '┤', - Line::VerticalRight => '├', - Line::HorizontalDown => '┬', - Line::HorizontalUp => '┴', - } - } -} - -fn hline(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer { - Buffer::filled(Rect { - x: x, - y: y, - width: len, - height: 1, - }, - Cell { - symbol: Line::Horizontal.get(), - fg: fg, - bg: bg, - }) -} -fn vline(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer { - Buffer::filled(Rect { - x: x, - y: y, - width: 1, - height: len, - }, - Cell { - symbol: Line::Vertical.get(), - fg: fg, - bg: bg, - }) -} - -#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] -pub enum WidgetType { - Block, - Text, - List, - Gauge, - Sparkline, - Chart, -} - -pub trait Widget: Hash { - fn buffer(&self, area: &Rect) -> Buffer; - fn widget_type(&self) -> WidgetType; - fn render(&self, area: &Rect) -> Tree { - let widget_type = self.widget_type(); - let hash = hash(&self, area); - let buffer = self.buffer(area); - Tree::Leaf(Leaf { - widget_type: widget_type, - hash: hash, - buffer: buffer, - }) +pub trait Widget<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a>; + fn render(&'a self, area: &Rect, t: &mut Terminal) { + t.render_buffer(self.buffer(area)); } } diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index 24ce2d6..e6ea2e6 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -2,11 +2,10 @@ use std::cmp::min; use layout::Rect; use buffer::Buffer; -use widgets::{Widget, WidgetType, Block}; +use widgets::{Widget, Block}; use style::Color; use symbols::bar; -#[derive(Hash)] pub struct Sparkline<'a> { block: Option>, fg: Color, @@ -55,8 +54,8 @@ impl<'a> Sparkline<'a> { } } -impl<'a> Widget for Sparkline<'a> { - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for Sparkline<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let (mut buf, spark_area) = match self.block { Some(ref b) => (b.buffer(area), b.inner(*area)), None => (Buffer::empty(*area), *area), @@ -76,18 +75,21 @@ impl<'a> Widget for Sparkline<'a> { .map(|e| e * spark_area.height as u64 * 8 / max) .collect::>(); for j in (0..spark_area.height).rev() { - let mut line = String::with_capacity(max_index); - for d in data.iter_mut().take(max_index) { - line.push(match *d { - 0 => ' ', - 1 => bar::ONE_EIGHTH, - 2 => bar::ONE_QUATER, - 3 => bar::THREE_EIGHTHS, - 4 => bar::HALF, - 5 => bar::FIVE_EIGHTHS, - 6 => bar::THREE_QUATERS, - 7 => bar::SEVEN_EIGHTHS, - _ => bar::FULL, + for (i, d) in data.iter_mut().take(max_index).enumerate() { + buf.update_cell(margin_x + i as u16, margin_y + j, |c| { + c.symbol = &match *d { + 0 => " ", + 1 => bar::ONE_EIGHTH, + 2 => bar::ONE_QUATER, + 3 => bar::THREE_EIGHTHS, + 4 => bar::HALF, + 5 => bar::FIVE_EIGHTHS, + 6 => bar::THREE_QUATERS, + 7 => bar::SEVEN_EIGHTHS, + _ => bar::FULL, + }; + c.fg = self.fg; + c.bg = self.bg; }); if *d > 8 { *d -= 8; @@ -95,13 +97,8 @@ impl<'a> Widget for Sparkline<'a> { *d = 0; } } - buf.set_string(margin_x, margin_y + j, &line, self.fg, self.bg); } } buf } - - fn widget_type(&self) -> WidgetType { - WidgetType::Sparkline - } } diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 1639c42..3f9085b 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -1,16 +1,16 @@ use std::cmp::min; -use widgets::{Widget, WidgetType, Block}; +use widgets::{Widget, Block}; use buffer::Buffer; use layout::Rect; use style::Color; -#[derive(Hash)] pub struct Text<'a> { block: Option>, fg: Color, bg: Color, text: &'a str, + colors: &'a [(u16, u16, u16, Color, Color)], } impl<'a> Default for Text<'a> { @@ -20,6 +20,7 @@ impl<'a> Default for Text<'a> { fg: Color::White, bg: Color::Black, text: "", + colors: &[], } } } @@ -44,26 +45,35 @@ impl<'a> Text<'a> { self.fg = fg; self } + + pub fn colors(&mut self, colors: &'a [(u16, u16, u16, Color, Color)]) -> &mut Text<'a> { + self.colors = colors; + self + } } -impl<'a> Widget for Text<'a> { - fn buffer(&self, area: &Rect) -> Buffer { +impl<'a> Widget<'a> for Text<'a> { + fn buffer(&'a self, area: &Rect) -> Buffer<'a> { let (mut buf, text_area) = match self.block { - Some(b) => (b.buffer(area), b.inner(*area)), + Some(ref b) => (b.buffer(area), b.inner(*area)), None => (Buffer::empty(*area), *area), }; - let mut lines = self.text.lines().map(String::from).collect::>(); + let mut lines = self.text.lines().collect::>(); let margin_x = text_area.x - area.x; let margin_y = text_area.y - area.y; let height = min(lines.len(), text_area.height as usize); let width = text_area.width as usize; for line in lines.iter_mut().take(height) { - line.truncate(width); - buf.set_string(margin_x, margin_y, line, self.fg, self.bg); + buf.set_string(margin_x, margin_y, &line, self.fg, self.bg); + } + for &(x, y, width, fg, bg) in self.colors { + for i in 0..width { + buf.update_cell(x + i, y + margin_y, |c| { + c.fg = fg; + c.bg = bg; + }) + } } buf } - fn widget_type(&self) -> WidgetType { - WidgetType::Text - } }