mirror of
https://github.com/ratatui-org/ratatui
synced 2025-02-16 14:08:44 +00:00
Cleanup code and add chart widget
This commit is contained in:
parent
2ffb63363c
commit
bd404f0238
12 changed files with 322 additions and 68 deletions
|
@ -9,6 +9,9 @@ use std::thread;
|
|||
use std::time;
|
||||
use std::sync::mpsc;
|
||||
use std::io::stdin;
|
||||
use std::cmp::min;
|
||||
|
||||
use rand::distributions::{IndependentSample, Range};
|
||||
|
||||
use termion::event;
|
||||
use termion::input::TermRead;
|
||||
|
@ -19,18 +22,68 @@ use log4rs::encode::pattern::PatternEncoder;
|
|||
use log4rs::config::{Appender, Config, Logger, Root};
|
||||
|
||||
use tui::Terminal;
|
||||
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, border};
|
||||
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart};
|
||||
use tui::layout::{Group, Direction, Alignment, Size};
|
||||
use tui::style::Color;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RandomSignal {
|
||||
range: Range<u64>,
|
||||
rng: rand::ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
fn new(r: Range<u64>) -> RandomSignal {
|
||||
RandomSignal {
|
||||
range: r,
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomSignal {
|
||||
type Item = u64;
|
||||
fn next(&mut self) -> Option<u64> {
|
||||
Some(self.range.ind_sample(&mut self.rng))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SinSignal {
|
||||
x: f64,
|
||||
period: f64,
|
||||
scale: f64,
|
||||
}
|
||||
|
||||
impl SinSignal {
|
||||
fn new(period: f64, scale: f64) -> SinSignal {
|
||||
SinSignal {
|
||||
x: 0.0,
|
||||
period: period,
|
||||
scale: scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SinSignal {
|
||||
type Item = f64;
|
||||
fn next(&mut self) -> Option<f64> {
|
||||
self.x += 1.0;
|
||||
Some(((self.x * 1.0 / self.period).sin() + 1.0) * self.scale)
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
name: String,
|
||||
fetching: bool,
|
||||
items: Vec<String>,
|
||||
selected: usize,
|
||||
show_episodes: bool,
|
||||
show_chart: bool,
|
||||
progress: u16,
|
||||
data: Vec<u64>,
|
||||
data2: Vec<u64>,
|
||||
colors: [Color; 2],
|
||||
color_index: usize,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
|
@ -53,14 +106,20 @@ fn main() {
|
|||
let handle = log4rs::init_config(config).unwrap();
|
||||
info!("Start");
|
||||
|
||||
let mut rand_signal = RandomSignal::new(Range::new(0, 100));
|
||||
let mut sin_signal = SinSignal::new(4.0, 20.0);
|
||||
|
||||
let mut app = App {
|
||||
name: String::from("Test app"),
|
||||
fetching: false,
|
||||
items: ["1", "2", "3"].into_iter().map(|e| String::from(*e)).collect(),
|
||||
selected: 0,
|
||||
show_episodes: false,
|
||||
show_chart: true,
|
||||
progress: 0,
|
||||
data: (0..100).map(|_| rand::random::<u8>() as u64).collect(),
|
||||
data: rand_signal.clone().take(100).collect(),
|
||||
data2: sin_signal.clone().take(100).map(|i| i as u64).collect(),
|
||||
colors: [Color::Magenta, Color::Red],
|
||||
color_index: 0,
|
||||
};
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let input_tx = tx.clone();
|
||||
|
@ -80,7 +139,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(500));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -108,7 +167,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
event::Key::Char('t') => {
|
||||
app.show_episodes = !app.show_episodes;
|
||||
app.show_chart = !app.show_chart;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -118,12 +177,18 @@ fn main() {
|
|||
if app.progress > 100 {
|
||||
app.progress = 0;
|
||||
}
|
||||
app.data.insert(0, rand::random::<u8>() as u64);
|
||||
app.data.insert(0, rand_signal.next().unwrap());
|
||||
app.data.pop();
|
||||
app.data2.remove(0);
|
||||
app.data2.push(sin_signal.next().unwrap() as u64);
|
||||
app.selected += 1;
|
||||
if app.selected >= app.items.len() {
|
||||
app.selected = 0;
|
||||
}
|
||||
app.color_index += 1;
|
||||
if app.color_index >= app.colors.len() {
|
||||
app.color_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +201,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
|||
.direction(Direction::Vertical)
|
||||
.alignment(Alignment::Left)
|
||||
.chunks(&[Size::Fixed(7), Size::Min(5), Size::Fixed(3)])
|
||||
.render(&terminal.area(), |chunks, tree| {
|
||||
.render(&Terminal::size().unwrap(), |chunks, tree| {
|
||||
tree.add(Block::default().borders(border::ALL).title("Graphs").render(&chunks[0]));
|
||||
tree.add(Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
@ -144,21 +209,21 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
|||
.margin(1)
|
||||
.chunks(&[Size::Fixed(2), Size::Fixed(3)])
|
||||
.render(&chunks[0], |chunks, tree| {
|
||||
tree.add(Gauge::new()
|
||||
tree.add(Gauge::default()
|
||||
.block(*Block::default().title("Gauge:"))
|
||||
.bg(Color::Yellow)
|
||||
.percent(app.progress)
|
||||
.render(&chunks[0]));
|
||||
tree.add(Sparkline::new()
|
||||
tree.add(Sparkline::default()
|
||||
.block(*Block::default().title("Sparkline:"))
|
||||
.fg(Color::Green)
|
||||
.data(&app.data)
|
||||
.render(&chunks[1]));
|
||||
}));
|
||||
let sizes = if app.show_episodes {
|
||||
vec![Size::Min(20), Size::Max(40)]
|
||||
let sizes = if app.show_chart {
|
||||
vec![Size::Max(40), Size::Min(20)]
|
||||
} else {
|
||||
vec![Size::Min(20)]
|
||||
vec![Size::Max(40)]
|
||||
};
|
||||
tree.add(Group::default()
|
||||
.direction(Direction::Horizontal)
|
||||
|
@ -166,7 +231,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
|||
.chunks(&sizes)
|
||||
.render(&chunks[1], |chunks, tree| {
|
||||
tree.add(List::default()
|
||||
.block(*Block::default().borders(border::ALL).title("Podcasts"))
|
||||
.block(*Block::default().borders(border::ALL).title("List"))
|
||||
.items(&app.items)
|
||||
.select(app.selected)
|
||||
.formatter(|i, s| {
|
||||
|
@ -178,14 +243,22 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
|||
(format!("{} {}", prefix, i), fg, Color::Black)
|
||||
})
|
||||
.render(&chunks[0]));
|
||||
if app.show_episodes {
|
||||
tree.add(Block::default()
|
||||
.borders(border::ALL)
|
||||
.title("Episodes")
|
||||
if app.show_chart {
|
||||
tree.add(Chart::default()
|
||||
.block(*Block::default()
|
||||
.borders(border::ALL)
|
||||
.title("Chart"))
|
||||
.fg(Color::Cyan)
|
||||
.axis([0, 40])
|
||||
.data(&app.data2)
|
||||
.render(&chunks[1]));
|
||||
}
|
||||
}));
|
||||
tree.add(Block::default().borders(border::ALL).title("Footer").render(&chunks[2]));
|
||||
tree.add(Text::default()
|
||||
.block(*Block::default().borders(border::ALL).title("Footer"))
|
||||
.fg(app.colors[app.color_index])
|
||||
.text("This is a footer")
|
||||
.render(&chunks[2]));
|
||||
});
|
||||
terminal.render(ui);
|
||||
}
|
||||
|
|
|
@ -71,12 +71,11 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn next_pos(&self, x: u16, y: u16) -> Option<(u16, u16)> {
|
||||
let mut nx = x + 1;
|
||||
let mut ny = y;
|
||||
if nx >= self.area.width {
|
||||
nx = 0;
|
||||
ny = y + 1;
|
||||
}
|
||||
let (nx, ny) = if x + 1 > self.area.width {
|
||||
(0, y + 1)
|
||||
} else {
|
||||
(x + 1, y)
|
||||
};
|
||||
if ny >= self.area.height {
|
||||
None
|
||||
} else {
|
||||
|
@ -121,6 +120,13 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_cell<F>(&mut self, x: u16, y: u16, f: F)
|
||||
where F: Fn(&mut Cell)
|
||||
{
|
||||
let i = self.index_of(x, y);
|
||||
f(&mut self.content[i]);
|
||||
}
|
||||
|
||||
pub fn get(&self, x: u16, y: u16) -> &Cell {
|
||||
let i = self.index_of(x, y);
|
||||
&self.content[i]
|
||||
|
|
|
@ -150,17 +150,17 @@ pub fn split(area: &Rect,
|
|||
if let Some(last) = elements.last() {
|
||||
constraints.push(match *dir {
|
||||
Direction::Horizontal => {
|
||||
last.x + last.width | EQ(WEAK) | (dest_area.x + dest_area.width) as f64
|
||||
(last.x + last.width) | EQ(WEAK) | (dest_area.x + dest_area.width) as f64
|
||||
}
|
||||
Direction::Vertical => {
|
||||
last.y + last.height | EQ(WEAK) | (dest_area.y + dest_area.height) as f64
|
||||
(last.y + last.height) | EQ(WEAK) | (dest_area.y + dest_area.height) as f64
|
||||
}
|
||||
})
|
||||
}
|
||||
match *dir {
|
||||
Direction::Horizontal => {
|
||||
for pair in elements.windows(2) {
|
||||
constraints.push(pair[0].x + pair[0].width | EQ(REQUIRED) | pair[1].x);
|
||||
constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].y | EQ(REQUIRED) | dest_area.y as f64,
|
||||
|
@ -175,7 +175,7 @@ pub fn split(area: &Rect,
|
|||
}
|
||||
Direction::Vertical => {
|
||||
for pair in elements.windows(2) {
|
||||
constraints.push(pair[0].y + pair[0].height | EQ(REQUIRED) | pair[1].y);
|
||||
constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].x | EQ(REQUIRED) | dest_area.x as f64,
|
||||
|
@ -345,7 +345,7 @@ impl Group {
|
|||
&self.alignment,
|
||||
self.margin,
|
||||
&self.chunks);
|
||||
let mut node = Node { children: Vec::new() };
|
||||
let mut node = Node { children: Vec::with_capacity(chunks.len()) };
|
||||
f(&chunks, &mut node);
|
||||
Tree::Node(node)
|
||||
}
|
||||
|
|
|
@ -19,3 +19,5 @@ pub mod bar {
|
|||
pub const ONE_QUATER: char = '▂';
|
||||
pub const ONE_EIGHTH: char = '▁';
|
||||
}
|
||||
|
||||
pub const DOT: char = '•';
|
||||
|
|
|
@ -10,40 +10,36 @@ use widgets::WidgetType;
|
|||
use layout::{Rect, Tree};
|
||||
|
||||
pub struct Terminal {
|
||||
width: u16,
|
||||
height: u16,
|
||||
stdout: RawTerminal<io::Stdout>,
|
||||
previous: HashMap<(WidgetType, Rect), u64>,
|
||||
cache: HashMap<(WidgetType, Rect), u64>,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn new() -> Result<Terminal, io::Error> {
|
||||
let terminal = try!(termion::terminal_size());
|
||||
let stdout = try!(io::stdout().into_raw_mode());
|
||||
Ok(Terminal {
|
||||
width: terminal.0,
|
||||
height: terminal.1,
|
||||
stdout: stdout,
|
||||
previous: HashMap::new(),
|
||||
cache: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn area(&self) -> Rect {
|
||||
Rect {
|
||||
pub fn size() -> Result<Rect, io::Error> {
|
||||
let terminal = try!(termion::terminal_size());
|
||||
Ok(Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
width: terminal.0,
|
||||
height: terminal.1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self, ui: Tree) {
|
||||
debug!("Render Pass");
|
||||
let mut buffers: Vec<Buffer> = Vec::new();
|
||||
let mut previous: HashMap<(WidgetType, Rect), u64> = HashMap::new();
|
||||
for node in ui.into_iter() {
|
||||
let mut cache: HashMap<(WidgetType, Rect), u64> = HashMap::new();
|
||||
for node in ui {
|
||||
let area = *node.buffer.area();
|
||||
match self.previous.remove(&(node.widget_type, area)) {
|
||||
match self.cache.remove(&(node.widget_type, area)) {
|
||||
Some(h) => {
|
||||
if h == node.hash {
|
||||
debug!("Skip {:?} at {:?}", node.widget_type, area);
|
||||
|
@ -57,16 +53,16 @@ impl Terminal {
|
|||
debug!("Render {:?} at {:?}", node.widget_type, area);
|
||||
}
|
||||
}
|
||||
previous.insert((node.widget_type, area), node.hash);
|
||||
cache.insert((node.widget_type, area), node.hash);
|
||||
}
|
||||
for (&(t, a), _h) in &self.previous {
|
||||
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.previous = previous;
|
||||
self.cache = cache;
|
||||
}
|
||||
|
||||
pub fn render_buffer(&mut self, buffer: &Buffer) {
|
||||
|
|
|
@ -43,6 +43,17 @@ impl<'a> Block<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn border_fg(&mut self, color: Color) -> &mut Block<'a> {
|
||||
self.border_fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn border_bg(&mut self, color: Color) -> &mut Block<'a> {
|
||||
self.border_bg = color;
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn borders(&mut self, flag: border::Flags) -> &mut Block<'a> {
|
||||
self.borders = flag;
|
||||
self
|
||||
|
@ -119,11 +130,11 @@ impl<'a> Widget for Block<'a> {
|
|||
if self.borders.contains(border::BOTTOM | border::RIGHT) {
|
||||
buf.set_symbol(area.width - 1, area.height - 1, Line::BottomRight.get());
|
||||
}
|
||||
if let Some(ref title) = self.title {
|
||||
if let Some(title) = self.title {
|
||||
let (margin_x, string) = if self.borders.intersects(border::LEFT) {
|
||||
(1, format!(" {} ", title))
|
||||
} else {
|
||||
(0, format!("{}", title))
|
||||
(0, String::from(title))
|
||||
};
|
||||
buf.set_string(margin_x, 0, &string, self.title_fg, self.title_bg);
|
||||
}
|
||||
|
|
88
src/widgets/chart.rs
Normal file
88
src/widgets/chart.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use std::cmp::min;
|
||||
|
||||
use widgets::{Widget, WidgetType, Block};
|
||||
use buffer::Buffer;
|
||||
use layout::Rect;
|
||||
use style::Color;
|
||||
use symbols;
|
||||
|
||||
#[derive(Hash)]
|
||||
pub struct Chart<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
axis: [u64; 2],
|
||||
data: &'a [u64],
|
||||
}
|
||||
|
||||
impl<'a> Default for Chart<'a> {
|
||||
fn default() -> Chart<'a> {
|
||||
Chart {
|
||||
block: None,
|
||||
fg: Color::White,
|
||||
bg: Color::Black,
|
||||
axis: [0, 1],
|
||||
data: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Chart<'a> {
|
||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut Chart<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&mut self, bg: Color) -> &mut Chart<'a> {
|
||||
self.bg = bg;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(&mut self, fg: Color) -> &mut Chart<'a> {
|
||||
self.fg = fg;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn axis(&mut self, axis: [u64; 2]) -> &mut Chart<'a> {
|
||||
debug_assert!(self.axis[0] <= self.axis[1]);
|
||||
self.axis = axis;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn data(&mut self, data: &'a [u64]) -> &mut Chart<'a> {
|
||||
self.data = data;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Chart<'a> {
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
let (mut buf, chart_area) = match self.block {
|
||||
Some(ref b) => (b.buffer(area), b.inner(*area)),
|
||||
None => (Buffer::empty(*area), *area),
|
||||
};
|
||||
|
||||
if self.axis[1] == 0 {
|
||||
return buf;
|
||||
}
|
||||
|
||||
let margin_x = chart_area.x - area.x;
|
||||
let margin_y = chart_area.y - area.y;
|
||||
let max_index = min(chart_area.width as usize, self.data.len());
|
||||
for (i, &y) in self.data.iter().take(max_index).enumerate() {
|
||||
if y < self.axis[1] {
|
||||
let dy = (self.axis[1] - y) * (chart_area.height - 1) as u64 /
|
||||
(self.axis[1] - self.axis[0]);
|
||||
buf.update_cell(i as u16 + margin_x, dy as u16 + margin_y, |c| {
|
||||
c.symbol = symbols::DOT;
|
||||
c.fg = self.fg;
|
||||
c.bg = self.bg;
|
||||
})
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
fn widget_type(&self) -> WidgetType {
|
||||
WidgetType::Chart
|
||||
}
|
||||
}
|
|
@ -25,8 +25,8 @@ pub struct Gauge<'a> {
|
|||
bg: Color,
|
||||
}
|
||||
|
||||
impl<'a> Gauge<'a> {
|
||||
pub fn new() -> Gauge<'a> {
|
||||
impl<'a> Default for Gauge<'a> {
|
||||
fn default() -> Gauge<'a> {
|
||||
Gauge {
|
||||
block: None,
|
||||
percent: 0,
|
||||
|
@ -34,7 +34,9 @@ impl<'a> Gauge<'a> {
|
|||
fg: Color::Black,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Gauge<'a> {
|
||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut Gauge<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::cmp::min;
|
||||
use std::fmt::Display;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use buffer::Buffer;
|
||||
|
@ -62,7 +61,7 @@ impl<'a, T> List<'a, T>
|
|||
}
|
||||
|
||||
impl<'a, T> Widget for List<'a, T>
|
||||
where T: Display + Hash
|
||||
where T: Hash
|
||||
{
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
|
||||
|
@ -81,8 +80,8 @@ impl<'a, T> Widget for List<'a, T>
|
|||
};
|
||||
for i in 0..bound {
|
||||
let index = i + offset;
|
||||
let ref item = self.items[index];
|
||||
let ref formatter = self.formatter;
|
||||
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);
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
mod block;
|
||||
mod text;
|
||||
mod list;
|
||||
mod gauge;
|
||||
mod sparkline;
|
||||
mod chart;
|
||||
|
||||
pub use self::block::Block;
|
||||
pub use self::text::Text;
|
||||
pub use self::list::List;
|
||||
pub use self::gauge::Gauge;
|
||||
pub use self::sparkline::Sparkline;
|
||||
pub use self::chart::Chart;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
|
@ -43,7 +47,7 @@ pub mod border {
|
|||
}
|
||||
|
||||
impl Line {
|
||||
fn get<'a>(&self) -> char {
|
||||
fn get(&self) -> char {
|
||||
match *self {
|
||||
Line::TopRight => '┐',
|
||||
Line::Vertical => '│',
|
||||
|
@ -59,7 +63,7 @@ impl Line {
|
|||
}
|
||||
}
|
||||
|
||||
fn hline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
fn hline(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
Buffer::filled(Rect {
|
||||
x: x,
|
||||
y: y,
|
||||
|
@ -72,7 +76,7 @@ fn hline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
|||
bg: bg,
|
||||
})
|
||||
}
|
||||
fn vline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
fn vline(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
Buffer::filled(Rect {
|
||||
x: x,
|
||||
y: y,
|
||||
|
@ -89,9 +93,11 @@ fn vline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
|||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum WidgetType {
|
||||
Block,
|
||||
Text,
|
||||
List,
|
||||
Gauge,
|
||||
Sparkline,
|
||||
Chart,
|
||||
}
|
||||
|
||||
pub trait Widget: Hash {
|
||||
|
|
|
@ -15,8 +15,8 @@ pub struct Sparkline<'a> {
|
|||
max: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'a> Sparkline<'a> {
|
||||
pub fn new() -> Sparkline<'a> {
|
||||
impl<'a> Default for Sparkline<'a> {
|
||||
fn default() -> Sparkline<'a> {
|
||||
Sparkline {
|
||||
block: None,
|
||||
fg: Color::White,
|
||||
|
@ -25,7 +25,9 @@ impl<'a> Sparkline<'a> {
|
|||
max: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sparkline<'a> {
|
||||
pub fn block(&mut self, block: Block<'a>) -> &mut Sparkline<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
|
@ -75,22 +77,22 @@ impl<'a> Widget for Sparkline<'a> {
|
|||
.collect::<Vec<u64>>();
|
||||
for j in (0..spark_area.height).rev() {
|
||||
let mut line = String::with_capacity(max_index);
|
||||
for i in 0..max_index {
|
||||
line.push(match data[i] {
|
||||
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_EIGHTHS,
|
||||
7 => bar::THREE_QUATERS,
|
||||
6 => bar::THREE_QUATERS,
|
||||
7 => bar::SEVEN_EIGHTHS,
|
||||
_ => bar::FULL,
|
||||
});
|
||||
if data[i] > 8 {
|
||||
data[i] -= 8;
|
||||
if *d > 8 {
|
||||
*d -= 8;
|
||||
} else {
|
||||
data[i] = 0;
|
||||
*d = 0;
|
||||
}
|
||||
}
|
||||
buf.set_string(margin_x, margin_y + j, &line, self.fg, self.bg);
|
||||
|
|
69
src/widgets/text.rs
Normal file
69
src/widgets/text.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::cmp::min;
|
||||
|
||||
use widgets::{Widget, WidgetType, 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,
|
||||
}
|
||||
|
||||
impl<'a> Default for Text<'a> {
|
||||
fn default() -> Text<'a> {
|
||||
Text {
|
||||
block: None,
|
||||
fg: Color::White,
|
||||
bg: Color::Black,
|
||||
text: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Text<'a> {
|
||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut Text<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(&mut self, text: &'a str) -> &mut Text<'a> {
|
||||
self.text = text;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&mut self, bg: Color) -> &mut Text<'a> {
|
||||
self.bg = bg;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(&mut self, fg: Color) -> &mut Text<'a> {
|
||||
self.fg = fg;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Text<'a> {
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
let (mut buf, text_area) = match self.block {
|
||||
Some(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 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
|
||||
}
|
||||
fn widget_type(&self) -> WidgetType {
|
||||
WidgetType::Text
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue