gardesk/garshot / 65e71e3

Browse files

annotate: add rectangle tool

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
65e71e373ec4d1e964a126b9dd273a2ea10d438e
Parents
332733e
Tree
1183a0c

1 changed file

StatusFile+-
A garshot/src/annotate/tools/rectangle.rs 106 0
garshot/src/annotate/tools/rectangle.rsadded
@@ -0,0 +1,106 @@
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
+}