refactor(canvas): update shape drawing strategy

* Update the `Shape` trait. Instead of returning an iterator of point, all
shapes are now aware of the surface they will be drawn to through a `Painter`.
In order to draw themselves, they paint points of the "braille grid".
* Rewrite how lines are drawn using a common line drawing algorithm (Bresenham).
pull/242/head
Florian Dehau 4 years ago
parent a6b35031ae
commit 140db9b2e2

@ -20,10 +20,10 @@ use crate::util::event::{Config, Event, Events};
struct App { struct App {
x: f64, x: f64,
y: f64, y: f64,
ball: Rect, ball: Rectangle,
playground: Rect, playground: Rect,
vx: u16, vx: f64,
vy: u16, vy: f64,
dir_x: bool, dir_x: bool,
dir_y: bool, dir_y: bool,
} }
@ -33,21 +33,29 @@ impl App {
App { App {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
ball: Rect::new(10, 30, 10, 10), ball: Rectangle {
x: 10.0,
y: 30.0,
width: 10.0,
height: 10.0,
color: Color::Yellow,
},
playground: Rect::new(10, 10, 100, 100), playground: Rect::new(10, 10, 100, 100),
vx: 1, vx: 1.0,
vy: 1, vy: 1.0,
dir_x: true, dir_x: true,
dir_y: true, dir_y: true,
} }
} }
fn update(&mut self) { fn update(&mut self) {
if self.ball.left() < self.playground.left() || self.ball.right() > self.playground.right() if self.ball.x < self.playground.left() as f64
|| self.ball.x + self.ball.width > self.playground.right() as f64
{ {
self.dir_x = !self.dir_x; self.dir_x = !self.dir_x;
} }
if self.ball.top() < self.playground.top() || self.ball.bottom() > self.playground.bottom() if self.ball.y < self.playground.top() as f64
|| self.ball.y + self.ball.height > self.playground.bottom() as f64
{ {
self.dir_y = !self.dir_y; self.dir_y = !self.dir_y;
} }
@ -77,7 +85,7 @@ fn main() -> Result<(), failure::Error> {
// Setup event handlers // Setup event handlers
let config = Config { let config = Config {
tick_rate: Duration::from_millis(100), tick_rate: Duration::from_millis(250),
..Default::default() ..Default::default()
}; };
let events = Events::with_config(config); let events = Events::with_config(config);
@ -106,10 +114,7 @@ fn main() -> Result<(), failure::Error> {
let canvas = Canvas::default() let canvas = Canvas::default()
.block(Block::default().borders(Borders::ALL).title("Pong")) .block(Block::default().borders(Borders::ALL).title("Pong"))
.paint(|ctx| { .paint(|ctx| {
ctx.draw(&Rectangle { ctx.draw(&app.ball);
rect: app.ball,
color: Color::Yellow,
});
}) })
.x_bounds([10.0, 110.0]) .x_bounds([10.0, 110.0])
.y_bounds([10.0, 110.0]); .y_bounds([10.0, 110.0]);

@ -1,19 +1,30 @@
#[allow(dead_code)] #[allow(dead_code)]
mod util; mod util;
use crate::util::{
event::{Event, Events},
SinSignal,
};
use std::io; use std::io;
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, Marker},
Terminal,
};
use termion::event::Key; const DATA: [(f64, f64); 5] = [(0.0, 0.0), (1.0, 1.0), (2.0, 2.0), (3.0, 3.0), (4.0, 4.0)];
use termion::input::MouseTerminal; const DATA2: [(f64, f64); 7] = [
use termion::raw::IntoRawMode; (0.0, 0.0),
use termion::screen::AlternateScreen; (10.0, 1.0),
use tui::backend::TermionBackend; (20.0, 0.5),
use tui::style::{Color, Modifier, Style}; (30.0, 1.5),
use tui::widgets::{Axis, Block, Borders, Chart, Dataset, Marker}; (40.0, 1.0),
use tui::Terminal; (50.0, 2.5),
(60.0, 3.0),
use crate::util::event::{Event, Events}; ];
use crate::util::SinSignal;
struct App { struct App {
signal1: SinSignal, signal1: SinSignal,
@ -69,6 +80,17 @@ fn main() -> Result<(), failure::Error> {
loop { loop {
terminal.draw(|mut f| { terminal.draw(|mut f| {
let size = f.size(); let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
Constraint::Ratio(1, 3),
]
.as_ref(),
)
.split(size);
let x_labels = [ let x_labels = [
format!("{}", app.window[0]), format!("{}", app.window[0]),
format!("{}", (app.window[0] + app.window[1]) / 2.0), format!("{}", (app.window[0] + app.window[1]) / 2.0),
@ -89,7 +111,7 @@ fn main() -> Result<(), failure::Error> {
let chart = Chart::default() let chart = Chart::default()
.block( .block(
Block::default() Block::default()
.title("Chart") .title("Chart 1")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD)) .title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
.borders(Borders::ALL), .borders(Borders::ALL),
) )
@ -110,7 +132,71 @@ fn main() -> Result<(), failure::Error> {
.labels(&["-20", "0", "20"]), .labels(&["-20", "0", "20"]),
) )
.datasets(&datasets); .datasets(&datasets);
f.render_widget(chart, size); f.render_widget(chart, chunks[0]);
let datasets = [Dataset::default()
.name("data")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.graph_type(GraphType::Line)
.data(&DATA)];
let chart = Chart::default()
.block(
Block::default()
.title("Chart 2")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
.borders(Borders::ALL),
)
.x_axis(
Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::ITALIC))
.bounds([0.0, 5.0])
.labels(&["0", "2.5", "5.0"]),
)
.y_axis(
Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::ITALIC))
.bounds([0.0, 5.0])
.labels(&["0", "2.5", "5.0"]),
)
.datasets(&datasets);
f.render_widget(chart, chunks[1]);
let datasets = [Dataset::default()
.name("data")
.marker(Marker::Braille)
.style(Style::default().fg(Color::Yellow))
.graph_type(GraphType::Line)
.data(&DATA2)];
let chart = Chart::default()
.block(
Block::default()
.title("Chart 3")
.title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD))
.borders(Borders::ALL),
)
.x_axis(
Axis::default()
.title("X Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::ITALIC))
.bounds([0.0, 50.0])
.labels(&["0", "25", "50"]),
)
.y_axis(
Axis::default()
.title("Y Axis")
.style(Style::default().fg(Color::Gray))
.labels_style(Style::default().modifier(Modifier::ITALIC))
.bounds([0.0, 5.0])
.labels(&["0", "2.5", "5"]),
)
.datasets(&datasets);
f.render_widget(chart, chunks[2]);
})?; })?;
match events.next()? { match events.next()? {

@ -259,12 +259,10 @@ where
}); });
ctx.layer(); ctx.layer();
ctx.draw(&Rectangle { ctx.draw(&Rectangle {
rect: Rect { x: 0.0,
x: 0, y: 30.0,
y: 30, width: 10.0,
width: 10, height: 10.0,
height: 10,
},
color: Color::Yellow, color: Color::Yellow,
}); });
for (i, s1) in app.servers.iter().enumerate() { for (i, s1) in app.servers.iter().enumerate() {

@ -47,8 +47,8 @@ impl Constraint {
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Margin { pub struct Margin {
vertical: u16, pub vertical: u16,
horizontal: u16, pub horizontal: u16,
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -58,7 +58,6 @@ pub enum Alignment {
Right, Right,
} }
// TODO: enforce constraints size once const generics has landed
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Layout { pub struct Layout {
direction: Direction, direction: Direction,

@ -1,7 +1,10 @@
use super::Shape; use crate::{
use crate::style::Color; style::Color,
widgets::canvas::{Painter, Shape},
};
/// Shape to draw a line from (x1, y1) to (x2, y2) with the given color /// Shape to draw a line from (x1, y1) to (x2, y2) with the given color
#[derive(Debug, Clone)]
pub struct Line { pub struct Line {
pub x1: f64, pub x1: f64,
pub y1: f64, pub y1: f64,
@ -10,63 +13,77 @@ pub struct Line {
pub color: Color, pub color: Color,
} }
pub struct LineIterator { impl Shape for Line {
x: f64, fn draw(&self, painter: &mut Painter) {
y: f64, let (x1, y1) = match painter.get_point(self.x1, self.y1) {
dx: f64, Some(c) => c,
dy: f64, None => return,
dir_x: f64, };
dir_y: f64, let (x2, y2) = match painter.get_point(self.x2, self.y2) {
current: f64, Some(c) => c,
end: f64, None => return,
} };
let (dx, x_range) = if x2 >= x1 {
impl Iterator for LineIterator { (x2 - x1, x1..=x2)
type Item = (f64, f64); } else {
(x1 - x2, x2..=x1)
};
let (dy, y_range) = if y2 >= y1 {
(y2 - y1, y1..=y2)
} else {
(y1 - y2, y2..=y1)
};
fn next(&mut self) -> Option<Self::Item> { if dx == 0 {
if self.current < self.end { for y in y_range {
let pos = ( painter.paint(x1, y, self.color);
self.x + (self.current * self.dx) / self.end * self.dir_x, }
self.y + (self.current * self.dy) / self.end * self.dir_y, } else if dy == 0 {
); for x in x_range {
self.current += 1.0; painter.paint(x, y1, self.color);
Some(pos) }
} else if dy < dx {
if x1 > x2 {
draw_line_low(painter, x2, y2, x1, y1, self.color);
} else {
draw_line_low(painter, x1, y1, x2, y2, self.color);
}
} else { } else {
None if y1 > y2 {
draw_line_high(painter, x2, y2, x1, y1, self.color);
} else {
draw_line_high(painter, x1, y1, x2, y2, self.color);
}
} }
} }
} }
impl<'a> IntoIterator for &'a Line { fn draw_line_low(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
type Item = (f64, f64); let dx = (x2 - x1) as isize;
type IntoIter = LineIterator; let dy = (y2 as isize - y1 as isize).abs();
let mut d = 2 * dy - dx;
fn into_iter(self) -> Self::IntoIter { let mut y = y1;
let dx = self.x1.max(self.x2) - self.x1.min(self.x2); for x in x1..=x2 {
let dy = self.y1.max(self.y2) - self.y1.min(self.y2); painter.paint(x, y, color);
let dir_x = if self.x1 <= self.x2 { 1.0 } else { -1.0 }; if d > 0 {
let dir_y = if self.y1 <= self.y2 { 1.0 } else { -1.0 }; y = if y1 > y2 { y - 1 } else { y + 1 };
let end = dx.max(dy); d -= 2 * dx;
LineIterator {
x: self.x1,
y: self.y1,
dx,
dy,
dir_x,
dir_y,
current: 0.0,
end,
} }
d += 2 * dy;
} }
} }
impl<'a> Shape<'a> for Line { fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
fn color(&self) -> Color { let dx = (x2 as isize - x1 as isize).abs();
self.color let dy = (y2 - y1) as isize;
} let mut d = 2 * dx - dy;
let mut x = x1;
fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a> { for y in y1..=y2 {
Box::new(self.into_iter()) painter.paint(x, y, color);
if d > 0 {
x = if x1 > x2 { x - 1 } else { x + 1 };
d -= 2 * dy;
}
d += 2 * dx;
} }
} }

@ -1,9 +1,12 @@
use crate::style::Color; use crate::{
use crate::widgets::canvas::points::PointsIterator; style::Color,
use crate::widgets::canvas::world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION}; widgets::canvas::{
use crate::widgets::canvas::Shape; world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
Painter, Shape,
},
};
#[derive(Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MapResolution { pub enum MapResolution {
Low, Low,
High, High,
@ -19,6 +22,7 @@ impl MapResolution {
} }
/// Shape to draw a world map with the given resolution and color /// Shape to draw a world map with the given resolution and color
#[derive(Debug, Clone)]
pub struct Map { pub struct Map {
pub resolution: MapResolution, pub resolution: MapResolution,
pub color: Color, pub color: Color,
@ -33,19 +37,12 @@ impl Default for Map {
} }
} }
impl<'a> Shape<'a> for Map { impl Shape for Map {
fn color(&self) -> Color { fn draw(&self, painter: &mut Painter) {
self.color for (x, y) in self.resolution.data() {
} if let Some((x, y)) = painter.get_point(*x, *y) {
fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a> { painter.paint(x, y, self.color);
Box::new(self.into_iter()) }
} }
}
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())
} }
} }

