use crate::shape::{ShapeBuilder, ShapeFinished};
use crate::draw_commands::{DrawCommand, circle_helper, cancel_helper};
use crate::path_command::PathCommand::*;
use crate::point::{Vec2D, WorldUnit, ScreenUnit};
use crate::transform::Transform;
use crate::style::Style;
use crate::geom::rombus_to_rectangle;
use crate::shape::builder::grid::grid_from_cell_and_point;
use crate::shape::ShapeStored;
use crate::shape::stored::path::Path;
#[derive(Debug, Copy, Clone)]
enum State {
Initial(Vec2D<WorldUnit>),
OneDimension {
start: Vec2D<WorldUnit>,
free: Vec2D<WorldUnit>
},
TwoDimension {
start: Vec2D<WorldUnit>,
p1: Vec2D<WorldUnit>,
free: Vec2D<WorldUnit>,
},
DefineGrid {
start: Vec2D<WorldUnit>,
p1: Vec2D<WorldUnit>,
p2: Vec2D<WorldUnit>,
free: Vec2D<WorldUnit>,
t: Transform,
},
}
#[derive(Debug, Copy, Clone)]
pub struct FreeGrid {
style: Style<WorldUnit>,
state: State,
}
impl FreeGrid {
pub fn start(initial: Vec2D<WorldUnit>, style: Style<WorldUnit>) -> FreeGrid {
FreeGrid {
style,
state: State::Initial(initial),
}
}
}
impl ShapeBuilder for FreeGrid {
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(_) => {
self.state = State::Initial(wpos);
}
State::OneDimension { start, .. } => {
self.state = State::OneDimension { start, free: wpos };
}
State::TwoDimension { start, p1, .. } => {
self.state = State::TwoDimension { start, p1, free: wpos };
}
State::DefineGrid { start, p1, p2, t, .. } => {
self.state = State::DefineGrid { start, p1, p2, t, 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(p) => {
self.state = State::OneDimension { start: p, free: wpos };
ShapeFinished::No
}
State::OneDimension { start, free } => {
self.state = State::TwoDimension { start, p1: free, free: wpos };
if t.to_screen_coordinates(start).distance(pos) < snap {
ShapeFinished::Cancelled
} else {
ShapeFinished::No
}
}
State::TwoDimension { start, p1, free } => {
let p2 = free;
self.state = State::DefineGrid {
start, p1, p2,
t: rombus_to_rectangle(start.to_vec2d(), p1.to_vec2d(), p2.to_vec2d()),
free: wpos,
};
let sstart = t.to_screen_coordinates(start);
let sp1 = t.to_screen_coordinates(p1);
if sstart.distance(pos) < snap || sp1.distance(pos) < snap {
ShapeFinished::Cancelled
} else {
ShapeFinished::No
}
}
State::DefineGrid { start, p2, free, t, ..} => {
let cell = (
t.to_screen_coordinates(start),
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::OneDimension { start, free } => vec![
DrawCommand::Path {
commands: vec![
MoveTo(start),
LineTo(free),
],
style: self.style,
},
cancel_helper(start, free, t, snap),
],
State::TwoDimension { start, p1, free } => vec![
DrawCommand::Path {
commands: vec![
MoveTo(start),
LineTo(p1),
LineTo(free),
LineTo(start + (free - p1)),
LineTo(start),
],
style: self.style,
},
cancel_helper(start, free, t, snap),
cancel_helper(p1, free, t, snap),
],
State::DefineGrid { start, p1: _, p2, t, free } => {
let cell = (
t.apply(start.to_vec2d()),
t.apply(p2.to_vec2d()),
);
let free = t.apply(free.to_vec2d());
let inverse = t.invert();
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(inverse.apply(row[0]).into()),
LineTo(inverse.apply(*row.last().unwrap()).into()),
],
style: self.style,
});
}
for col in grid.cols() {
commands.push(DrawCommand::Path {
commands: vec![
MoveTo(inverse.apply(col[0]).into()),
LineTo(inverse.apply(*col.last().unwrap()).into()),
],
style: self.style,
});
}
commands
}
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
const SNAP: ScreenUnit = ScreenUnit::from_float(10.0);
#[test]
fn figure_is_cancelled() {
let t = Default::default();
let mut grid = FreeGrid::start(Vec2D::new_world(0.0, 0.0), Default::default());
grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::ScreenCircle {
center: Vec2D::new_screen(1.0, 1.0),
radius: 10.0.into(),
style: Style::circle_helper(),
}
]);
grid.handle_mouse_moved((0.0, 0.0).into(), t, SNAP);
grid.handle_button_released((0.0, 0.0).into(), t, SNAP);
grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::Path {
commands: vec![
MoveTo((0.0, 0.0).into()),
LineTo((1.0, 1.0).into()),
],
style: Default::default(),
},
DrawCommand::ScreenCircle {
center: Vec2D::new_screen(0.0, 0.0),
radius: SNAP,
style: Style::red_circle_helper(),
},
]);
Some(grid).map(|mut grid| {
grid.handle_button_pressed((1.0, 1.0).into(), t, SNAP);
match grid.handle_button_released((1.0, 1.0).into(), t, SNAP) {
ShapeFinished::Cancelled => {}
_ => panic!(),
}
}).unwrap();
grid.handle_mouse_moved((20.0, 0.0).into(), t, SNAP);
grid.handle_button_pressed((20.0, 0.0).into(), t, SNAP);
grid.handle_button_released((20.0, 0.0).into(), t, SNAP);
grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::Path {
commands: vec![
MoveTo((0.0, 0.0).into()),
LineTo((20.0, 0.0).into()),
LineTo((1.0, 1.0).into()),
LineTo((-19.0, 1.0).into()),
LineTo((0.0, 0.0).into()),
],
style: Default::default(),
},
DrawCommand::ScreenCircle {
center: (0.0, 0.0).into(),
radius: SNAP,
style: Style::red_circle_helper(),
},
DrawCommand::ScreenCircle {
center: (20.0, 0.0).into(),
radius: SNAP,
style: Style::circle_helper(),
},
]);
Some(grid).map(|mut grid| {
grid.handle_button_pressed((1.0, 1.0).into(), t, SNAP);
match grid.handle_button_released((1.0, 1.0).into(), t, SNAP) {
ShapeFinished::Cancelled => {}
_ => panic!()
}
}).unwrap();
grid.handle_mouse_moved((21.0, 1.0).into(), t, SNAP);
assert_eq!(grid.draw_commands(t, SNAP), vec![
DrawCommand::Path {
commands: vec![
MoveTo((0.0, 0.0).into()),
LineTo((20.0, 0.0).into()),
LineTo((21.0, 1.0).into()),
LineTo((1.0, 1.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: (20.0, 0.0).into(),
radius: SNAP,
style: Style::red_circle_helper(),
},
]);
Some(grid).map(|mut grid| {
grid.handle_button_pressed((21.0, 1.0).into(), t, SNAP);
match grid.handle_button_released((21.0, 1.0).into(), t, SNAP) {
ShapeFinished::Cancelled => {}
_ => panic!()
}
}).unwrap();
}
}