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 | |
parent | 8d5fecc23f4e986c74295c58473c6ea8d840d955 (diff) |
add rect select brush and keybinds
-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; |