@ -9,10 +9,13 @@ pub use self::map::{Map, MapResolution};
pub use self::points::Points; pub use self::points::Points;
pub use self::rectangle::Rectangle; pub use self::rectangle::Rectangle;
use crate::buffer::Buffer; use crate::{
use crate::layout::Rect; buffer::Buffer,
use crate::style::{Color, Style}; layout::Rect,
use crate::widgets::{Block, Widget}; style::{Color, Style},
widgets::{Block, Widget},
};
use std::fmt::Debug;
pub const DOTS: [[u16; 2]; 4] = [ pub const DOTS: [[u16; 2]; 4] = [
[0x0001, 0x0008], [0x0001, 0x0008],
@ -24,14 +27,12 @@ pub const BRAILLE_OFFSET: u16 = 0x2800;
pub const BRAILLE_BLANK: char = ''; pub const BRAILLE_BLANK: char = '';
/// Interface for all shapes that may be drawn on a Canvas widget. /// Interface for all shapes that may be drawn on a Canvas widget.
pub trait Shape<'a> { pub trait Shape {
/// Returns the color of the shape fn draw(&self, painter: &mut Painter);
fn color(&self) -> Color;
/// Returns an iterator over all points of the shape
fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a>;
} }
/// Label to draw some text on the canvas /// Label to draw some text on the canvas
#[derive(Debug, Clone)]
pub struct Label<'a> { pub struct Label<'a> {
pub x: f64, pub x: f64,
pub y: f64, pub y: f64,
@ -39,11 +40,13 @@ pub struct Label<'a> {
pub color: Color, pub color: Color,
} }
#[derive(Debug, Clone)]
struct Layer { struct Layer {
string: String, string: String,
colors: Vec<Color>, colors: Vec<Color>,
} }
#[derive(Debug, Clone)]
struct Grid { struct Grid {
cells: Vec<u16>, cells: Vec<u16>,
colors: Vec<Color>, colors: Vec<Color>,
@ -74,7 +77,82 @@ impl Grid {
} }
} }
#[derive(Debug)]
pub struct Painter<'a, 'b> {
context: &'a mut Context<'b>,
resolution: [f64; 2],
}
impl<'a, 'b> Painter<'a, 'b> {
/// Convert the (x, y) coordinates to location of a braille dot on the grid
///
/// # Examples:
/// ```
/// use tui::widgets::canvas::{Painter, Context};
///
/// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0]);
/// let mut painter = Painter::from(&mut ctx);
/// let point = painter.get_point(1.0, 0.0);
/// assert_eq!(point, Some((0, 7)));
/// let point = painter.get_point(1.5, 1.0);
/// assert_eq!(point, Some((1, 3)));
/// let point = painter.get_point(0.0, 0.0);
/// assert_eq!(point, None);
/// let point = painter.get_point(2.0, 2.0);
/// assert_eq!(point, Some((3, 0)));
/// let point = painter.get_point(1.0, 2.0);
/// assert_eq!(point, Some((0, 0)));
/// ```
pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
let left = self.context.x_bounds[0];
let right = self.context.x_bounds[1];
let top = self.context.y_bounds[1];
let bottom = self.context.y_bounds[0];
if x < left || x > right || y < bottom || y > top {
return None;
}
let width = (self.context.x_bounds[1] - self.context.x_bounds[0]).abs();
let height = (self.context.y_bounds[1] - self.context.y_bounds[0]).abs();
let x = ((x - left) * self.resolution[0] / width) as usize;
let y = ((top - y) * self.resolution[1] / height) as usize;
Some((x, y))
}
/// Paint a braille dot
///
/// # Examples:
/// ```
/// use tui::{style::Color, widgets::canvas::{Painter, Context}};
///
/// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0]);
/// let mut painter = Painter::from(&mut ctx);
/// let cell = painter.paint(1, 3, Color::Red);
/// ```
pub fn paint(&mut self, x: usize, y: usize, color: Color) {
let index = y / 4 * self.context.width as usize + x / 2;
if let Some(c) = self.context.grid.cells.get_mut(index) {
*c |= DOTS[y % 4][x % 2];
}
if let Some(c) = self.context.grid.colors.get_mut(index) {
*c = color;
}
}
}
impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
fn from(context: &'a mut Context<'b>) -> Painter<'a, 'b> {
Painter {
resolution: [
f64::from(context.width) * 2.0 - 1.0,
f64::from(context.height) * 4.0 - 1.0,
],
context,
}
}
}
/// Holds the state of the Canvas when painting to it. /// Holds the state of the Canvas when painting to it.
#[derive(Debug, Clone)]
pub struct Context<'a> { pub struct Context<'a> {
width: u16, width: u16,
height: u16, height: u16,
@ -87,26 +165,27 @@ pub struct Context<'a> {
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
pub fn new(width: u16, height: u16, x_bounds: [f64; 2], y_bounds: [f64; 2]) -> Context<'a> {
Context {
width,
height,
x_bounds,
y_bounds,
grid: Grid::new(width as usize, height as usize),
dirty: false,
layers: Vec::new(),
labels: Vec::new(),
}
}
/// Draw any object that may implement the Shape trait /// Draw any object that may implement the Shape trait
pub fn draw<'b, S>(&mut self, shape: &'b S) pub fn draw<S>(&mut self, shape: &S)
where where
S: Shape<'b>, S: Shape,
{ {
self.dirty = true; self.dirty = true;
let left = self.x_bounds[0]; let mut painter = Painter::from(self);
let right = self.x_bounds[1]; shape.draw(&mut painter);
let bottom = self.y_bounds[0];
let top = self.y_bounds[1];
for (x, y) in shape
.points()
.filter(|&(x, y)| !(x <= left || x >= right || y <= bottom || y >= top))
{
let dy = ((top - y) * f64::from(self.height - 1) * 4.0 / (top - bottom)) as usize;
let dx = ((x - left) * f64::from(self.width - 1) * 2.0 / (right - left)) as usize;
let index = dy / 4 * self.width as usize + dx / 2;
self.grid.cells[index] |= DOTS[dy % 4][dx % 2];
self.grid.colors[index] = shape.color();
}
} }
/// Go one layer above in the canvas. /// Go one layer above in the canvas.
@ -156,12 +235,10 @@ impl<'a> Context<'a> {
/// color: Color::White, /// color: Color::White,
/// }); /// });
/// ctx.draw(&Rectangle { /// ctx.draw(&Rectangle {
/// rect: Rect { /// x: 10.0,
/// x: 10, /// y: 20.0,
/// y: 20, /// width: 10.0,
/// width: 10, /// height: 10.0,
/// height: 10,
/// },
/// color: Color::Red /// color: Color::Red
/// }); /// });
/// }); /// });
@ -235,61 +312,68 @@ where
}; };
let width = canvas_area.width as usize; let width = canvas_area.width as usize;
let height = canvas_area.height as usize;
if let Some(ref painter) = self.painter { let painter = match self.painter {
// Create a blank context that match the size of the terminal Some(ref p) => p,
let mut ctx = Context { None => return,
x_bounds: self.x_bounds, };
y_bounds: self.y_bounds,
width: canvas_area.width, // Create a blank context that match the size of the canvas
height: canvas_area.height, let mut ctx = Context::new(
grid: Grid::new(width, height), canvas_area.width,
dirty: false, canvas_area.height,
layers: Vec::new(), self.x_bounds,
labels: Vec::new(), self.y_bounds,
}; );
// Paint to this context // Paint to this context
painter(&mut ctx); painter(&mut ctx);
ctx.finish(); ctx.finish();
// Retreive painted points for each layer // Retreive painted points for each layer
for layer in ctx.layers { for layer in ctx.layers {
for (i, (ch, color)) in layer for (i, (ch, color)) in layer
.string .string
.chars() .chars()
.zip(layer.colors.into_iter()) .zip(layer.colors.into_iter())
.enumerate() .enumerate()
{ {
if ch != BRAILLE_BLANK { if ch != BRAILLE_BLANK {
let (x, y) = (i % width, i / width); let (x, y) = (i % width, i / width);
buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top()) buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
.set_char(ch) .set_char(ch)
.set_fg(color) .set_fg(color)
.set_bg(self.background_color); .set_bg(self.background_color);
}
} }
} }
}
// Finally draw the labels // Finally draw the labels
let style = Style::default().bg(self.background_color); let style = Style::default().bg(self.background_color);
for label in ctx.labels.iter().filter(|l| { let left = self.x_bounds[0];
!(l.x < self.x_bounds[0] let right = self.x_bounds[1];
|| l.x > self.x_bounds[1] let top = self.y_bounds[1];
|| l.y < self.y_bounds[0] let bottom = self.y_bounds[0];
|| l.y > self.y_bounds[1]) let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
}) { let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1) let resolution = {
/ (self.y_bounds[1] - self.y_bounds[0])) as u16; let width = f64::from(canvas_area.width - 1);
let dx = ((label.x - self.x_bounds[0]) * f64::from(canvas_area.width - 1) let height = f64::from(canvas_area.height - 1);
/ (self.x_bounds[1] - self.x_bounds[0])) as u16; (width, height)
buf.set_string( };
dx + canvas_area.left(), for label in ctx
dy + canvas_area.top(), .labels
label.text, .iter()
style.fg(label.color), .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
); {
} let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
buf.set_stringn(
x,
y,
label.text,
(canvas_area.right() - x) as usize,
style.fg(label.color),
);
} }
} }
} }

