diff --git a/src/terminal.rs b/src/terminal.rs index 09d3525..6c0bd67 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -98,7 +98,7 @@ impl Terminal { bg = cell.bg; inst += 1; } - string.push_str(&format!("{}", cell.symbol)); + string.push_str(&cell.symbol); inst += 1; } string.push_str(&format!("{}{}", Color::Reset.fg(), Color::Reset.bg())); diff --git a/src/widgets/canvas/line.rs b/src/widgets/canvas/line.rs new file mode 100644 index 0000000..3691473 --- /dev/null +++ b/src/widgets/canvas/line.rs @@ -0,0 +1,65 @@ +use super::Shape; +use style::Color; + +pub struct Line { + pub x1: f64, + pub y1: f64, + pub x2: f64, + pub y2: f64, + pub color: Color, +} + +pub struct LineIterator { + x: f64, + y: f64, + dx: f64, + dy: f64, + dir_x: f64, + dir_y: f64, + current: f64, + end: f64, +} + +impl Iterator for LineIterator { + type Item = (f64, f64); + fn next(&mut self) -> Option { + 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)) + } else { + None + } + } +} + +impl<'a> IntoIterator for &'a Line { + type Item = (f64, f64); + type IntoIter = LineIterator; + fn into_iter(self) -> Self::IntoIter { + let dx = self.x1.max(self.x2) - self.x1.min(self.x2); + let dy = self.y1.max(self.y2) - self.y1.min(self.y2); + let dir_x = if self.x1 <= self.x2 { 1.0 } else { -1.0 }; + let dir_y = if self.y1 <= self.y2 { 1.0 } else { -1.0 }; + let end = dx.max(dy); + LineIterator { + x: self.x1, + y: self.y1, + dx: dx, + dy: dy, + dir_x: dir_x, + dir_y: dir_y, + current: 0.0, + end: end, + } + } +} + +impl<'a> Shape<'a> for Line { + fn color(&self) -> Color { + self.color + } + fn points(&'a self) -> Box + 'a> { + Box::new(self.into_iter()) + } +} diff --git a/src/widgets/canvas.rs b/src/widgets/canvas/mod.rs similarity index 61% rename from src/widgets/canvas.rs rename to src/widgets/canvas/mod.rs index 9f927cb..1a3df20 100644 --- a/src/widgets/canvas.rs +++ b/src/widgets/canvas/mod.rs @@ -1,3 +1,9 @@ +mod points; +mod line; + +pub use self::points::Points; +pub use self::line::Line; + use style::Color; use buffer::Buffer; use widgets::{Block, Widget}; @@ -8,37 +14,16 @@ pub const DOTS: [[u16; 2]; 4] = pub const BRAILLE_OFFSET: u16 = 0x2800; pub const BRAILLE_BLANK: char = '⠀'; -pub struct Shape<'a> { - data: &'a [(f64, f64)], - color: Color, -} - -impl<'a> Default for Shape<'a> { - fn default() -> Shape<'a> { - Shape { - data: &[], - color: Color::Reset, - } - } -} - -impl<'a> Shape<'a> { - pub fn data(mut self, data: &'a [(f64, f64)]) -> Shape<'a> { - self.data = data; - self - } - - pub fn color(mut self, color: Color) -> Shape<'a> { - self.color = color; - self - } +pub trait Shape<'a> { + fn color(&self) -> Color; + fn points(&'a self) -> Box + 'a>; } pub struct Canvas<'a> { block: Option>, x_bounds: [f64; 2], y_bounds: [f64; 2], - shapes: &'a [Shape<'a>], + shapes: &'a [&'a Shape<'a>], } impl<'a> Default for Canvas<'a> { @@ -65,7 +50,7 @@ impl<'a> Canvas<'a> { self.y_bounds = bounds; self } - pub fn shapes(&mut self, shapes: &'a [Shape<'a>]) -> &mut Canvas<'a> { + pub fn shapes(&mut self, shapes: &'a [&'a Shape<'a>]) -> &mut Canvas<'a> { self.shapes = shapes; self } @@ -80,34 +65,42 @@ impl<'a> Widget for Canvas<'a> { } None => *area, }; + let width = canvas_area.width as usize; let height = canvas_area.height as usize; + let mut grid: Vec = vec![BRAILLE_OFFSET; width * height + 1]; - let mut x_bounds = self.x_bounds.clone(); + let mut colors: Vec = 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.clone(); + 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.data.iter().filter(|&&(x, y)| { + 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; - grid[dy / 4 * width + dx / 2] |= DOTS[dy % 4][dx % 2]; + 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) in string.chars().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 = shape.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; + }); } } } diff --git a/src/widgets/canvas/points.rs b/src/widgets/canvas/points.rs new file mode 100644 index 0000000..1c7e64e --- /dev/null +++ b/src/widgets/canvas/points.rs @@ -0,0 +1,54 @@ +use super::Shape; +use style::Color; + +pub struct Points<'a> { + pub coords: &'a [(f64, f64)], + pub color: Color, +} + +impl<'a> Shape<'a> for Points<'a> { + fn color(&self) -> Color { + self.color + } + fn points(&'a self) -> Box + 'a> { + Box::new(self.into_iter()) + } +} + +impl<'a> Default for Points<'a> { + fn default() -> Points<'a> { + Points { + coords: &[], + color: Color::Reset, + } + } +} + +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, + } + } +} + +pub struct PointsIterator<'a> { + coords: &'a [(f64, f64)], + index: usize, +} + +impl<'a> Iterator for PointsIterator<'a> { + type Item = (f64, f64); + fn next(&mut self) -> Option { + let point = if self.index < self.coords.len() { + Some(self.coords[self.index]) + } else { + None + }; + self.index += 1; + point + } +} diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index f34749f..01b929e 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -2,7 +2,7 @@ use std::cmp::max; use unicode_width::UnicodeWidthStr; -use widgets::{Widget, Block, Canvas, Shape}; +use widgets::{Widget, Block, Canvas, Points}; use buffer::Buffer; use layout::Rect; use style::Color; @@ -325,7 +325,10 @@ impl<'a> Widget for Chart<'a> { Canvas::default() .x_bounds(self.x_axis.bounds) .y_bounds(self.y_axis.bounds) - .shapes(&[Shape::default().data(dataset.data).color(dataset.color)]) + .shapes(&[&Points { + coords: dataset.data, + color: dataset.color, + }]) .buffer(&graph_area, buf); } } diff --git a/src/widgets/map.rs b/src/widgets/map.rs index 07f49a0..2335717 100644 --- a/src/widgets/map.rs +++ b/src/widgets/map.rs @@ -1,6 +1,7 @@ -use widgets::{Widget, Block, Canvas, Shape}; +use widgets::{Widget, Block, Canvas, Points}; use buffer::Buffer; use layout::Rect; +use style::Color; pub struct Map<'a> { block: Option>, @@ -32,7 +33,10 @@ impl<'a> Widget for Map<'a> { Canvas::default() .x_bounds([180.0, -180.0]) .y_bounds([-90.0, 90.0]) - .shapes(&[Shape::default().data(&WORLD)]) + .shapes(&[&Points { + coords: &WORLD, + color: Color::Reset, + }]) .buffer(&map_area, buf); } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 3691644..8dbb378 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -17,7 +17,7 @@ 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}; +pub use self::canvas::{Canvas, Shape, Line, Points}; pub use self::map::Map; use buffer::Buffer;