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 | } |
