1use crate::shapes::{Shape, ShapeId, ShapeTrait};
4use kurbo::{Point, Rect};
5use serde::{Deserialize, Serialize};
6
7pub const HANDLE_SIZE: f64 = 8.0;
9pub const HANDLE_HIT_TOLERANCE: f64 = 12.0;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum HandleKind {
15 Endpoint(usize),
17 Corner(Corner),
19 Edge(Edge),
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum Corner {
26 TopLeft,
27 TopRight,
28 BottomLeft,
29 BottomRight,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum Edge {
35 Top,
36 Right,
37 Bottom,
38 Left,
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct Handle {
44 pub position: Point,
46 pub kind: HandleKind,
48}
49
50impl Handle {
51 pub fn new(position: Point, kind: HandleKind) -> Self {
53 Self { position, kind }
54 }
55
56 pub fn hit_test(&self, point: Point, tolerance: f64) -> bool {
59 let dx = point.x - self.position.x;
60 let dy = point.y - self.position.y;
61 let dist_sq = dx * dx + dy * dy;
62 dist_sq <= tolerance * tolerance
63 }
64}
65
66pub fn get_handles(shape: &Shape) -> Vec<Handle> {
68 match shape {
69 Shape::Line(line) => vec![
70 Handle::new(line.start, HandleKind::Endpoint(0)),
71 Handle::new(line.end, HandleKind::Endpoint(1)),
72 ],
73 Shape::Arrow(arrow) => vec![
74 Handle::new(arrow.start, HandleKind::Endpoint(0)),
75 Handle::new(arrow.end, HandleKind::Endpoint(1)),
76 ],
77 Shape::Rectangle(_) | Shape::Ellipse(_) => {
78 let bounds = shape.bounds();
79 corner_handles(bounds)
80 }
81 Shape::Freehand(_) => {
82 let bounds = shape.bounds();
84 corner_handles(bounds)
85 }
86 Shape::Text(_) => {
87 let bounds = shape.bounds();
89 corner_handles(bounds)
90 }
91 Shape::Group(_) => {
92 let bounds = shape.bounds();
94 corner_handles(bounds)
95 }
96 Shape::Image(_) => {
97 let bounds = shape.bounds();
99 corner_handles(bounds)
100 }
101 }
102}
103
104fn corner_handles(bounds: Rect) -> Vec<Handle> {
106 vec![
107 Handle::new(
108 Point::new(bounds.x0, bounds.y0),
109 HandleKind::Corner(Corner::TopLeft),
110 ),
111 Handle::new(
112 Point::new(bounds.x1, bounds.y0),
113 HandleKind::Corner(Corner::TopRight),
114 ),
115 Handle::new(
116 Point::new(bounds.x0, bounds.y1),
117 HandleKind::Corner(Corner::BottomLeft),
118 ),
119 Handle::new(
120 Point::new(bounds.x1, bounds.y1),
121 HandleKind::Corner(Corner::BottomRight),
122 ),
123 ]
124}
125
126pub fn hit_test_handles(shape: &Shape, point: Point, tolerance: f64) -> Option<HandleKind> {
129 let handles = get_handles(shape);
130 for handle in handles {
131 if handle.hit_test(point, tolerance) {
132 return Some(handle.kind);
133 }
134 }
135 None
136}
137
138#[derive(Debug, Clone)]
140pub struct ManipulationState {
141 pub shape_id: ShapeId,
143 pub handle: Option<HandleKind>,
145 pub start_point: Point,
147 pub current_point: Point,
149 pub original_shape: Shape,
151}
152
153#[derive(Debug, Clone)]
155pub struct MultiMoveState {
156 pub start_point: Point,
158 pub current_point: Point,
160 pub original_shapes: std::collections::HashMap<ShapeId, Shape>,
162 pub is_duplicate: bool,
164 pub duplicated_ids: Vec<ShapeId>,
166}
167
168impl ManipulationState {
169 pub fn new(shape_id: ShapeId, handle: Option<HandleKind>, start_point: Point, original_shape: Shape) -> Self {
171 Self {
172 shape_id,
173 handle,
174 start_point,
175 current_point: start_point,
176 original_shape,
177 }
178 }
179
180 pub fn delta(&self) -> kurbo::Vec2 {
182 kurbo::Vec2::new(
183 self.current_point.x - self.start_point.x,
184 self.current_point.y - self.start_point.y,
185 )
186 }
187}
188
189impl MultiMoveState {
190 pub fn new(start_point: Point, original_shapes: std::collections::HashMap<ShapeId, Shape>) -> Self {
192 Self {
193 start_point,
194 current_point: start_point,
195 original_shapes,
196 is_duplicate: false,
197 duplicated_ids: Vec::new(),
198 }
199 }
200
201 pub fn new_duplicate(start_point: Point, original_shapes: std::collections::HashMap<ShapeId, Shape>) -> Self {
203 Self {
204 start_point,
205 current_point: start_point,
206 original_shapes,
207 is_duplicate: true,
208 duplicated_ids: Vec::new(),
209 }
210 }
211
212 pub fn delta(&self) -> kurbo::Vec2 {
214 kurbo::Vec2::new(
215 self.current_point.x - self.start_point.x,
216 self.current_point.y - self.start_point.y,
217 )
218 }
219
220 pub fn shape_ids(&self) -> Vec<ShapeId> {
222 self.original_shapes.keys().copied().collect()
223 }
224}
225
226pub fn get_manipulation_target_position(shape: &Shape, handle: Option<HandleKind>) -> Point {
229 match handle {
230 None => {
231 let bounds = shape.bounds();
233 Point::new(bounds.x0, bounds.y0)
234 }
235 Some(HandleKind::Endpoint(idx)) => {
236 match shape {
237 Shape::Line(line) => {
238 if idx == 0 { line.start } else { line.end }
239 }
240 Shape::Arrow(arrow) => {
241 if idx == 0 { arrow.start } else { arrow.end }
242 }
243 _ => shape.bounds().center(),
244 }
245 }
246 Some(HandleKind::Corner(corner)) => {
247 let bounds = shape.bounds();
248 match corner {
249 Corner::TopLeft => Point::new(bounds.x0, bounds.y0),
250 Corner::TopRight => Point::new(bounds.x1, bounds.y0),
251 Corner::BottomLeft => Point::new(bounds.x0, bounds.y1),
252 Corner::BottomRight => Point::new(bounds.x1, bounds.y1),
253 }
254 }
255 Some(HandleKind::Edge(edge)) => {
256 let bounds = shape.bounds();
257 match edge {
258 Edge::Top => Point::new(bounds.center().x, bounds.y0),
259 Edge::Right => Point::new(bounds.x1, bounds.center().y),
260 Edge::Bottom => Point::new(bounds.center().x, bounds.y1),
261 Edge::Left => Point::new(bounds.x0, bounds.center().y),
262 }
263 }
264 }
265}
266
267pub fn apply_manipulation(shape: &Shape, handle: Option<HandleKind>, delta: kurbo::Vec2) -> Shape {
270 let mut shape = shape.clone();
271
272 match handle {
273 None => {
274 let translation = kurbo::Affine::translate(delta);
276 shape.transform(translation);
277 }
278 Some(HandleKind::Endpoint(idx)) => {
279 match &mut shape {
281 Shape::Line(line) => {
282 if idx == 0 {
283 line.start.x += delta.x;
284 line.start.y += delta.y;
285 } else {
286 line.end.x += delta.x;
287 line.end.y += delta.y;
288 }
289 }
290 Shape::Arrow(arrow) => {
291 if idx == 0 {
292 arrow.start.x += delta.x;
293 arrow.start.y += delta.y;
294 } else {
295 arrow.end.x += delta.x;
296 arrow.end.y += delta.y;
297 }
298 }
299 _ => {}
300 }
301 }
302 Some(HandleKind::Corner(corner)) => {
303 match &mut shape {
305 Shape::Rectangle(rect) => {
306 apply_corner_resize_rect(rect, corner, delta);
307 }
308 Shape::Ellipse(ellipse) => {
309 apply_corner_resize_ellipse(ellipse, corner, delta);
310 }
311 _ => {}
312 }
313 }
314 Some(HandleKind::Edge(_)) => {
315 }
317 }
318
319 shape
320}
321
322fn apply_corner_resize_rect(rect: &mut crate::shapes::Rectangle, corner: Corner, delta: kurbo::Vec2) {
324 let bounds = rect.bounds();
325 let (new_x0, new_y0, new_x1, new_y1) = match corner {
326 Corner::TopLeft => (bounds.x0 + delta.x, bounds.y0 + delta.y, bounds.x1, bounds.y1),
327 Corner::TopRight => (bounds.x0, bounds.y0 + delta.y, bounds.x1 + delta.x, bounds.y1),
328 Corner::BottomLeft => (bounds.x0 + delta.x, bounds.y0, bounds.x1, bounds.y1 + delta.y),
329 Corner::BottomRight => (bounds.x0, bounds.y0, bounds.x1 + delta.x, bounds.y1 + delta.y),
330 };
331
332 let (x0, x1) = if new_x0 < new_x1 { (new_x0, new_x1) } else { (new_x1, new_x0) };
334 let (y0, y1) = if new_y0 < new_y1 { (new_y0, new_y1) } else { (new_y1, new_y0) };
335
336 rect.position = Point::new(x0, y0);
337 rect.width = (x1 - x0).max(1.0);
338 rect.height = (y1 - y0).max(1.0);
339}
340
341fn apply_corner_resize_ellipse(ellipse: &mut crate::shapes::Ellipse, corner: Corner, delta: kurbo::Vec2) {
343 let bounds = ellipse.bounds();
344 let (new_x0, new_y0, new_x1, new_y1) = match corner {
345 Corner::TopLeft => (bounds.x0 + delta.x, bounds.y0 + delta.y, bounds.x1, bounds.y1),
346 Corner::TopRight => (bounds.x0, bounds.y0 + delta.y, bounds.x1 + delta.x, bounds.y1),
347 Corner::BottomLeft => (bounds.x0 + delta.x, bounds.y0, bounds.x1, bounds.y1 + delta.y),
348 Corner::BottomRight => (bounds.x0, bounds.y0, bounds.x1 + delta.x, bounds.y1 + delta.y),
349 };
350
351 let (x0, x1) = if new_x0 < new_x1 { (new_x0, new_x1) } else { (new_x1, new_x0) };
353 let (y0, y1) = if new_y0 < new_y1 { (new_y0, new_y1) } else { (new_y1, new_y0) };
354
355 let width = (x1 - x0).max(1.0);
356 let height = (y1 - y0).max(1.0);
357
358 ellipse.center = Point::new(x0 + width / 2.0, y0 + height / 2.0);
359 ellipse.radius_x = width / 2.0;
360 ellipse.radius_y = height / 2.0;
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::shapes::{Line, Rectangle};
367
368 #[test]
369 fn test_line_handles() {
370 let line = Line::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
371 let handles = get_handles(&Shape::Line(line));
372
373 assert_eq!(handles.len(), 2);
374 assert!(matches!(handles[0].kind, HandleKind::Endpoint(0)));
375 assert!(matches!(handles[1].kind, HandleKind::Endpoint(1)));
376 }
377
378 #[test]
379 fn test_rectangle_handles() {
380 let rect = Rectangle::new(Point::new(0.0, 0.0), 100.0, 50.0);
381 let handles = get_handles(&Shape::Rectangle(rect));
382
383 assert_eq!(handles.len(), 4);
384 assert!(matches!(handles[0].kind, HandleKind::Corner(Corner::TopLeft)));
385 }
386
387 #[test]
388 fn test_handle_hit_test() {
389 let handle = Handle::new(Point::new(50.0, 50.0), HandleKind::Endpoint(0));
390
391 assert!(handle.hit_test(Point::new(50.0, 50.0), 10.0));
392 assert!(handle.hit_test(Point::new(55.0, 55.0), 10.0));
393 assert!(!handle.hit_test(Point::new(70.0, 70.0), 10.0));
394 }
395
396 #[test]
397 fn test_apply_endpoint_manipulation() {
398 let line = Line::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
399 let shape = Shape::Line(line);
400
401 let result = apply_manipulation(&shape, Some(HandleKind::Endpoint(1)), kurbo::Vec2::new(10.0, 20.0));
402
403 if let Shape::Line(line) = result {
404 assert!((line.end.x - 110.0).abs() < f64::EPSILON);
405 assert!((line.end.y - 120.0).abs() < f64::EPSILON);
406 } else {
407 panic!("Expected Line shape");
408 }
409 }
410
411 #[test]
412 fn test_apply_corner_manipulation() {
413 let rect = Rectangle::new(Point::new(0.0, 0.0), 100.0, 100.0);
414 let shape = Shape::Rectangle(rect);
415
416 let result = apply_manipulation(
417 &shape,
418 Some(HandleKind::Corner(Corner::BottomRight)),
419 kurbo::Vec2::new(50.0, 50.0)
420 );
421
422 if let Shape::Rectangle(rect) = result {
423 assert!((rect.width - 150.0).abs() < f64::EPSILON);
424 assert!((rect.height - 150.0).abs() < f64::EPSILON);
425 } else {
426 panic!("Expected Rectangle shape");
427 }
428 }
429}