mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-28 21:41:49 +00:00
Add gauge, fix rendering and cleanup code
This commit is contained in:
parent
93f3263e2b
commit
5b5d37ee69
@ -5,6 +5,7 @@ extern crate log4rs;
|
||||
extern crate termion;
|
||||
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::sync::mpsc;
|
||||
use std::io::{Write, stdin};
|
||||
|
||||
@ -18,7 +19,7 @@ use log4rs::encode::pattern::PatternEncoder;
|
||||
use log4rs::config::{Appender, Config, Logger, Root};
|
||||
|
||||
use tui::Terminal;
|
||||
use tui::widgets::{Widget, Block, List, Border};
|
||||
use tui::widgets::{Widget, Block, List, Gauge, border};
|
||||
use tui::layout::{Group, Direction, Alignment, Size};
|
||||
|
||||
struct App {
|
||||
@ -27,26 +28,24 @@ struct App {
|
||||
items: Vec<String>,
|
||||
selected: usize,
|
||||
show_episodes: bool,
|
||||
progress: u16,
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Input(event::Key),
|
||||
Tick,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
let log = FileAppender::builder()
|
||||
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
|
||||
.encoder(Box::new(PatternEncoder::new("{l} / {d(%H:%M:%S)} / {M}:{L}{n}{m}{n}{n}")))
|
||||
.build("prototype.log")
|
||||
.unwrap();
|
||||
|
||||
let config = Config::builder()
|
||||
.appender(Appender::builder().build("log", Box::new(log)))
|
||||
.logger(Logger::builder()
|
||||
.appender("log")
|
||||
.additive(false)
|
||||
.build("log", LogLevelFilter::Info))
|
||||
.build(Root::builder().appender("log").build(LogLevelFilter::Info))
|
||||
.build(Root::builder().appender("log").build(LogLevelFilter::Debug))
|
||||
.unwrap();
|
||||
|
||||
let handle = log4rs::init_config(config).unwrap();
|
||||
@ -58,21 +57,30 @@ fn main() {
|
||||
items: ["1", "2", "3"].into_iter().map(|e| String::from(*e)).collect(),
|
||||
selected: 0,
|
||||
show_episodes: false,
|
||||
progress: 0,
|
||||
};
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let input_tx = tx.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let tx = tx.clone();
|
||||
let stdin = stdin();
|
||||
for c in stdin.keys() {
|
||||
let evt = c.unwrap();
|
||||
tx.send(Event::Input(evt)).unwrap();
|
||||
input_tx.send(Event::Input(evt)).unwrap();
|
||||
if evt == event::Key::Char('q') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
let tx = tx.clone();
|
||||
loop {
|
||||
tx.send(Event::Tick).unwrap();
|
||||
thread::sleep(time::Duration::from_millis(1000));
|
||||
}
|
||||
});
|
||||
|
||||
let mut terminal = Terminal::new().unwrap();
|
||||
terminal.clear();
|
||||
terminal.hide_cursor();
|
||||
@ -102,6 +110,12 @@ fn main() {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Event::Tick => {
|
||||
app.progress += 5;
|
||||
if app.progress > 100 {
|
||||
app.progress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
terminal.show_cursor();
|
||||
@ -112,12 +126,22 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
||||
let ui = Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
.alignment(Alignment::Left)
|
||||
.chunks(&[Size::Fixed(3), Size::Percent(100), Size::Fixed(3)])
|
||||
.chunks(&[Size::Fixed(5), Size::Percent(80), Size::Fixed(10)])
|
||||
.render(&terminal.area(), |chunks, tree| {
|
||||
tree.add(Block::default()
|
||||
.borders(Border::ALL)
|
||||
.title("Header")
|
||||
.render(&chunks[0]));
|
||||
tree.add(Block::default().borders(border::ALL).title("Gauges").render(&chunks[0]));
|
||||
tree.add(Group::default()
|
||||
.direction(Direction::Vertical)
|
||||
.alignment(Alignment::Left)
|
||||
.margin(1)
|
||||
.chunks(&[Size::Fixed(1), Size::Fixed(1), Size::Fixed(1)])
|
||||
.render(&chunks[0], |chunks, tree| {
|
||||
tree.add(Gauge::new()
|
||||
.percent(app.progress)
|
||||
.render(&chunks[0]));
|
||||
tree.add(Gauge::new()
|
||||
.percent(app.progress)
|
||||
.render(&chunks[2]));
|
||||
}));
|
||||
let sizes = if app.show_episodes {
|
||||
vec![Size::Percent(50), Size::Percent(50)]
|
||||
} else {
|
||||
@ -130,7 +154,7 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
||||
.render(&chunks[1], |chunks, tree| {
|
||||
tree.add(List::default()
|
||||
.block(|b| {
|
||||
b.borders(Border::ALL).title("Podcasts");
|
||||
b.borders(border::ALL).title("Podcasts");
|
||||
})
|
||||
.items(&app.items)
|
||||
.select(app.selected)
|
||||
@ -141,12 +165,12 @@ fn draw(terminal: &mut Terminal, app: &App) {
|
||||
.render(&chunks[0]));
|
||||
if app.show_episodes {
|
||||
tree.add(Block::default()
|
||||
.borders(Border::ALL)
|
||||
.borders(border::ALL)
|
||||
.title("Episodes")
|
||||
.render(&chunks[1]));
|
||||
}
|
||||
}));
|
||||
tree.add(Block::default().borders(Border::ALL).title("Footer").render(&chunks[2]));
|
||||
tree.add(Block::default().borders(border::ALL).title("Footer").render(&chunks[2]));
|
||||
});
|
||||
terminal.render(ui);
|
||||
}
|
||||
|
22
src/.lib.rs.rustfmt
Normal file
22
src/.lib.rs.rustfmt
Normal file
@ -0,0 +1,22 @@
|
||||
extern crate termion;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate cassowary;
|
||||
|
||||
mod buffer;
|
||||
mod util;
|
||||
pub mod symbols;
|
||||
pub mod terminal;
|
||||
pub mod widgets;
|
||||
pub mod style;
|
||||
pub mod layout;
|
||||
|
||||
pub use self::terminal::Terminal;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {}
|
||||
}
|
@ -94,6 +94,15 @@ impl Buffer {
|
||||
self.content[i].symbol = symbol;
|
||||
}
|
||||
|
||||
pub fn set_fg(&mut self, x: u16, y: u16, color: Color) {
|
||||
let i = self.index_of(x, y);
|
||||
self.content[i].fg = color;
|
||||
}
|
||||
pub fn set_bg(&mut self, x: u16, y: u16, color: Color) {
|
||||
let i = self.index_of(x, y);
|
||||
self.content[i].bg = color;
|
||||
}
|
||||
|
||||
pub fn set_string(&mut self, x: u16, y: u16, string: &str) {
|
||||
let mut cursor = (x, y);
|
||||
for c in string.chars() {
|
||||
|
@ -3,9 +3,8 @@ use std::collections::HashMap;
|
||||
|
||||
use cassowary::{Solver, Variable, Constraint};
|
||||
use cassowary::WeightedRelation::*;
|
||||
use cassowary::strength::{WEAK, MEDIUM, STRONG, REQUIRED};
|
||||
use cassowary::strength::{WEAK, MEDIUM, REQUIRED};
|
||||
|
||||
use util::hash;
|
||||
use buffer::Buffer;
|
||||
use widgets::WidgetType;
|
||||
|
||||
@ -24,7 +23,7 @@ pub enum Direction {
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Hash, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct Rect {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
@ -46,10 +45,10 @@ impl Default for Rect {
|
||||
impl Rect {
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,15 +56,15 @@ impl Rect {
|
||||
self.width * self.height
|
||||
}
|
||||
|
||||
pub fn inner(&self, spacing: u16) -> Rect {
|
||||
if self.width - spacing < 0 || self.height - spacing < 0 {
|
||||
pub fn inner(&self, margin: u16) -> Rect {
|
||||
if self.width < 2 * margin || self.height < 2 * margin {
|
||||
Rect::default()
|
||||
} else {
|
||||
Rect {
|
||||
x: self.x + spacing,
|
||||
y: self.y + spacing,
|
||||
width: self.width - 2 * spacing,
|
||||
height: self.height - 2 * spacing,
|
||||
x: self.x + margin,
|
||||
y: self.y + margin,
|
||||
width: self.width - 2 * margin,
|
||||
height: self.height - 2 * margin,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,16 +113,25 @@ pub enum Size {
|
||||
/// use tui::layout::{Rect, Size, Alignment, Direction, split};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10}, Direction::Vertical,
|
||||
/// Alignment::Left, &[Size::Fixed(5.0), Size::Percent(80.0)]);
|
||||
/// let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
|
||||
/// &Direction::Vertical,
|
||||
/// &Alignment::Left,
|
||||
/// 0,
|
||||
/// &[Size::Fixed(5), Size::Percent(80)]);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) -> Vec<Rect> {
|
||||
pub fn split(area: &Rect,
|
||||
dir: &Direction,
|
||||
align: &Alignment,
|
||||
margin: u16,
|
||||
sizes: &[Size])
|
||||
-> Vec<Rect> {
|
||||
let mut solver = Solver::new();
|
||||
let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
|
||||
let elements = sizes.iter().map(|e| Element::new()).collect::<Vec<Element>>();
|
||||
let mut results = sizes.iter().map(|e| Rect::default()).collect::<Vec<Rect>>();
|
||||
let elements = sizes.iter().map(|_| Element::new()).collect::<Vec<Element>>();
|
||||
let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
|
||||
let dest_area = area.inner(margin);
|
||||
for (i, e) in elements.iter().enumerate() {
|
||||
vars.insert(e.x, (i, 0));
|
||||
vars.insert(e.y, (i, 1));
|
||||
@ -131,20 +139,19 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
|
||||
vars.insert(e.height, (i, 3));
|
||||
}
|
||||
let mut constraints: Vec<Constraint> = Vec::new();
|
||||
if let Some(size) = sizes.first() {
|
||||
if let Some(first) = elements.first() {
|
||||
constraints.push(match *dir {
|
||||
Direction::Horizontal => elements[0].x | EQ(REQUIRED) | area.x as f64,
|
||||
Direction::Vertical => elements[0].y | EQ(REQUIRED) | area.y as f64,
|
||||
Direction::Horizontal => first.x | EQ(REQUIRED) | dest_area.x as f64,
|
||||
Direction::Vertical => first.y | EQ(REQUIRED) | dest_area.y as f64,
|
||||
})
|
||||
}
|
||||
if let Some(size) = sizes.last() {
|
||||
let last = elements.last().unwrap();
|
||||
if let Some(last) = elements.last() {
|
||||
constraints.push(match *dir {
|
||||
Direction::Horizontal => {
|
||||
last.x + last.width | EQ(REQUIRED) | (area.x + area.width) as f64
|
||||
last.x + last.width | EQ(REQUIRED) | (dest_area.x + dest_area.width) as f64
|
||||
}
|
||||
Direction::Vertical => {
|
||||
last.y + last.height | EQ(REQUIRED) | (area.y + area.height) as f64
|
||||
last.y + last.height | EQ(REQUIRED) | (dest_area.y + dest_area.height) as f64
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -154,12 +161,13 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
|
||||
constraints.push(pair[0].x + pair[0].width | LE(REQUIRED) | pair[1].x);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].y | EQ(REQUIRED) | area.y as f64,
|
||||
elements[i].height | EQ(REQUIRED) | area.height as f64,
|
||||
let cs = [elements[i].y | EQ(REQUIRED) | dest_area.y as f64,
|
||||
elements[i].height | EQ(REQUIRED) | dest_area.height as f64,
|
||||
match *size {
|
||||
Size::Fixed(f) => elements[i].width | EQ(REQUIRED) | f as f64,
|
||||
Size::Fixed(f) => elements[i].width | EQ(MEDIUM) | f as f64,
|
||||
Size::Percent(p) => {
|
||||
elements[i].width | EQ(WEAK) | (area.width * p) as f64 / 100.0
|
||||
elements[i].width | EQ(WEAK) |
|
||||
(dest_area.width * p) as f64 / 100.0
|
||||
}
|
||||
}];
|
||||
constraints.extend_from_slice(&cs);
|
||||
@ -170,12 +178,13 @@ pub fn split(area: &Rect, dir: &Direction, align: &Alignment, sizes: &[Size]) ->
|
||||
constraints.push(pair[0].y + pair[0].height | LE(REQUIRED) | pair[1].y);
|
||||
}
|
||||
for (i, size) in sizes.iter().enumerate() {
|
||||
let cs = [elements[i].x | EQ(REQUIRED) | area.x as f64,
|
||||
elements[i].width | EQ(REQUIRED) | area.width as f64,
|
||||
let cs = [elements[i].x | EQ(REQUIRED) | dest_area.x as f64,
|
||||
elements[i].width | EQ(REQUIRED) | dest_area.width as f64,
|
||||
match *size {
|
||||
Size::Fixed(f) => elements[i].height | EQ(REQUIRED) | f as f64,
|
||||
Size::Percent(p) => {
|
||||
elements[i].height | EQ(WEAK) | (area.height * p) as f64 / 100.0
|
||||
elements[i].height | EQ(WEAK) |
|
||||
(dest_area.height * p) as f64 / 100.0
|
||||
}
|
||||
}];
|
||||
constraints.extend_from_slice(&cs);
|
||||
@ -286,6 +295,7 @@ pub struct Leaf {
|
||||
pub struct Group {
|
||||
direction: Direction,
|
||||
alignment: Alignment,
|
||||
margin: u16,
|
||||
chunks: Vec<Size>,
|
||||
}
|
||||
|
||||
@ -294,6 +304,7 @@ impl Default for Group {
|
||||
Group {
|
||||
direction: Direction::Horizontal,
|
||||
alignment: Alignment::Left,
|
||||
margin: 0,
|
||||
chunks: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -310,6 +321,11 @@ impl Group {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn margin(&mut self, margin: u16) -> &mut Group {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn chunks(&mut self, chunks: &[Size]) -> &mut Group {
|
||||
self.chunks = Vec::from(chunks);
|
||||
self
|
||||
@ -317,9 +333,13 @@ impl Group {
|
||||
pub fn render<F>(&self, area: &Rect, f: F) -> Tree
|
||||
where F: Fn(&[Rect], &mut Node)
|
||||
{
|
||||
let chunks = split(area, &self.direction, &self.alignment, &self.chunks);
|
||||
let chunks = split(area,
|
||||
&self.direction,
|
||||
&self.alignment,
|
||||
self.margin,
|
||||
&self.chunks);
|
||||
let mut node = Node { children: Vec::new() };
|
||||
let results = f(&chunks, &mut node);
|
||||
f(&chunks, &mut node);
|
||||
Tree::Node(node)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ extern crate cassowary;
|
||||
|
||||
mod buffer;
|
||||
mod util;
|
||||
pub mod symbols;
|
||||
pub mod terminal;
|
||||
pub mod widgets;
|
||||
pub mod style;
|
||||
|
10
src/symbols.rs
Normal file
10
src/symbols.rs
Normal file
@ -0,0 +1,10 @@
|
||||
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 = '▏';
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
use std::iter;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::collections::HashMap;
|
||||
@ -8,13 +7,13 @@ use termion::raw::{IntoRawMode, RawTerminal};
|
||||
|
||||
use buffer::Buffer;
|
||||
use widgets::WidgetType;
|
||||
use layout::{Rect, Tree, Node, Leaf};
|
||||
use layout::{Rect, Tree};
|
||||
|
||||
pub struct Terminal {
|
||||
width: u16,
|
||||
height: u16,
|
||||
stdout: RawTerminal<io::Stdout>,
|
||||
previous: HashMap<(WidgetType, u64), Rect>,
|
||||
previous: HashMap<(WidgetType, Rect), u64>,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
@ -39,29 +38,33 @@ impl Terminal {
|
||||
}
|
||||
|
||||
pub fn render(&mut self, ui: Tree) {
|
||||
info!("Render");
|
||||
debug!("Render Pass");
|
||||
let mut buffers: Vec<Buffer> = Vec::new();
|
||||
let mut previous: HashMap<(WidgetType, u64), Rect> = HashMap::new();
|
||||
let mut previous: HashMap<(WidgetType, Rect), u64> = HashMap::new();
|
||||
for node in ui.into_iter() {
|
||||
let area = *node.buffer.area();
|
||||
match self.previous.remove(&(node.widget_type, node.hash)) {
|
||||
Some(r) => {
|
||||
if r != area {
|
||||
match self.previous.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);
|
||||
}
|
||||
}
|
||||
previous.insert((node.widget_type, node.hash), area);
|
||||
previous.insert((node.widget_type, area), node.hash);
|
||||
}
|
||||
for (_, area) in &self.previous {
|
||||
buffers.insert(0, Buffer::empty(*area));
|
||||
for (&(t, a), h) in &self.previous {
|
||||
buffers.insert(0, Buffer::empty(a));
|
||||
debug!("Erased {:?} at {:?}", t, a);
|
||||
}
|
||||
for buf in buffers {
|
||||
self.render_buffer(&buf);
|
||||
info!("{:?}", buf.area());
|
||||
}
|
||||
self.previous = previous;
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
use std::hash::{Hash, SipHasher, Hasher};
|
||||
|
||||
pub fn hash<T: Hash>(t: &T) -> u64 {
|
||||
use layout::Rect;
|
||||
|
||||
pub fn hash<T: Hash>(t: &T, area: &Rect) -> u64 {
|
||||
let mut s = SipHasher::new();
|
||||
t.hash(&mut s);
|
||||
area.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
|
@ -2,12 +2,12 @@
|
||||
use buffer::Buffer;
|
||||
use layout::Rect;
|
||||
use style::Color;
|
||||
use widgets::{Widget, WidgetType, Border, Line, vline, hline};
|
||||
use widgets::{Widget, WidgetType, border, Line, vline, hline};
|
||||
|
||||
#[derive(Hash)]
|
||||
#[derive(Hash, Clone, Copy)]
|
||||
pub struct Block<'a> {
|
||||
title: Option<&'a str>,
|
||||
borders: Border::Flags,
|
||||
borders: border::Flags,
|
||||
border_fg: Color,
|
||||
border_bg: Color,
|
||||
}
|
||||
@ -16,7 +16,7 @@ impl<'a> Default for Block<'a> {
|
||||
fn default() -> Block<'a> {
|
||||
Block {
|
||||
title: None,
|
||||
borders: Border::NONE,
|
||||
borders: border::NONE,
|
||||
border_fg: Color::White,
|
||||
border_bg: Color::Black,
|
||||
}
|
||||
@ -29,35 +29,31 @@ impl<'a> Block<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn borders(&mut self, flag: Border::Flags) -> &mut Block<'a> {
|
||||
pub fn borders(&mut self, flag: border::Flags) -> &mut Block<'a> {
|
||||
self.borders = flag;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Block<'a> {
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
fn _buffer(&self, area: &Rect) -> Buffer {
|
||||
|
||||
let mut buf = Buffer::empty(*area);
|
||||
|
||||
if area.area() == 0 {
|
||||
return buf;
|
||||
}
|
||||
|
||||
if self.borders == Border::NONE {
|
||||
if self.borders == border::NONE {
|
||||
return buf;
|
||||
}
|
||||
|
||||
// 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);
|
||||
buf.merge(&line);
|
||||
}
|
||||
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);
|
||||
buf.merge(&line);
|
||||
}
|
||||
if self.borders.intersects(Border::RIGHT) {
|
||||
if self.borders.intersects(border::RIGHT) {
|
||||
let line = vline(area.x + area.width - 1,
|
||||
area.y,
|
||||
area.height,
|
||||
@ -65,7 +61,7 @@ impl<'a> Widget for Block<'a> {
|
||||
self.border_bg);
|
||||
buf.merge(&line);
|
||||
}
|
||||
if self.borders.intersects(Border::BOTTOM) {
|
||||
if self.borders.intersects(border::BOTTOM) {
|
||||
let line = hline(area.x,
|
||||
area.y + area.height - 1,
|
||||
area.width,
|
||||
@ -75,16 +71,16 @@ impl<'a> Widget for Block<'a> {
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
if let Some(ref title) = self.title {
|
||||
|
75
src/widgets/gauge.rs
Normal file
75
src/widgets/gauge.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use widgets::{Widget, WidgetType, Block};
|
||||
use buffer::Buffer;
|
||||
use style::Color;
|
||||
use layout::Rect;
|
||||
|
||||
/// Progress bar widget
|
||||
///
|
||||
/// # Examples:
|
||||
///
|
||||
/// ```
|
||||
/// extern crate tui;
|
||||
/// use tui::widgets::{Widget, Gauge, Block, border};
|
||||
///
|
||||
/// fn main() {
|
||||
/// Gauge::new()
|
||||
/// .block(*Block::default().borders(border::ALL).title("Progress"))
|
||||
/// .percent(20);
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Hash)]
|
||||
pub struct Gauge<'a> {
|
||||
block: Option<Block<'a>>,
|
||||
percent: u16,
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
}
|
||||
|
||||
impl<'a> Gauge<'a> {
|
||||
pub fn new() -> Gauge<'a> {
|
||||
Gauge {
|
||||
block: None,
|
||||
percent: 0,
|
||||
bg: Color::White,
|
||||
fg: Color::Black,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block(&'a mut self, block: Block<'a>) -> &mut Gauge<'a> {
|
||||
self.block = Some(block);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn percent(&mut self, percent: u16) -> &mut Gauge<'a> {
|
||||
self.percent = percent;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Gauge<'a> {
|
||||
fn _buffer(&self, area: &Rect) -> Buffer {
|
||||
let (mut buf, gauge_area) = match self.block {
|
||||
Some(ref b) => (b._buffer(area), area.inner(1)),
|
||||
None => (Buffer::empty(*area), *area),
|
||||
};
|
||||
if gauge_area.height < 1 {
|
||||
return buf;
|
||||
} else {
|
||||
let margin = gauge_area.x - area.x;
|
||||
let width = (gauge_area.width * self.percent) / 100;
|
||||
for i in 0..width {
|
||||
buf.set_bg(margin + i, margin, self.bg);
|
||||
buf.set_fg(margin + i, margin, self.fg);
|
||||
}
|
||||
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, &percent_string);
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
fn widget_type(&self) -> WidgetType {
|
||||
WidgetType::Gauge
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
use std::cmp::{min, max};
|
||||
use std::cmp::min;
|
||||
use std::fmt::Display;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use buffer::Buffer;
|
||||
use widgets::{Widget, WidgetType, Block};
|
||||
use style::Color;
|
||||
use layout::Rect;
|
||||
|
||||
pub struct List<'a, T> {
|
||||
@ -29,7 +28,7 @@ impl<'a, T> Default for List<'a, T> {
|
||||
List {
|
||||
block: Block::default(),
|
||||
selected: 0,
|
||||
formatter: Box::new(|e: &T, selected: bool| String::from("")),
|
||||
formatter: Box::new(|_, _| String::from("")),
|
||||
items: Vec::new(),
|
||||
}
|
||||
}
|
||||
@ -66,7 +65,7 @@ impl<'a, T> List<'a, T>
|
||||
impl<'a, T> Widget for List<'a, T>
|
||||
where T: Display + Hash
|
||||
{
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
fn _buffer(&self, area: &Rect) -> Buffer {
|
||||
let mut buf = self.block.buffer(area);
|
||||
if area.area() == 0 {
|
||||
return buf;
|
||||
|
@ -1,9 +1,12 @@
|
||||
mod block;
|
||||
mod list;
|
||||
mod gauge;
|
||||
|
||||
pub use self::block::Block;
|
||||
pub use self::list::List;
|
||||
use std::hash::{Hash, SipHasher, Hasher};
|
||||
pub use self::gauge::Gauge;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use util::hash;
|
||||
use buffer::{Buffer, Cell};
|
||||
@ -23,7 +26,7 @@ enum Line {
|
||||
HorizontalUp,
|
||||
}
|
||||
|
||||
pub mod Border {
|
||||
pub mod border {
|
||||
bitflags! {
|
||||
pub flags Flags: u32 {
|
||||
const NONE = 0b00000001,
|
||||
@ -53,7 +56,6 @@ impl Line {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn hline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
Buffer::filled(Rect {
|
||||
x: x,
|
||||
@ -85,14 +87,21 @@ fn vline<'a>(x: u16, y: u16, len: u16, fg: Color, bg: Color) -> Buffer {
|
||||
pub enum WidgetType {
|
||||
Block,
|
||||
List,
|
||||
Gauge,
|
||||
}
|
||||
|
||||
pub trait Widget: Hash {
|
||||
fn buffer(&self, area: &Rect) -> Buffer;
|
||||
fn _buffer(&self, area: &Rect) -> Buffer;
|
||||
fn buffer(&self, area: &Rect) -> Buffer {
|
||||
match area.area() {
|
||||
0 => Buffer::empty(*area),
|
||||
_ => self._buffer(area),
|
||||
}
|
||||
}
|
||||
fn widget_type(&self) -> WidgetType;
|
||||
fn render(&self, area: &Rect) -> Tree {
|
||||
let widget_type = self.widget_type();
|
||||
let hash = hash(&self);
|
||||
let hash = hash(&self, area);
|
||||
let buffer = self.buffer(area);
|
||||
Tree::Leaf(Leaf {
|
||||
widget_type: widget_type,
|
||||
|
Loading…
x
Reference in New Issue
Block a user