Redefine canvas widget and add shapes

* Give the canvas widget a proper modules and define a standard way to
draw to it (Shape trait)
* Add Points, Line, Rectangle and Map shapes
pull/3/head
Florian Dehau 8 years ago
parent 107d7297af
commit 654be037be

@ -22,7 +22,8 @@ use log4rs::config::{Appender, Config, Root};
use tui::Terminal;
use tui::widgets::{Widget, Block, List, Gauge, Sparkline, Text, border, Chart, Axis, Dataset,
BarChart, Marker, Tabs, Map};
BarChart, Marker, Tabs};
use tui::widgets::canvas::{Canvas, Shape, Line, Map, MapResolution};
use tui::layout::{Group, Direction, Size, Rect};
use tui::style::Color;
@ -286,8 +287,18 @@ fn draw(t: &mut Terminal, app: &App) {
draw_main(t, app, &chunks[1]);
}
1 => {
Map::default()
Canvas::default()
.block(Block::default().title("World").borders(border::ALL))
.layers(&[&[Map::default().resolution(MapResolution::High)],
&[&Line {
x1: 0.0,
y1: 0.0,
x2: 10.0,
y2: 10.0,
color: Color::Red,
}]])
.x_bounds([180.0, 0.0])
.y_bounds([0.0, 90.0])
.render(&chunks[1], t);
}
_ => {}

