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