1use crate::shape::{ShapeBuilder, ShapeFinished};
2use crate::draw_commands::{DrawCommand, circle_helper, cancel_helper};
3use crate::path_command::PathCommand::*;
4use crate::point::{Vec2D, WorldUnit, ScreenUnit};
5use crate::transform::Transform;
6use crate::style::Style;
7use crate::geom::{rombus_to_rectangle, project};
8use crate::shape::builder::grid::grid_from_cell_and_point;
9use crate::shape::ShapeStored;
10use crate::shape::stored::path::Path;
11
12#[derive(Debug, Copy, Clone)]
13enum State {
14 Initial(Vec2D<WorldUnit>),
15
16 OneDimension {
17 start: Vec2D<WorldUnit>,
18 free: Vec2D<WorldUnit>
19 },
20
21 TwoDimension {
22 start: Vec2D<WorldUnit>,
23 p1: Vec2D<WorldUnit>,
24 free: Vec2D<WorldUnit>,
25 },
26
27 DefineGrid {
28 start: Vec2D<WorldUnit>,
29 p1: Vec2D<WorldUnit>,
30 p2: Vec2D<WorldUnit>,
31 free: Vec2D<WorldUnit>,
32
33 t: Transform,
36 },
37}
38
39#[derive(Debug, Copy, Clone)]
44pub struct FreeGrid {
45 style: Style<WorldUnit>,
46 state: State,
47}
48
49impl FreeGrid {
50 pub fn start(initial: Vec2D<WorldUnit>, style: Style<WorldUnit>) -> FreeGrid {
51 FreeGrid {
52 style,
53 state: State::Initial(initial),
54 }
55 }
56}
57
58impl ShapeBuilder for FreeGrid {
59 fn handle_mouse_moved(&mut self, pos: Vec2D<ScreenUnit>, t: Transform, _snap: ScreenUnit) {
60 let wpos = t.to_world_coordinates(pos);
61
62 match self.state {
63 State::Initial(_) => {
64 self.state = State::Initial(wpos);
65 }
66
67 State::OneDimension { start, .. } => {
68 self.state = State::OneDimension { start, free: wpos };
69 }
70
71 State::TwoDimension { start, p1, .. } => {
72 self.state = State::TwoDimension { start, p1, free: wpos };
73 }
74
75 State::DefineGrid { start, p1, p2, t, .. } => {
76 self.state = State::DefineGrid { start, p1, p2, t, free: wpos };
77 }
78 }
79 }
80
81 fn handle_button_pressed(&mut self, _pos: Vec2D<ScreenUnit>, _t: Transform, _snap: ScreenUnit) { }
82
83 fn handle_button_released(&mut self, pos: Vec2D<ScreenUnit>, t: Transform, snap: ScreenUnit) -> ShapeFinished {
84 let wpos = t.to_world_coordinates(pos);
85
86 match self.state {
87 State::Initial(p) => {
88 self.state = State::OneDimension { start: p, free: wpos };
89
90 ShapeFinished::No
91 }
92
93 State::OneDimension { start, free } => {
94 self.state = State::TwoDimension { start, p1: free, free: wpos };
95
96 if t.to_screen_coordinates(start).distance(pos) < snap {
97 ShapeFinished::Cancelled
98 } else {
99 ShapeFinished::No
100 }
101 }
102
103 State::TwoDimension { start, p1, free } => {
104 let p2 = free;
105
106 self.state = State::DefineGrid {
107 start, p1, p2,
108 t: rombus_to_rectangle(start.to_vec2d(), p1.to_vec2d(), p2.to_vec2d()),
109 free: wpos,
110 };
111
112 let sstart = t.to_screen_coordinates(start);
113 let sp1 = t.to_screen_coordinates(p1);
114 let proj = t.to_screen_coordinates(project(free, [start, p1]));
115 let proj2 = t.to_screen_coordinates(project(start, [p1, free]));
116
117 if sstart.distance(pos) < snap || sp1.distance(pos) < snap || proj.distance(pos) < snap || proj2.distance(sstart) < snap {
118 ShapeFinished::Cancelled
119 } else {
120 ShapeFinished::No
121 }
122 }
123
124 State::DefineGrid { start, p2, free, t, ..} => {
125 let cell = (
126 t.to_screen_coordinates(start),
127 t.to_screen_coordinates(p2),
128 );
129 let free = t.to_screen_coordinates(free);
130 let grid = grid_from_cell_and_point(cell, free);
131
132 let mut shapes: Vec<Box<dyn ShapeStored>> = Vec::with_capacity(
133 grid.row_count() * (grid.col_count() - 1) +
134 grid.col_count() * (grid.row_count() - 1)
135 );
136
137 let rows = grid.rows();
138 let cols = grid.cols();
139
140 for row in rows.iter() {
141 for (&a, &b) in row.iter().zip(row.iter().skip(1)) {
142 shapes.push(Box::new(Path::from_parts(
143 vec![
144 MoveTo(t.to_world_coordinates(a)),
145 LineTo(t.to_world_coordinates(b)),
146 ],
147 self.style,
148 )));
149 }
150 }
151
152 for col in cols.iter() {
153 for (&a, &b) in col.iter().zip(col.iter().skip(1)) {
154 shapes.push(Box::new(Path::from_parts(
155 vec![
156 MoveTo(t.to_world_coordinates(a)),
157 LineTo(t.to_world_coordinates(b)),
158 ],
159 self.style,
160 )));
161 }
162 }
163
164 ShapeFinished::Yes(shapes)
165 }
166 }
167 }
168
169 fn draw_commands(&self, t: Transform, snap: ScreenUnit) -> Vec<DrawCommand> {
170 match self.state {
171 State::Initial(p) => vec![
172 circle_helper(t.to_screen_coordinates(p), snap),
173 ],
174
175 State::OneDimension { start, free } => vec![
176 DrawCommand::Path {
177 commands: vec![
178 MoveTo(start),
179 LineTo(free),
180 ],
181 style: self.style,
182 },
183 cancel_helper(start, free, t, snap),
184 ],
185
186 State::TwoDimension { start, p1, free } => vec![
187 DrawCommand::Path {
188 commands: vec![
189 MoveTo(start),
190 LineTo(p1),
191 LineTo(free),
192 LineTo(start + (free - p1)),
193 LineTo(start),
194 ],
195 style: self.style,
196 },
197
198 cancel_helper(start, free, t, snap),
199 cancel_helper(p1, free, t, snap),
200 cancel_helper(project(free, [start, p1]), free, t, snap),
203 cancel_helper(project(start, [p1, free]), start, t, snap),
206 ],
207
208 State::DefineGrid { start, p1: _, p2, t, free } => {
209 let cell = (
210 t.apply(start.to_vec2d()),
211 t.apply(p2.to_vec2d()),
212 );
213 let free = t.apply(free.to_vec2d());
214 let inverse = t.invert();
215 let grid = grid_from_cell_and_point(cell, free);
216
217 let mut commands = Vec::with_capacity(grid.row_count() + grid.col_count());
218
219 for row in grid.rows() {
220 commands.push(DrawCommand::Path {
221 commands: vec![
222 MoveTo(inverse.apply(row[0]).into()),
223 LineTo(inverse.apply(*row.last().unwrap()).into()),
224 ],
225 style: self.style,
226 });
227 }
228
229 for col in grid.cols() {
230 commands.push(DrawCommand::Path {
231 commands: vec![
232 MoveTo(inverse.apply(col[0]).into()),
233 LineTo(inverse.apply(*col.last().unwrap()).into()),
234 ],
235 style: self.style,
236 });
237 }
238
239 commands
240 }
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use pretty_assertions::assert_eq;
248
249 use super::*;
250
251 const SNAP: ScreenUnit = ScreenUnit::from_float(10.0);
252
253 #[test]
254 fn figure_is_cancelled() {
255 let t = Default::default();
256 let mut grid = FreeGrid::start(Vec2D::new_world(0.0, 0.0), Default::default());
257
258 grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
259
260 assert_eq!(grid.draw_commands(t, SNAP), vec![
262 DrawCommand::ScreenCircle {
263 center: Vec2D::new_screen(1.0, 1.0),
264 radius: 10.0.into(),
265 style: Style::circle_helper(),
266 }
267 ]);
268
269 grid.handle_mouse_moved((0.0, 0.0).into(), t, SNAP);
271 grid.handle_button_released((0.0, 0.0).into(), t, SNAP);
272
273 grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
275
276 assert_eq!(grid.draw_commands(t, SNAP), vec![
277 DrawCommand::Path {
278 commands: vec![
279 MoveTo((0.0, 0.0).into()),
280 LineTo((1.0, 1.0).into()),
281 ],
282 style: Default::default(),
283 },
284
285 DrawCommand::ScreenCircle {
286 center: Vec2D::new_screen(0.0, 0.0),
287 radius: SNAP,
288 style: Style::red_circle_helper(),
289 },
290 ]);
291
292 Some(grid).map(|mut grid| {
293 grid.handle_button_pressed((1.0, 1.0).into(), t, SNAP);
295 match grid.handle_button_released((1.0, 1.0).into(), t, SNAP) {
296 ShapeFinished::Cancelled => {}
297 _ => panic!(),
298 }
299 }).unwrap();
300
301 grid.handle_mouse_moved((20.0, 0.0).into(), t, SNAP);
303 grid.handle_button_pressed((20.0, 0.0).into(), t, SNAP);
304 grid.handle_button_released((20.0, 0.0).into(), t, SNAP);
305
306 grid.handle_mouse_moved((1.0, 1.0).into(), t, SNAP);
309
310 assert_eq!(grid.draw_commands(t, SNAP), vec![
311 DrawCommand::Path {
312 commands: vec![
313 MoveTo((0.0, 0.0).into()),
314 LineTo((20.0, 0.0).into()),
315 LineTo((1.0, 1.0).into()),
316 LineTo((-19.0, 1.0).into()),
317 LineTo((0.0, 0.0).into()),
318 ],
319 style: Default::default(),
320 },
321
322 DrawCommand::ScreenCircle {
324 center: (0.0, 0.0).into(),
325 radius: SNAP,
326 style: Style::red_circle_helper(),
327 },
328
329 DrawCommand::ScreenCircle {
331 center: (20.0, 0.0).into(),
332 radius: SNAP,
333 style: Style::circle_helper(),
334 },
335
336 DrawCommand::ScreenCircle {
338 center: (1.0, 0.0).into(),
339 radius: SNAP,
340 style: Style::red_circle_helper(),
341 },
342
343 DrawCommand::ScreenCircle {
345 center: (0.0552486187845318, 1.0497237569060773).into(),
346 radius: SNAP,
347 style: Style::red_circle_helper(),
348 },
349 ]);
350
351 Some(grid).map(|mut grid| {
353 grid.handle_button_pressed((1.0, 1.0).into(), t, SNAP);
354 match grid.handle_button_released((1.0, 1.0).into(), t, SNAP) {
355 ShapeFinished::Cancelled => {}
356 _ => panic!()
357 }
358 }).unwrap();
359
360 grid.handle_mouse_moved((21.0, 1.0).into(), t, SNAP);
361
362 assert_eq!(grid.draw_commands(t, SNAP), vec![
363 DrawCommand::Path {
364 commands: vec![
365 MoveTo((0.0, 0.0).into()),
366 LineTo((20.0, 0.0).into()),
367 LineTo((21.0, 1.0).into()),
368 LineTo((1.0, 1.0).into()),
369 LineTo((0.0, 0.0).into()),
370 ],
371 style: Default::default(),
372 },
373
374 DrawCommand::ScreenCircle {
376 center: (0.0, 0.0).into(),
377 radius: SNAP,
378 style: Style::circle_helper(),
379 },
380
381 DrawCommand::ScreenCircle {
383 center: (20.0, 0.0).into(),
384 radius: SNAP,
385 style: Style::red_circle_helper(),
386 },
387
388 DrawCommand::ScreenCircle {
389 center: (21.0, 0.0).into(),
390 radius: SNAP,
391 style: Style::red_circle_helper(),
392 },
393
394 DrawCommand::ScreenCircle {
395 center: (10.0, -10.0).into(),
396 radius: SNAP,
397 style: Style::circle_helper(),
398 },
399 ]);
400
401 Some(grid).map(|mut grid| {
403 grid.handle_button_pressed((21.0, 1.0).into(), t, SNAP);
404 match grid.handle_button_released((21.0, 1.0).into(), t, SNAP) {
405 ShapeFinished::Cancelled => {}
406 _ => panic!()
407 }
408 }).unwrap();
409 }
410}