@ -1,20 +1,22 @@
use std::slice; use crate::{
style::Color,
use super::Shape; widgets::canvas::{Painter, Shape},
use crate::style::Color; };
/// A shape to draw a group of points with the given color /// A shape to draw a group of points with the given color
#[derive(Debug, Clone)]
pub struct Points<'a> { pub struct Points<'a> {
pub coords: &'a [(f64, f64)], pub coords: &'a [(f64, f64)],
pub color: Color, pub color: Color,
} }
impl<'a> Shape<'a> for Points<'a> { impl<'a> Shape for Points<'a> {
fn color(&self) -> Color { fn draw(&self, painter: &mut Painter) {
self.color for (x, y) in self.coords {
} if let Some((x, y)) = painter.get_point(*x, *y) {
fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a> { painter.paint(x, y, self.color);
Box::new(self.into_iter()) }
}
} }
} }
@ -26,33 +28,3 @@ impl<'a> Default for Points<'a> {
} }
} }
} }
impl<'a> IntoIterator for &'a Points<'a> {
type Item = (f64, f64);
type IntoIter = PointsIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
PointsIterator {
iter: self.coords.iter(),
}
}
}
pub struct PointsIterator<'a> {
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> {
match self.iter.next() {
Some(p) => Some(*p),
None => None,
}
}
}

