From b0685c1638044b85dc7e8b07555a7b639b54d69a Mon Sep 17 00:00:00 2001 From: Akshay Date: Sun, 11 Apr 2021 15:31:18 +0530 Subject: add rect select brush and keybinds --- src/app.rs | 176 +++++++++++++++++++++++++++++++++++++++++++--------------- src/bitmap.rs | 2 +- src/brush.rs | 19 ++++++- src/utils.rs | 16 ++++++ 4 files changed, 163 insertions(+), 50 deletions(-) diff --git a/src/app.rs b/src/app.rs index e8c5260..83e30e8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use crate::{ - bitmap::{positive_angle_with_x, Axis, MapPoint, Pixmap}, - brush::{Brush, CircleBrush, LineBrush}, + bitmap::{positive_angle_with_x, abs_difference, Axis, MapPoint, Pixmap}, + brush::{Brush, CircleBrush, LineBrush, RectSelectBrush}, cache::Cache, command::CommandBox, consts::{colors::*, ANGLE, FONT_PATH, RC_PATH, STDLIB_PATH}, @@ -152,6 +152,7 @@ impl<'ctx> AppState<'ctx> { Brush::Circle(_) => Brush::line(0, false), Brush::Line(LineBrush { extend: false, .. }) => Brush::line(0, true), Brush::Line(LineBrush { extend: true, .. }) => Brush::Fill, + Brush::Fill => Brush::rect(), _ => Brush::new(0), } } @@ -444,34 +445,54 @@ impl<'ctx> AppState<'ctx> { } } } - if let Brush::Line(LineBrush { start, size, .. }) = self.brush { - let size = self.zoom as u32 * (size as u32 + 5); - if let (Some(from), Some(to)) = (start, pt) { - let line = self.pixmap.get_line(from, to.into()); - let angle = positive_angle_with_x(from, to.into()); + match self.brush { + Brush::Line(LineBrush { start, size, .. }) => { + let size = self.zoom as u32 * (size as u32 + 5); + if let (Some(from), Some(to)) = (start, pt) { + let line = self.pixmap.get_line(from, to.into()); + let angle = positive_angle_with_x(from, to.into()); + draw_text( + &mut self.canvas, + self.ttf_context, + format!( + "{:.width$}°", + angle, + width = if (angle - ANGLE).abs() < 1e-3 { 3 } else { 0 } + ), + PINK, + (self.mouse.0 + size as i32, self.mouse.1 + size as i32), + ); + for MapPoint { x, y } in line.into_iter() { + self.canvas.set_draw_color(PINK); + self.canvas + .fill_rect(Rect::new( + x as i32 * cs as i32 + self.start.x(), + y as i32 * cs as i32 + self.start.y(), + cs, + cs, + )) + .unwrap(); + } + } + } + Brush::RectSelect(RectSelectBrush { start: Some(s), end: Some(e), active_end }) => { + self.canvas.set_draw_color(PINK); + let (width, height) = (abs_difference(s.x, e.x), abs_difference(s.y, e.y)); + let MapPoint{x: start_x, y: start_y} = utils::rect_coords(s, e).0; + let start_loc_x = self.start.x() + (start_x * cs) as i32; + let start_loc_y = self.start.y() + (start_y * cs) as i32; draw_text( &mut self.canvas, self.ttf_context, - format!( - "{:.width$}°", - angle, - width = if (angle - ANGLE).abs() < 1e-3 { 3 } else { 0 } - ), + format!("{}x{}", width, height), PINK, - (self.mouse.0 + size as i32, self.mouse.1 + size as i32), + (start_loc_x, start_loc_y - 20), ); - for MapPoint { x, y } in line.into_iter() { - self.canvas.set_draw_color(PINK); - self.canvas - .fill_rect(Rect::new( - x as i32 * cs as i32 + self.start.x(), - y as i32 * cs as i32 + self.start.y(), - cs, - cs, - )) - .unwrap(); - } + self.canvas.draw_rect( + rect!(start_loc_x, start_loc_y, width * cs, height * cs) + ).unwrap(); } + _ => () } } @@ -699,27 +720,37 @@ impl<'ctx> AppState<'ctx> { self.pixmap.invert(); self.undo_stack.push(ModifyRecord::Invert); } + // cycle through brushes Keycode::F => self.cycle_brush(), - // bucket fill tool + // change rect select active end + Keycode::O => { + match &mut self.brush { + Brush::RectSelect(RectSelectBrush { + active_end, + .. + }) => *active_end = !*active_end, + _ => (), + }; + } Keycode::V => self.cycle_symmetry(), - // undo & redo + // undo Keycode::U => { if let Some(op) = self.undo_stack.undo() { self.apply_operation(op, OpKind::Undo); } } + // redo Keycode::R => { if let Some(op) = self.undo_stack.redo() { self.apply_operation(op, OpKind::Redo); } } - // export to file - Keycode::N => { - let image = self.export(); - let encoded = image.encode().unwrap(); - let mut buffer = File::create("test.obi").unwrap(); - eprintln!("writing to file"); - buffer.write_all(&encoded[..]).unwrap(); + Keycode::Escape => { + match self.brush { + Brush::RectSelect(_) => self.brush = Brush::rect(), + _ => () + } + continue; } _ if keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD => { self.brush = Brush::line( @@ -783,6 +814,37 @@ impl<'ctx> AppState<'ctx> { }); } } + Brush::RectSelect(RectSelectBrush { + start, + end, + active_end, + }) => { + if start.is_none() { + self.brush = Brush::RectSelect(RectSelectBrush { + start: contact, + end, + active_end, + }); + } else if end.is_none() { + self.brush = Brush::RectSelect(RectSelectBrush { + start, + end: contact, + active_end, + }); + } else if active_end { + self.brush = Brush::RectSelect(RectSelectBrush { + start, + end: contact, + active_end, + }); + } else { + self.brush = Brush::RectSelect(RectSelectBrush { + start: contact, + end, + active_end, + }); + }; + } Brush::Fill => { if let Some(c) = contact { // this `get` is unchecked because contact is checked @@ -815,22 +877,44 @@ impl<'ctx> AppState<'ctx> { Event::MouseMotion { x, y, mousestate, .. } => { - let size = match self.brush { - Brush::Circle(CircleBrush { size }) => size, - Brush::Line(LineBrush { size, .. }) => size, - _ => continue, - }; if mousestate.is_mouse_button_pressed(MouseButton::Left) { - let pt = (x, y); - let val = self.active_color; - if let Ok(o) = self.paint_point(pt, val, size) { - self.current_operation.extend(o); + match self.brush { + Brush::RectSelect(RectSelectBrush{start, end, active_end}) => { + if active_end { + self.brush = Brush::RectSelect(RectSelectBrush{ + start, + end: self.idx_at_coord((x, y)).map(MapPoint::from), + active_end + }); + } else { + self.brush = Brush::RectSelect(RectSelectBrush{ + start: self.idx_at_coord((x, y)).map(MapPoint::from), + end, + active_end + }); + } + }, + Brush::Circle(CircleBrush { size }) + | Brush::Line(LineBrush { size, .. }) => { + let pt = (x, y); + let val = self.active_color; + if let Ok(o) = self.paint_point(pt, val, size) { + self.current_operation.extend(o); + } + }, + _ => () } } else if mousestate.is_mouse_button_pressed(MouseButton::Right) { - let pt = (x, y); - let val = !self.active_color; - if let Ok(o) = self.paint_point(pt, val, size) { - self.current_operation.extend(o); + match self.brush { + Brush::Circle(CircleBrush { size }) + | Brush::Line(LineBrush { size, .. }) => { + let pt = (x, y); + let val = !self.active_color; + if let Ok(o) = self.paint_point(pt, val, size) { + self.current_operation.extend(o); + } + }, + _ => () } } } 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 { } } -fn abs_difference + Ord>(x: T, y: T) -> T { +pub fn abs_difference + Ord>(x: T, y: T) -> T { if x < y { y - x } 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 { #[derive(Debug, Copy, Clone)] pub struct RectSelectBrush { - pub start: MapPoint, - pub end: MapPoint, + pub start: Option, + pub end: Option, + pub active_end: bool, } impl Brush { @@ -60,8 +61,20 @@ impl Brush { }) } + pub fn rect() -> Self { + Brush::RectSelect(RectSelectBrush { + start: None, + end: None, + active_end: true, + }) + } + pub fn is_line(&self) -> bool { - matches!(self, Self::Line { .. }) + matches!(self, Self::Line(_)) + } + + pub fn is_rect(&self) -> bool { + matches!(self, Self::RectSelect(_)) } pub fn size(&self) -> Option { 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 @@ use crate::{ app::AppState, consts::FONT_PATH, + bitmap::{abs_difference, MapPoint}, lisp::{ error::{EvalError, LispError, ParseError}, eval::Evaluator, @@ -132,6 +133,21 @@ pub fn compress(scanline: &[T]) -> Vec<(T, usize)> { runs } +pub fn rect_coords(s: MapPoint, e: MapPoint) -> (MapPoint, MapPoint) { + let (width, height) = (abs_difference(s.x, e.x), abs_difference(s.y, e.y)); + let start_loc = if e.x >= s.x && e.y >= s.y { + (s.x, s.y) + } else if e.x >= s.x && e.y < s.y { + (s.x, e.y) + } else if e.x < s.x && e.y >= s.y { + (e.x, s.y) + } else { + (e.x, e.y) + }.into(); + let end_loc = start_loc + (width, height).into(); + (start_loc, end_loc) +} + #[cfg(test)] mod tests { use super::compress; -- cgit v1.2.3