From 140db9b2e251e1567656dc2bb29be1de81269077 Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Tue, 3 Mar 2020 09:27:28 +0100 Subject: [PATCH] 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). --- examples/canvas.rs | 31 ++-- examples/chart.rs | 112 +++++++++++++-- examples/demo/ui.rs | 10 +- src/layout.rs | 5 +- src/widgets/canvas/line.rs | 117 ++++++++------- src/widgets/canvas/map.rs | 35 +++-- src/widgets/canvas/mod.rs | 246 +++++++++++++++++++++----------- src/widgets/canvas/points.rs | 52 ++----- src/widgets/canvas/rectangle.rs | 90 ++++++------ src/widgets/chart.rs | 18 +-- 10 files changed, 437 insertions(+), 279 deletions(-) diff --git a/examples/canvas.rs b/examples/canvas.rs index ae45681..b1c5a89 100644 --- a/examples/canvas.rs +++ b/examples/canvas.rs @@ -20,10 +20,10 @@ use crate::util::event::{Config, Event, Events}; struct App { x: f64, y: f64, - ball: Rect, + ball: Rectangle, playground: Rect, - vx: u16, - vy: u16, + vx: f64, + vy: f64, dir_x: bool, dir_y: bool, } @@ -33,21 +33,29 @@ impl App { App { x: 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), - vx: 1, - vy: 1, + vx: 1.0, + vy: 1.0, dir_x: true, dir_y: true, } } 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; } - 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; } @@ -77,7 +85,7 @@ fn main() -> Result<(), failure::Error> { // Setup event handlers let config = Config { - tick_rate: Duration::from_millis(100), + tick_rate: Duration::from_millis(250), ..Default::default() }; let events = Events::with_config(config); @@ -106,10 +114,7 @@ fn main() -> Result<(), failure::Error> { let canvas = Canvas::default() .block(Block::default().borders(Borders::ALL).title("Pong")) .paint(|ctx| { - ctx.draw(&Rectangle { - rect: app.ball, - color: Color::Yellow, - }); + ctx.draw(&app.ball); }) .x_bounds([10.0, 110.0]) .y_bounds([10.0, 110.0]); diff --git a/examples/chart.rs b/examples/chart.rs index f213947..086c275 100644 --- a/examples/chart.rs +++ b/examples/chart.rs @@ -1,19 +1,30 @@ #[allow(dead_code)] mod util; +use crate::util::{ + event::{Event, Events}, + SinSignal, +}; 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; -use termion::input::MouseTerminal; -use termion::raw::IntoRawMode; -use termion::screen::AlternateScreen; -use tui::backend::TermionBackend; -use tui::style::{Color, Modifier, Style}; -use tui::widgets::{Axis, Block, Borders, Chart, Dataset, Marker}; -use tui::Terminal; - -use crate::util::event::{Event, Events}; -use crate::util::SinSignal; +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)]; +const DATA2: [(f64, f64); 7] = [ + (0.0, 0.0), + (10.0, 1.0), + (20.0, 0.5), + (30.0, 1.5), + (40.0, 1.0), + (50.0, 2.5), + (60.0, 3.0), +]; struct App { signal1: SinSignal, @@ -69,6 +80,17 @@ fn main() -> Result<(), failure::Error> { loop { terminal.draw(|mut f| { 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 = [ format!("{}", app.window[0]), format!("{}", (app.window[0] + app.window[1]) / 2.0), @@ -89,7 +111,7 @@ fn main() -> Result<(), failure::Error> { let chart = Chart::default() .block( Block::default() - .title("Chart") + .title("Chart 1") .title_style(Style::default().fg(Color::Cyan).modifier(Modifier::BOLD)) .borders(Borders::ALL), ) @@ -110,7 +132,71 @@ fn main() -> Result<(), failure::Error> { .labels(&["-20", "0", "20"]), ) .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()? { diff --git a/examples/demo/ui.rs b/examples/demo/ui.rs index 9b749f4..1fdee87 100644 --- a/examples/demo/ui.rs +++ b/examples/demo/ui.rs @@ -259,12 +259,10 @@ where }); ctx.layer(); ctx.draw(&Rectangle { - rect: Rect { - x: 0, - y: 30, - width: 10, - height: 10, - }, + x: 0.0, + y: 30.0, + width: 10.0, + height: 10.0, color: Color::Yellow, }); for (i, s1) in app.servers.iter().enumerate() { diff --git a/src/layout.rs b/src/layout.rs index f43dc42..0224538 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -47,8 +47,8 @@ impl Constraint { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Margin { - vertical: u16, - horizontal: u16, + pub vertical: u16, + pub horizontal: u16, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -58,7 +58,6 @@ pub enum Alignment { Right, } -// TODO: enforce constraints size once const generics has landed #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Layout { direction: Direction, diff --git a/src/widgets/canvas/line.rs b/src/widgets/canvas/line.rs index 6e99637..1a1e062 100644 --- a/src/widgets/canvas/line.rs +++ b/src/widgets/canvas/line.rs @@ -1,7 +1,10 @@ -use super::Shape; -use crate::style::Color; +use crate::{ + style::Color, + widgets::canvas::{Painter, Shape}, +}; /// Shape to draw a line from (x1, y1) to (x2, y2) with the given color +#[derive(Debug, Clone)] pub struct Line { pub x1: f64, pub y1: f64, @@ -10,63 +13,77 @@ pub struct Line { 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); +impl Shape for Line { + fn draw(&self, painter: &mut Painter) { + let (x1, y1) = match painter.get_point(self.x1, self.y1) { + Some(c) => c, + None => return, + }; + let (x2, y2) = match painter.get_point(self.x2, self.y2) { + Some(c) => c, + None => return, + }; + let (dx, x_range) = if x2 >= x1 { + (x2 - x1, x1..=x2) + } 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 { - 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) + if dx == 0 { + for y in y_range { + painter.paint(x1, y, self.color); + } + } else if dy == 0 { + for x in x_range { + painter.paint(x, y1, self.color); + } + } 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 { - 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 { - 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, - dy, - dir_x, - dir_y, - current: 0.0, - end, +fn draw_line_low(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) { + let dx = (x2 - x1) as isize; + let dy = (y2 as isize - y1 as isize).abs(); + let mut d = 2 * dy - dx; + let mut y = y1; + for x in x1..=x2 { + painter.paint(x, y, color); + if d > 0 { + y = if y1 > y2 { y - 1 } else { y + 1 }; + d -= 2 * dx; } + d += 2 * dy; } } -impl<'a> Shape<'a> for Line { - fn color(&self) -> Color { - self.color - } - - fn points(&'a self) -> Box + 'a> { - Box::new(self.into_iter()) +fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) { + let dx = (x2 as isize - x1 as isize).abs(); + let dy = (y2 - y1) as isize; + let mut d = 2 * dx - dy; + let mut x = x1; + for y in y1..=y2 { + painter.paint(x, y, color); + if d > 0 { + x = if x1 > x2 { x - 1 } else { x + 1 }; + d -= 2 * dy; + } + d += 2 * dx; } } diff --git a/src/widgets/canvas/map.rs b/src/widgets/canvas/map.rs index cc16840..05a9de7 100644 --- a/src/widgets/canvas/map.rs +++ b/src/widgets/canvas/map.rs @@ -1,9 +1,12 @@ -use crate::style::Color; -use crate::widgets::canvas::points::PointsIterator; -use crate::widgets::canvas::world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION}; -use crate::widgets::canvas::Shape; +use crate::{ + style::Color, + widgets::canvas::{ + world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION}, + Painter, Shape, + }, +}; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum MapResolution { Low, High, @@ -19,6 +22,7 @@ impl MapResolution { } /// Shape to draw a world map with the given resolution and color +#[derive(Debug, Clone)] pub struct Map { pub resolution: MapResolution, pub color: Color, @@ -33,19 +37,12 @@ impl Default for Map { } } -impl<'a> Shape<'a> for Map { - fn color(&self) -> Color { - self.color - } - fn points(&'a self) -> Box + 'a> { - 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()) +impl Shape for Map { + fn draw(&self, painter: &mut Painter) { + for (x, y) in self.resolution.data() { + if let Some((x, y)) = painter.get_point(*x, *y) { + painter.paint(x, y, self.color); + } + } } } diff --git a/src/widgets/canvas/mod.rs b/src/widgets/canvas/mod.rs index 84a166d..4423160 100644 --- a/src/widgets/canvas/mod.rs +++ b/src/widgets/canvas/mod.rs @@ -9,10 +9,13 @@ pub use self::map::{Map, MapResolution}; pub use self::points::Points; pub use self::rectangle::Rectangle; -use crate::buffer::Buffer; -use crate::layout::Rect; -use crate::style::{Color, Style}; -use crate::widgets::{Block, Widget}; +use crate::{ + buffer::Buffer, + layout::Rect, + style::{Color, Style}, + widgets::{Block, Widget}, +}; +use std::fmt::Debug; pub const DOTS: [[u16; 2]; 4] = [ [0x0001, 0x0008], @@ -24,14 +27,12 @@ pub const BRAILLE_OFFSET: u16 = 0x2800; pub const BRAILLE_BLANK: char = '⠀'; /// Interface for all shapes that may be drawn on a Canvas widget. -pub trait Shape<'a> { - /// Returns the color of the shape - fn color(&self) -> Color; - /// Returns an iterator over all points of the shape - fn points(&'a self) -> Box + 'a>; +pub trait Shape { + fn draw(&self, painter: &mut Painter); } /// Label to draw some text on the canvas +#[derive(Debug, Clone)] pub struct Label<'a> { pub x: f64, pub y: f64, @@ -39,11 +40,13 @@ pub struct Label<'a> { pub color: Color, } +#[derive(Debug, Clone)] struct Layer { string: String, colors: Vec, } +#[derive(Debug, Clone)] struct Grid { cells: Vec, colors: Vec, @@ -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. +#[derive(Debug, Clone)] pub struct Context<'a> { width: u16, height: u16, @@ -87,26 +165,27 @@ pub struct 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 - pub fn draw<'b, S>(&mut self, shape: &'b S) + pub fn draw(&mut self, shape: &S) where - S: Shape<'b>, + S: Shape, { self.dirty = true; - let left = self.x_bounds[0]; - let right = self.x_bounds[1]; - 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(); - } + let mut painter = Painter::from(self); + shape.draw(&mut painter); } /// Go one layer above in the canvas. @@ -156,12 +235,10 @@ impl<'a> Context<'a> { /// color: Color::White, /// }); /// ctx.draw(&Rectangle { -/// rect: Rect { -/// x: 10, -/// y: 20, -/// width: 10, -/// height: 10, -/// }, +/// x: 10.0, +/// y: 20.0, +/// width: 10.0, +/// height: 10.0, /// color: Color::Red /// }); /// }); @@ -235,61 +312,68 @@ where }; let width = canvas_area.width as usize; - let height = canvas_area.height as usize; - if let Some(ref painter) = self.painter { - // Create a blank context that match the size of the terminal - let mut ctx = Context { - x_bounds: self.x_bounds, - y_bounds: self.y_bounds, - width: canvas_area.width, - height: canvas_area.height, - grid: Grid::new(width, height), - dirty: false, - layers: Vec::new(), - labels: Vec::new(), - }; - // Paint to this context - painter(&mut ctx); - ctx.finish(); + let painter = match self.painter { + Some(ref p) => p, + None => return, + }; + + // Create a blank context that match the size of the canvas + let mut ctx = Context::new( + canvas_area.width, + canvas_area.height, + self.x_bounds, + self.y_bounds, + ); + // Paint to this context + painter(&mut ctx); + ctx.finish(); - // Retreive painted points for each layer - for layer in ctx.layers { - for (i, (ch, color)) in layer - .string - .chars() - .zip(layer.colors.into_iter()) - .enumerate() - { - if ch != BRAILLE_BLANK { - let (x, y) = (i % width, i / width); - buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top()) - .set_char(ch) - .set_fg(color) - .set_bg(self.background_color); - } + // Retreive painted points for each layer + for layer in ctx.layers { + for (i, (ch, color)) in layer + .string + .chars() + .zip(layer.colors.into_iter()) + .enumerate() + { + if ch != BRAILLE_BLANK { + let (x, y) = (i % width, i / width); + buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top()) + .set_char(ch) + .set_fg(color) + .set_bg(self.background_color); } } + } - // Finally draw the labels - let style = Style::default().bg(self.background_color); - for label in ctx.labels.iter().filter(|l| { - !(l.x < self.x_bounds[0] - || l.x > self.x_bounds[1] - || l.y < self.y_bounds[0] - || l.y > self.y_bounds[1]) - }) { - let dy = ((self.y_bounds[1] - label.y) * f64::from(canvas_area.height - 1) - / (self.y_bounds[1] - self.y_bounds[0])) as u16; - let dx = ((label.x - self.x_bounds[0]) * f64::from(canvas_area.width - 1) - / (self.x_bounds[1] - self.x_bounds[0])) as u16; - buf.set_string( - dx + canvas_area.left(), - dy + canvas_area.top(), - label.text, - style.fg(label.color), - ); - } + // Finally draw the labels + let style = Style::default().bg(self.background_color); + let left = self.x_bounds[0]; + let right = self.x_bounds[1]; + let top = self.y_bounds[1]; + let bottom = self.y_bounds[0]; + let width = (self.x_bounds[1] - self.x_bounds[0]).abs(); + let height = (self.y_bounds[1] - self.y_bounds[0]).abs(); + let resolution = { + let width = f64::from(canvas_area.width - 1); + let height = f64::from(canvas_area.height - 1); + (width, height) + }; + for label in ctx + .labels + .iter() + .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), + ); } } } diff --git a/src/widgets/canvas/points.rs b/src/widgets/canvas/points.rs index 2453e48..83aeaba 100644 --- a/src/widgets/canvas/points.rs +++ b/src/widgets/canvas/points.rs @@ -1,20 +1,22 @@ -use std::slice; - -use super::Shape; -use crate::style::Color; +use crate::{ + style::Color, + widgets::canvas::{Painter, Shape}, +}; /// A shape to draw a group of points with the given color +#[derive(Debug, Clone)] 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> Shape for Points<'a> { + fn draw(&self, painter: &mut Painter) { + for (x, y) in self.coords { + if let Some((x, y)) = painter.get_point(*x, *y) { + painter.paint(x, y, self.color); + } + } } } @@ -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 { - match self.iter.next() { - Some(p) => Some(*p), - None => None, - } - } -} diff --git a/src/widgets/canvas/rectangle.rs b/src/widgets/canvas/rectangle.rs index 9408ee4..2a211e7 100644 --- a/src/widgets/canvas/rectangle.rs +++ b/src/widgets/canvas/rectangle.rs @@ -1,54 +1,52 @@ -use crate::layout::Rect; -use crate::style::Color; -use crate::widgets::canvas::{Line, Shape}; -use itertools::Itertools; +use crate::{ + style::Color, + widgets::canvas::{Line, Painter, Shape}, +}; /// Shape to draw a rectangle from a `Rect` with the given color +#[derive(Debug, Clone)] pub struct Rectangle { - pub rect: Rect, + pub x: f64, + pub y: f64, + pub width: f64, + pub height: f64, pub color: Color, } -impl<'a> Shape<'a> for Rectangle { - fn color(&self) -> Color { - self.color - } - - fn points(&'a self) -> Box + 'a> { - let left_line = Line { - x1: f64::from(self.rect.x), - y1: f64::from(self.rect.y), - x2: f64::from(self.rect.x), - y2: f64::from(self.rect.y + self.rect.height), - color: self.color, - }; - let top_line = Line { - x1: f64::from(self.rect.x), - y1: f64::from(self.rect.y + self.rect.height), - x2: f64::from(self.rect.x + self.rect.width), - y2: f64::from(self.rect.y + self.rect.height), - color: self.color, - }; - let right_line = Line { - x1: f64::from(self.rect.x + self.rect.width), - y1: f64::from(self.rect.y), - x2: f64::from(self.rect.x + self.rect.width), - y2: f64::from(self.rect.y + self.rect.height), - color: self.color, - }; - let bottom_line = Line { - x1: f64::from(self.rect.x), - y1: f64::from(self.rect.y), - x2: f64::from(self.rect.x + self.rect.width), - y2: f64::from(self.rect.y), - color: self.color, - }; - Box::new( - left_line.into_iter().merge( - top_line - .into_iter() - .merge(right_line.into_iter().merge(bottom_line.into_iter())), - ), - ) +impl Shape for Rectangle { + fn draw(&self, painter: &mut Painter) { + let lines: [Line; 4] = [ + Line { + x1: self.x, + y1: self.y, + x2: self.x, + y2: self.y + self.height, + color: self.color, + }, + Line { + x1: self.x, + y1: self.y + self.height, + x2: self.x + self.width, + y2: self.y + self.height, + color: self.color, + }, + Line { + x1: self.x + self.width, + y1: self.y, + x2: self.x + self.width, + y2: self.y + self.height, + color: self.color, + }, + Line { + x1: self.x, + y1: self.y, + x2: self.x + self.width, + y2: self.y, + color: self.color, + }, + ]; + for line in &lines { + line.draw(painter); + } } } diff --git a/src/widgets/chart.rs b/src/widgets/chart.rs index f091cab..44738c8 100644 --- a/src/widgets/chart.rs +++ b/src/widgets/chart.rs @@ -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 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 pub struct Axis<'a, L> where