Change rendering method and adapt widget trait accordingly

pull/3/head
Florian Dehau 8 years ago
parent ea485b5439
commit c91436baee

@ -6,8 +6,9 @@ use termion::event;
use termion::input::TermRead; use termion::input::TermRead;
use tui::Terminal; use tui::Terminal;
use tui::widgets::{Widget, Block}; use tui::widgets::{Widget, Block, border};
use tui::layout::{Group, Direction, Alignment, Size}; use tui::layout::{Group, Direction, Size};
use tui::style::Color;
fn main() { fn main() {
let mut terminal = Terminal::new().unwrap(); let mut terminal = Terminal::new().unwrap();
@ -28,14 +29,16 @@ fn draw(t: &mut Terminal) {
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.alignment(Alignment::Left) .sizes(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
.render(t, &Terminal::size().unwrap(), |t, chunks| { .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() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.alignment(Alignment::Left) .sizes(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
.render(t, &chunks[1], |t, chunks| { .render(t, &chunks[1], |t, chunks| {
Block::default().title("Block").render(&chunks[0], t); Block::default().title("Block").render(&chunks[0], t);
}); });

@ -23,7 +23,7 @@ use log4rs::config::{Appender, Config, Root};
use tui::Terminal; use tui::Terminal;
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset, use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
BarChart}; BarChart};
use tui::layout::{Group, Direction, Size}; use tui::layout::{Group, Direction, Size, Rect};
use tui::style::Color; use tui::style::Color;
#[derive(Clone)] #[derive(Clone)]
@ -74,6 +74,7 @@ impl Iterator for SinSignal {
} }
struct App<'a> { struct App<'a> {
size: Rect,
items: Vec<&'a str>, items: Vec<&'a str>,
items2: Vec<&'a str>, items2: Vec<&'a str>,
selected: usize, selected: usize,
@ -113,11 +114,12 @@ fn main() {
let mut sin_signal2 = SinSignal::new(2.0, 10.0); let mut sin_signal2 = SinSignal::new(2.0, 10.0);
let mut app = App { let mut app = App {
size: Rect::default(),
items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8", items: vec!["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Item8",
"Item9", "Item10"], "Item9", "Item10"],
items2: vec!["Event1", "Event2", "Event3", "Event4", "Event5", "Event6", "Event7", items2: vec!["Event1", "Event2", "Event3", "Event4", "Event5", "Event6", "Event7",
"Event8", "Event9", "Event10", "Event11", "Event12", "Event13", "Event14", "Event8", "Event9", "Event10", "Event11", "Event12", "Event13", "Event14",
"Event15", "Event16", "Event17"], "Event15", "Event16", "Event17", "Event18", "Event19"],
selected: 0, selected: 0,
show_chart: true, show_chart: true,
progress: 0, progress: 0,
@ -135,7 +137,10 @@ fn main() {
("B9", 14), ("B9", 14),
("B10", 15), ("B10", 15),
("B11", 1), ("B11", 1),
("B12", 0)], ("B12", 0),
("B13", 4),
("B14", 6),
("B15", 4)],
window: [0.0, 20.0], window: [0.0, 20.0],
colors: [Color::Magenta, Color::Red], colors: [Color::Magenta, Color::Red],
color_index: 0, color_index: 0,
@ -163,7 +168,7 @@ fn main() {
let tx = tx.clone(); let tx = tx.clone();
loop { loop {
tx.send(Event::Tick).unwrap(); 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(); terminal.hide_cursor();
loop { loop {
terminal.clear(); let size = Terminal::size().unwrap();
if size != app.size {
terminal.resize(size);
app.size = size;
}
draw(&mut terminal, &app); draw(&mut terminal, &app);
let evt = rx.recv().unwrap(); let evt = rx.recv().unwrap();
match evt { match evt {
@ -212,10 +221,6 @@ fn main() {
app.data4.insert(0, i); app.data4.insert(0, i);
app.window[0] += 1.0; app.window[0] += 1.0;
app.window[1] += 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(); let i = app.items2.pop().unwrap();
app.items2.insert(0, i); app.items2.insert(0, i);
app.color_index += 1; app.color_index += 1;
@ -230,12 +235,11 @@ fn main() {
fn draw(t: &mut Terminal, app: &App) { fn draw(t: &mut Terminal, app: &App) {
let size = Terminal::size().unwrap();
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.sizes(&[Size::Fixed(7), Size::Min(7), Size::Fixed(7)]) .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); Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t);
Group::default() Group::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@ -244,7 +248,7 @@ fn draw(t: &mut Terminal, app: &App) {
.render(t, &chunks[0], |t, chunks| { .render(t, &chunks[0], |t, chunks| {
Gauge::default() Gauge::default()
.block(Block::default().title("Gauge:")) .block(Block::default().title("Gauge:"))
.background_color(Color::Magenta) .color(Color::Magenta)
.percent(app.progress) .percent(app.progress)
.render(&chunks[0], t); .render(&chunks[0], t);
Sparkline::default() Sparkline::default()
@ -274,7 +278,7 @@ fn draw(t: &mut Terminal, app: &App) {
.block(Block::default().borders(border::ALL).title("List")) .block(Block::default().borders(border::ALL).title("List"))
.items(&app.items) .items(&app.items)
.select(app.selected) .select(app.selected)
.selection_color(Color::LightYellow) .selection_color(Color::Yellow)
.selection_symbol(">") .selection_symbol(">")
.render(&chunks[0], t); .render(&chunks[0], t);
List::default() List::default()
@ -287,9 +291,9 @@ fn draw(t: &mut Terminal, app: &App) {
.data(&app.data4) .data(&app.data4)
.bar_width(3) .bar_width(3)
.bar_gap(2) .bar_gap(2)
.bar_color(Color::LightGreen) .bar_color(Color::Green)
.value_color(Color::Black) .value_color(Color::Black)
.label_color(Color::LightYellow) .label_color(Color::Yellow)
.render(&chunks[1], t); .render(&chunks[1], t);
}); });
if app.show_chart { if app.show_chart {
@ -324,4 +328,5 @@ fn draw(t: &mut Terminal, app: &App) {
you can automatically wrap your text =).") you can automatically wrap your text =).")
.render(&chunks[2], t); .render(&chunks[2], t);
}); });
t.finish();
} }

@ -6,17 +6,25 @@ use unicode_segmentation::UnicodeSegmentation;
use layout::Rect; use layout::Rect;
use style::Color; use style::Color;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Cell<'a> { pub struct Cell {
pub symbol: &'a str, pub symbol: String,
pub fg: Color, pub fg: Color,
pub bg: Color, pub bg: Color,
} }
impl<'a> Default for Cell<'a> { impl Cell {
fn default() -> Cell<'a> { pub fn reset(&mut self) {
self.symbol = " ".into();
self.fg = Color::Reset;
self.bg = Color::Reset;
}
}
impl Default for Cell {
fn default() -> Cell {
Cell { Cell {
symbol: "", symbol: " ".into(),
fg: Color::Reset, fg: Color::Reset,
bg: Color::Reset, bg: Color::Reset,
} }
@ -24,13 +32,13 @@ impl<'a> Default for Cell<'a> {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Buffer<'a> { pub struct Buffer {
area: Rect, pub area: Rect,
content: Vec<Cell<'a>>, pub content: Vec<Cell>,
} }
impl<'a> Default for Buffer<'a> { impl Default for Buffer {
fn default() -> Buffer<'a> { fn default() -> Buffer {
Buffer { Buffer {
area: Default::default(), area: Default::default(),
content: Vec::new(), content: Vec::new(),
@ -38,13 +46,13 @@ impl<'a> Default for Buffer<'a> {
} }
} }
impl<'a> Buffer<'a> { impl Buffer {
pub fn empty(area: Rect) -> Buffer<'a> { pub fn empty(area: Rect) -> Buffer {
let cell: Cell = Default::default(); let cell: Cell = Default::default();
Buffer::filled(area, cell) 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 size = area.area() as usize;
let mut content = Vec::with_capacity(size); let mut content = Vec::with_capacity(size);
for _ in 0..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 &self.content
} }
@ -64,79 +72,59 @@ impl<'a> Buffer<'a> {
&self.area &self.area
} }
pub fn index_of(&self, x: u16, y: u16) -> Option<usize> { pub fn at(&self, x: u16, y: u16) -> &Cell {
let index = (y * self.area.width + x) as usize; let i = self.index_of(x, y);
if index < self.content.len() { &self.content[i]
Some(index)
} else {
None
}
} }
pub fn pos_of(&self, i: usize) -> Option<(u16, u16)> { pub fn index_of(&self, x: u16, y: u16) -> usize {
if self.area.width > 0 { let index = (y * self.area.width + x) as usize;
Some((i as u16 % self.area.width, i as u16 / self.area.width)) debug_assert!(index < self.content.len());
} else { index
None
}
} }
pub fn next_pos(&self, x: u16, y: u16) -> Option<(u16, u16)> { pub fn pos_of(&self, i: usize) -> (u16, u16) {
let (nx, ny) = if x + 1 > self.area.width { debug_assert!(self.area.width > 0);
(0, y + 1) (i as u16 % self.area.width, i as u16 / self.area.width)
} else {
(x + 1, y)
};
if ny >= self.area.height {
None
} else {
Some((nx, ny))
}
} }
pub fn set(&mut self, x: u16, y: u16, cell: Cell<'a>) { pub fn set(&mut self, x: u16, y: u16, cell: Cell) {
if let Some(i) = self.index_of(x, y) { let i = self.index_of(x, y);
self.content[i] = cell; self.content[i] = cell;
}
} }
pub fn set_symbol(&mut self, x: u16, y: u16, symbol: &'a str) { pub fn set_symbol<S>(&mut self, x: u16, y: u16, symbol: S)
if let Some(i) = self.index_of(x, y) { where S: Into<String>
self.content[i].symbol = symbol; {
} 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) { pub fn set_fg(&mut self, x: u16, y: u16, color: Color) {
if let Some(i) = self.index_of(x, y) { let i = self.index_of(x, y);
self.content[i].fg = color; self.content[i].fg = color;
}
} }
pub fn set_bg(&mut self, x: u16, y: u16, color: Color) { pub fn set_bg(&mut self, x: u16, y: u16, color: Color) {
if let Some(i) = self.index_of(x, y) { let i = self.index_of(x, y);
self.content[i].bg = color; 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); self.set_characters(x, y, string, usize::MAX, fg, bg);
} }
pub fn set_characters(&mut self, pub fn set_characters(&mut self,
x: u16, x: u16,
y: u16, y: u16,
string: &'a str, string: &str,
limit: usize, limit: usize,
fg: Color, fg: Color,
bg: Color) { bg: Color) {
let index = self.index_of(x, y); let mut index = self.index_of(x, y);
if index.is_none() {
return;
}
let mut index = index.unwrap();
let graphemes = UnicodeSegmentation::graphemes(string, true).collect::<Vec<&str>>(); let graphemes = UnicodeSegmentation::graphemes(string, true).collect::<Vec<&str>>();
let max_index = min((self.area.width - x) as usize, limit); let max_index = min((self.area.width - x) as usize, limit);
for s in graphemes.iter().take(max_index) { for s in graphemes.into_iter().take(max_index) {
self.content[index].symbol = s; self.content[index].symbol = s.into();
self.content[index].fg = fg; self.content[index].fg = fg;
self.content[index].bg = bg; self.content[index].bg = bg;
index += 1; index += 1;
@ -145,21 +133,37 @@ impl<'a> Buffer<'a> {
pub fn update_colors(&mut self, x: u16, y: u16, fg: Color, bg: Color) { pub fn update_colors(&mut self, x: u16, y: u16, fg: Color, bg: Color) {
if let Some(i) = self.index_of(x, y) { let i = self.index_of(x, y);
self.content[i].fg = fg; self.content[i].fg = fg;
self.content[i].bg = bg; self.content[i].bg = bg;
}
pub fn update_cell<S>(&mut self, x: u16, y: u16, symbol: S, fg: Color, bg: Color)
where S: Into<String>
{
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) { pub fn reset(&mut self) {
if let Some(i) = self.index_of(x, y) { for c in &mut self.content {
self.content[i].symbol = symbol; c.reset();
self.content[i].fg = fg;
self.content[i].bg = bg;
} }
} }
pub fn merge(&'a mut self, other: Buffer<'a>) { pub fn merge(&mut self, other: Buffer) {
let area = self.area.union(&other.area); let area = self.area.union(&other.area);
let cell: Cell = Default::default(); let cell: Cell = Default::default();
self.content.resize(area.area() as usize, cell.clone()); 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 offset_y = self.area.y - area.y;
let size = self.area.area() as usize; let size = self.area.area() as usize;
for i in (0..size).rev() { 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 // New index in content
let k = ((y + offset_y) * area.width + (x + offset_x)) as usize; let k = ((y + offset_y) * area.width + (x + offset_x)) as usize;
self.content[k] = self.content[i].clone(); self.content[k] = self.content[i].clone();
@ -184,7 +188,7 @@ impl<'a> Buffer<'a> {
let offset_y = other.area.y - area.y; let offset_y = other.area.y - area.y;
let size = other.area.area() as usize; let size = other.area.area() as usize;
for i in 0..size { for i in 0..size {
let (x, y) = other.pos_of(i).unwrap(); let (x, y) = other.pos_of(i);
// New index in content // New index in content
let k = ((y + offset_y) * area.width + (x + offset_x)) as usize; let k = ((y + offset_y) * area.width + (x + offset_x)) as usize;
self.content[k] = other.content[i].clone(); self.content[k] = other.content[i].clone();

@ -6,7 +6,6 @@ use cassowary::WeightedRelation::*;
use cassowary::strength::{REQUIRED, WEAK}; use cassowary::strength::{REQUIRED, WEAK};
use terminal::Terminal; use terminal::Terminal;
use util::hash;
#[derive(Hash, PartialEq)] #[derive(Hash, PartialEq)]
pub enum Direction { pub enum Direction {
@ -47,6 +46,22 @@ impl Rect {
self.width * self.height 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 { pub fn inner(&self, margin: u16) -> Rect {
if self.width < 2 * margin || self.height < 2 * margin { if self.width < 2 * margin || self.height < 2 * margin {
Rect::default() Rect::default()
@ -209,7 +224,6 @@ pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<R
_ => {} _ => {}
} }
} }
info!("{:?}, {:?}", results, dest_area);
results results
} }
@ -233,9 +247,9 @@ impl Element {
#[derive(Hash)] #[derive(Hash)]
pub struct Group { pub struct Group {
direction: Direction, pub direction: Direction,
margin: u16, pub margin: u16,
sizes: Vec<Size>, pub sizes: Vec<Size>,
} }
impl Default for Group { impl Default for Group {
@ -266,16 +280,7 @@ impl Group {
pub fn render<F>(&self, t: &mut Terminal, area: &Rect, mut f: F) pub fn render<F>(&self, t: &mut Terminal, area: &Rect, mut f: F)
where F: FnMut(&mut Terminal, &[Rect]) where F: FnMut(&mut Terminal, &[Rect])
{ {
let hash = hash(self, area); let chunks = t.compute_layout(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)),
};
f(t, &chunks); f(t, &chunks);
if cache_update {
t.set_layout(hash, chunks);
}
} }
} }

@ -1,6 +1,6 @@
use termion; use termion;
#[derive(Debug, Clone, Copy, Hash)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Color { pub enum Color {
Reset, Reset,
Black, Black,

@ -6,19 +6,29 @@ use termion;
use termion::raw::{IntoRawMode, RawTerminal}; use termion::raw::{IntoRawMode, RawTerminal};
use buffer::Buffer; use buffer::Buffer;
use layout::Rect; use layout::{Rect, Group, split};
use widgets::Widget;
use style::Color;
use util::hash;
pub struct Terminal { pub struct Terminal {
stdout: RawTerminal<io::Stdout>, stdout: RawTerminal<io::Stdout>,
layout_cache: HashMap<u64, Vec<Rect>>, layout_cache: HashMap<u64, Vec<Rect>>,
layout_queue: Vec<(u64, Vec<Rect>)>,
previous: Buffer,
current: Buffer,
} }
impl Terminal { impl Terminal {
pub fn new() -> Result<Terminal, io::Error> { pub fn new() -> Result<Terminal, io::Error> {
let stdout = try!(io::stdout().into_raw_mode()); let stdout = try!(io::stdout().into_raw_mode());
let size = try!(Terminal::size());
Ok(Terminal { Ok(Terminal {
stdout: stdout, stdout: stdout,
layout_cache: HashMap::new(), 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 compute_layout(&mut self, group: &Group, area: &Rect) -> Vec<Rect> {
pub fn get_layout(&self, hash: u64) -> Option<&Vec<Rect>> { let hash = hash(group, area);
self.layout_cache.get(&hash) 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<Rect>) { pub fn draw(&mut self) {
self.layout_cache.insert(hash, chunks); let width = self.current.area.width;
} let mut string = String::with_capacity(self.current.content.len());
let mut fg = Color::Reset;
pub fn render_buffer(&mut self, buffer: Buffer) { let mut bg = Color::Reset;
let mut string = String::with_capacity(buffer.area().area() as usize); let content = self.current
for (i, cell) in buffer.content().iter().enumerate() { .content
let (lx, ly) = buffer.pos_of(i).unwrap(); .iter()
let (x, y) = (lx + buffer.area().x, ly + buffer.area().y); .zip(self.previous.content.iter())
if cell.symbol != "" { .enumerate()
string.push_str(&format!("{}{}{}{}", .filter_map(|(i, (c, p))| if c != p {
termion::cursor::Goto(x + 1, y + 1), let i = i as u16;
cell.fg.fg(), let x = i % width;
cell.bg.bg(), let y = i / width;
cell.symbol)) 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(); write!(self.stdout, "{}", string).unwrap();
}
pub fn render<W>(&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(); self.stdout.flush().unwrap();
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
write!(self.stdout, "{}", termion::clear::All).unwrap(); write!(self.stdout, "{}", termion::clear::All).unwrap();
write!(self.stdout, "{}", termion::cursor::Goto(1, 1)).unwrap(); write!(self.stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();

@ -77,19 +77,20 @@ impl<'a> BarChart<'a> {
} }
} }
impl<'a> Widget<'a> for BarChart<'a> { impl<'a> Widget for BarChart<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, chart_area) = match self.block { let chart_area = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), Some(ref b) => {
None => (Buffer::empty(*area), *area), b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
if chart_area.height < 1 { 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 = 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, let max_index = min((chart_area.width / (self.bar_width + self.bar_gap)) as usize,
self.data.len()); self.data.len());
@ -113,8 +114,9 @@ impl<'a> Widget<'a> for BarChart<'a> {
}; };
for x in 0..self.bar_width { for x in 0..self.bar_width {
buf.update_cell(margin_x + i as u16 * (self.bar_width + self.bar_gap) + x, buf.update_cell(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) +
margin_y + j, x,
chart_area.top() + j,
symbol, symbol,
self.bar_color, self.bar_color,
Color::Reset); Color::Reset);
@ -134,22 +136,20 @@ impl<'a> Widget<'a> for BarChart<'a> {
let value_label = &self.values[i]; let value_label = &self.values[i];
let width = value_label.width() as u16; let width = value_label.width() as u16;
if width < self.bar_width { 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, (self.bar_width - width) / 2,
chart_area.height - 1, chart_area.bottom() - 2,
value_label, value_label,
self.value_color, self.value_color,
self.bar_color); self.bar_color);
} }
} }
buf.set_characters(margin_x + i as u16 * (self.bar_width + self.bar_gap), buf.set_characters(chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
chart_area.height, chart_area.bottom() - 1,
label, label,
self.bar_width as usize, self.bar_width as usize,
self.label_color, self.label_color,
Color::Reset); Color::Reset);
} }
buf
} }
} }

@ -52,11 +52,11 @@ impl<'a> Block<'a> {
self self
} }
pub fn inner(&self, area: Rect) -> Rect { pub fn inner(&self, area: &Rect) -> Rect {
if area.width < 2 || area.height < 2 { if area.width < 2 || area.height < 2 {
return Rect::default(); return Rect::default();
} }
let mut inner = area; let mut inner = *area;
if self.borders.intersects(border::LEFT) { if self.borders.intersects(border::LEFT) {
inner.x += 1; inner.x += 1;
inner.width -= 1; inner.width -= 1;
@ -75,60 +75,61 @@ impl<'a> Block<'a> {
} }
} }
impl<'a> Widget<'a> for Block<'a> { impl<'a> Widget for Block<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let mut buf = Buffer::empty(*area);
if area.width < 2 || area.height < 2 { if area.width < 2 || area.height < 2 {
return buf; return;
} }
// Sides // Sides
if self.borders.intersects(border::LEFT) { if self.borders.intersects(border::LEFT) {
for y in 0..area.height { for y in area.top()..area.bottom() {
buf.update_cell(0, y, line::VERTICAL, self.border_color, self.bg); buf.update_cell(area.left(), y, line::VERTICAL, self.border_color, self.bg);
} }
} }
if self.borders.intersects(border::TOP) { if self.borders.intersects(border::TOP) {
for x in 0..area.width { for x in area.left()..area.right() {
buf.update_cell(x, 0, line::HORIZONTAL, self.border_color, self.bg); buf.update_cell(x, area.top(), line::HORIZONTAL, self.border_color, self.bg);
} }
} }
if self.borders.intersects(border::RIGHT) { if self.borders.intersects(border::RIGHT) {
let x = area.width - 1; let x = area.right() - 1;
for y in 0..area.height { for y in area.top()..area.bottom() {
buf.update_cell(x, y, line::VERTICAL, self.border_color, self.bg); buf.update_cell(x, y, line::VERTICAL, self.border_color, self.bg);
} }
} }
if self.borders.intersects(border::BOTTOM) { if self.borders.intersects(border::BOTTOM) {
let y = area.height - 1; let y = area.bottom() - 1;
for x in 0..area.width { for x in area.left()..area.right() {
buf.update_cell(x, y, line::HORIZONTAL, self.border_color, self.bg); buf.update_cell(x, y, line::HORIZONTAL, self.border_color, self.bg);
} }
} }
// Corners // Corners
if self.borders.contains(border::LEFT | border::TOP) { 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) { 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) { if self.borders.contains(border::LEFT | border::BOTTOM) {
buf.set_symbol(0, area.height - 1, line::BOTTOM_LEFT); buf.set_symbol(area.left(), area.bottom() - 1, line::BOTTOM_LEFT);
} }
if self.borders.contains(border::BOTTOM | border::RIGHT) { if self.borders.contains(border::RIGHT | border::BOTTOM) {
buf.set_symbol(area.width - 1, area.height - 1, line::BOTTOM_RIGHT); buf.set_symbol(area.right() - 1, area.bottom() - 1, line::BOTTOM_RIGHT);
} }
if let Some(title) = self.title { if let Some(title) = self.title {
let margin_x = if self.borders.intersects(border::LEFT) { let dx = if self.borders.intersects(border::LEFT) {
1 1
} else { } else {
0 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
} }
} }

@ -108,6 +108,7 @@ impl<'a> Default for Chart<'a> {
} }
} }
#[derive(Debug)]
struct ChartLayout { struct ChartLayout {
legend_x: Option<(u16, u16)>, legend_x: Option<(u16, u16)>,
legend_y: Option<(u16, u16)>, legend_y: Option<(u16, u16)>,
@ -158,39 +159,39 @@ impl<'a> Chart<'a> {
self self
} }
fn layout(&self, inner: &Rect, outer: &Rect) -> ChartLayout { fn layout(&self, area: &Rect) -> ChartLayout {
let mut layout = ChartLayout::default(); let mut layout = ChartLayout::default();
if inner.height == 0 || inner.width == 0 { if area.height == 0 || area.width == 0 {
return layout; return layout;
} }
let mut x = inner.x - outer.x; let mut x = area.left();
let mut y = inner.height + (inner.y - outer.y) - 1; 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); layout.label_x = Some(y);
y -= 1; y -= 1;
} }
if let Some(labels) = self.y_axis.labels { if let Some(labels) = self.y_axis.labels {
let max_width = labels.iter().fold(0, |acc, l| max(l.width(), acc)) as u16; 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); layout.label_y = Some(x);
x += max_width; 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); layout.axis_x = Some(y);
y -= 1; 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); layout.axis_y = Some(x);
x += 1; x += 1;
} }
if x < inner.width && y > 1 { if x < area.right() && y > 1 {
layout.graph_area = Rect::new(outer.x + x, inner.y, inner.width - x, y); layout.graph_area = Rect::new(x, area.top(), area.right() - x, y - area.top() + 1);
} }
if let Some(title) = self.x_axis.title { if let Some(title) = self.x_axis.title {
@ -203,28 +204,28 @@ impl<'a> Chart<'a> {
if let Some(title) = self.y_axis.title { if let Some(title) = self.y_axis.title {
let w = title.width() as u16; let w = title.width() as u16;
if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 { 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 layout
} }
} }
impl<'a> Widget<'a> for Chart<'a> { impl<'a> Widget for Chart<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, chart_area) = match self.block { let chart_area = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), Some(ref b) => {
None => (Buffer::empty(*area), *area), b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
let layout = self.layout(&chart_area, area); let layout = self.layout(&chart_area);
if layout.graph_area.width == 0 || layout.graph_area.height == 0 { let graph_area = layout.graph_area;
return buf; 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 { if let Some((x, y)) = layout.legend_x {
let title = self.x_axis.title.unwrap(); 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 labels = self.x_axis.labels.unwrap();
let total_width = labels.iter().fold(0, |acc, l| l.width() + acc) as u16; let total_width = labels.iter().fold(0, |acc, l| l.width() + acc) as u16;
let labels_len = labels.len() 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() { 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, label.width() as u16,
y, y,
label, label,
@ -256,9 +258,10 @@ impl<'a> Widget<'a> for Chart<'a> {
let labels = self.y_axis.labels.unwrap(); let labels = self.y_axis.labels.unwrap();
let labels_len = labels.len() as u16; let labels_len = labels.len() as u16;
if labels_len > 1 { if labels_len > 1 {
for (i, label) in labels.iter().rev().enumerate() { for (i, label) in labels.iter().enumerate() {
buf.set_string(x, 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, label,
self.y_axis.labels_color, self.y_axis.labels_color,
self.bg); self.bg);
@ -267,22 +270,14 @@ impl<'a> Widget<'a> for Chart<'a> {
} }
if let Some(y) = layout.axis_x { if let Some(y) = layout.axis_x {
for x in 0..width { for x in graph_area.left()..graph_area.right() {
buf.update_cell(margin_x + x, buf.update_cell(x, y, symbols::line::HORIZONTAL, self.x_axis.color, self.bg);
y,
symbols::line::HORIZONTAL,
self.x_axis.color,
self.bg);
} }
} }
if let Some(x) = layout.axis_y { if let Some(x) = layout.axis_y {
for y in 0..height { for y in graph_area.top()..graph_area.bottom() {
buf.update_cell(x, buf.update_cell(x, y, symbols::line::VERTICAL, self.y_axis.color, self.bg);
margin_y + 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] { y < self.y_axis.bounds[0] || y > self.y_axis.bounds[1] {
continue; 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]); (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]); (self.x_axis.bounds[1] - self.x_axis.bounds[0]);
buf.update_cell(dx as u16 + margin_x, buf.update_cell(dx as u16 + graph_area.left(),
dy as u16 + margin_y, dy as u16 + graph_area.top(),
symbols::BLACK_CIRCLE, symbols::BLACK_CIRCLE,
dataset.color, dataset.color,
self.bg); self.bg);
} }
} }
buf
} }
} }

@ -1,4 +1,4 @@
use std::cmp::{max, min}; use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block}; use widgets::{Widget, Block};
use buffer::Buffer; use buffer::Buffer;
@ -22,7 +22,6 @@ use layout::Rect;
pub struct Gauge<'a> { pub struct Gauge<'a> {
block: Option<Block<'a>>, block: Option<Block<'a>>,
percent: u16, percent: u16,
percent_string: String,
color: Color, color: Color,
background_color: Color, background_color: Color,
} }
@ -32,7 +31,6 @@ impl<'a> Default for Gauge<'a> {
Gauge { Gauge {
block: None, block: None,
percent: 0, percent: 0,
percent_string: String::from("0%"),
color: Color::Reset, color: Color::Reset,
background_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> { pub fn percent(&mut self, percent: u16) -> &mut Gauge<'a> {
self.percent = percent; self.percent = percent;
self.percent_string = format!("{}%", percent);
self self
} }
@ -62,39 +59,39 @@ impl<'a> Gauge<'a> {
} }
} }
impl<'a> Widget<'a> for Gauge<'a> { impl<'a> Widget for Gauge<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, gauge_area) = match self.block { let gauge_area = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), Some(ref b) => {
None => (Buffer::empty(*area), *area), b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
if gauge_area.height < 1 { if gauge_area.height < 1 {
return buf; return;
} else { } else {
let margin_x = gauge_area.x - area.x;
let margin_y = gauge_area.y - area.y;
// Gauge // Gauge
let width = (gauge_area.width * self.percent) / 100; let width = (gauge_area.width * self.percent) / 100;
for i in 0..width { let end = gauge_area.left() + width;
buf.update_cell(margin_x + i,
margin_y, for x in gauge_area.left()..end {
" ", buf.set_symbol(x, gauge_area.top(), " ");
self.color,
self.background_color);
} }
// Label // Label
let len = self.percent_string.len() as u16; let label = format!("{}%", self.percent);
let middle = gauge_area.width / 2 - len / 2; let label_width = label.width() as u16;
let middle = (gauge_area.width - label_width) / 2 + gauge_area.left();
buf.set_string(middle, buf.set_string(middle,
margin_y, gauge_area.top(),
&self.percent_string, &label,
self.background_color, self.color,
self.color); self.background_color);
let bound = max(middle, min(middle + len, width));
for i in middle..bound { for x in gauge_area.left()..end {
buf.update_colors(margin_x + i, margin_y, self.color, self.background_color); buf.update_colors(x, gauge_area.top(), self.background_color, self.color);
} }
} }
buf
} }
} }

@ -68,11 +68,15 @@ impl<'a> List<'a> {
} }
} }
impl<'a> Widget<'a> for List<'a> { impl<'a> Widget for List<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, list_area) = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), let list_area = match self.block {
None => (Buffer::empty(*area), *area), Some(ref b) => {
b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
let list_length = self.items.len(); let list_length = self.items.len();
@ -84,8 +88,8 @@ impl<'a> Widget<'a> for List<'a> {
0 0
}; };
let x = match self.selection_symbol { let x = match self.selection_symbol {
Some(s) => (s.width() + 2) as u16, Some(s) => (s.width() + 1) as u16 + list_area.left(),
None => 1, None => list_area.left(),
}; };
for i in 0..bound { for i in 0..bound {
let index = i + offset; let index = i + offset;
@ -95,15 +99,18 @@ impl<'a> Widget<'a> for List<'a> {
} else { } else {
self.color 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 { if let Some(s) = self.selection_symbol {
buf.set_string(1, buf.set_string(list_area.left(),
(1 + self.selected - offset) as u16, list_area.top() + (self.selected - offset) as u16,
s, s,
self.selection_color, self.selection_color,
self.background_color); self.background_color);
} }
buf
} }
} }

@ -31,9 +31,11 @@ pub mod border {
} }
} }
pub trait Widget<'a> { pub trait Widget {
fn buffer(&'a self, area: &Rect) -> Buffer<'a>; fn buffer(&self, area: &Rect, buf: &mut Buffer);
fn render(&'a self, area: &Rect, t: &mut Terminal) { fn render(&self, area: &Rect, t: &mut Terminal)
t.render_buffer(self.buffer(area)); where Self: Sized
{
t.render(self, area);
} }
} }

@ -54,17 +54,18 @@ impl<'a> Sparkline<'a> {
} }
} }
impl<'a> Widget<'a> for Sparkline<'a> { impl<'a> Widget for Sparkline<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, spark_area) = match self.block { let spark_area = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), Some(ref b) => {
None => (Buffer::empty(*area), *area), b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
if spark_area.height < 1 { if spark_area.height < 1 {
return buf; return;
} else { } else {
let margin_x = spark_area.x - area.x;
let margin_y = spark_area.y - area.y;
let max = match self.max { let max = match self.max {
Some(v) => v, Some(v) => v,
None => *self.data.iter().max().unwrap_or(&1u64), None => *self.data.iter().max().unwrap_or(&1u64),
@ -88,8 +89,8 @@ impl<'a> Widget<'a> for Sparkline<'a> {
7 => bar::SEVEN_EIGHTHS, 7 => bar::SEVEN_EIGHTHS,
_ => bar::FULL, _ => bar::FULL,
}; };
buf.update_cell(margin_x + i as u16, buf.update_cell(spark_area.left() + i as u16,
margin_y + j, spark_area.top() + j,
symbol, symbol,
self.color, self.color,
self.background_color); self.background_color);
@ -102,6 +103,5 @@ impl<'a> Widget<'a> for Sparkline<'a> {
} }
} }
} }
buf
} }
} }