@ -23,10 +23,11 @@ pub struct LineIterator {
impl Iterator for LineIterator {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
self.current += 1.0;
if self.current < self.end + 1.0 {
Some((self.x + (self.current * self.dx) / self.end * self.dir_x,
self.y + (self.current * self.dy) / self.end * self.dir_y))
if self.current < self.end {
let pos = (self.x + (self.current * self.dx) / self.end * self.dir_x,
self.y + (self.current * self.dy) / self.end * self.dir_y);
self.current += 1.0;
Some(pos)
} else {
None
}

@ -0,0 +1,57 @@
use widgets::canvas::Shape;
use widgets::canvas::points::PointsIterator;
use widgets::canvas::world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION};
use style::Color;
#[derive(Clone, Copy)]
pub enum MapResolution {
Low,
High,
}
impl MapResolution {
fn data(&self) -> &'static [(f64, f64)] {
match *self {
MapResolution::Low => &WORLD_LOW_RESOLUTION,
MapResolution::High => &WORLD_HIGH_RESOLUTION,
}
}
}
pub struct Map {
resolution: MapResolution,
color: Color,
}
impl Default for Map {
fn default() -> Map {
Map {
resolution: MapResolution::Low,
color: Color::Reset,
}
}
}
impl<'a> Shape<'a> for Map {
fn color(&self) -> Color {
self.color
}
fn points(&'a self) -> Box<Iterator<Item = (f64, f64)> + 'a> {
Box::new(self.into_iter())
}
}
impl Map {
pub fn resolution(&mut self, resolution: MapResolution) -> &mut Map {
self.resolution = resolution;
self
}
}
impl<'a> IntoIterator for &'a Map {
type Item = (f64, f64);
type IntoIter = PointsIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
PointsIterator::from(self.resolution.data())
}
}

@ -1,8 +1,13 @@
mod points;
mod line;
mod rectangle;
mod map;
mod world;
pub use self::points::Points;
pub use self::line::Line;
pub use self::rectangle::Rectangle;
pub use self::map::{Map, MapResolution};
use style::Color;
use buffer::Buffer;
@ -23,7 +28,7 @@ pub struct Canvas<'a> {
block: Option<Block<'a>>,
x_bounds: [f64; 2],
y_bounds: [f64; 2],
shapes: &'a [&'a Shape<'a>],
layers: &'a [&'a [&'a Shape<'a>]],
}
impl<'a> Default for Canvas<'a> {
@ -32,7 +37,7 @@ impl<'a> Default for Canvas<'a> {
block: None,
x_bounds: [0.0, 0.0],
y_bounds: [0.0, 0.0],
shapes: &[],
layers: &[],
}
}
}
@ -50,8 +55,8 @@ impl<'a> Canvas<'a> {
self.y_bounds = bounds;
self
}
pub fn shapes(&mut self, shapes: &'a [&'a Shape<'a>]) -> &mut Canvas<'a> {
self.shapes = shapes;
pub fn layers(&mut self, layers: &'a [&'a [&'a Shape<'a>]]) -> &mut Canvas<'a> {
self.layers = layers;
self
}
}
@ -69,38 +74,43 @@ impl<'a> Widget for Canvas<'a> {
let width = canvas_area.width as usize;
let height = canvas_area.height as usize;
let mut grid: Vec<u16> = vec![BRAILLE_OFFSET; width * height + 1];
let mut colors: Vec<Color> = vec![Color::Reset; width * height + 1];
let mut x_bounds = self.x_bounds;
x_bounds.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mut y_bounds = self.y_bounds;
y_bounds.sort_by(|a, b| a.partial_cmp(b).unwrap());
for shape in self.shapes {
for (x, y) in shape.points().filter(|&(x, y)| {
!(x < x_bounds[0] || x > x_bounds[1] || y < y_bounds[0] || y > y_bounds[1])
}) {
let dy = ((self.y_bounds[1] - y) * canvas_area.height as f64 * 4.0 /
(self.y_bounds[1] - self.y_bounds[0])) as usize;
let dx = ((self.x_bounds[1] - x) * canvas_area.width as f64 * 2.0 /
(self.x_bounds[1] - self.x_bounds[0])) as usize;
let index = dy / 4 * width + dx / 2;
grid[index] |= DOTS[dy % 4][dx % 2];
colors[index] = shape.color();
for layer in self.layers {
let mut grid: Vec<u16> = vec![BRAILLE_OFFSET; width * height + 1];
let mut colors: Vec<Color> = vec![Color::Reset; width * height + 1];
for shape in layer.iter() {
for (x, y) in shape.points().filter(|&(x, y)| {
!(x < x_bounds[0] || x > x_bounds[1] || y < y_bounds[0] || y > y_bounds[1])
}) {
let dy = ((self.y_bounds[1] - y) * canvas_area.height as f64 * 4.0 /
(self.y_bounds[1] -
self.y_bounds[0])) as usize;
let dx = ((self.x_bounds[1] - x) * canvas_area.width as f64 * 2.0 /
(self.x_bounds[1] -
self.x_bounds[0])) as usize;
let index = dy / 4 * width + dx / 2;
grid[index] |= DOTS[dy % 4][dx % 2];
colors[index] = shape.color();
}
}
}
let string = String::from_utf16(&grid).unwrap();
for (i, (ch, color)) in string.chars().zip(colors.into_iter()).enumerate() {
if ch != BRAILLE_BLANK {
let (x, y) = (i % width, i / width);
buf.update_cell(x as u16 + canvas_area.left(), y as u16 + area.top(), |c| {
c.symbol.clear();
c.symbol.push(ch);
c.fg = color;
c.bg = Color::Reset;
});
let string = String::from_utf16(&grid).unwrap();
for (i, (ch, color)) in string.chars().zip(colors.into_iter()).enumerate() {
if ch != BRAILLE_BLANK {
let (x, y) = (i % width, i / width);
buf.update_cell(x as u16 + canvas_area.left(), y as u16 + area.top(), |c| {
c.symbol.clear();
c.symbol.push(ch);
c.fg = color;
c.bg = Color::Reset;
});
}
}
}
}

@ -1,3 +1,5 @@
use std::slice;
use super::Shape;
use style::Color;
@ -28,27 +30,26 @@ impl<'a> IntoIterator for &'a Points<'a> {
type Item = (f64, f64);
type IntoIter = PointsIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
PointsIterator {
coords: self.coords,
index: 0,
}
PointsIterator { iter: self.coords.iter() }
}
}
pub struct PointsIterator<'a> {
coords: &'a [(f64, f64)],
index: usize,
iter: slice::Iter<'a, (f64, f64)>,
}
impl<'a> From<&'a [(f64, f64)]> for PointsIterator<'a> {
fn from(data: &'a [(f64, f64)]) -> PointsIterator<'a> {
PointsIterator { iter: data.iter() }
}
}
impl<'a> Iterator for PointsIterator<'a> {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
let point = if self.index < self.coords.len() {
Some(self.coords[self.index])
} else {
None
};
self.index += 1;
point
match self.iter.next() {
Some(p) => Some(*p),
None => None,
}
}
}

