1use crate::{
2 CanvasDefaultEdgeRouter, CanvasDocument, CanvasGeometryFacts, CanvasKindRegistry,
3 CanvasRecordGeometry, CanvasRecordId, CanvasSelection, NodeId, ShapeId,
4};
5use open_gpui::{Bounds, Pixels, Point, Size, px};
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
9pub enum CanvasResizeHandle {
10 TopLeft,
11 TopRight,
12 BottomLeft,
13 BottomRight,
14}
15
16#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
17pub enum CanvasTransformTarget {
18 Node(NodeId),
19 Shape(ShapeId),
20}
21
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23pub struct CanvasTransformHandle {
24 pub target: CanvasTransformTarget,
25 pub handle: CanvasResizeHandle,
26 pub document_bounds: Bounds<Pixels>,
27}
28
29pub fn canvas_transform_handles(
30 document: &CanvasDocument,
31 selection: &CanvasSelection,
32 viewport: crate::CanvasViewport,
33 kind_registry: Option<&CanvasKindRegistry>,
34) -> Vec<CanvasTransformHandle> {
35 let facts = CanvasGeometryFacts::with_router_and_kind_registry(
36 document,
37 CanvasDefaultEdgeRouter,
38 kind_registry,
39 );
40 let handle_size = transform_handle_document_size(viewport.zoom);
41 let mut handles = Vec::new();
42
43 for geometry in facts.selected_record_geometries(selection) {
44 if !geometry.is_visible_unlocked() {
45 continue;
46 }
47 let Some(target) = transform_target_for_record(&geometry) else {
48 continue;
49 };
50 handles.extend(transform_handles_for_bounds(
51 target,
52 geometry.bounds,
53 handle_size,
54 ));
55 }
56
57 handles
58}
59
60fn transform_target_for_record(geometry: &CanvasRecordGeometry) -> Option<CanvasTransformTarget> {
61 match &geometry.id {
62 CanvasRecordId::Node(id) => Some(CanvasTransformTarget::Node(id.clone())),
63 CanvasRecordId::Shape(id) => Some(CanvasTransformTarget::Shape(id.clone())),
64 CanvasRecordId::Edge(_) => None,
65 }
66}
67
68pub(crate) fn resize_bounds_by_handle(
69 bounds: Bounds<Pixels>,
70 handle: CanvasResizeHandle,
71 delta: Point<Pixels>,
72) -> Bounds<Pixels> {
73 let min_size = Size::new(px(8.0), px(8.0));
74 let left = bounds.origin.x;
75 let top = bounds.origin.y;
76 let right = bounds.origin.x + bounds.size.width;
77 let bottom = bounds.origin.y + bounds.size.height;
78
79 let (new_left, new_top, new_right, new_bottom) = match handle {
80 CanvasResizeHandle::TopLeft => (
81 (left + delta.x).min(right - min_size.width),
82 (top + delta.y).min(bottom - min_size.height),
83 right,
84 bottom,
85 ),
86 CanvasResizeHandle::TopRight => (
87 left,
88 (top + delta.y).min(bottom - min_size.height),
89 (right + delta.x).max(left + min_size.width),
90 bottom,
91 ),
92 CanvasResizeHandle::BottomLeft => (
93 (left + delta.x).min(right - min_size.width),
94 top,
95 right,
96 (bottom + delta.y).max(top + min_size.height),
97 ),
98 CanvasResizeHandle::BottomRight => (
99 left,
100 top,
101 (right + delta.x).max(left + min_size.width),
102 (bottom + delta.y).max(top + min_size.height),
103 ),
104 };
105
106 Bounds::from_corners(
107 Point::new(new_left, new_top),
108 Point::new(new_right, new_bottom),
109 )
110}
111
112fn transform_handles_for_bounds(
113 target: CanvasTransformTarget,
114 bounds: Bounds<Pixels>,
115 handle_size: Size<Pixels>,
116) -> Vec<CanvasTransformHandle> {
117 resize_handle_centers(bounds)
118 .into_iter()
119 .map(|(handle, center)| CanvasTransformHandle {
120 target: target.clone(),
121 handle,
122 document_bounds: Bounds::centered_at(center, handle_size),
123 })
124 .collect()
125}
126
127fn resize_handle_centers(bounds: Bounds<Pixels>) -> [(CanvasResizeHandle, Point<Pixels>); 4] {
128 let left = bounds.origin.x;
129 let top = bounds.origin.y;
130 let right = bounds.origin.x + bounds.size.width;
131 let bottom = bounds.origin.y + bounds.size.height;
132 [
133 (CanvasResizeHandle::TopLeft, Point::new(left, top)),
134 (CanvasResizeHandle::TopRight, Point::new(right, top)),
135 (CanvasResizeHandle::BottomLeft, Point::new(left, bottom)),
136 (CanvasResizeHandle::BottomRight, Point::new(right, bottom)),
137 ]
138}
139
140fn transform_handle_document_size(zoom: f32) -> Size<Pixels> {
141 let scale = if zoom.is_finite() && zoom > 0.0 {
142 1.0 / zoom
143 } else {
144 1.0
145 };
146 let edge = px(8.0) * scale;
147 Size::new(edge, edge)
148}