Rust · 3542 bytes Raw Blame History
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 }
129