@ -1,54 +1,52 @@
use crate::layout::Rect; use crate::{
use crate::style::Color; style::Color,
use crate::widgets::canvas::{Line, Shape}; widgets::canvas::{Line, Painter, Shape},
use itertools::Itertools; };
/// Shape to draw a rectangle from a `Rect` with the given color /// Shape to draw a rectangle from a `Rect` with the given color
#[derive(Debug, Clone)]
pub struct Rectangle { pub struct Rectangle {
pub rect: Rect, pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub color: Color, pub color: Color,
} }
impl<'a> Shape<'a> for Rectangle { impl Shape for Rectangle {
fn color(&self) -> Color { fn draw(&self, painter: &mut Painter) {
self.color let lines: [Line; 4] = [
} Line {
x1: self.x,
fn points(&'a self) -> Box<dyn Iterator<Item = (f64, f64)> + 'a> { y1: self.y,
let left_line = Line { x2: self.x,
x1: f64::from(self.rect.x), y2: self.y + self.height,
y1: f64::from(self.rect.y), color: self.color,
x2: f64::from(self.rect.x), },
y2: f64::from(self.rect.y + self.rect.height), Line {
color: self.color, x1: self.x,
}; y1: self.y + self.height,
let top_line = Line { x2: self.x + self.width,
x1: f64::from(self.rect.x), y2: self.y + self.height,
y1: f64::from(self.rect.y + self.rect.height), color: self.color,
x2: f64::from(self.rect.x + self.rect.width), },
y2: f64::from(self.rect.y + self.rect.height), Line {
color: self.color, x1: self.x + self.width,
}; y1: self.y,
let right_line = Line { x2: self.x + self.width,
x1: f64::from(self.rect.x + self.rect.width), y2: self.y + self.height,
y1: f64::from(self.rect.y), color: self.color,
x2: f64::from(self.rect.x + self.rect.width), },
y2: f64::from(self.rect.y + self.rect.height), Line {
color: self.color, x1: self.x,
}; y1: self.y,
let bottom_line = Line { x2: self.x + self.width,
x1: f64::from(self.rect.x), y2: self.y,
y1: f64::from(self.rect.y), color: self.color,
x2: f64::from(self.rect.x + self.rect.width), },
y2: f64::from(self.rect.y), ];
color: self.color, for line in &lines {
}; line.draw(painter);
Box::new( }
left_line.into_iter().merge(
top_line
.into_iter()
.merge(right_line.into_iter().merge(bottom_line.into_iter())),
),
)
} }
} }

@ -1,14 +1,16 @@
use crate::{
buffer::Buffer,
layout::{Constraint, Rect},
style::Style,
symbols,
widgets::{
canvas::{Canvas, Line, Points},
Block, Borders, Widget,
},
};
use std::{borrow::Cow, cmp::max}; use std::{borrow::Cow, cmp::max};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::buffer::Buffer;
use crate::layout::{Constraint, Rect};
use crate::style::Style;
use crate::symbols;
use crate::widgets::canvas::{Canvas, Line, Points};
use crate::widgets::{Block, Borders, Widget};
/// An X or Y axis for the chart widget /// An X or Y axis for the chart widget
pub struct Axis<'a, L> pub struct Axis<'a, L>
where where

Loading…
Cancel
Save