diff options
| author | Akshay <[email protected]> | 2021-04-11 11:01:18 +0100 |
|---|---|---|
| committer | Akshay <[email protected]> | 2021-04-11 11:01:18 +0100 |
| commit | b0685c1638044b85dc7e8b07555a7b639b54d69a (patch) | |
| tree | 3ba4f28a1a8f4c1b409df2d4f5082056ece2f8e6 /src | |
| parent | 8d5fecc23f4e986c74295c58473c6ea8d840d955 (diff) | |
add rect select brush and keybinds
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 176 | ||||
| -rw-r--r-- | src/bitmap.rs | 2 | ||||
| -rw-r--r-- | src/brush.rs | 19 | ||||
| -rw-r--r-- | src/utils.rs | 16 |
4 files changed, 163 insertions, 50 deletions
| @@ -1,6 +1,6 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | bitmap::{positive_angle_with_x, Axis, MapPoint, Pixmap}, | 2 | bitmap::{positive_angle_with_x, abs_difference, Axis, MapPoint, Pixmap}, |
| 3 | brush::{Brush, CircleBrush, LineBrush}, | 3 | brush::{Brush, CircleBrush, LineBrush, RectSelectBrush}, |
| 4 | cache::Cache, | 4 | cache::Cache, |
| 5 | command::CommandBox, | 5 | command::CommandBox, |
| 6 | consts::{colors::*, ANGLE, FONT_PATH, RC_PATH, STDLIB_PATH}, | 6 | consts::{colors::*, ANGLE, FONT_PATH, RC_PATH, STDLIB_PATH}, |
| @@ -152,6 +152,7 @@ impl<'ctx> AppState<'ctx> { | |||
| 152 | Brush::Circle(_) => Brush::line(0, false), | 152 | Brush::Circle(_) => Brush::line(0, false), |
| 153 | Brush::Line(LineBrush { extend: false, .. }) => Brush::line(0, true), | 153 | Brush::Line(LineBrush { extend: false, .. }) => Brush::line(0, true), |
| 154 | Brush::Line(LineBrush { extend: true, .. }) => Brush::Fill, | 154 | Brush::Line(LineBrush { extend: true, .. }) => Brush::Fill, |
| 155 | Brush::Fill => Brush::rect(), | ||
| 155 | _ => Brush::new(0), | 156 | _ => Brush::new(0), |
| 156 | } | 157 | } |
| 157 | } | 158 | } |
| @@ -444,34 +445,54 @@ impl<'ctx> AppState<'ctx> { | |||
| 444 | } | 445 | } |
| 445 | } | 446 | } |
| 446 | } | 447 | } |
| 447 | if let Brush::Line(LineBrush { start, size, .. }) = self.brush { | 448 | match self.brush { |
| 448 | let size = self.zoom as u32 * (size as u32 + 5); | 449 | Brush::Line(LineBrush { start, size, .. }) => { |
| 449 | if let (Some(from), Some(to)) = (start, pt) { | 450 | let size = self.zoom as u32 * (size as u32 + 5); |
| 450 | let line = self.pixmap.get_line(from, to.into()); | 451 | if let (Some(from), Some(to)) = (start, pt) { |
| 451 | let angle = positive_angle_with_x(from, to.into()); | 452 | let line = self.pixmap.get_line(from, to.into()); |
| 453 | let angle = positive_angle_with_x(from, to.into()); | ||
| 454 | draw_text( | ||
| 455 | &mut self.canvas, | ||
| 456 | self.ttf_context, | ||
| 457 | format!( | ||
| 458 | "{:.width$}°", | ||
| 459 | angle, | ||
| 460 | width = if (angle - ANGLE).abs() < 1e-3 { 3 } else { 0 } | ||
| 461 | ), | ||
| 462 | PINK, | ||
| 463 | (self.mouse.0 + size as i32, self.mouse.1 + size as i32), | ||
| 464 | ); | ||
| 465 | for MapPoint { x, y } in line.into_iter() { | ||
| 466 | self.canvas.set_draw_color(PINK); | ||
| 467 | self.canvas | ||
| 468 | .fill_rect(Rect::new( | ||
| 469 | x as i32 * cs as i32 + self.start.x(), | ||
| 470 | y as i32 * cs as i32 + self.start.y(), | ||
| 471 | cs, | ||
| 472 | cs, | ||
| 473 | )) | ||
| 474 | .unwrap(); | ||
| 475 | } | ||
| 476 | } | ||
| 477 | } | ||
| 478 | Brush::RectSelect(RectSelectBrush { start: Some(s), end: Some(e), active_end }) => { | ||
| 479 | self.canvas.set_draw_color(PINK); | ||
| 480 | let (width, height) = (abs_difference(s.x, e.x), abs_difference(s.y, e.y)); | ||
| 481 | let MapPoint{x: start_x, y: start_y} = utils::rect_coords(s, e).0; | ||
| 482 | let start_loc_x = self.start.x() + (start_x * cs) as i32; | ||
| 483 | let start_loc_y = self.start.y() + (start_y * cs) as i32; | ||
| 452 | draw_text( | 484 | draw_text( |
| 453 | &mut self.canvas, | 485 | &mut self.canvas, |
| 454 | self.ttf_context, | 486 | self.ttf_context, |
| 455 | format!( | 487 | format!("{}x{}", width, height), |
| 456 | "{:.width$}°", | ||
| 457 | angle, | ||
| 458 | width = if (angle - ANGLE).abs() < 1e-3 { 3 } else { 0 } | ||
| 459 | ), | ||
| 460 | PINK, | 488 | PINK, |
| 461 | (self.mouse.0 + size as i32, self.mouse.1 + size as i32), | 489 | (start_loc_x, start_loc_y - 20), |
| 462 | ); | 490 | ); |
| 463 | for MapPoint { x, y } in line.into_iter() { | 491 | self.canvas.draw_rect( |
| 464 | self.canvas.set_draw_color(PINK); | 492 | rect!(start_loc_x, start_loc_y, width * cs, height * cs) |
| 465 | self.canvas | 493 | ).unwrap(); |
| 466 | .fill_rect(Rect::new( | ||
| 467 | x as i32 * cs as i32 + self.start.x(), | ||
| 468 | y as i32 * cs as i32 + self.start.y(), | ||
| 469 | cs, | ||
| 470 | cs, | ||
| 471 | )) | ||
| 472 | .unwrap(); | ||
| 473 | } | ||
| 474 | } | 494 | } |
| 495 | _ => () | ||
| 475 | } | 496 | } |
| 476 | } | 497 | } |
| 477 | 498 | ||
| @@ -699,27 +720,37 @@ impl<'ctx> AppState<'ctx> { | |||
| 699 | self.pixmap.invert(); | 720 | self.pixmap.invert(); |
| 700 | self.undo_stack.push(ModifyRecord::Invert); | 721 | self.undo_stack.push(ModifyRecord::Invert); |
| 701 | } | 722 | } |
| 723 | // cycle through brushes | ||
| 702 | Keycode::F => self.cycle_brush(), | 724 | Keycode::F => self.cycle_brush(), |
| 703 | // bucket fill tool | 725 | // change rect select active end |
| 726 | Keycode::O => { | ||
| 727 | match &mut self.brush { | ||
| 728 | Brush::RectSelect(RectSelectBrush { | ||
| 729 | active_end, | ||
| 730 | .. | ||
| 731 | }) => *active_end = !*active_end, | ||
| 732 | _ => (), | ||
| 733 | }; | ||
| 734 | } | ||
| 704 | Keycode::V => self.cycle_symmetry(), | 735 | Keycode::V => self.cycle_symmetry(), |
| 705 | // undo & redo | 736 | // undo |
| 706 | Keycode::U => { | 737 | Keycode::U => { |
| 707 | if let Some(op) = self.undo_stack.undo() { | 738 | if let Some(op) = self.undo_stack.undo() { |
| 708 | self.apply_operation(op, OpKind::Undo); | 739 | self.apply_operation(op, OpKind::Undo); |
| 709 | } | 740 | } |
| 710 | } | 741 | } |
| 742 | // redo | ||
| 711 | Keycode::R => { | 743 | Keycode::R => { |
| 712 | if let Some(op) = self.undo_stack.redo() { | 744 | if let Some(op) = self.undo_stack.redo() { |
| 713 | self.apply_operation(op, OpKind::Redo); | 745 | self.apply_operation(op, OpKind::Redo); |
| 714 | } | 746 | } |
| 715 | } | 747 | } |
| 716 | // export to file | 748 | Keycode::Escape => { |
| 717 | Keycode::N => { | 749 | match self.brush { |
| 718 | let image = self.export(); | 750 | Brush::RectSelect(_) => self.brush = Brush::rect(), |
| 719 | let encoded = image.encode().unwrap(); | 751 | _ => () |
| 720 | let mut buffer = File::create("test.obi").unwrap(); | 752 | } |
| 721 | eprintln!("writing to file"); | 753 | continue; |
| 722 | buffer.write_all(&encoded[..]).unwrap(); | ||
| 723 | } | 754 | } |
| 724 | _ if keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD => { | 755 | _ if keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD => { |
| 725 | self.brush = Brush::line( | 756 | self.brush = Brush::line( |
| @@ -783,6 +814,37 @@ impl<'ctx> AppState<'ctx> { | |||
| 783 | }); | 814 | }); |
| 784 | } | 815 | } |
| 785 | } | 816 | } |
| 817 | Brush::RectSelect(RectSelectBrush { | ||
| 818 | start, | ||
| 819 | end, | ||
| 820 | active_end, | ||
| 821 | }) => { | ||
| 822 | if start.is_none() { | ||
| 823 | self.brush = Brush::RectSelect(RectSelectBrush { | ||
| 824 | start: contact, | ||
| 825 | end, | ||
| 826 | active_end, | ||
| 827 | }); | ||
| 828 | } else if end.is_none() { | ||
| 829 | self.brush = Brush::RectSelect(RectSelectBrush { | ||
| 830 | start, | ||
| 831 | end: contact, | ||
| 832 | active_end, | ||
| 833 | }); | ||
| 834 | } else if active_end { | ||
| 835 | self.brush = Brush::RectSelect(RectSelectBrush { | ||
| 836 | start, | ||
| 837 | end: contact, | ||
| 838 | active_end, | ||
| 839 | }); | ||
| 840 | } else { | ||
| 841 | self.brush = Brush::RectSelect(RectSelectBrush { | ||
| 842 | start: contact, | ||
| 843 | end, | ||
| 844 | active_end, | ||
| 845 | }); | ||
| 846 | }; | ||
| 847 | } | ||
| 786 | Brush::Fill => { | 848 | Brush::Fill => { |
| 787 | if let Some(c) = contact { | 849 | if let Some(c) = contact { |
| 788 | // this `get` is unchecked because contact is checked | 850 | // this `get` is unchecked because contact is checked |
| @@ -815,22 +877,44 @@ impl<'ctx> AppState<'ctx> { | |||
| 815 | Event::MouseMotion { | 877 | Event::MouseMotion { |
| 816 | x, y, mousestate, .. | 878 | x, y, mousestate, .. |
| 817 | } => { | 879 | } => { |
| 818 | let size = match self.brush { | ||
| 819 | Brush::Circle(CircleBrush { size }) => size, | ||
| 820 | Brush::Line(LineBrush { size, .. }) => size, | ||
| 821 | _ => continue, | ||
| 822 | }; | ||
| 823 | if mousestate.is_mouse_button_pressed(MouseButton::Left) { | 880 | if mousestate.is_mouse_button_pressed(MouseButton::Left) { |
| 824 | let pt = (x, y); | 881 | match self.brush { |
| 825 | let val = self.active_color; | 882 | Brush::RectSelect(RectSelectBrush{start, end, active_end}) => { |
| 826 | if let Ok(o) = self.paint_point(pt, val, size) { | 883 | if active_end { |
| 827 | self.current_operation.extend(o); | 884 | self.brush = Brush::RectSelect(RectSelectBrush{ |
| 885 | start, | ||
| 886 | end: self.idx_at_coord((x, y)).map(MapPoint::from), | ||
| 887 | active_end | ||
| 888 | }); | ||
| 889 | } else { | ||
| 890 | self.brush = Brush::RectSelect(RectSelectBrush{ | ||
| 891 | start: self.idx_at_coord((x, y)).map(MapPoint::from), | ||
| 892 | end, | ||
| 893 | active_end | ||
| 894 | }); | ||
| 895 | } | ||
| 896 | }, | ||
| 897 | Brush::Circle(CircleBrush { size }) | ||
| 898 | | Brush::Line(LineBrush { size, .. }) => { | ||
| 899 | let pt = (x, y); | ||
| 900 | let val = self.active_color; | ||
| 901 | if let Ok(o) = self.paint_point(pt, val, size) { | ||
| 902 | self.current_operation.extend(o); | ||
| 903 | } | ||
| 904 | }, | ||
| 905 | _ => () | ||
| 828 | } | 906 | } |
| 829 | } else if mousestate.is_mouse_button_pressed(MouseButton::Right) { | 907 | } else if mousestate.is_mouse_button_pressed(MouseButton::Right) { |
| 830 | let pt = (x, y); | 908 | match self.brush { |
| 831 | let val = !self.active_color; | 909 | Brush::Circle(CircleBrush { size }) |
| 832 | if let Ok(o) = self.paint_point(pt, val, size) { | 910 | | Brush::Line(LineBrush { size, .. }) => { |
| 833 | self.current_operation.extend(o); | 911 | let pt = (x, y); |
| 912 | let val = !self.active_color; | ||
| 913 | if let Ok(o) = self.paint_point(pt, val, size) { | ||
| 914 | self.current_operation.extend(o); | ||
| 915 | } | ||
| 916 | }, | ||
| 917 | _ => () | ||
| 834 | } | 918 | } |
| 835 | } | 919 | } |
| 836 | } | 920 | } |
diff --git a/src/bitmap.rs b/src/bitmap.rs index ba5c8f1..0b1754a 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs | |||
| @@ -306,7 +306,7 @@ impl Pixmap<bool> { | |||
| 306 | } | 306 | } |
| 307 | } | 307 | } |
| 308 | 308 | ||
| 309 | fn abs_difference<T: Sub<Output = T> + Ord>(x: T, y: T) -> T { | 309 | pub fn abs_difference<T: Sub<Output = T> + Ord>(x: T, y: T) -> T { |
| 310 | if x < y { | 310 | if x < y { |
| 311 | y - x | 311 | y - x |
| 312 | } else { | 312 | } else { |
diff --git a/src/brush.rs b/src/brush.rs index 8557ba7..8d3ee1c 100644 --- a/src/brush.rs +++ b/src/brush.rs | |||
| @@ -25,8 +25,9 @@ pub struct CircleBrush { | |||
| 25 | 25 | ||
| 26 | #[derive(Debug, Copy, Clone)] | 26 | #[derive(Debug, Copy, Clone)] |
| 27 | pub struct RectSelectBrush { | 27 | pub struct RectSelectBrush { |
| 28 | pub start: MapPoint, | 28 | pub start: Option<MapPoint>, |
| 29 | pub end: MapPoint, | 29 | pub end: Option<MapPoint>, |
| 30 | pub active_end: bool, | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | impl Brush { | 33 | impl Brush { |
| @@ -60,8 +61,20 @@ impl Brush { | |||
| 60 | }) | 61 | }) |
| 61 | } | 62 | } |
| 62 | 63 | ||
| 64 | pub fn rect() -> Self { | ||
| 65 | Brush::RectSelect(RectSelectBrush { | ||
| 66 | start: None, | ||
| 67 | end: None, | ||
| 68 | active_end: true, | ||
| 69 | }) | ||
| 70 | } | ||
| 71 | |||
| 63 | pub fn is_line(&self) -> bool { | 72 | pub fn is_line(&self) -> bool { |
| 64 | matches!(self, Self::Line { .. }) | 73 | matches!(self, Self::Line(_)) |
| 74 | } | ||
| 75 | |||
| 76 | pub fn is_rect(&self) -> bool { | ||
| 77 | matches!(self, Self::RectSelect(_)) | ||
| 65 | } | 78 | } |
| 66 | 79 | ||
| 67 | pub fn size(&self) -> Option<u8> { | 80 | pub fn size(&self) -> Option<u8> { |
diff --git a/src/utils.rs b/src/utils.rs index 8c3b144..0825c8c 100644 --- a/src/utils.rs +++ b/src/utils.rs | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | app::AppState, | 2 | app::AppState, |
| 3 | consts::FONT_PATH, | 3 | consts::FONT_PATH, |
| 4 | bitmap::{abs_difference, MapPoint}, | ||
| 4 | lisp::{ | 5 | lisp::{ |
| 5 | error::{EvalError, LispError, ParseError}, | 6 | error::{EvalError, LispError, ParseError}, |
| 6 | eval::Evaluator, | 7 | eval::Evaluator, |
| @@ -132,6 +133,21 @@ pub fn compress<T: PartialEq + Clone>(scanline: &[T]) -> Vec<(T, usize)> { | |||
| 132 | runs | 133 | runs |
| 133 | } | 134 | } |
| 134 | 135 | ||
| 136 | pub fn rect_coords(s: MapPoint, e: MapPoint) -> (MapPoint, MapPoint) { | ||
| 137 | let (width, height) = (abs_difference(s.x, e.x), abs_difference(s.y, e.y)); | ||
| 138 | let start_loc = if e.x >= s.x && e.y >= s.y { | ||
| 139 | (s.x, s.y) | ||
| 140 | } else if e.x >= s.x && e.y < s.y { | ||
| 141 | (s.x, e.y) | ||
| 142 | } else if e.x < s.x && e.y >= s.y { | ||
| 143 | (e.x, s.y) | ||
| 144 | } else { | ||
| 145 | (e.x, e.y) | ||
| 146 | }.into(); | ||
| 147 | let end_loc = start_loc + (width, height).into(); | ||
| 148 | (start_loc, end_loc) | ||
| 149 | } | ||
| 150 | |||
| 135 | #[cfg(test)] | 151 | #[cfg(test)] |
| 136 | mod tests { | 152 | mod tests { |
| 137 | use super::compress; | 153 | use super::compress; |