@ -0,0 +1,76 @@
use super::Shape;
use style::Color;
/// # Examples
///
/// ```
/// use tui::style::Color;
/// use tui::widgets::canvas::{Shape, Rectangle};
///
/// let rectangle = Rectangle { x: 4.0, y: 4.0, width: 4.0, height: 4.0, color: Color::Red };
/// let points = rectangle.points().collect::<Vec<(f64, f64)>>();
/// assert_eq!(&points, &[(4.0, 4.0), (5.0, 4.0), (6.0, 4.0), (7.0, 4.0), (4.0, 5.0), (7.0, 5.0),
/// (4.0, 6.0), (7.0, 6.0), (4.0, 7.0), (5.0, 7.0), (6.0, 7.0), (7.0, 7.0)]);
/// ```
pub struct Rectangle {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub color: Color,
}
pub struct RectangleIterator<'a> {
rect: &'a Rectangle,
x: f64,
y: f64,
right: f64,
bottom: f64,
}
impl<'a> Iterator for RectangleIterator<'a> {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
if self.y < self.bottom {
let pos = (self.x, self.y);
let dx = if self.y == self.rect.y || self.y == self.bottom - 1.0 {
1.0
} else {
self.rect.width - 1.0
};
self.x += dx;
if self.x >= self.right {
self.x = self.rect.x;
self.y += 1.0;
}
Some(pos)
} else {
None
}
}
}
impl<'a> IntoIterator for &'a Rectangle {
type Item = (f64, f64);
type IntoIter = RectangleIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
let right = self.x + self.width;
let bottom = self.y + self.height;
RectangleIterator {
rect: self,
x: self.x,
y: self.y,
right: right,
bottom: bottom,
}
}
}
impl<'a> Shape<'a> for Rectangle {
fn color(&self) -> Color {
self.color
}
fn points(&'a self) -> Box<Iterator<Item = (f64, f64)> + 'a> {
Box::new(self.into_iter())
}
}

File diff suppressed because it is too large Load Diff

@ -2,7 +2,8 @@ use std::cmp::max;
use unicode_width::UnicodeWidthStr;
use widgets::{Widget, Block, Canvas, Points};
use widgets::{Widget, Block};
use widgets::canvas::{Canvas, Points};
use buffer::Buffer;
use layout::Rect;
use style::Color;
@ -270,11 +271,11 @@ impl<'a> Widget for Chart<'a> {
if let Some(x) = layout.label_y {
let labels = self.y_axis.labels.unwrap();
let labels_len = labels.len() as u16;
if labels_len > 1 {
for (i, label) in labels.iter().enumerate() {
for (i, label) in labels.iter().enumerate() {
let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
if dy < graph_area.bottom() {
buf.set_string(x,
graph_area.bottom() -
i as u16 * (graph_area.height - 1) / (labels_len - 1),
graph_area.bottom() - 1 - dy,
label,
self.y_axis.labels_color,
self.bg);
@ -325,10 +326,10 @@ impl<'a> Widget for Chart<'a> {
Canvas::default()
.x_bounds(self.x_axis.bounds)
.y_bounds(self.y_axis.bounds)
.shapes(&[&Points {
coords: dataset.data,
color: dataset.color,
}])
.layers(&[&[&Points {
coords: dataset.data,
color: dataset.color,
}]])
.buffer(&graph_area, buf);
}
}

@ -14,8 +14,8 @@ use layout::Rect;
/// use tui::widgets::{Widget, Gauge, Block, border};
///
/// fn main() {
/// Gauge::new()
/// .block(*Block::default().borders(border::ALL).title("Progress"))
/// Gauge::default()
/// .block(Block::default().borders(border::ALL).title("Progress"))
/// .percent(20);
/// }
/// ```

File diff suppressed because it is too large Load Diff

@ -6,8 +6,7 @@ mod sparkline;
mod chart;
mod barchart;
mod tabs;
mod canvas;
mod map;
pub mod canvas;
pub use self::block::Block;
pub use self::text::Text;
@ -17,8 +16,6 @@ pub use self::sparkline::Sparkline;
pub use self::chart::{Chart, Axis, Dataset, Marker};
pub use self::barchart::BarChart;
pub use self::tabs::Tabs;
pub use self::canvas::{Canvas, Shape, Line, Points};
pub use self::map::Map;
use buffer::Buffer;
use layout::Rect;

Loading…
Cancel
Save