Skip to main content

open_gpui_canvas/
transform.rs

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}