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