1use crate::session::ToolState;
2use crate::{
3 CanvasConnectionEndpointRole, CanvasDefaultEdgeRouter, CanvasDocument, CanvasEdgeRoute,
4 CanvasEdgeRouteKind, CanvasEdgeRouter, CanvasEditor, CanvasEndpoint, CanvasKindRegistry,
5 CanvasRuntime, CanvasSelection, CanvasSnapGuide, CanvasViewport, HitTarget,
6};
7use open_gpui::{Hsla, Pixels, TextAlign, px, rgb};
8use std::sync::Arc;
9
10#[derive(Clone, Debug, PartialEq)]
11pub(crate) enum CanvasPaintInteractionState {
12 Idle,
13 Selecting {
14 origin: open_gpui::Point<Pixels>,
15 current: open_gpui::Point<Pixels>,
16 },
17 Connecting {
18 source: CanvasEndpoint,
19 current: open_gpui::Point<Pixels>,
20 },
21 Reconnecting {
22 edge_id: crate::EdgeId,
23 endpoint: CanvasConnectionEndpointRole,
24 fixed: CanvasEndpoint,
25 current: open_gpui::Point<Pixels>,
26 },
27 Transforming {
28 snap_guides: Vec<CanvasSnapGuide>,
29 },
30}
31
32#[derive(Clone, Debug)]
33pub struct CanvasPaintModel {
34 pub(super) document: Arc<CanvasDocument>,
35 pub(super) runtime: Arc<CanvasRuntime>,
36 pub(super) kind_registry: Arc<CanvasKindRegistry>,
37 pub(super) viewport: CanvasViewport,
38 pub(super) interaction: CanvasPaintInteraction,
39}
40
41impl CanvasPaintModel {
42 pub fn new(document: CanvasDocument, viewport: CanvasViewport) -> Self {
43 Self::new_with_router(document, viewport, &CanvasDefaultEdgeRouter)
44 }
45
46 pub fn new_with_kind_registry(
47 document: CanvasDocument,
48 viewport: CanvasViewport,
49 kind_registry: CanvasKindRegistry,
50 ) -> Self {
51 Self::new_with_router_and_kind_registry(
52 document,
53 viewport,
54 &CanvasDefaultEdgeRouter,
55 kind_registry,
56 )
57 }
58
59 pub fn new_with_router<R>(
60 document: CanvasDocument,
61 viewport: CanvasViewport,
62 router: &R,
63 ) -> Self
64 where
65 R: CanvasEdgeRouter + ?Sized,
66 {
67 Self::new_with_router_and_kind_registry(
68 document,
69 viewport,
70 router,
71 CanvasKindRegistry::open(),
72 )
73 }
74
75 pub fn new_with_router_and_kind_registry<R>(
76 document: CanvasDocument,
77 viewport: CanvasViewport,
78 router: &R,
79 kind_registry: CanvasKindRegistry,
80 ) -> Self
81 where
82 R: CanvasEdgeRouter + ?Sized,
83 {
84 let runtime =
85 CanvasRuntime::rebuild_with_router_and_kind_registry(&document, router, &kind_registry);
86 Self {
87 document: Arc::new(document),
88 runtime: Arc::new(runtime),
89 kind_registry: Arc::new(kind_registry),
90 viewport,
91 interaction: CanvasPaintInteraction::default(),
92 }
93 }
94
95 pub fn document(&self) -> &CanvasDocument {
96 self.document.as_ref()
97 }
98
99 pub fn runtime(&self) -> &CanvasRuntime {
100 self.runtime.as_ref()
101 }
102
103 pub fn kind_registry(&self) -> &CanvasKindRegistry {
104 self.kind_registry.as_ref()
105 }
106
107 pub fn viewport(&self) -> CanvasViewport {
108 self.viewport
109 }
110
111 pub fn interaction(&self) -> &CanvasPaintInteraction {
112 &self.interaction
113 }
114
115 pub fn with_interaction(mut self, interaction: CanvasPaintInteraction) -> Self {
116 self.interaction = interaction;
117 self
118 }
119
120 pub fn with_selection(mut self, selection: CanvasSelection) -> Self {
121 self.interaction = self.interaction.with_selection(selection);
122 self
123 }
124}
125
126impl From<&CanvasEditor> for CanvasPaintModel {
127 fn from(editor: &CanvasEditor) -> Self {
128 let session = editor.session_snapshot();
129 Self {
130 document: editor.document_snapshot(),
131 runtime: editor.runtime_snapshot(),
132 kind_registry: editor.kind_registry_snapshot(),
133 viewport: session.viewport,
134 interaction: CanvasPaintInteraction::new(session.selection)
135 .with_internal_tool_state(session.state),
136 }
137 }
138}
139
140#[derive(Clone, Debug, PartialEq)]
141pub struct CanvasPaintInteraction {
142 selection: CanvasSelection,
143 state: CanvasPaintInteractionState,
144 hovered_target: Option<HitTarget>,
145}
146
147impl CanvasPaintInteraction {
148 pub fn new(selection: CanvasSelection) -> Self {
149 Self {
150 selection,
151 state: CanvasPaintInteractionState::Idle,
152 hovered_target: None,
153 }
154 }
155
156 pub fn selection(&self) -> &CanvasSelection {
157 &self.selection
158 }
159
160 pub(crate) fn state(&self) -> &CanvasPaintInteractionState {
161 &self.state
162 }
163
164 pub fn hovered_target(&self) -> Option<&HitTarget> {
165 self.hovered_target.as_ref()
166 }
167
168 pub fn with_selection(mut self, selection: CanvasSelection) -> Self {
169 self.selection = selection;
170 self
171 }
172
173 pub fn with_hovered_target(mut self, target: Option<HitTarget>) -> Self {
174 self.hovered_target = target;
175 self
176 }
177
178 pub(crate) fn with_internal_tool_state(mut self, state: ToolState) -> Self {
179 self.state = CanvasPaintInteractionState::from_tool_state(state);
180 self
181 }
182}
183
184impl Default for CanvasPaintInteraction {
185 fn default() -> Self {
186 Self {
187 selection: CanvasSelection::default(),
188 state: CanvasPaintInteractionState::Idle,
189 hovered_target: None,
190 }
191 }
192}
193
194impl CanvasPaintInteractionState {
195 pub(crate) fn from_tool_state(state: ToolState) -> Self {
196 match state {
197 ToolState::Idle
198 | ToolState::Pointing { .. }
199 | ToolState::PendingTranslation { .. }
200 | ToolState::Panning { .. } => Self::Idle,
201 ToolState::Selecting {
202 origin, current, ..
203 } => Self::Selecting { origin, current },
204 ToolState::Connecting { source, current } => Self::Connecting { source, current },
205 ToolState::Reconnecting {
206 edge_id,
207 endpoint,
208 fixed,
209 current,
210 ..
211 } => Self::Reconnecting {
212 edge_id,
213 endpoint,
214 fixed,
215 current,
216 },
217 ToolState::Translating { snap_guides, .. }
218 | ToolState::Resizing { snap_guides, .. } => Self::Transforming { snap_guides },
219 }
220 }
221}
222
223#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
224pub enum CanvasConnectionPreviewRoute {
225 #[default]
226 Straight,
227 Polyline,
228 Orthogonal,
229 CubicBezier,
230}
231
232impl CanvasConnectionPreviewRoute {
233 pub(crate) fn edge_route(self) -> CanvasEdgeRoute {
234 match self {
235 Self::Straight => CanvasEdgeRoute::straight(),
236 Self::Polyline => CanvasEdgeRoute::polyline(std::iter::empty()),
237 Self::Orthogonal => CanvasEdgeRoute::orthogonal(),
238 Self::CubicBezier => CanvasEdgeRoute::new(CanvasEdgeRouteKind::CUBIC_BEZIER),
239 }
240 }
241}
242
243#[derive(Clone, Copy, Debug, PartialEq)]
244pub struct CanvasPaintOptions {
245 pub include_hidden: bool,
246 pub include_handles: bool,
247 pub include_interaction_feedback: bool,
248 pub cull_margin: Pixels,
249 pub connection_preview_route: CanvasConnectionPreviewRoute,
250}
251
252impl Default for CanvasPaintOptions {
253 fn default() -> Self {
254 Self {
255 include_hidden: false,
256 include_handles: false,
257 include_interaction_feedback: true,
258 cull_margin: Pixels::ZERO,
259 connection_preview_route: CanvasConnectionPreviewRoute::default(),
260 }
261 }
262}
263
264#[derive(Clone, Copy, Debug, PartialEq)]
265pub struct CanvasPaintTheme {
266 pub background: Option<Hsla>,
267 pub node_fill: Hsla,
268 pub node_stroke: Hsla,
269 pub node_stroke_width: Pixels,
270 pub node_corner_radius: Pixels,
271 pub shape_fill: Hsla,
272 pub shape_stroke: Hsla,
273 pub shape_stroke_width: Pixels,
274 pub edge_stroke: Hsla,
275 pub edge_stroke_width: Pixels,
276 pub handle_fill: Hsla,
277 pub handle_stroke: Hsla,
278 pub handle_stroke_width: Pixels,
279 pub handle_corner_radius: Pixels,
280 pub selection_fill: Hsla,
281 pub selection_stroke: Hsla,
282 pub selection_stroke_width: Pixels,
283 pub selection_corner_radius: Pixels,
284 pub selection_bounds_fill: Hsla,
285 pub selection_bounds_stroke: Hsla,
286 pub selection_bounds_stroke_width: Pixels,
287 pub connection_preview_stroke: Hsla,
288 pub connection_preview_stroke_width: Pixels,
289 pub connection_free_target_fill: Hsla,
290 pub connection_free_target_stroke: Hsla,
291 pub connection_valid_target_fill: Hsla,
292 pub connection_valid_target_stroke: Hsla,
293 pub connection_invalid_target_fill: Hsla,
294 pub connection_invalid_target_stroke: Hsla,
295 pub snap_guide_stroke: Hsla,
296 pub snap_guide_stroke_width: Pixels,
297 pub label_color: Hsla,
298 pub label_font_size: Pixels,
299 pub label_line_height: Pixels,
300 pub label_line_clamp: Option<usize>,
301 pub label_text_align: TextAlign,
302}
303
304impl Default for CanvasPaintTheme {
305 fn default() -> Self {
306 Self {
307 background: None,
308 node_fill: Hsla::from(rgb(0xffffff)),
309 node_stroke: Hsla::from(rgb(0xd0d7de)),
310 node_stroke_width: px(1.0),
311 node_corner_radius: px(6.0),
312 shape_fill: Hsla::from(rgb(0xf6f8fa)),
313 shape_stroke: Hsla::from(rgb(0xd0d7de)),
314 shape_stroke_width: px(1.0),
315 edge_stroke: Hsla::from(rgb(0x57606a)),
316 edge_stroke_width: px(2.0),
317 handle_fill: Hsla::from(rgb(0x0969da)),
318 handle_stroke: Hsla::from(rgb(0xffffff)),
319 handle_stroke_width: px(1.0),
320 handle_corner_radius: px(6.0),
321 selection_fill: Hsla::from(rgb(0x0969da)).alpha(0.08),
322 selection_stroke: Hsla::from(rgb(0x0969da)),
323 selection_stroke_width: px(2.0),
324 selection_corner_radius: px(7.0),
325 selection_bounds_fill: Hsla::from(rgb(0x0969da)).alpha(0.08),
326 selection_bounds_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
327 selection_bounds_stroke_width: px(1.0),
328 connection_preview_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
329 connection_preview_stroke_width: px(2.0),
330 connection_free_target_fill: Hsla::from(rgb(0x0969da)).alpha(0.12),
331 connection_free_target_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
332 connection_valid_target_fill: Hsla::from(rgb(0x1a7f37)).alpha(0.14),
333 connection_valid_target_stroke: Hsla::from(rgb(0x1a7f37)).alpha(0.9),
334 connection_invalid_target_fill: Hsla::from(rgb(0xcf222e)).alpha(0.12),
335 connection_invalid_target_stroke: Hsla::from(rgb(0xcf222e)).alpha(0.9),
336 snap_guide_stroke: Hsla::from(rgb(0xbf8700)).alpha(0.9),
337 snap_guide_stroke_width: px(1.0),
338 label_color: Hsla::from(rgb(0x24292f)),
339 label_font_size: px(14.0),
340 label_line_height: px(18.0),
341 label_line_clamp: Some(3),
342 label_text_align: TextAlign::Center,
343 }
344 }
345}