@ -148,19 +148,20 @@ impl<'a> Iterator for Parser<'a> {
} }
} }
impl<'a> Widget<'a> for Text<'a> { impl<'a> Widget for Text<'a> {
fn buffer(&'a self, area: &Rect) -> Buffer<'a> { fn buffer(&self, area: &Rect, buf: &mut Buffer) {
let (mut buf, text_area) = match self.block { let text_area = match self.block {
Some(ref b) => (b.buffer(area), b.inner(*area)), Some(ref b) => {
None => (Buffer::empty(*area), *area), b.buffer(area, buf);
b.inner(area)
}
None => *area,
}; };
if text_area.height < 1 { 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 x = 0;
let mut y = 0; let mut y = 0;
for (s, c) in Parser::new(self.text) { for (s, c) in Parser::new(self.text) {
@ -181,16 +182,15 @@ impl<'a> Widget<'a> for Text<'a> {
break; 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; x += 1;
} }
for &(x, y, width, fg, bg) in self.colors { for &(x, y, width, fg, bg) in self.colors {
for i in 0..width { for i in 0..width {
buf.set_fg(x + i, y + margin_y, fg); buf.set_fg(x + i, y + text_area.top(), fg);
buf.set_bg(x + i, y + margin_y, bg); buf.set_bg(x + i, y + text_area.top(), bg);
} }
} }
buf
} }
} }

Loading…
Cancel
Save