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 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);
});

@ -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();
}

@ -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<Cell<'a>>,
pub struct Buffer {
pub area: Rect,
pub content: Vec<Cell>,
}
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<usize> {
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<S>(&mut self, x: u16, y: u16, symbol: S)
where S: Into<String>
{
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::<Vec<&str>>();
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<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) {
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();

@ -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<R
_ => {}
}
}
info!("{:?}, {:?}", results, dest_area);
results
}
@ -233,9 +247,9 @@ impl Element {
#[derive(Hash)]
pub struct Group {
direction: Direction,
margin: u16,
sizes: Vec<Size>,
pub direction: Direction,
pub margin: u16,
pub sizes: Vec<Size>,
}
impl Default for Group {
@ -266,16 +280,7 @@ impl Group {
pub fn render<F>(&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);
}
}
}

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

@ -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<io::Stdout>,
layout_cache: HashMap<u64, Vec<Rect>>,
layout_queue: Vec<(u64, Vec<Rect>)>,
previous: Buffer,
current: Buffer,
}
impl Terminal {
pub fn new() -> Result<Terminal, io::Error> {
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<Rect>> {
self.layout_cache.get(&hash)
pub fn compute_layout(&mut self, group: &Group, area: &Rect) -> Vec<Rect> {
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<Rect>) {
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<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();
}
pub fn clear(&mut self) {
write!(self.stdout, "{}", termion::clear::All).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> {
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
}
}

@ -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
}
}

@ -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
}
}

@ -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<Block<'a>>,
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
}
}

@ -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
}
}

@ -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);
}
}

@ -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
}
}

@ -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
}
}

Loading…
Cancel
Save