use crate::shape::{ShapeBuilder, ShapeFinished};
use crate::draw_commands::{DrawCommand, circle_helper, cancel_helper};
use crate::path_command::PathCommand::{self, *};
use crate::point::{Vec2D, Unit, WorldUnit, ScreenUnit};
use crate::transform::Transform;
use crate::style::Style;
use crate::matrix::Matrix;
use crate::shape::stored::path::Path;
use crate::shape::ShapeStored;
#[derive(Debug, Copy, Clone)]
enum State {
Initial(Vec2D<WorldUnit>),
DefineCell(Vec2D<WorldUnit>, Vec2D<WorldUnit>),
DefineGrid {
cell: (Vec2D<WorldUnit>, Vec2D<WorldUnit>),
free: Vec2D<WorldUnit>,
},
}
#[derive(Debug, Copy, Clone)]
pub struct Grid {
style: Style<WorldUnit>,
state: State,
}
impl Grid {
pub fn start(initial: Vec2D<WorldUnit>, style: Style<WorldUnit>) -> Grid {
Grid {
style,
state: State::Initial(initial),
}
}
}
impl ShapeBuilder for Grid {
fn handle_mouse_moved(&mut self, pos: Vec2D<ScreenUnit>, t: Transform, _snap: ScreenUnit) {
let wpos = t.to_world_coordinates(pos);
match self.state {
State::Initial(p) => {
self.state = State::DefineCell(p, wpos);
}
State::DefineCell(p1, _) => {
self.state = State::DefineCell(p1, wpos);
}
State::DefineGrid { cell, .. } => {
self.state = State::DefineGrid { cell, free: wpos };
}
}
}
fn handle_button_pressed(&mut self, _pos: Vec2D<ScreenUnit>, _t: Transform, _snap: ScreenUnit) { }
fn handle_button_released(&mut self, pos: Vec2D<ScreenUnit>, t: Transform, snap: ScreenUnit) -> ShapeFinished {
let wpos = t.to_world_coordinates(pos);
match self.state {
State::Initial(_) => ShapeFinished::Cancelled,
State::DefineCell(p1, p2) => {
let corners = corners_from_two_points(p1, p2, t);
let c4 = t.to_screen_coordinates(corners[3]);
let c1 = t.to_screen_coordinates(corners[0]);
let c2 = t.to_screen_coordinates(corners[1]);
let distance_to_l = c4.distance(c1).min(c2.distance(c1));
if distance_to_l < snap {
ShapeFinished::Cancelled
} else {
self.state = State::DefineGrid {
cell: (p1, p2),
free: wpos,
};
ShapeFinished::No
}
}
State::DefineGrid { cell: (p1, p2), free } => {
let cell = (
t.to_screen_coordinates(p1),
t.to_screen_coordinates(p2),
);
let free = t.to_screen_coordinates(free);
let grid = grid_from_cell_and_point(cell, free);
let mut shapes: Vec<Box<dyn ShapeStored>> = Vec::with_capacity(
grid.row_count() * (grid.col_count() - 1) +
grid.col_count() * (grid.row_count() - 1)
);
let rows = grid.rows();
let cols = grid.cols();
for row in rows.iter() {
for (&a, &b) in row.iter().zip(row.iter().skip(1)) {
shapes.push(Box::new(Path::from_parts(
vec![
MoveTo(t.to_world_coordinates(a)),
LineTo(t.to_world_coordinates(b)),
],
self.style,
)));
}
}
for col in cols.iter() {
for (&a, &b) in col.iter().zip(col.iter().skip(1)) {
shapes.push(Box::new(Path::from_parts(
vec![
MoveTo(t.to_world_coordinates(a)),
LineTo(t.to_world_coordinates(b)),
],
self.style,
)));
}
}
ShapeFinished::Yes(shapes)
}
}
}
fn draw_commands(&self, t: Transform, snap: ScreenUnit) -> Vec<DrawCommand> {
match self.state {
State::Initial(p) => vec![
circle_helper(t.to_screen_coordinates(p), snap),
],
State::DefineCell(p1, p2) => {
let corners = corners_from_two_points(p1, p2, t);
vec![
DrawCommand::Path {
commands: corners_to_path_commands(corners),
style: self.style,
},
cancel_helper(corners[1], p2, t, snap),
cancel_helper(corners[3], p2, t, snap),
]
}
State::DefineGrid { cell: (p1, p2), free } => {
let cell = (
t.to_screen_coordinates(p1),
t.to_screen_coordinates(p2),
);
let free = t.to_screen_coordinates(free);
let grid = grid_from_cell_and_point(cell, free);
let mut commands = Vec::with_capacity(grid.row_count() + grid.col_count());
for row in grid.rows() {
commands.push(DrawCommand::Path {
commands: vec![
MoveTo(t.to_world_coordinates(row[0])),
LineTo(t.to_world_coordinates(*row.last().unwrap())),
],
style: self.style,
});
}
for col in grid.cols() {
commands.push(DrawCommand::Path {
commands: vec![
MoveTo(t.to_world_coordinates(col[0])),
LineTo(t.to_world_coordinates(*col.last().unwrap())),
],
style: self.style,
});
}
commands
}
}
}
}
pub(crate) fn grid_from_cell_and_point<T: Unit>((p1, p2): (Vec2D<T>, Vec2D<T>), free: Vec2D<T>) -> Matrix<Vec2D<T>> {
let diagonal = p2 - p1;
let freep = free - p1;
let width = diagonal.x.val();
let height = diagonal.y.val();
let x_cells = (freep.x / width).val();
let y_cells = (freep.y / height).val();
let num_x_cells = x_cells.abs().ceil() as usize;
let num_y_cells = y_cells.abs().ceil() as usize;
let x_separators: Vec<_> = (0..num_x_cells+1).map(|n| n as i32 * x_cells.signum() as i32).collect();
let y_separators: Vec<_> = (0..num_y_cells+1).map(|n| n as i32 * y_cells.signum() as i32).collect();
let mut grid = Vec::with_capacity((num_x_cells + 1) * (num_y_cells + 1));
for yi in y_separators {
for &xi in x_separators.iter() {
grid.push(p1 + Vec2D::new((xi as f64 * width).into(), (yi as f64 * height).into()));
}
}
Matrix::from_parts(grid, num_x_cells + 1)
}
fn corners_from_two_points(p1: Vec2D<WorldUnit>, p2: Vec2D<WorldUnit>, t: Transform) -> [Vec2D<WorldUnit>; 4] {
let p1s = t.to_screen_coordinates(p1);
let p2s = t.to_screen_coordinates(p2);
let diagonal = p2s - p1s;
[
p1,
t.to_world_coordinates(p1s + Vec2D::new(diagonal.x, 0.0.into())),
p2,
t.to_world_coordinates(p1s + Vec2D::new(0.0.into(), diagonal.y)),
]
}
fn corners_to_path_commands(corners: [Vec2D<WorldUnit>; 4]) -> Vec<PathCommand<WorldUnit>> {
corners.into_iter().cycle().take(5).enumerate().map(|(i, p)| if i == 0 {
MoveTo(p)
} else {
LineTo(p)
}).collect()
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::point::{Vec2D, Unittless};
use super::*;
const SNAP: ScreenUnit = ScreenUnit::from_float(10.0);
#[test]
fn can_make_a_grid() {
let a: Vec2D<Unittless> = (-2.9,1.66).into();
let b: Vec2D<Unittless> = (-1.44,0.82).into();
let c: Vec2D<Unittless> = (-1.0, 0.0).into();
let grid = grid_from_cell_and_point((a, b), c);
let xs = [-2.9, -1.44, 0.020000000000000018];
let ys = [1.66, 0.82, -0.020000000000000018];
assert_eq!(grid, Matrix::from_parts(ys.into_iter().flat_map(|y| {
xs.iter().map(move |&x| Vec2D::new_unitless(x, y))
}).collect(), 3));
}
#[test]
fn can_make_a_rectangular_grid() {
let a = Vec2D::new_unitless(-3.0, 3.0);
let b = Vec2D::new_unitless(-1.5, 2.0);
let c = Vec2D::new_unitless(0.85, 1.39);
let grid = grid_from_cell_and_point((a, b), c);
let xs = [-3.0, -1.5, 0.0, 1.5];
let ys = [3.0, 2.0, 1.0];
assert_eq!(grid, Matrix::from_parts(ys.into_iter().flat_map(|y| {
xs.iter().map(move |x| Vec2D::new_unitless(*x, y))
}).collect(), 4));
}
#[test]
fn can_render_a_grid() {
let a = Vec2D::new_screen(-3.0, 3.0);
let b = Vec2D::new_screen(-1.5, 2.0);
let p = Vec2D::new_screen(0.85, 1.39);
let cols = [-3.0, -1.5, 0.0, 1.5];
let rows = [3.0, 2.0, 1.0];
let t = Transform::default();
let snap = 1.0.into();
let mut grid = Grid::start(t.to_world_coordinates(a), Default::default());
grid.handle_mouse_moved(b, t, snap);
grid.handle_button_released(b, t, snap);
grid.handle_mouse_moved(p, t, snap);
let mut expected_rows: Vec<_> = rows.iter().map(|&r| {
DrawCommand::Path {
commands: vec![
MoveTo(Vec2D::new_world(cols[0], r)),
LineTo(Vec2D::new_world(cols[3], r)),
],
style: Default::default(),
}
}).collect();
expected_rows.extend(cols.iter().map(|&c| {
DrawCommand::Path {
commands: vec![
MoveTo(Vec2D::new_world(c, rows[0])),
LineTo(Vec2D::new_world(c, rows[2])),
],
style: Default::default(),
}
}));
assert_eq!(grid.draw_commands(t, snap), expected_rows);
}
#[test]
fn grid_cancel_helpers_are_two_circles() {
let t = Default::default();
let mut grid = Grid::start((0.0, 0.0).into(), Default::default());
grid.handle_mouse_moved((50.0, 0.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::Path {
commands: vec![
MoveTo((0.0, 0.0).into()),
LineTo((50.0, 0.0).into()),
LineTo((50.0, 0.0).into()),
LineTo((0.0, 0.0).into()),
LineTo((0.0, 0.0).into()),
],
style: Default::default(),
},
DrawCommand::ScreenCircle {
center: (50.0, 0.0).into(),
radius: SNAP,
style: Style::red_circle_helper(),
},
DrawCommand::ScreenCircle {
center: (0.0, 0.0).into(),
radius: SNAP,
style: Style::circle_helper(),
},
]);
Some(grid).map(|mut grid| {
match grid.handle_button_released((50.0, 0.0).into(), t, SNAP) {
ShapeFinished::Cancelled => {}
_ => panic!()
}
}).unwrap();
grid.handle_mouse_moved((0.0, 50.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::Path {
commands: vec![
MoveTo((0.0, 0.0).into()),
LineTo((0.0, 0.0).into()),
LineTo((0.0, 50.0).into()),
LineTo((0.0, 50.0).into()),
LineTo((0.0, 0.0).into()),
],
style: Default::default(),
},
DrawCommand::ScreenCircle {
center: (0.0, 0.0).into(),
radius: SNAP,
style: Style::circle_helper(),
},
DrawCommand::ScreenCircle {
center: (0.0, 50.0).into(),
radius: SNAP,
style: Style::red_circle_helper(),
},
]);
Some(grid).map(|mut grid| {
match grid.handle_button_released((50.0, 0.0).into(), t, SNAP) {
ShapeFinished::Cancelled => {}
_ => panic!()
}
}).unwrap();
}
}