mirror of
https://github.com/ratatui/ratatui.git
synced 2025-10-03 07:45:59 +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"
|
bitflags = "0.7"
|
||||||
cassowary = "0.2.0"
|
cassowary = "0.2.0"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
|
unicode-segmentation = "0.1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
log4rs = "*"
|
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();
|
terminal.hide_cursor();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
terminal.clear();
|
||||||
draw(&mut terminal, &app);
|
draw(&mut terminal, &app);
|
||||||
let evt = rx.recv().unwrap();
|
let evt = rx.recv().unwrap();
|
||||||
match evt {
|
match evt {
|
||||||
@ -195,70 +196,59 @@ fn main() {
|
|||||||
terminal.show_cursor();
|
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)
|
.direction(Direction::Vertical)
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
|
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
|
||||||
.render(&Terminal::size().unwrap(), |chunks, tree| {
|
.render(&Terminal::size().unwrap(), |chunks| {
|
||||||
tree.add(Block::default().borders(border::ALL).title("Graphs").render(&chunks[0]));
|
Block::default().borders(border::ALL).title("Graphs").render(&chunks[0], t);
|
||||||
tree.add(Group::default()
|
Group::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.chunks(&[Size::Fixed(2), Size::Fixed(3)])
|
.chunks(&[Size::Fixed(2), Size::Fixed(3)])
|
||||||
.render(&chunks[0], |chunks, tree| {
|
.render(&chunks[0], |chunks| {
|
||||||
tree.add(Gauge::default()
|
Gauge::default()
|
||||||
.block(*Block::default().title("Gauge:"))
|
.block(*Block::default().title("Gauge:"))
|
||||||
.bg(Color::Yellow)
|
.bg(Color::Yellow)
|
||||||
.percent(app.progress)
|
.percent(app.progress)
|
||||||
.render(&chunks[0]));
|
.render(&chunks[0], t);
|
||||||
tree.add(Sparkline::default()
|
Sparkline::default()
|
||||||
.block(*Block::default().title("Sparkline:"))
|
.block(*Block::default().title("Sparkline:"))
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.data(&app.data)
|
.data(&app.data)
|
||||||
.render(&chunks[1]));
|
.render(&chunks[1], t);
|
||||||
}));
|
});
|
||||||
let sizes = if app.show_chart {
|
let sizes = if app.show_chart {
|
||||||
vec![Size::Max(40), Size::Min(20)]
|
vec![Size::Max(40), Size::Min(20)]
|
||||||
} else {
|
} else {
|
||||||
vec![Size::Max(40)]
|
vec![Size::Max(40)]
|
||||||
};
|
};
|
||||||
tree.add(Group::default()
|
Group::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.chunks(&sizes)
|
.chunks(&sizes)
|
||||||
.render(&chunks[1], |chunks, tree| {
|
.render(&chunks[1], |chunks| {
|
||||||
tree.add(List::default()
|
List::default()
|
||||||
.block(*Block::default().borders(border::ALL).title("List"))
|
.block(*Block::default().borders(border::ALL).title("List"))
|
||||||
.items(&app.items)
|
.render(&chunks[0], t);
|
||||||
.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]));
|
|
||||||
if app.show_chart {
|
if app.show_chart {
|
||||||
tree.add(Chart::default()
|
Chart::default()
|
||||||
.block(*Block::default()
|
.block(*Block::default()
|
||||||
.borders(border::ALL)
|
.borders(border::ALL)
|
||||||
.title("Chart"))
|
.title("Chart"))
|
||||||
.fg(Color::Cyan)
|
.fg(Color::Cyan)
|
||||||
.axis([0, 40])
|
.axis([0, 40])
|
||||||
.data(&app.data2)
|
.data(&app.data2)
|
||||||
.render(&chunks[1]));
|
.render(&chunks[1], t);
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
tree.add(Text::default()
|
Text::default()
|
||||||
.block(*Block::default().borders(border::ALL).title("Footer"))
|
.block(*Block::default().borders(border::ALL).title("Footer"))
|
||||||
.fg(app.colors[app.color_index])
|
.fg(app.colors[app.color_index])
|
||||||
.text("This is a footer")
|
.text("This żółw is a footer")
|
||||||
.render(&chunks[2]));
|
.render(&chunks[2], t);
|
||||||
});
|
});
|
||||||
terminal.render(ui);
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cell {
|
pub struct Cell<'a> {
|
||||||
pub symbol: char,
|
pub symbol: &'a str,
|
||||||
pub fg: Color,
|
pub fg: Color,
|
||||||
pub bg: Color,
|
pub bg: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cell {
|
impl<'a> Default for Cell<'a> {
|
||||||
fn default() -> Cell {
|
fn default() -> Cell<'a> {
|
||||||
Cell {
|
Cell {
|
||||||
symbol: ' ',
|
symbol: "",
|
||||||
fg: Color::White,
|
fg: Color::White,
|
||||||
bg: Color::Black,
|
bg: Color::Black,
|
||||||
}
|
}
|
||||||
@ -19,13 +21,13 @@ impl Default for Cell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Buffer {
|
pub struct Buffer<'a> {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
content: Vec<Cell>,
|
content: Vec<Cell<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Buffer {
|
impl<'a> Default for Buffer<'a> {
|
||||||
fn default() -> Buffer {
|
fn default() -> Buffer<'a> {
|
||||||
Buffer {
|
Buffer {
|
||||||
area: Default::default(),
|
area: Default::default(),
|
||||||
content: Vec::new(),
|
content: Vec::new(),
|
||||||
@ -33,13 +35,13 @@ impl Default for Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl<'a> Buffer<'a> {
|
||||||
pub fn empty(area: Rect) -> Buffer {
|
pub fn empty(area: Rect) -> Buffer<'a> {
|
||||||
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) -> Buffer {
|
pub fn filled(area: Rect, cell: Cell<'a>) -> Buffer<'a> {
|
||||||
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 {
|
||||||
@ -51,7 +53,7 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> &[Cell] {
|
pub fn content(&'a self) -> &'a [Cell<'a>] {
|
||||||
&self.content
|
&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);
|
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: char) {
|
pub fn set_symbol(&mut self, x: u16, y: u16, symbol: &'a str) {
|
||||||
let i = self.index_of(x, y);
|
let i = self.index_of(x, y);
|
||||||
self.content[i].symbol = symbol;
|
self.content[i].symbol = symbol;
|
||||||
}
|
}
|
||||||
@ -102,11 +104,12 @@ impl Buffer {
|
|||||||
self.content[i].bg = color;
|
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);
|
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);
|
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].fg = fg;
|
||||||
self.content[index].bg = bg;
|
self.content[index].bg = bg;
|
||||||
match self.next_pos(cursor.0, cursor.1) {
|
match self.next_pos(cursor.0, cursor.1) {
|
||||||
@ -127,12 +130,7 @@ impl Buffer {
|
|||||||
f(&mut self.content[i]);
|
f(&mut self.content[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, x: u16, y: u16) -> &Cell {
|
pub fn merge(&'a mut self, other: Buffer<'a>) {
|
||||||
let i = self.index_of(x, y);
|
|
||||||
&self.content[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
||||||
|
@ -6,9 +6,7 @@ use cassowary::WeightedRelation::*;
|
|||||||
use cassowary::strength::{WEAK, REQUIRED};
|
use cassowary::strength::{WEAK, REQUIRED};
|
||||||
|
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use widgets::WidgetType;
|
|
||||||
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
Top,
|
Top,
|
||||||
Left,
|
Left,
|
||||||
@ -17,13 +15,12 @@ pub enum Alignment {
|
|||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash, Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
pub x: u16,
|
pub x: u16,
|
||||||
pub y: 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 {
|
pub struct Group {
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
@ -337,16 +273,14 @@ impl Group {
|
|||||||
self.chunks = Vec::from(chunks);
|
self.chunks = Vec::from(chunks);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn render<F>(&self, area: &Rect, f: F) -> Tree
|
pub fn render<F>(&self, area: &Rect, mut f: F)
|
||||||
where F: Fn(&[Rect], &mut Node)
|
where F: FnMut(&[Rect])
|
||||||
{
|
{
|
||||||
let chunks = split(area,
|
let chunks = split(area,
|
||||||
&self.direction,
|
&self.direction,
|
||||||
&self.alignment,
|
&self.alignment,
|
||||||
self.margin,
|
self.margin,
|
||||||
&self.chunks);
|
&self.chunks);
|
||||||
let mut node = Node { children: Vec::with_capacity(chunks.len()) };
|
f(&chunks);
|
||||||
f(&chunks, &mut node);
|
|
||||||
Tree::Node(node)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ extern crate bitflags;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate cassowary;
|
extern crate cassowary;
|
||||||
|
extern crate unicode_segmentation;
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -1,23 +1,36 @@
|
|||||||
pub mod block {
|
pub mod block {
|
||||||
pub const FULL: char = '█';
|
pub const FULL: &'static str = "█";
|
||||||
pub const SEVEN_EIGHTHS: char = '▉';
|
pub const SEVEN_EIGHTHS: &'static str = "▉";
|
||||||
pub const THREE_QUATERS: char = '▊';
|
pub const THREE_QUATERS: &'static str = "▊";
|
||||||
pub const FIVE_EIGHTHS: char = '▋';
|
pub const FIVE_EIGHTHS: &'static str = "▋";
|
||||||
pub const HALF: char = '▌';
|
pub const HALF: &'static str = "▌";
|
||||||
pub const THREE_EIGHTHS: char = '▍';
|
pub const THREE_EIGHTHS: &'static str = "▍";
|
||||||
pub const ONE_QUATER: char = '▎';
|
pub const ONE_QUATER: &'static str = "▎";
|
||||||
pub const ONE_EIGHTH: char = '▏';
|
pub const ONE_EIGHTH: &'static str = "▏";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod bar {
|
pub mod bar {
|
||||||
pub const FULL: char = '█';
|
pub const FULL: &'static str = "█";
|
||||||
pub const SEVEN_EIGHTHS: char = '▇';
|
pub const SEVEN_EIGHTHS: &'static str = "▇";
|
||||||
pub const THREE_QUATERS: char = '▆';
|
pub const THREE_QUATERS: &'static str = "▆";
|
||||||
pub const FIVE_EIGHTHS: char = '▅';
|
pub const FIVE_EIGHTHS: &'static str = "▅";
|
||||||
pub const HALF: char = '▄';
|
pub const HALF: &'static str = "▄";
|
||||||
pub const THREE_EIGHTHS: char = '▃';
|
pub const THREE_EIGHTHS: &'static str = "▃";
|
||||||
pub const ONE_QUATER: char = '▂';
|
pub const ONE_QUATER: &'static str = "▂";
|
||||||
pub const ONE_EIGHTH: char = '▁';
|
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 termion::raw::{IntoRawMode, RawTerminal};
|
||||||
|
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use widgets::WidgetType;
|
use widgets::Widget;
|
||||||
use layout::{Rect, Tree};
|
use layout::Rect;
|
||||||
|
use util::hash;
|
||||||
|
|
||||||
pub struct Terminal {
|
pub struct Terminal {
|
||||||
stdout: RawTerminal<io::Stdout>,
|
stdout: RawTerminal<io::Stdout>,
|
||||||
cache: HashMap<(WidgetType, Rect), u64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
Ok(Terminal {
|
Ok(Terminal { stdout: stdout })
|
||||||
stdout: stdout,
|
|
||||||
cache: HashMap::new(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size() -> Result<Rect, io::Error> {
|
pub fn size() -> Result<Rect, io::Error> {
|
||||||
@ -33,42 +30,11 @@ impl Terminal {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, ui: Tree) {
|
pub fn render_buffer(&mut self, buffer: Buffer) {
|
||||||
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) {
|
|
||||||
for (i, cell) in buffer.content().iter().enumerate() {
|
for (i, cell) in buffer.content().iter().enumerate() {
|
||||||
let (lx, ly) = buffer.pos_of(i);
|
let (lx, ly) = buffer.pos_of(i);
|
||||||
let (x, y) = (lx + buffer.area().x, ly + buffer.area().y);
|
let (x, y) = (lx + buffer.area().x, ly + buffer.area().y);
|
||||||
|
if cell.symbol != "" {
|
||||||
write!(self.stdout,
|
write!(self.stdout,
|
||||||
"{}{}{}{}",
|
"{}{}{}{}",
|
||||||
termion::cursor::Goto(x + 1, y + 1),
|
termion::cursor::Goto(x + 1, y + 1),
|
||||||
@ -77,6 +43,7 @@ impl Terminal {
|
|||||||
cell.symbol)
|
cell.symbol)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.stdout.flush().unwrap();
|
self.stdout.flush().unwrap();
|
||||||
}
|
}
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
|
@ -3,10 +3,9 @@ use std::hash::{Hash, Hasher, BuildHasher};
|
|||||||
|
|
||||||
use layout::Rect;
|
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 state = RandomState::new();
|
||||||
let mut hasher = state.build_hasher();
|
let mut hasher = state.build_hasher();
|
||||||
t.hash(&mut hasher);
|
t.hash(&mut hasher);
|
||||||
area.hash(&mut hasher);
|
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use style::Color;
|
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> {
|
pub struct Block<'a> {
|
||||||
title: Option<&'a str>,
|
title: Option<&'a str>,
|
||||||
title_fg: Color,
|
title_fg: Color,
|
||||||
@ -82,8 +83,8 @@ impl<'a> Block<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Block<'a> {
|
impl<'a> Widget<'a> for Block<'a> {
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
|
|
||||||
let mut buf = Buffer::empty(*area);
|
let mut buf = Buffer::empty(*area);
|
||||||
|
|
||||||
@ -93,55 +94,65 @@ impl<'a> Widget for Block<'a> {
|
|||||||
|
|
||||||
// Sides
|
// Sides
|
||||||
if self.borders.intersects(border::LEFT) {
|
if self.borders.intersects(border::LEFT) {
|
||||||
let line = vline(area.x, area.y, area.height, self.border_fg, self.border_bg);
|
for y in 0..area.height {
|
||||||
buf.merge(&line);
|
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) {
|
if self.borders.intersects(border::TOP) {
|
||||||
let line = hline(area.x, area.y, area.width, self.border_fg, self.border_bg);
|
for x in 0..area.width {
|
||||||
buf.merge(&line);
|
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) {
|
if self.borders.intersects(border::RIGHT) {
|
||||||
let line = vline(area.x + area.width - 1,
|
let x = area.width - 1;
|
||||||
area.y,
|
for y in 0..area.height {
|
||||||
area.height,
|
buf.update_cell(x, y, |c| {
|
||||||
self.border_fg,
|
c.symbol = line::VERTICAL;
|
||||||
self.border_bg);
|
c.fg = self.border_fg;
|
||||||
buf.merge(&line);
|
c.bg = self.border_bg;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if self.borders.intersects(border::BOTTOM) {
|
if self.borders.intersects(border::BOTTOM) {
|
||||||
let line = hline(area.x,
|
let y = area.height - 1;
|
||||||
area.y + area.height - 1,
|
for x in 0..area.width {
|
||||||
area.width,
|
buf.update_cell(x, y, |c| {
|
||||||
self.border_fg,
|
c.symbol = line::HORIZONTAL;
|
||||||
self.border_bg);
|
c.fg = self.border_fg;
|
||||||
buf.merge(&line);
|
c.bg = self.border_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::TopLeft.get());
|
buf.set_symbol(0, 0, 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::TopRight.get());
|
buf.set_symbol(area.width - 1, 0, line::TOP_RIGHT);
|
||||||
}
|
}
|
||||||
if self.borders.contains(border::BOTTOM | border::LEFT) {
|
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) {
|
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 {
|
if let Some(title) = self.title {
|
||||||
let (margin_x, string) = if self.borders.intersects(border::LEFT) {
|
let margin_x = if self.borders.intersects(border::LEFT) {
|
||||||
(1, format!(" {} ", title))
|
1
|
||||||
} else {
|
} 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
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_type(&self) -> WidgetType {
|
|
||||||
WidgetType::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
use widgets::{Widget, WidgetType, Block};
|
use widgets::{Widget, Block};
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
|
use util::hash;
|
||||||
use symbols;
|
use symbols;
|
||||||
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub struct Chart<'a> {
|
pub struct Chart<'a> {
|
||||||
block: Option<Block<'a>>,
|
block: Option<Block<'a>>,
|
||||||
fg: Color,
|
fg: Color,
|
||||||
@ -55,8 +55,8 @@ impl<'a> Chart<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Chart<'a> {
|
impl<'a> Widget<'a> for Chart<'a> {
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
let (mut buf, chart_area) = match self.block {
|
let (mut buf, chart_area) = match self.block {
|
||||||
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
||||||
None => (Buffer::empty(*area), *area),
|
None => (Buffer::empty(*area), *area),
|
||||||
@ -82,7 +82,4 @@ impl<'a> Widget for Chart<'a> {
|
|||||||
}
|
}
|
||||||
buf
|
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 buffer::Buffer;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
|
use util::hash;
|
||||||
|
|
||||||
/// Progress bar widget
|
/// Progress bar widget
|
||||||
///
|
///
|
||||||
@ -17,10 +18,10 @@ use layout::Rect;
|
|||||||
/// .percent(20);
|
/// .percent(20);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Hash)]
|
|
||||||
pub struct Gauge<'a> {
|
pub struct Gauge<'a> {
|
||||||
block: Option<Block<'a>>,
|
block: Option<Block<'a>>,
|
||||||
percent: u16,
|
percent: u16,
|
||||||
|
percent_string: String,
|
||||||
fg: Color,
|
fg: Color,
|
||||||
bg: Color,
|
bg: Color,
|
||||||
}
|
}
|
||||||
@ -30,6 +31,7 @@ impl<'a> Default for Gauge<'a> {
|
|||||||
Gauge {
|
Gauge {
|
||||||
block: None,
|
block: None,
|
||||||
percent: 0,
|
percent: 0,
|
||||||
|
percent_string: String::from("0%"),
|
||||||
bg: Color::White,
|
bg: Color::White,
|
||||||
fg: Color::Black,
|
fg: Color::Black,
|
||||||
}
|
}
|
||||||
@ -44,6 +46,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +61,8 @@ impl<'a> Gauge<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Gauge<'a> {
|
impl<'a> Widget<'a> for Gauge<'a> {
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
let (mut buf, gauge_area) = match self.block {
|
let (mut buf, gauge_area) = match self.block {
|
||||||
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
||||||
None => (Buffer::empty(*area), *area),
|
None => (Buffer::empty(*area), *area),
|
||||||
@ -69,22 +72,23 @@ impl<'a> Widget for Gauge<'a> {
|
|||||||
} else {
|
} else {
|
||||||
let margin_x = gauge_area.x - area.x;
|
let margin_x = gauge_area.x - area.x;
|
||||||
let margin_y = gauge_area.y - area.y;
|
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
|
// Gauge
|
||||||
let width = (gauge_area.width * self.percent) / 100;
|
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 {
|
for i in 0..width {
|
||||||
buf.set_bg(margin_x + i, margin_y, self.bg);
|
buf.update_cell(margin_x + i, margin_y, |c| {
|
||||||
buf.set_fg(margin_x + i, margin_y, self.fg);
|
if c.symbol == "" {
|
||||||
|
c.symbol = " "
|
||||||
|
};
|
||||||
|
c.fg = self.fg;
|
||||||
|
c.bg = self.bg;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_type(&self) -> WidgetType {
|
|
||||||
WidgetType::Gauge
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,71 +2,44 @@ use std::cmp::min;
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use widgets::{Widget, WidgetType, Block};
|
use widgets::{Widget, Block};
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
|
use util::hash;
|
||||||
|
|
||||||
pub struct List<'a, T> {
|
pub struct List<'a> {
|
||||||
block: Option<Block<'a>>,
|
block: Option<Block<'a>>,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
formatter: Box<Fn(&T, bool) -> (String, Color, Color)>,
|
items: Vec<(&'a str, Color, Color)>,
|
||||||
items: Vec<T>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Hash for List<'a, T>
|
impl<'a> Default for List<'a> {
|
||||||
where T: Hash
|
fn default() -> List<'a> {
|
||||||
{
|
|
||||||
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> {
|
|
||||||
List {
|
List {
|
||||||
block: None,
|
block: None,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
formatter: Box::new(|_, _| (String::from(""), Color::White, Color::Black)),
|
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> List<'a, T>
|
impl<'a> List<'a> {
|
||||||
where T: Clone
|
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a> {
|
||||||
{
|
|
||||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut List<'a, T> {
|
|
||||||
self.block = Some(block);
|
self.block = Some(block);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn formatter<F>(&'a mut self, f: F) -> &mut List<'a, T>
|
pub fn items(&'a mut self, items: &[(&'a str, Color, Color)]) -> &mut List<'a> {
|
||||||
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> {
|
|
||||||
self.items = items.to_vec();
|
self.items = items.to_vec();
|
||||||
self
|
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>
|
impl<'a> Widget<'a> for List<'a> {
|
||||||
where T: Hash
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
{
|
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
|
||||||
|
|
||||||
let (mut buf, list_area) = match self.block {
|
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),
|
None => (Buffer::empty(*area), *area),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,16 +53,9 @@ impl<'a, T> Widget for List<'a, T>
|
|||||||
};
|
};
|
||||||
for i in 0..bound {
|
for i in 0..bound {
|
||||||
let index = i + offset;
|
let index = i + offset;
|
||||||
let item = &self.items[index];
|
let (item, fg, bg) = self.items[index];
|
||||||
let formatter = &self.formatter;
|
buf.set_string(1, 1 + i as u16, item, fg, bg);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_type(&self) -> WidgetType {
|
|
||||||
WidgetType::List
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,9 @@ use std::hash::Hash;
|
|||||||
|
|
||||||
use util::hash;
|
use util::hash;
|
||||||
use buffer::{Buffer, Cell};
|
use buffer::{Buffer, Cell};
|
||||||
use layout::{Rect, Tree, Leaf};
|
use layout::Rect;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
|
use terminal::Terminal;
|
||||||
#[allow(dead_code)]
|
|
||||||
enum Line {
|
|
||||||
Horizontal,
|
|
||||||
Vertical,
|
|
||||||
TopLeft,
|
|
||||||
TopRight,
|
|
||||||
BottomLeft,
|
|
||||||
BottomRight,
|
|
||||||
VerticalLeft,
|
|
||||||
VerticalRight,
|
|
||||||
HorizontalDown,
|
|
||||||
HorizontalUp,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod border {
|
pub mod border {
|
||||||
bitflags! {
|
bitflags! {
|
||||||
@ -46,71 +33,9 @@ pub mod border {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
pub trait Widget<'a> {
|
||||||
fn get(&self) -> char {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a>;
|
||||||
match *self {
|
fn render(&'a self, area: &Rect, t: &mut Terminal) {
|
||||||
Line::TopRight => '┐',
|
t.render_buffer(self.buffer(area));
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,10 @@ use std::cmp::min;
|
|||||||
|
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use widgets::{Widget, WidgetType, Block};
|
use widgets::{Widget, Block};
|
||||||
use style::Color;
|
use style::Color;
|
||||||
use symbols::bar;
|
use symbols::bar;
|
||||||
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub struct Sparkline<'a> {
|
pub struct Sparkline<'a> {
|
||||||
block: Option<Block<'a>>,
|
block: Option<Block<'a>>,
|
||||||
fg: Color,
|
fg: Color,
|
||||||
@ -55,8 +54,8 @@ impl<'a> Sparkline<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Sparkline<'a> {
|
impl<'a> Widget<'a> for Sparkline<'a> {
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
let (mut buf, spark_area) = match self.block {
|
let (mut buf, spark_area) = match self.block {
|
||||||
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
||||||
None => (Buffer::empty(*area), *area),
|
None => (Buffer::empty(*area), *area),
|
||||||
@ -76,10 +75,10 @@ impl<'a> Widget for Sparkline<'a> {
|
|||||||
.map(|e| e * spark_area.height as u64 * 8 / max)
|
.map(|e| e * spark_area.height as u64 * 8 / max)
|
||||||
.collect::<Vec<u64>>();
|
.collect::<Vec<u64>>();
|
||||||
for j in (0..spark_area.height).rev() {
|
for j in (0..spark_area.height).rev() {
|
||||||
let mut line = String::with_capacity(max_index);
|
for (i, d) in data.iter_mut().take(max_index).enumerate() {
|
||||||
for d in data.iter_mut().take(max_index) {
|
buf.update_cell(margin_x + i as u16, margin_y + j, |c| {
|
||||||
line.push(match *d {
|
c.symbol = &match *d {
|
||||||
0 => ' ',
|
0 => " ",
|
||||||
1 => bar::ONE_EIGHTH,
|
1 => bar::ONE_EIGHTH,
|
||||||
2 => bar::ONE_QUATER,
|
2 => bar::ONE_QUATER,
|
||||||
3 => bar::THREE_EIGHTHS,
|
3 => bar::THREE_EIGHTHS,
|
||||||
@ -88,6 +87,9 @@ impl<'a> Widget for Sparkline<'a> {
|
|||||||
6 => bar::THREE_QUATERS,
|
6 => bar::THREE_QUATERS,
|
||||||
7 => bar::SEVEN_EIGHTHS,
|
7 => bar::SEVEN_EIGHTHS,
|
||||||
_ => bar::FULL,
|
_ => bar::FULL,
|
||||||
|
};
|
||||||
|
c.fg = self.fg;
|
||||||
|
c.bg = self.bg;
|
||||||
});
|
});
|
||||||
if *d > 8 {
|
if *d > 8 {
|
||||||
*d -= 8;
|
*d -= 8;
|
||||||
@ -95,13 +97,8 @@ impl<'a> Widget for Sparkline<'a> {
|
|||||||
*d = 0;
|
*d = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.set_string(margin_x, margin_y + j, &line, self.fg, self.bg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget_type(&self) -> WidgetType {
|
|
||||||
WidgetType::Sparkline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
use widgets::{Widget, WidgetType, Block};
|
use widgets::{Widget, Block};
|
||||||
use buffer::Buffer;
|
use buffer::Buffer;
|
||||||
use layout::Rect;
|
use layout::Rect;
|
||||||
use style::Color;
|
use style::Color;
|
||||||
|
|
||||||
#[derive(Hash)]
|
|
||||||
pub struct Text<'a> {
|
pub struct Text<'a> {
|
||||||
block: Option<Block<'a>>,
|
block: Option<Block<'a>>,
|
||||||
fg: Color,
|
fg: Color,
|
||||||
bg: Color,
|
bg: Color,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
|
colors: &'a [(u16, u16, u16, Color, Color)],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for Text<'a> {
|
impl<'a> Default for Text<'a> {
|
||||||
@ -20,6 +20,7 @@ impl<'a> Default for Text<'a> {
|
|||||||
fg: Color::White,
|
fg: Color::White,
|
||||||
bg: Color::Black,
|
bg: Color::Black,
|
||||||
text: "",
|
text: "",
|
||||||
|
colors: &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,26 +45,35 @@ impl<'a> Text<'a> {
|
|||||||
self.fg = fg;
|
self.fg = fg;
|
||||||
self
|
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> {
|
impl<'a> Widget<'a> for Text<'a> {
|
||||||
fn buffer(&self, area: &Rect) -> Buffer {
|
fn buffer(&'a self, area: &Rect) -> Buffer<'a> {
|
||||||
let (mut buf, text_area) = match self.block {
|
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),
|
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_x = text_area.x - area.x;
|
||||||
let margin_y = text_area.y - area.y;
|
let margin_y = text_area.y - area.y;
|
||||||
let height = min(lines.len(), text_area.height as usize);
|
let height = min(lines.len(), text_area.height as usize);
|
||||||
let width = text_area.width as usize;
|
let width = text_area.width as usize;
|
||||||
for line in lines.iter_mut().take(height) {
|
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
|
buf
|
||||||
}
|
}
|
||||||
fn widget_type(&self) -> WidgetType {
|
|
||||||
WidgetType::Text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user