From c91436baeeacc4f52958ca16df8dd6d09ad8815e Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Wed, 26 Oct 2016 14:32:45 +0200 Subject: [PATCH] Change rendering method and adapt widget trait accordingly --- examples/block.rs | 17 +++-- examples/prototype.rs | 35 +++++---- src/buffer.rs | 150 ++++++++++++++++++++------------------- src/layout.rs | 35 +++++---- src/style.rs | 2 +- src/terminal.rs | 102 +++++++++++++++++++++----- src/widgets/barchart.rs | 32 ++++----- src/widgets/block.rs | 49 ++++++------- src/widgets/chart.rs | 82 ++++++++++----------- src/widgets/gauge.rs | 53 +++++++------- src/widgets/list.rs | 29 +++++--- src/widgets/mod.rs | 10 +-- src/widgets/sparkline.rs | 22 +++--- src/widgets/text.rs | 24 +++---- 14 files changed, 362 insertions(+), 280 deletions(-) diff --git a/examples/block.rs b/examples/block.rs index 15150e5..f07d9e3 100644 --- a/examples/block.rs +++ b/examples/block.rs @@ -6,8 +6,9 @@ use termion::event; use termion::input::TermRead; use tui::Terminal; -use tui::widgets::{Widget, Block}; -use tui::layout::{Group, Direction, Alignment, Size}; +use tui::widgets::{Widget, Block, border}; +use tui::layout::{Group, Direction, Size}; +use tui::style::Color; fn main() { let mut terminal = Terminal::new().unwrap(); @@ -28,14 +29,16 @@ fn draw(t: &mut Terminal) { Group::default() .direction(Direction::Vertical) - .alignment(Alignment::Left) - .chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) + .sizes(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) .render(t, &Terminal::size().unwrap(), |t, chunks| { - Block::default().title("Block").render(&chunks[0], t); + Block::default() + .title("Block") + .title_color(Color::Red) + .borders(border::ALL) + .render(&chunks[0], t); Group::default() .direction(Direction::Vertical) - .alignment(Alignment::Left) - .chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) + .sizes(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)]) .render(t, &chunks[1], |t, chunks| { Block::default().title("Block").render(&chunks[0], t); }); diff --git a/examples/prototype.rs b/examples/prototype.rs index 2547435..9e6f23f 100644 --- a/examples/prototype.rs +++ b/examples/prototype.rs @@ -23,7 +23,7 @@ use log4rs::config::{Appender, Config, Root}; use tui::Terminal; use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, BarChart}; -use tui::layout::{Group, Direction, Size}; +use tui::layout::{Group, Direction, Size, Rect}; use tui::style::Color; #[derive(Clone)] @@ -74,6 +74,7 @@ impl Iterator for SinSignal { } struct App<'a> { + size: Rect, items: Vec<&'a str>, items2: Vec<&'a str>, selected: usize, @@ -113,11 +114,12 @@ fn main() { let mut sin_signal2 = SinSignal::new(2.0, 10.0); let mut app = App { + size: Rect::default(), items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", "Item9", "Item10"], items2: vec!["Event1", "Event2", "Event3", "Event4", "Event5", "Event6", "Event7", "Event8", "Event9", "Event10", "Event11", "Event12", "Event13", "Event14", - "Event15", "Event16", "Event17"], + "Event15", "Event16", "Event17", "Event18", "Event19"], selected: 0, show_chart: true, progress: 0, @@ -135,7 +137,10 @@ fn main() { ("B9", 14), ("B10", 15), ("B11", 1), - ("B12", 0)], + ("B12", 0), + ("B13", 4), + ("B14", 6), + ("B15", 4)], window: [0.0, 20.0], colors: [Color::Magenta, Color::Red], color_index: 0, @@ -163,7 +168,7 @@ fn main() { let tx = tx.clone(); loop { tx.send(Event::Tick).unwrap(); - thread::sleep(time::Duration::from_millis(1000)); + thread::sleep(time::Duration::from_millis(100)); } }); @@ -172,7 +177,11 @@ fn main() { terminal.hide_cursor(); loop { - terminal.clear(); + let size = Terminal::size().unwrap(); + if size != app.size { + terminal.resize(size); + app.size = size; + } draw(&mut terminal, &app); let evt = rx.recv().unwrap(); match evt { @@ -212,10 +221,6 @@ fn main() { app.data4.insert(0, i); app.window[0] += 1.0; app.window[1] += 1.0; - app.selected += 1; - if app.selected >= app.items.len() { - app.selected = 0; - } let i = app.items2.pop().unwrap(); app.items2.insert(0, i); app.color_index += 1; @@ -230,12 +235,11 @@ fn main() { fn draw(t: &mut Terminal, app: &App) { - let size = Terminal::size().unwrap(); Group::default() .direction(Direction::Vertical) .sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)]) - .render(t, &size, |t, chunks| { + .render(t, &app.size, |t, chunks| { Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t); Group::default() .direction(Direction::Vertical) @@ -244,7 +248,7 @@ fn draw(t: &mut Terminal, app: &App) { .render(t, &chunks[0], |t, chunks| { Gauge::default() .block(Block::default().title("Gauge:")) - .background_color(Color::Magenta) + .color(Color::Magenta) .percent(app.progress) .render(&chunks[0], t); Sparkline::default() @@ -274,7 +278,7 @@ fn draw(t: &mut Terminal, app: &App) { .block(Block::default().borders(border::ALL).title("List")) .items(&app.items) .select(app.selected) - .selection_color(Color::LightYellow) + .selection_color(Color::Yellow) .selection_symbol(">") .render(&chunks[0], t); List::default() @@ -287,9 +291,9 @@ fn draw(t: &mut Terminal, app: &App) { .data(&app.data4) .bar_width(3) .bar_gap(2) - .bar_color(Color::LightGreen) + .bar_color(Color::Green) .value_color(Color::Black) - .label_color(Color::LightYellow) + .label_color(Color::Yellow) .render(&chunks[1], t); }); if app.show_chart { @@ -324,4 +328,5 @@ fn draw(t: &mut Terminal, app: &App) { you can automatically wrap your text =).") .render(&chunks[2], t); }); + t.finish(); } diff --git a/src/buffer.rs b/src/buffer.rs index 27324e4..b097dbf 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -6,17 +6,25 @@ use unicode_segmentation::UnicodeSegmentation; use layout::Rect; use style::Color; -#[derive(Debug, Clone)] -pub struct Cell<'a> { - pub symbol: &'a str, +#[derive(Debug, Clone, PartialEq)] +pub struct Cell { + pub symbol: String, pub fg: Color, pub bg: Color, } -impl<'a> Default for Cell<'a> { - fn default() -> Cell<'a> { +impl Cell { + pub fn reset(&mut self) { + self.symbol = " ".into(); + self.fg = Color::Reset; + self.bg = Color::Reset; + } +} + +impl Default for Cell { + fn default() -> Cell { Cell { - symbol: "", + symbol: " ".into(), fg: Color::Reset, bg: Color::Reset, } @@ -24,13 +32,13 @@ impl<'a> Default for Cell<'a> { } #[derive(Debug, Clone)] -pub struct Buffer<'a> { - area: Rect, - content: Vec>, +pub struct Buffer { + pub area: Rect, + pub content: Vec, } -impl<'a> Default for Buffer<'a> { - fn default() -> Buffer<'a> { +impl Default for Buffer { + fn default() -> Buffer { Buffer { area: Default::default(), content: Vec::new(), @@ -38,13 +46,13 @@ impl<'a> Default for Buffer<'a> { } } -impl<'a> Buffer<'a> { - pub fn empty(area: Rect) -> Buffer<'a> { +impl Buffer { + pub fn empty(area: Rect) -> Buffer { let cell: Cell = Default::default(); Buffer::filled(area, cell) } - pub fn filled(area: Rect, cell: Cell<'a>) -> Buffer<'a> { + pub fn filled(area: Rect, cell: Cell) -> Buffer { let size = area.area() as usize; let mut content = Vec::with_capacity(size); for _ in 0..size { @@ -56,7 +64,7 @@ impl<'a> Buffer<'a> { } } - pub fn content(&'a self) -> &'a [Cell<'a>] { + pub fn content(&self) -> &[Cell] { &self.content } @@ -64,79 +72,59 @@ impl<'a> Buffer<'a> { &self.area } - pub fn index_of(&self, x: u16, y: u16) -> Option { - let index = (y * self.area.width + x) as usize; - if index < self.content.len() { - Some(index) - } else { - None - } + pub fn at(&self, x: u16, y: u16) -> &Cell { + let i = self.index_of(x, y); + &self.content[i] } - pub fn pos_of(&self, i: usize) -> Option<(u16, u16)> { - if self.area.width > 0 { - Some((i as u16 % self.area.width, i as u16 / self.area.width)) - } else { - None - } + pub fn index_of(&self, x: u16, y: u16) -> usize { + let index = (y * self.area.width + x) as usize; + debug_assert!(index < self.content.len()); + index } - pub fn next_pos(&self, x: u16, y: u16) -> Option<(u16, u16)> { - let (nx, ny) = if x + 1 > self.area.width { - (0, y + 1) - } else { - (x + 1, y) - }; - if ny >= self.area.height { - None - } else { - Some((nx, ny)) - } + pub fn pos_of(&self, i: usize) -> (u16, u16) { + debug_assert!(self.area.width > 0); + (i as u16 % self.area.width, i as u16 / self.area.width) } - pub fn set(&mut self, x: u16, y: u16, cell: Cell<'a>) { - if let Some(i) = self.index_of(x, y) { - self.content[i] = cell; - } + pub fn set(&mut self, x: u16, y: u16, cell: Cell) { + let i = self.index_of(x, y); + self.content[i] = cell; } - pub fn set_symbol(&mut self, x: u16, y: u16, symbol: &'a str) { - if let Some(i) = self.index_of(x, y) { - self.content[i].symbol = symbol; - } + pub fn set_symbol(&mut self, x: u16, y: u16, symbol: S) + where S: Into + { + let i = self.index_of(x, y); + self.content[i].symbol = symbol.into(); } pub fn set_fg(&mut self, x: u16, y: u16, color: Color) { - if let Some(i) = self.index_of(x, y) { - self.content[i].fg = 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) { - if let Some(i) = self.index_of(x, y) { - self.content[i].bg = color; - } + let i = self.index_of(x, y); + self.content[i].bg = color; } - pub fn set_string(&mut self, x: u16, y: u16, string: &'a str, fg: Color, bg: Color) { + pub fn set_string(&mut self, x: u16, y: u16, string: &str, fg: Color, bg: Color) { self.set_characters(x, y, string, usize::MAX, fg, bg); } pub fn set_characters(&mut self, x: u16, y: u16, - string: &'a str, + string: &str, limit: usize, fg: Color, bg: Color) { - let index = self.index_of(x, y); - if index.is_none() { - return; - } - let mut index = index.unwrap(); + let mut index = self.index_of(x, y); let graphemes = UnicodeSegmentation::graphemes(string, true).collect::>(); let max_index = min((self.area.width - x) as usize, limit); - for s in graphemes.iter().take(max_index) { - self.content[index].symbol = s; + for s in graphemes.into_iter().take(max_index) { + self.content[index].symbol = s.into(); self.content[index].fg = fg; self.content[index].bg = bg; index += 1; @@ -145,21 +133,37 @@ impl<'a> Buffer<'a> { pub fn update_colors(&mut self, x: u16, y: u16, fg: Color, bg: Color) { - if let Some(i) = self.index_of(x, y) { - self.content[i].fg = fg; - self.content[i].bg = bg; + let i = self.index_of(x, y); + self.content[i].fg = fg; + self.content[i].bg = bg; + } + + pub fn update_cell(&mut self, x: u16, y: u16, symbol: S, fg: Color, bg: Color) + where S: Into + { + let i = self.index_of(x, y); + self.content[i].symbol = symbol.into(); + self.content[i].fg = fg; + self.content[i].bg = bg; + } + + pub fn resize(&mut self, area: Rect) { + let length = area.area() as usize; + if self.content.len() > length { + self.content.truncate(length); + } else { + self.content.resize(length, Default::default()); } + self.area = area; } - pub fn update_cell(&mut self, x: u16, y: u16, symbol: &'a str, fg: Color, bg: Color) { - if let Some(i) = self.index_of(x, y) { - self.content[i].symbol = symbol; - self.content[i].fg = fg; - self.content[i].bg = bg; + pub fn reset(&mut self) { + for c in &mut self.content { + c.reset(); } } - pub fn merge(&'a mut self, other: Buffer<'a>) { + pub fn merge(&mut self, other: Buffer) { let area = self.area.union(&other.area); let cell: Cell = Default::default(); self.content.resize(area.area() as usize, cell.clone()); @@ -169,7 +173,7 @@ impl<'a> Buffer<'a> { let offset_y = self.area.y - area.y; let size = self.area.area() as usize; for i in (0..size).rev() { - let (x, y) = self.pos_of(i).unwrap(); + let (x, y) = self.pos_of(i); // New index in content let k = ((y + offset_y) * area.width + (x + offset_x)) as usize; self.content[k] = self.content[i].clone(); @@ -184,7 +188,7 @@ impl<'a> Buffer<'a> { let offset_y = other.area.y - area.y; let size = other.area.area() as usize; for i in 0..size { - let (x, y) = other.pos_of(i).unwrap(); + let (x, y) = other.pos_of(i); // New index in content let k = ((y + offset_y) * area.width + (x + offset_x)) as usize; self.content[k] = other.content[i].clone(); diff --git a/src/layout.rs b/src/layout.rs index 25317aa..925c73f 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -6,7 +6,6 @@ use cassowary::WeightedRelation::*; use cassowary::strength::{REQUIRED, WEAK}; use terminal::Terminal; -use util::hash; #[derive(Hash, PartialEq)] pub enum Direction { @@ -47,6 +46,22 @@ impl Rect { self.width * self.height } + pub fn left(&self) -> u16 { + self.x + } + + pub fn right(&self) -> u16 { + self.x + self.width + } + + pub fn top(&self) -> u16 { + self.y + } + + pub fn bottom(&self) -> u16 { + self.y + self.height + } + pub fn inner(&self, margin: u16) -> Rect { if self.width < 2 * margin || self.height < 2 * margin { Rect::default() @@ -209,7 +224,6 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec {} } } - info!("{:?}, {:?}", results, dest_area); results } @@ -233,9 +247,9 @@ impl Element { #[derive(Hash)] pub struct Group { - direction: Direction, - margin: u16, - sizes: Vec, + pub direction: Direction, + pub margin: u16, + pub sizes: Vec, } impl Default for Group { @@ -266,16 +280,7 @@ impl Group { pub fn render(&self, t: &mut Terminal, area: &Rect, mut f: F) where F: FnMut(&mut Terminal, &[Rect]) { - let hash = hash(self, area); - let (cache_update, chunks) = match t.get_layout(hash) { - Some(chs) => (false, chs.to_vec()), - None => (true, split(area, &self.direction, self.margin, &self.sizes)), - }; - + let chunks = t.compute_layout(self, area); f(t, &chunks); - - if cache_update { - t.set_layout(hash, chunks); - } } } diff --git a/src/style.rs b/src/style.rs index db70888..db502ae 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,6 @@ use termion; -#[derive(Debug, Clone, Copy, Hash)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Color { Reset, Black, diff --git a/src/terminal.rs b/src/terminal.rs index caa48f4..7d7c93a 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -6,19 +6,29 @@ use termion; use termion::raw::{IntoRawMode, RawTerminal}; use buffer::Buffer; -use layout::Rect; +use layout::{Rect, Group, split}; +use widgets::Widget; +use style::Color; +use util::hash; pub struct Terminal { stdout: RawTerminal, layout_cache: HashMap>, + layout_queue: Vec<(u64, Vec)>, + previous: Buffer, + current: Buffer, } impl Terminal { pub fn new() -> Result { let stdout = try!(io::stdout().into_raw_mode()); + let size = try!(Terminal::size()); Ok(Terminal { stdout: stdout, layout_cache: HashMap::new(), + layout_queue: Vec::new(), + previous: Buffer::empty(size), + current: Buffer::empty(size), }) } @@ -32,31 +42,85 @@ impl Terminal { }) } - // FIXME: Clean cache to prevent memory leak - pub fn get_layout(&self, hash: u64) -> Option<&Vec> { - self.layout_cache.get(&hash) + pub fn compute_layout(&mut self, group: &Group, area: &Rect) -> Vec { + let hash = hash(group, area); + let chunks = match self.layout_cache.get(&hash) { + Some(chunks) => chunks.clone(), + None => split(area, &group.direction, group.margin, &group.sizes), + }; + self.layout_queue.push((hash, chunks.clone())); + chunks } - pub fn set_layout(&mut self, hash: u64, chunks: Vec) { - self.layout_cache.insert(hash, chunks); - } - - pub fn render_buffer(&mut self, buffer: Buffer) { - let mut string = String::with_capacity(buffer.area().area() as usize); - for (i, cell) in buffer.content().iter().enumerate() { - let (lx, ly) = buffer.pos_of(i).unwrap(); - let (x, y) = (lx + buffer.area().x, ly + buffer.area().y); - if cell.symbol != "" { - string.push_str(&format!("{}{}{}{}", - termion::cursor::Goto(x + 1, y + 1), - cell.fg.fg(), - cell.bg.bg(), - cell.symbol)) + pub fn draw(&mut self) { + let width = self.current.area.width; + let mut string = String::with_capacity(self.current.content.len()); + let mut fg = Color::Reset; + let mut bg = Color::Reset; + let content = self.current + .content + .iter() + .zip(self.previous.content.iter()) + .enumerate() + .filter_map(|(i, (c, p))| if c != p { + let i = i as u16; + let x = i % width; + let y = i / width; + Some((x, y, c)) + } else { + None + }); + for (x, y, cell) in content { + string.push_str(&format!("{}", termion::cursor::Goto(x + 1, y + 1))); + if cell.fg != fg { + string.push_str(&cell.fg.fg()); + fg = cell.fg; } + if cell.bg != bg { + string.push_str(&cell.bg.bg()); + bg = cell.bg; + } + string.push_str(&format!("{}", cell.symbol)); } + string.push_str(&format!("{}{}", Color::Reset.fg(), Color::Reset.bg())); + info!("{}", string.len()); write!(self.stdout, "{}", string).unwrap(); + } + + pub fn render(&mut self, widget: &W, area: &Rect) + where W: Widget + { + widget.buffer(area, &mut self.current); + } + + pub fn resize(&mut self, area: Rect) { + self.current.resize(area); + self.previous.resize(area); + self.previous.reset(); + self.clear(); + } + + pub fn finish(&mut self) { + + // Draw to stdout + self.draw(); + + // Update layout cache + self.layout_cache.clear(); + for (hash, chunks) in self.layout_queue.drain(..) { + self.layout_cache.insert(hash, chunks); + } + + // Swap buffers + for (i, c) in self.current.content.iter().enumerate() { + self.previous.content[i] = c.clone(); + } + self.current.reset(); + + // Flush self.stdout.flush().unwrap(); } + pub fn clear(&mut self) { write!(self.stdout, "{}", termion::clear::All).unwrap(); write!(self.stdout, "{}", termion::cursor::Goto(1, 1)).unwrap(); diff --git a/src/widgets/barchart.rs b/src/widgets/barchart.rs index dfc28d5..7fd5a6d 100644 --- a/src/widgets/barchart.rs +++ b/src/widgets/barchart.rs @@ -77,19 +77,20 @@ impl<'a> BarChart<'a> { } } -impl<'a> Widget<'a> for BarChart<'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), +impl<'a> Widget for BarChart<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + let chart_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; if chart_area.height < 1 { - return buf; + return; } - let margin_x = chart_area.x - area.x; - let margin_y = chart_area.y - area.y; let max = self.max.unwrap_or(self.data.iter().fold(0, |acc, &(_, v)| max(v, acc))); let max_index = min((chart_area.width / (self.bar_width + self.bar_gap)) as usize, self.data.len()); @@ -113,8 +114,9 @@ impl<'a> Widget<'a> for BarChart<'a> { }; for x in 0..self.bar_width { - buf.update_cell(margin_x + i as u16 * (self.bar_width + self.bar_gap) + x, - margin_y + j, + buf.update_cell(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + + x, + chart_area.top() + j, symbol, self.bar_color, Color::Reset); @@ -134,22 +136,20 @@ impl<'a> Widget<'a> for BarChart<'a> { let value_label = &self.values[i]; let width = value_label.width() as u16; if width < self.bar_width { - buf.set_string(margin_x + i as u16 * (self.bar_width + self.bar_gap) + + buf.set_string(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + (self.bar_width - width) / 2, - chart_area.height - 1, + chart_area.bottom() - 2, value_label, self.value_color, self.bar_color); } } - buf.set_characters(margin_x + i as u16 * (self.bar_width + self.bar_gap), - chart_area.height, + buf.set_characters(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap), + chart_area.bottom() - 1, label, self.bar_width as usize, self.label_color, Color::Reset); } - - buf } } diff --git a/src/widgets/block.rs b/src/widgets/block.rs index 884257d..f725479 100644 --- a/src/widgets/block.rs +++ b/src/widgets/block.rs @@ -52,11 +52,11 @@ impl<'a> Block<'a> { self } - pub fn inner(&self, area: Rect) -> Rect { + pub fn inner(&self, area: &Rect) -> Rect { if area.width < 2 || area.height < 2 { return Rect::default(); } - let mut inner = area; + let mut inner = *area; if self.borders.intersects(border::LEFT) { inner.x += 1; inner.width -= 1; @@ -75,60 +75,61 @@ impl<'a> Block<'a> { } } -impl<'a> Widget<'a> for Block<'a> { - fn buffer(&'a self, area: &Rect) -> Buffer<'a> { - - let mut buf = Buffer::empty(*area); +impl<'a> Widget for Block<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { if area.width < 2 || area.height < 2 { - return buf; + return; } // Sides if self.borders.intersects(border::LEFT) { - for y in 0..area.height { - buf.update_cell(0, y, line::VERTICAL, self.border_color, self.bg); + for y in area.top()..area.bottom() { + buf.update_cell(area.left(), y, line::VERTICAL, self.border_color, self.bg); } } if self.borders.intersects(border::TOP) { - for x in 0..area.width { - buf.update_cell(x, 0, line::HORIZONTAL, self.border_color, self.bg); + for x in area.left()..area.right() { + buf.update_cell(x, area.top(), line::HORIZONTAL, self.border_color, self.bg); } } if self.borders.intersects(border::RIGHT) { - let x = area.width - 1; - for y in 0..area.height { + let x = area.right() - 1; + for y in area.top()..area.bottom() { buf.update_cell(x, y, line::VERTICAL, self.border_color, self.bg); } } if self.borders.intersects(border::BOTTOM) { - let y = area.height - 1; - for x in 0..area.width { + let y = area.bottom() - 1; + for x in area.left()..area.right() { buf.update_cell(x, y, line::HORIZONTAL, self.border_color, self.bg); } } // Corners if self.borders.contains(border::LEFT | border::TOP) { - buf.set_symbol(0, 0, line::TOP_LEFT); + buf.set_symbol(area.left(), area.top(), line::TOP_LEFT); } if self.borders.contains(border::RIGHT | border::TOP) { - buf.set_symbol(area.width - 1, 0, line::TOP_RIGHT); + buf.set_symbol(area.right() - 1, area.top(), line::TOP_RIGHT); } - if self.borders.contains(border::BOTTOM | border::LEFT) { - buf.set_symbol(0, area.height - 1, line::BOTTOM_LEFT); + if self.borders.contains(border::LEFT | border::BOTTOM) { + buf.set_symbol(area.left(), area.bottom() - 1, line::BOTTOM_LEFT); } - if self.borders.contains(border::BOTTOM | border::RIGHT) { - buf.set_symbol(area.width - 1, area.height - 1, line::BOTTOM_RIGHT); + if self.borders.contains(border::RIGHT | border::BOTTOM) { + buf.set_symbol(area.right() - 1, area.bottom() - 1, line::BOTTOM_RIGHT); } if let Some(title) = self.title { - let margin_x = if self.borders.intersects(border::LEFT) { + let dx = if self.borders.intersects(border::LEFT) { 1 } else { 0 }; - buf.set_string(margin_x, 0, title, self.title_color, self.bg); + buf.set_string(area.left() + dx, + area.top(), + title, + self.title_color, + self.bg); } - buf } } diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index 49bf95f..544b6a2 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -108,6 +108,7 @@ impl<'a> Default for Chart<'a> { } } +#[derive(Debug)] struct ChartLayout { legend_x: Option<(u16, u16)>, legend_y: Option<(u16, u16)>, @@ -158,39 +159,39 @@ impl<'a> Chart<'a> { self } - fn layout(&self, inner: &Rect, outer: &Rect) -> ChartLayout { + fn layout(&self, area: &Rect) -> ChartLayout { let mut layout = ChartLayout::default(); - if inner.height == 0 || inner.width == 0 { + if area.height == 0 || area.width == 0 { return layout; } - let mut x = inner.x - outer.x; - let mut y = inner.height + (inner.y - outer.y) - 1; + let mut x = area.left(); + let mut y = area.bottom() - 1; - if self.x_axis.labels.is_some() && y > 1 { + if self.x_axis.labels.is_some() && y > area.top() { layout.label_x = Some(y); y -= 1; } if let Some(labels) = self.y_axis.labels { let max_width = labels.iter().fold(0, |acc, l| max(l.width(), acc)) as u16; - if x + max_width < inner.width { + if x + max_width < area.right() { layout.label_y = Some(x); x += max_width; } } - if self.x_axis.labels.is_some() && y > 1 { + if self.x_axis.labels.is_some() && y > area.top() { layout.axis_x = Some(y); y -= 1; } - if self.y_axis.labels.is_some() && x + 1 < inner.width { + if self.y_axis.labels.is_some() && x + 1 < area.right() { layout.axis_y = Some(x); x += 1; } - if x < inner.width && y > 1 { - layout.graph_area = Rect::new(outer.x + x, inner.y, inner.width - x, y); + if x < area.right() && y > 1 { + layout.graph_area = Rect::new(x, area.top(), area.right() - x, y - area.top() + 1); } if let Some(title) = self.x_axis.title { @@ -203,28 +204,28 @@ impl<'a> Chart<'a> { if let Some(title) = self.y_axis.title { let w = title.width() as u16; if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 { - layout.legend_y = Some((x + 1, inner.y - outer.y)); + layout.legend_y = Some((x + 1, area.top())); } } layout } } -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), +impl<'a> Widget for Chart<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + let chart_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; - let layout = self.layout(&chart_area, area); - if layout.graph_area.width == 0 || layout.graph_area.height == 0 { - return buf; + let layout = self.layout(&chart_area); + let graph_area = layout.graph_area; + if graph_area.width == 0 || graph_area.height == 0 { + return; } - let width = layout.graph_area.width; - let height = layout.graph_area.height; - let margin_x = layout.graph_area.x - area.x; - let margin_y = layout.graph_area.y - area.y; if let Some((x, y)) = layout.legend_x { let title = self.x_axis.title.unwrap(); @@ -240,9 +241,10 @@ impl<'a> Widget<'a> for Chart<'a> { let labels = self.x_axis.labels.unwrap(); let total_width = labels.iter().fold(0, |acc, l| l.width() + acc) as u16; let labels_len = labels.len() as u16; - if total_width < width && labels_len > 1 { + if total_width < graph_area.width && labels_len > 1 { for (i, label) in labels.iter().enumerate() { - buf.set_string(margin_x + i as u16 * (width - 1) / (labels_len - 1) - + buf.set_string(graph_area.left() + + i as u16 * (graph_area.width - 1) / (labels_len - 1) - label.width() as u16, y, label, @@ -256,9 +258,10 @@ impl<'a> Widget<'a> for Chart<'a> { let labels = self.y_axis.labels.unwrap(); let labels_len = labels.len() as u16; if labels_len > 1 { - for (i, label) in labels.iter().rev().enumerate() { + for (i, label) in labels.iter().enumerate() { buf.set_string(x, - margin_y + i as u16 * (height - 1) / (labels_len - 1), + graph_area.bottom() - + i as u16 * (graph_area.height - 1) / (labels_len - 1), label, self.y_axis.labels_color, self.bg); @@ -267,22 +270,14 @@ impl<'a> Widget<'a> for Chart<'a> { } if let Some(y) = layout.axis_x { - for x in 0..width { - buf.update_cell(margin_x + x, - y, - symbols::line::HORIZONTAL, - self.x_axis.color, - self.bg); + for x in graph_area.left()..graph_area.right() { + buf.update_cell(x, y, symbols::line::HORIZONTAL, self.x_axis.color, self.bg); } } if let Some(x) = layout.axis_y { - for y in 0..height { - buf.update_cell(x, - margin_y + y, - symbols::line::VERTICAL, - self.y_axis.color, - self.bg); + for y in graph_area.top()..graph_area.bottom() { + buf.update_cell(x, y, symbols::line::VERTICAL, self.y_axis.color, self.bg); } } @@ -298,17 +293,16 @@ impl<'a> Widget<'a> for Chart<'a> { y < self.y_axis.bounds[0] || y > self.y_axis.bounds[1] { continue; } - let dy = (self.y_axis.bounds[1] - y) * height as f64 / + let dy = (self.y_axis.bounds[1] - y) * graph_area.height as f64 / (self.y_axis.bounds[1] - self.y_axis.bounds[0]); - let dx = (self.x_axis.bounds[1] - x) * width as f64 / + let dx = (self.x_axis.bounds[1] - x) * graph_area.width as f64 / (self.x_axis.bounds[1] - self.x_axis.bounds[0]); - buf.update_cell(dx as u16 + margin_x, - dy as u16 + margin_y, + buf.update_cell(dx as u16 + graph_area.left(), + dy as u16 + graph_area.top(), symbols::BLACK_CIRCLE, dataset.color, self.bg); } } - buf } } diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index ed549a6..4b420d7 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -1,4 +1,4 @@ -use std::cmp::{max, min}; +use unicode_width::UnicodeWidthStr; use widgets::{Widget, Block}; use buffer::Buffer; @@ -22,7 +22,6 @@ use layout::Rect; pub struct Gauge<'a> { block: Option>, percent: u16, - percent_string: String, color: Color, background_color: Color, } @@ -32,7 +31,6 @@ impl<'a> Default for Gauge<'a> { Gauge { block: None, percent: 0, - percent_string: String::from("0%"), color: Color::Reset, background_color: Color::Reset, } @@ -47,7 +45,6 @@ impl<'a> Gauge<'a> { pub fn percent(&mut self, percent: u16) -> &mut Gauge<'a> { self.percent = percent; - self.percent_string = format!("{}%", percent); self } @@ -62,39 +59,39 @@ impl<'a> Gauge<'a> { } } -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), +impl<'a> Widget for Gauge<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + let gauge_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; if gauge_area.height < 1 { - return buf; + return; } else { - let margin_x = gauge_area.x - area.x; - let margin_y = gauge_area.y - area.y; // Gauge let width = (gauge_area.width * self.percent) / 100; - for i in 0..width { - buf.update_cell(margin_x + i, - margin_y, - " ", - self.color, - self.background_color); + let end = gauge_area.left() + width; + + for x in gauge_area.left()..end { + buf.set_symbol(x, gauge_area.top(), " "); } + // Label - let len = self.percent_string.len() as u16; - let middle = gauge_area.width / 2 - len / 2; + let label = format!("{}%", self.percent); + let label_width = label.width() as u16; + let middle = (gauge_area.width - label_width) / 2 + gauge_area.left(); buf.set_string(middle, - margin_y, - &self.percent_string, - self.background_color, - self.color); - let bound = max(middle, min(middle + len, width)); - for i in middle..bound { - buf.update_colors(margin_x + i, margin_y, self.color, self.background_color); + gauge_area.top(), + &label, + self.color, + self.background_color); + + for x in gauge_area.left()..end { + buf.update_colors(x, gauge_area.top(), self.background_color, self.color); } } - buf } } diff --git a/src/widgets/list.rs b/src/widgets/list.rs index 8e6f1c1..6a186ee 100644 --- a/src/widgets/list.rs +++ b/src/widgets/list.rs @@ -68,11 +68,15 @@ impl<'a> List<'a> { } } -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), b.inner(*area)), - None => (Buffer::empty(*area), *area), +impl<'a> Widget for List<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + + let list_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; let list_length = self.items.len(); @@ -84,8 +88,8 @@ impl<'a> Widget<'a> for List<'a> { 0 }; let x = match self.selection_symbol { - Some(s) => (s.width() + 2) as u16, - None => 1, + Some(s) => (s.width() + 1) as u16 + list_area.left(), + None => list_area.left(), }; for i in 0..bound { let index = i + offset; @@ -95,15 +99,18 @@ impl<'a> Widget<'a> for List<'a> { } else { self.color }; - buf.set_string(x, 1 + i as u16, item, color, self.background_color); + buf.set_string(x, + list_area.top() + i as u16, + item, + color, + self.background_color); } if let Some(s) = self.selection_symbol { - buf.set_string(1, - (1 + self.selected - offset) as u16, + buf.set_string(list_area.left(), + list_area.top() + (self.selected - offset) as u16, s, self.selection_color, self.background_color); } - buf } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 976ef26..c749063 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -31,9 +31,11 @@ pub mod border { } } -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)); +pub trait Widget { + fn buffer(&self, area: &Rect, buf: &mut Buffer); + fn render(&self, area: &Rect, t: &mut Terminal) + where Self: Sized + { + t.render(self, area); } } diff --git a/src/widgets/sparkline.rs b/src/widgets/sparkline.rs index 9e1874d..6672edf 100644 --- a/src/widgets/sparkline.rs +++ b/src/widgets/sparkline.rs @@ -54,17 +54,18 @@ impl<'a> Sparkline<'a> { } } -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), +impl<'a> Widget for Sparkline<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + let spark_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; if spark_area.height < 1 { - return buf; + return; } else { - let margin_x = spark_area.x - area.x; - let margin_y = spark_area.y - area.y; let max = match self.max { Some(v) => v, None => *self.data.iter().max().unwrap_or(&1u64), @@ -88,8 +89,8 @@ impl<'a> Widget<'a> for Sparkline<'a> { 7 => bar::SEVEN_EIGHTHS, _ => bar::FULL, }; - buf.update_cell(margin_x + i as u16, - margin_y + j, + buf.update_cell(spark_area.left() + i as u16, + spark_area.top() + j, symbol, self.color, self.background_color); @@ -102,6 +103,5 @@ impl<'a> Widget<'a> for Sparkline<'a> { } } } - buf } } diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 23f4830..03461b2 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -148,19 +148,20 @@ impl<'a> Iterator for Parser<'a> { } } -impl<'a> Widget<'a> for Text<'a> { - fn buffer(&'a self, area: &Rect) -> Buffer<'a> { - let (mut buf, text_area) = match self.block { - Some(ref b) => (b.buffer(area), b.inner(*area)), - None => (Buffer::empty(*area), *area), +impl<'a> Widget for Text<'a> { + fn buffer(&self, area: &Rect, buf: &mut Buffer) { + let text_area = match self.block { + Some(ref b) => { + b.buffer(area, buf); + b.inner(area) + } + None => *area, }; if text_area.height < 1 { - return buf; + return; } - let margin_x = text_area.x - area.x; - let margin_y = text_area.y - area.y; let mut x = 0; let mut y = 0; for (s, c) in Parser::new(self.text) { @@ -181,16 +182,15 @@ impl<'a> Widget<'a> for Text<'a> { break; } - buf.update_cell(margin_x + x, margin_y + y, s, c, self.bg); + buf.update_cell(text_area.left() + x, text_area.top() + y, s, c, self.bg); x += 1; } for &(x, y, width, fg, bg) in self.colors { for i in 0..width { - buf.set_fg(x + i, y + margin_y, fg); - buf.set_bg(x + i, y + margin_y, bg); + buf.set_fg(x + i, y + text_area.top(), fg); + buf.set_bg(x + i, y + text_area.top(), bg); } } - buf } }