gardesk/garshot / b9ae3d1

Browse files

annotate: add ellipse tool

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
b9ae3d122dc12062b684a4525dee735d1febd66c
Parents
65e71e3
Tree
ab6629f

1 changed file

StatusFile+-
A garshot/src/annotate/tools/ellipse.rs 128 0
garshot/src/annotate/tools/ellipse.rsadded
@@ -0,0 +1,128 @@
1
+//! Ellipse tool - draw circles and ellipses.
2
+
3
+use super::Tool;
4
+use crate::annotate::state::ToolProperties;
5
+use gartk_core::{InputEvent, MouseButton, Point};
6
+use gartk_render::cairo::Context;
7
+use gartk_render::set_color;
8
+use gartk_x11::CursorShape;
9
+use std::f64::consts::PI;
10
+
11
+/// Ellipse drawing tool.
12
+pub struct EllipseTool {
13
+    /// Center or first corner of bounding box.
14
+    start: Option<Point>,
15
+    /// Edge point or opposite corner.
16
+    end: Option<Point>,
17
+    /// Whether we're currently drawing.
18
+    drawing: bool,
19
+}
20
+
21
+impl EllipseTool {
22
+    /// Create a new ellipse tool.
23
+    pub fn new() -> Self {
24
+        Self {
25
+            start: None,
26
+            end: None,
27
+            drawing: false,
28
+        }
29
+    }
30
+
31
+    /// Calculate ellipse parameters from start and end points.
32
+    /// Returns (center_x, center_y, radius_x, radius_y).
33
+    fn calculate_ellipse(&self) -> Option<(f64, f64, f64, f64)> {
34
+        let (start, end) = (self.start?, self.end?);
35
+
36
+        // Bounding box mode: start and end are opposite corners
37
+        let cx = (start.x + end.x) as f64 / 2.0;
38
+        let cy = (start.y + end.y) as f64 / 2.0;
39
+        let rx = ((end.x - start.x) as f64 / 2.0).abs();
40
+        let ry = ((end.y - start.y) as f64 / 2.0).abs();
41
+
42
+        if rx > 0.0 && ry > 0.0 {
43
+            Some((cx, cy, rx, ry))
44
+        } else {
45
+            None
46
+        }
47
+    }
48
+
49
+    /// Draw an ellipse using Cairo.
50
+    fn draw_ellipse(&self, ctx: &Context, props: &ToolProperties) {
51
+        if let Some((cx, cy, rx, ry)) = self.calculate_ellipse() {
52
+            // Use scaling to draw ellipse from circle
53
+            ctx.save().ok();
54
+            ctx.translate(cx, cy);
55
+            ctx.scale(rx, ry);
56
+
57
+            ctx.new_path();
58
+            ctx.arc(0.0, 0.0, 1.0, 0.0, 2.0 * PI);
59
+
60
+            ctx.restore().ok();
61
+
62
+            set_color(ctx, props.color);
63
+
64
+            if props.fill {
65
+                let _ = ctx.fill();
66
+            } else {
67
+                // Scale line width to maintain consistent appearance
68
+                ctx.set_line_width(props.line_width);
69
+                let _ = ctx.stroke();
70
+            }
71
+        }
72
+    }
73
+}
74
+
75
+impl Default for EllipseTool {
76
+    fn default() -> Self {
77
+        Self::new()
78
+    }
79
+}
80
+
81
+impl Tool for EllipseTool {
82
+    fn handle_event(&mut self, event: &InputEvent, _props: &ToolProperties) -> bool {
83
+        match event {
84
+            InputEvent::MousePress(e) if e.button == Some(MouseButton::Left) => {
85
+                self.start = Some(e.position);
86
+                self.end = Some(e.position);
87
+                self.drawing = true;
88
+                true
89
+            }
90
+            InputEvent::MouseMove(e) if self.drawing => {
91
+                self.end = Some(e.position);
92
+                true
93
+            }
94
+            InputEvent::MouseRelease(e) if e.button == Some(MouseButton::Left) && self.drawing => {
95
+                self.end = Some(e.position);
96
+                self.drawing = false;
97
+                true
98
+            }
99
+            _ => false,
100
+        }
101
+    }
102
+
103
+    fn draw_preview(&self, ctx: &Context, props: &ToolProperties) {
104
+        self.draw_ellipse(ctx, props);
105
+    }
106
+
107
+    fn commit(&self, ctx: &Context, props: &ToolProperties) {
108
+        self.draw_ellipse(ctx, props);
109
+    }
110
+
111
+    fn reset(&mut self) {
112
+        self.start = None;
113
+        self.end = None;
114
+        self.drawing = false;
115
+    }
116
+
117
+    fn cursor(&self) -> CursorShape {
118
+        CursorShape::Crosshair
119
+    }
120
+
121
+    fn is_drawing(&self) -> bool {
122
+        self.drawing
123
+    }
124
+
125
+    fn can_commit(&self) -> bool {
126
+        self.calculate_ellipse().is_some()
127
+    }
128
+}