diff options
-rw-r--r-- | src/app.rs | 115 | ||||
-rw-r--r-- | src/bitmap.rs | 103 | ||||
-rw-r--r-- | src/undo.rs | 8 |
3 files changed, 160 insertions, 66 deletions
@@ -103,61 +103,30 @@ impl<'ctx> AppState<'ctx> { | |||
103 | center: P, | 103 | center: P, |
104 | val: bool, | 104 | val: bool, |
105 | ) -> Result<Vec<ModifyRecord>, ()> { | 105 | ) -> Result<Vec<ModifyRecord>, ()> { |
106 | let radius = self.brush_size; | 106 | let radius = self.brush_size as u32; |
107 | if radius == 1 { | 107 | let center = self.idx_at_coord(center).ok_or(())?; |
108 | let center_point: Point = center.into(); | 108 | let mut circle_modify_record = vec![]; |
109 | return Ok(if let Some(pt) = self.idx_at_coord(center_point) { | 109 | for point in self.pixmap.get_circle(center, radius) { |
110 | let old_val = self.pixmap.set(pt, val); | 110 | let old_val = self.pixmap.set(point, val); |
111 | Ok(ModifyRecord::new(pt, old_val, val)) | 111 | circle_modify_record.push(ModifyRecord::new(point, old_val, val)); |
112 | } else { | 112 | } |
113 | Err(()) | 113 | Ok(circle_modify_record) |
114 | } | 114 | } |
115 | .map(|x| vec![x])?); | 115 | |
116 | } else { | 116 | fn paint_line<P: Into<Point>>(&mut self, start: P, end: P) -> Result<Vec<ModifyRecord>, ()> { |
117 | if let Some(center_on_grid) = self.idx_at_coord(center) { | 117 | let start = self.idx_at_coord(start).ok_or(())?; |
118 | // center_on_grid is now a coordinate on the drawing grid | 118 | let end = self.idx_at_coord(end).ok_or(())?; |
119 | let (x0, y0) = (center_on_grid.0 as i64, center_on_grid.1 as i64); | 119 | let line_coords = self.pixmap.get_line(start, end); |
120 | let (mut dx, mut dy, mut err) = (radius as i64, 0i64, 1 - radius as i64); | 120 | let mut line_modify_record = vec![]; |
121 | let mut circle = vec![]; | 121 | let val = self.active_color; |
122 | let mut old_vals = vec![]; | 122 | for point in line_coords { |
123 | while dx >= dy { | 123 | let circle_around_point = self.pixmap.get_circle(point, self.brush_size as u32); |
124 | circle.push((x0 + dx, y0 + dy)); | 124 | for c in circle_around_point { |
125 | circle.push((x0 - dx, y0 + dy)); | 125 | let old_val = self.pixmap.set(c, val); |
126 | circle.push((x0 + dx, y0 - dy)); | 126 | line_modify_record.push(ModifyRecord::new(c, old_val, val)); |
127 | circle.push((x0 - dx, y0 - dy)); | ||
128 | circle.push((x0 + dy, y0 + dx)); | ||
129 | circle.push((x0 - dy, y0 + dx)); | ||
130 | circle.push((x0 + dy, y0 - dx)); | ||
131 | circle.push((x0 - dy, y0 - dx)); | ||
132 | dy = dy + 1; | ||
133 | if err < 0 { | ||
134 | err = err + 2 * dy + 1; | ||
135 | } else { | ||
136 | dx -= 1; | ||
137 | err += 2 * (dy - dx) + 1; | ||
138 | } | ||
139 | } | ||
140 | // circle's insides | ||
141 | for x in 0..radius as i64 { | ||
142 | for y in 0..radius as i64 { | ||
143 | if x.pow(2) + y.pow(2) < (radius as i64).pow(2) { | ||
144 | circle.push((x0 + x, y0 + y)); | ||
145 | circle.push((x0 - x, y0 + y)); | ||
146 | circle.push((x0 + x, y0 - y)); | ||
147 | circle.push((x0 - x, y0 - y)); | ||
148 | } | ||
149 | } | ||
150 | } | ||
151 | for circumference_pt in circle { | ||
152 | if let Ok(mp) = MapPoint::try_from(circumference_pt) { | ||
153 | let old_val = self.pixmap.set(mp, val); | ||
154 | old_vals.push(ModifyRecord::new((mp.x, mp.y), old_val, val)); | ||
155 | } | ||
156 | } | ||
157 | return Ok(old_vals); | ||
158 | } | 127 | } |
159 | } | 128 | } |
160 | return Err(()); | 129 | Ok(line_modify_record) |
161 | } | 130 | } |
162 | 131 | ||
163 | fn apply_operation(&mut self, op: Operation, op_kind: OpKind) { | 132 | fn apply_operation(&mut self, op: Operation, op_kind: OpKind) { |
@@ -176,6 +145,17 @@ impl<'ctx> AppState<'ctx> { | |||
176 | } | 145 | } |
177 | } | 146 | } |
178 | 147 | ||
148 | fn commit_operation(&mut self) { | ||
149 | if !self.current_operation.is_empty() { | ||
150 | let op = self | ||
151 | .current_operation | ||
152 | .drain(..) | ||
153 | .filter(|v| !v.old_val == v.val) | ||
154 | .collect::<Vec<_>>(); | ||
155 | self.undo_stack.push(op); | ||
156 | } | ||
157 | } | ||
158 | |||
179 | fn zoom_in(&mut self, p: (i32, i32)) { | 159 | fn zoom_in(&mut self, p: (i32, i32)) { |
180 | // attempt to center around cursor | 160 | // attempt to center around cursor |
181 | if let Some(p) = self.idx_at_coord(p) { | 161 | if let Some(p) = self.idx_at_coord(p) { |
@@ -192,8 +172,8 @@ impl<'ctx> AppState<'ctx> { | |||
192 | self.brush_size += 1; | 172 | self.brush_size += 1; |
193 | } | 173 | } |
194 | 174 | ||
195 | fn descrease_brush_size(&mut self) { | 175 | fn decrease_brush_size(&mut self) { |
196 | if self.brush_size > 1 { | 176 | if self.brush_size > 0 { |
197 | self.brush_size -= 1; | 177 | self.brush_size -= 1; |
198 | } | 178 | } |
199 | } | 179 | } |
@@ -296,7 +276,7 @@ impl<'ctx> AppState<'ctx> { | |||
296 | Self { | 276 | Self { |
297 | start: Point::new(60, 60), | 277 | start: Point::new(60, 60), |
298 | zoom: 5, | 278 | zoom: 5, |
299 | brush_size: 1, | 279 | brush_size: 0, |
300 | pixmap, | 280 | pixmap, |
301 | grid: Grid::new(), | 281 | grid: Grid::new(), |
302 | canvas, | 282 | canvas, |
@@ -339,12 +319,25 @@ impl<'ctx> AppState<'ctx> { | |||
339 | self.modify(|e| e.zoom_out(cursor)); | 319 | self.modify(|e| e.zoom_out(cursor)); |
340 | } | 320 | } |
341 | // brush ops | 321 | // brush ops |
342 | Keycode::Q => self.modify(|e| e.descrease_brush_size()), | 322 | Keycode::Q => self.decrease_brush_size(), |
343 | Keycode::E => self.modify(|e| e.increase_brush_size()), | 323 | Keycode::E => self.increase_brush_size(), |
344 | // flip color | 324 | // flip color |
345 | Keycode::X => self.modify(|e| e.change_active_color()), | 325 | Keycode::X => self.modify(|e| e.change_active_color()), |
346 | // toggle grid | 326 | // toggle grid |
347 | Keycode::Tab => self.modify(|e| e.toggle_grid()), | 327 | Keycode::Tab => self.modify(|e| e.toggle_grid()), |
328 | // line drawing | ||
329 | Keycode::F => self.modify(|e| { | ||
330 | let end = (mouse.x(), mouse.y()).into(); | ||
331 | if let Some(start) = e.last_point { | ||
332 | if let Ok(o) = e.paint_line(start, end) { | ||
333 | e.commit_operation(); | ||
334 | e.current_operation = | ||
335 | o.into_iter().filter(|v| !v.old_val == v.val).collect(); | ||
336 | e.commit_operation(); | ||
337 | e.last_point = Some(end); | ||
338 | } | ||
339 | } | ||
340 | }), | ||
348 | // exit | 341 | // exit |
349 | Keycode::Escape => break 'running, | 342 | Keycode::Escape => break 'running, |
350 | // undo & redo | 343 | // undo & redo |
@@ -381,9 +374,7 @@ impl<'ctx> AppState<'ctx> { | |||
381 | Event::MouseMotion { | 374 | Event::MouseMotion { |
382 | x, y, mousestate, .. | 375 | x, y, mousestate, .. |
383 | } => { | 376 | } => { |
384 | let is_left = mousestate.is_mouse_button_pressed(MouseButton::Left); | 377 | if mousestate.is_mouse_button_pressed(MouseButton::Left) { |
385 | let is_right = mousestate.is_mouse_button_pressed(MouseButton::Right); | ||
386 | if is_left { | ||
387 | self.modify(|e| { | 378 | self.modify(|e| { |
388 | let pt = (x, y); | 379 | let pt = (x, y); |
389 | let val = e.active_color; | 380 | let val = e.active_color; |
@@ -391,7 +382,7 @@ impl<'ctx> AppState<'ctx> { | |||
391 | e.current_operation.extend(o); | 382 | e.current_operation.extend(o); |
392 | } | 383 | } |
393 | }); | 384 | }); |
394 | } else if is_right { | 385 | } else if mousestate.is_mouse_button_pressed(MouseButton::Right) { |
395 | self.modify(|e| { | 386 | self.modify(|e| { |
396 | let pt = (x, y); | 387 | let pt = (x, y); |
397 | let val = !e.active_color; | 388 | let val = !e.active_color; |
diff --git a/src/bitmap.rs b/src/bitmap.rs index 01a39d9..8ff311b 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs | |||
@@ -1,4 +1,7 @@ | |||
1 | use std::convert::{From, Into, TryFrom}; | 1 | use std::{ |
2 | convert::{From, Into, TryFrom}, | ||
3 | ops::Sub, | ||
4 | }; | ||
2 | 5 | ||
3 | #[derive(Debug)] | 6 | #[derive(Debug)] |
4 | pub struct Pixmap<T> { | 7 | pub struct Pixmap<T> { |
@@ -59,6 +62,7 @@ where | |||
59 | data, | 62 | data, |
60 | } | 63 | } |
61 | } | 64 | } |
65 | |||
62 | pub fn new_with(width: u32, height: u32, start: T) -> Self { | 66 | pub fn new_with(width: u32, height: u32, start: T) -> Self { |
63 | let data = vec![start; (width * height) as usize]; | 67 | let data = vec![start; (width * height) as usize]; |
64 | Pixmap { | 68 | Pixmap { |
@@ -67,18 +71,22 @@ where | |||
67 | data, | 71 | data, |
68 | } | 72 | } |
69 | } | 73 | } |
74 | |||
70 | pub fn is_inside<P: Into<MapPoint>>(&self, pt: P) -> bool { | 75 | pub fn is_inside<P: Into<MapPoint>>(&self, pt: P) -> bool { |
71 | let MapPoint { x, y } = pt.into(); | 76 | let MapPoint { x, y } = pt.into(); |
72 | x < self.width && y < self.height | 77 | x < self.width && y < self.height |
73 | } | 78 | } |
79 | |||
74 | pub fn idx<P: Into<MapPoint>>(&self, pt: P) -> usize { | 80 | pub fn idx<P: Into<MapPoint>>(&self, pt: P) -> usize { |
75 | let MapPoint { x, y } = pt.into(); | 81 | let MapPoint { x, y } = pt.into(); |
76 | (y * self.width + x) as usize | 82 | (y * self.width + x) as usize |
77 | } | 83 | } |
84 | |||
78 | pub fn get<P: Into<MapPoint>>(&self, pt: P) -> T { | 85 | pub fn get<P: Into<MapPoint>>(&self, pt: P) -> T { |
79 | let idx = self.idx(pt); | 86 | let idx = self.idx(pt); |
80 | self.data[idx] | 87 | self.data[idx] |
81 | } | 88 | } |
89 | |||
82 | pub fn set<P: Into<MapPoint>>(&mut self, pt: P, new_val: T) -> T { | 90 | pub fn set<P: Into<MapPoint>>(&mut self, pt: P, new_val: T) -> T { |
83 | let pt = pt.into(); | 91 | let pt = pt.into(); |
84 | let old_val = self.get(pt); | 92 | let old_val = self.get(pt); |
@@ -86,4 +94,97 @@ where | |||
86 | self.data[idx] = new_val; | 94 | self.data[idx] = new_val; |
87 | old_val | 95 | old_val |
88 | } | 96 | } |
97 | |||
98 | pub fn get_circle<P: Into<MapPoint>>(&self, center: P, radius: u32) -> Vec<MapPoint> { | ||
99 | let mut circle: Vec<(i64, i64)> = vec![]; | ||
100 | let MapPoint { x, y } = center.into(); | ||
101 | let x = x as i64; | ||
102 | let y = y as i64; | ||
103 | let (mut dx, mut dy, mut err) = (radius as i64, 0i64, 1 - radius as i64); | ||
104 | while dx >= dy { | ||
105 | circle.push((x + dx, y + dy)); | ||
106 | circle.push((x - dx, y + dy)); | ||
107 | circle.push((x + dx, y - dy)); | ||
108 | circle.push((x - dx, y - dy)); | ||
109 | circle.push((x + dy, y + dx)); | ||
110 | circle.push((x - dy, y + dx)); | ||
111 | circle.push((x + dy, y - dx)); | ||
112 | circle.push((x - dy, y - dx)); | ||
113 | dy = dy + 1; | ||
114 | if err < 0 { | ||
115 | err = err + 2 * dy + 1; | ||
116 | } else { | ||
117 | dx -= 1; | ||
118 | err += 2 * (dy - dx) + 1; | ||
119 | } | ||
120 | } | ||
121 | for xi in 0..radius as i64 { | ||
122 | for yi in 0..radius as i64 { | ||
123 | if xi.pow(2) + yi.pow(2) < (radius as i64).pow(2) { | ||
124 | circle.push((x + xi, y + yi)); | ||
125 | circle.push((x - xi, y + yi)); | ||
126 | circle.push((x + xi, y - yi)); | ||
127 | circle.push((x - xi, y - yi)); | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | circle | ||
132 | .into_iter() | ||
133 | .flat_map(|pt| MapPoint::try_from(pt)) | ||
134 | .filter(|&pt| self.is_inside(pt)) | ||
135 | .collect() | ||
136 | } | ||
137 | |||
138 | pub fn get_line<P: Into<MapPoint>>(&self, start: P, end: P) -> Vec<MapPoint> { | ||
139 | let MapPoint { x: x1, y: y1 } = start.into(); | ||
140 | let MapPoint { x: x2, y: y2 } = end.into(); | ||
141 | let mut coordinates = vec![]; | ||
142 | let dx = abs_difference(x1, x2) as i64; | ||
143 | let dy = abs_difference(y1, y2) as i64; | ||
144 | let sx = { | ||
145 | if x1 < x2 { | ||
146 | 1 | ||
147 | } else { | ||
148 | -1 | ||
149 | } | ||
150 | }; | ||
151 | let sy = { | ||
152 | if y1 < y2 { | ||
153 | 1 | ||
154 | } else { | ||
155 | -1 | ||
156 | } | ||
157 | }; | ||
158 | let mut err: i64 = dx - dy; | ||
159 | let mut current_x = x1 as i64; | ||
160 | let mut current_y = y1 as i64; | ||
161 | loop { | ||
162 | coordinates.push((current_x, current_y)); | ||
163 | if current_x == x2 as i64 && current_y == y2 as i64 { | ||
164 | break; | ||
165 | } | ||
166 | let e2 = 2 * err; | ||
167 | if e2 > -dy { | ||
168 | err -= dy; | ||
169 | current_x += sx; | ||
170 | } | ||
171 | if e2 < dx { | ||
172 | err += dx; | ||
173 | current_y += sy; | ||
174 | } | ||
175 | } | ||
176 | coordinates | ||
177 | .into_iter() | ||
178 | .flat_map(|pt| MapPoint::try_from(pt)) | ||
179 | .filter(|&pt| self.is_inside(pt)) | ||
180 | .collect() | ||
181 | } | ||
182 | } | ||
183 | |||
184 | fn abs_difference<T: Sub<Output = T> + Ord>(x: T, y: T) -> T { | ||
185 | if x < y { | ||
186 | y - x | ||
187 | } else { | ||
188 | x - y | ||
189 | } | ||
89 | } | 190 | } |
diff --git a/src/undo.rs b/src/undo.rs index b590312..ae96a66 100644 --- a/src/undo.rs +++ b/src/undo.rs | |||
@@ -1,14 +1,16 @@ | |||
1 | use crate::bitmap::MapPoint; | ||
2 | |||
1 | #[derive(Copy, Clone, Debug)] | 3 | #[derive(Copy, Clone, Debug)] |
2 | pub struct ModifyRecord { | 4 | pub struct ModifyRecord { |
3 | pub point: (u32, u32), | 5 | pub point: MapPoint, |
4 | pub old_val: bool, | 6 | pub old_val: bool, |
5 | pub val: bool, | 7 | pub val: bool, |
6 | } | 8 | } |
7 | 9 | ||
8 | impl ModifyRecord { | 10 | impl ModifyRecord { |
9 | pub fn new(point: (u32, u32), old_val: bool, val: bool) -> Self { | 11 | pub fn new<P: Into<MapPoint>>(point: P, old_val: bool, val: bool) -> Self { |
10 | ModifyRecord { | 12 | ModifyRecord { |
11 | point, | 13 | point: point.into(), |
12 | old_val, | 14 | old_val, |
13 | val, | 15 | val, |
14 | } | 16 | } |