gardesk/garshot / 603e127

Browse files

annotate: add arrow tool

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
603e127b64a6a390a6bed0766be94fa8fe1d82bd
Parents
3eb67f9
Tree
dd29272

1 changed file

StatusFile+-
A garshot/src/annotate/tools/arrow.rs 136 0
garshot/src/annotate/tools/arrow.rsadded
@@ -0,0 +1,136 @@
1
+//! Arrow tool - draw lines with arrowheads.
2
+
3
+use super::Tool;
4
+use crate::annotate::state::ToolProperties;
5
+use gartk_core::{Color, InputEvent, MouseButton, Point};
6
+use gartk_render::cairo::Context;
7
+use gartk_render::{line as draw_line, set_color};
8
+use gartk_x11::CursorShape;
9
+use std::f64::consts::PI;
10
+
11
+/// Arrow drawing tool.
12
+pub struct ArrowTool {
13
+    /// Starting point of the arrow (tail).
14
+    start: Option<Point>,
15
+    /// Ending point of the arrow (head).
16
+    end: Option<Point>,
17
+    /// Whether we're currently drawing.
18
+    drawing: bool,
19
+}
20
+
21
+impl ArrowTool {
22
+    /// Create a new arrow tool.
23
+    pub fn new() -> Self {
24
+        Self {
25
+            start: None,
26
+            end: None,
27
+            drawing: false,
28
+        }
29
+    }
30
+
31
+    /// Draw an arrowhead at the given point.
32
+    fn draw_arrowhead(ctx: &Context, tip: Point, angle: f64, size: f64, color: Color) {
33
+        let wing_angle = PI / 6.0; // 30 degrees
34
+
35
+        // Calculate wing points
36
+        let wing1_angle = angle + PI - wing_angle;
37
+        let wing2_angle = angle + PI + wing_angle;
38
+
39
+        let wing1 = Point::new(
40
+            tip.x + (size * wing1_angle.cos()) as i32,
41
+            tip.y + (size * wing1_angle.sin()) as i32,
42
+        );
43
+        let wing2 = Point::new(
44
+            tip.x + (size * wing2_angle.cos()) as i32,
45
+            tip.y + (size * wing2_angle.sin()) as i32,
46
+        );
47
+
48
+        // Draw filled triangle
49
+        set_color(ctx, color);
50
+        ctx.new_path();
51
+        ctx.move_to(tip.x as f64, tip.y as f64);
52
+        ctx.line_to(wing1.x as f64, wing1.y as f64);
53
+        ctx.line_to(wing2.x as f64, wing2.y as f64);
54
+        ctx.close_path();
55
+        let _ = ctx.fill();
56
+    }
57
+}
58
+
59
+impl Default for ArrowTool {
60
+    fn default() -> Self {
61
+        Self::new()
62
+    }
63
+}
64
+
65
+impl Tool for ArrowTool {
66
+    fn handle_event(&mut self, event: &InputEvent, _props: &ToolProperties) -> bool {
67
+        match event {
68
+            InputEvent::MousePress(e) if e.button == Some(MouseButton::Left) => {
69
+                self.start = Some(e.position);
70
+                self.end = Some(e.position);
71
+                self.drawing = true;
72
+                true
73
+            }
74
+            InputEvent::MouseMove(e) if self.drawing => {
75
+                self.end = Some(e.position);
76
+                true
77
+            }
78
+            InputEvent::MouseRelease(e) if e.button == Some(MouseButton::Left) && self.drawing => {
79
+                self.end = Some(e.position);
80
+                self.drawing = false;
81
+                true
82
+            }
83
+            _ => false,
84
+        }
85
+    }
86
+
87
+    fn draw_preview(&self, ctx: &Context, props: &ToolProperties) {
88
+        if let (Some(start), Some(end)) = (self.start, self.end) {
89
+            // Draw the line
90
+            draw_line(
91
+                ctx,
92
+                start.x as f64,
93
+                start.y as f64,
94
+                end.x as f64,
95
+                end.y as f64,
96
+                props.color,
97
+                props.line_width,
98
+            );
99
+
100
+            // Calculate arrow angle
101
+            let dx = (end.x - start.x) as f64;
102
+            let dy = (end.y - start.y) as f64;
103
+            let angle = dy.atan2(dx);
104
+
105
+            // Draw arrowhead (size proportional to line width)
106
+            let head_size = props.line_width * 4.0;
107
+            Self::draw_arrowhead(ctx, end, angle, head_size, props.color);
108
+        }
109
+    }
110
+
111
+    fn commit(&self, ctx: &Context, props: &ToolProperties) {
112
+        self.draw_preview(ctx, props);
113
+    }
114
+
115
+    fn reset(&mut self) {
116
+        self.start = None;
117
+        self.end = None;
118
+        self.drawing = false;
119
+    }
120
+
121
+    fn cursor(&self) -> CursorShape {
122
+        CursorShape::Crosshair
123
+    }
124
+
125
+    fn is_drawing(&self) -> bool {
126
+        self.drawing
127
+    }
128
+
129
+    fn can_commit(&self) -> bool {
130
+        if let (Some(start), Some(end)) = (self.start, self.end) {
131
+            start.x != end.x || start.y != end.y
132
+        } else {
133
+            false
134
+        }
135
+    }
136
+}