Rust · 2833 bytes Raw Blame History
1 //! Rectangle tool - draw rectangles.
2
3 use super::Tool;
4 use crate::annotate::state::ToolProperties;
5 use gartk_core::{InputEvent, MouseButton, Point, Rect};
6 use gartk_render::cairo::Context;
7 use gartk_render::{fill_rect, stroke_rect};
8 use gartk_x11::CursorShape;
9
10 /// Rectangle drawing tool.
11 pub struct RectangleTool {
12 /// Starting corner of the rectangle.
13 start: Option<Point>,
14 /// Opposite corner of the rectangle.
15 end: Option<Point>,
16 /// Whether we're currently drawing.
17 drawing: bool,
18 }
19
20 impl RectangleTool {
21 /// Create a new rectangle tool.
22 pub fn new() -> Self {
23 Self {
24 start: None,
25 end: None,
26 drawing: false,
27 }
28 }
29
30 /// Calculate normalized rectangle from start and end points.
31 fn calculate_rect(&self) -> Option<Rect> {
32 let (start, end) = (self.start?, self.end?);
33
34 let x = start.x.min(end.x);
35 let y = start.y.min(end.y);
36 let width = (end.x - start.x).unsigned_abs();
37 let height = (end.y - start.y).unsigned_abs();
38
39 if width > 0 && height > 0 {
40 Some(Rect::new(x, y, width, height))
41 } else {
42 None
43 }
44 }
45 }
46
47 impl Default for RectangleTool {
48 fn default() -> Self {
49 Self::new()
50 }
51 }
52
53 impl Tool for RectangleTool {
54 fn handle_event(&mut self, event: &InputEvent, _props: &ToolProperties) -> bool {
55 match event {
56 InputEvent::MousePress(e) if e.button == Some(MouseButton::Left) => {
57 self.start = Some(e.position);
58 self.end = Some(e.position);
59 self.drawing = true;
60 true
61 }
62 InputEvent::MouseMove(e) if self.drawing => {
63 self.end = Some(e.position);
64 true
65 }
66 InputEvent::MouseRelease(e) if e.button == Some(MouseButton::Left) && self.drawing => {
67 self.end = Some(e.position);
68 self.drawing = false;
69 true
70 }
71 _ => false,
72 }
73 }
74
75 fn draw_preview(&self, ctx: &Context, props: &ToolProperties) {
76 if let Some(rect) = self.calculate_rect() {
77 if props.fill {
78 fill_rect(ctx, rect, props.color);
79 } else {
80 stroke_rect(ctx, rect, props.color, props.line_width);
81 }
82 }
83 }
84
85 fn commit(&self, ctx: &Context, props: &ToolProperties) {
86 self.draw_preview(ctx, props);
87 }
88
89 fn reset(&mut self) {
90 self.start = None;
91 self.end = None;
92 self.drawing = false;
93 }
94
95 fn cursor(&self) -> CursorShape {
96 CursorShape::Crosshair
97 }
98
99 fn is_drawing(&self) -> bool {
100 self.drawing
101 }
102
103 fn can_commit(&self) -> bool {
104 self.calculate_rect().is_some()
105 }
106 }
107