1use futures::{StreamExt, channel::mpsc};
2use gpui::*;
3
4use crate::{
5 BackgroundPlugin, DeletePlugin, EdgePlugin, FlowTheme, GraphChange, HistoryPlugin,
6 NodeInteractionPlugin, NodePlugin, PortInteractionPlugin, SelectionPlugin, SyncPlugin,
7 ViewportPlugin,
8 copied_subgraph::CopiedSubgraph,
9 graph::Graph,
10 plugin::{
11 EventResult, FlowEvent, InitPluginContext, InputEvent, Plugin, PluginContext,
12 PluginRegistry, RenderContext, RenderLayer,
13 },
14 viewport::Viewport,
15};
16
17mod node_renderer;
18mod port_cache;
19mod types;
20mod undo;
21
22pub use port_cache::PortLayoutCache;
23
24pub use undo::{Command, CommandContext, CompositeCommand, HistoryProvider, LocalHistory};
25
26pub use types::{Interaction, InteractionResult, InteractionState};
27
28pub use node_renderer::{
29 NodeRenderer, RendererRegistry, default_node_caption, port_screen_position,
30};
31
32pub struct FlowCanvas {
33 pub graph: Graph,
34
35 pub(crate) viewport: Viewport,
36
37 pub(crate) plugins_registry: PluginRegistry,
38
39 pub(crate) sync_plugin: Option<Box<dyn SyncPlugin + 'static>>,
40
41 renderers: RendererRegistry,
42
43 pub(crate) focus_handle: FocusHandle,
44
45 pub(crate) interaction: InteractionState,
46
47 pub history: Box<dyn HistoryProvider>,
48
49 pub event_queue: Vec<FlowEvent>,
50 pub port_offset_cache: PortLayoutCache,
51
52 pub(crate) clipboard_subgraph: Option<CopiedSubgraph>,
54
55 pub theme: FlowTheme,
57}
58
59impl FlowCanvas {
74 pub fn new(graph: Graph, cx: &mut Context<Self>) -> Self {
75 let focus_handle = cx.focus_handle();
76 Self {
77 graph,
78 viewport: Viewport::new(),
79 plugins_registry: PluginRegistry::new(),
80 sync_plugin: None,
81 renderers: RendererRegistry::new(),
82 focus_handle,
83 interaction: InteractionState::new(),
84 history: Box::new(LocalHistory::new()),
85 event_queue: vec![],
86 port_offset_cache: PortLayoutCache::new(),
87 clipboard_subgraph: None,
88 theme: FlowTheme::default(),
89 }
90 }
91
92 pub fn builder<'a, 'b>(
93 graph: Graph,
94 ctx: &'a mut Context<'b, Self>,
95 window: &'a Window,
96 ) -> FlowCanvasBuilder<'a, 'b> {
97 FlowCanvasBuilder {
98 graph,
99 ctx,
100 window,
101 plugins: PluginRegistry::new(),
102 sync_plugin: None,
103 renderers: RendererRegistry::new(),
104 theme: FlowTheme::default(),
105 }
106 }
107
108 pub fn handle_event(&mut self, event: FlowEvent, cx: &mut Context<Self>) {
109 let event_queue = &mut self.event_queue;
110
111 let mut emit = |event: FlowEvent| {
112 event_queue.push(event);
113 };
114
115 let mut notify = || {
116 cx.notify();
117 };
118
119 if let Some(mut handler) = self.interaction.handler.take() {
121 let mut ctx = PluginContext::new(
122 &mut self.graph,
123 &mut self.port_offset_cache,
124 &mut self.viewport,
125 &mut self.interaction,
126 &mut self.renderers,
127 &mut self.sync_plugin,
128 self.history.as_mut(),
129 &mut self.clipboard_subgraph,
130 &mut self.theme,
131 &mut emit,
132 &mut notify,
133 );
134 let mut fast_return = false;
135 let result = match &event {
136 FlowEvent::Input(InputEvent::MouseMove(ev)) => {
137 fast_return = true;
138 handler.on_mouse_move(ev, &mut ctx)
139 }
140
141 FlowEvent::Input(InputEvent::MouseUp(ev)) => {
142 fast_return = true;
143 handler.on_mouse_up(ev, &mut ctx)
144 }
145
146 _ => InteractionResult::Continue,
147 };
148
149 if fast_return {
150 match result {
151 InteractionResult::Continue => self.interaction.handler = Some(handler),
152
153 InteractionResult::End => {
154 self.interaction.handler = None;
155 }
156
157 InteractionResult::Replace(h) => {
158 self.interaction.handler = Some(h);
159 }
160 }
161 return;
162 }
163 }
164
165 let mut ctx = PluginContext::new(
166 &mut self.graph,
167 &mut self.port_offset_cache,
168 &mut self.viewport,
169 &mut self.interaction,
170 &mut self.renderers,
171 &mut self.sync_plugin,
172 self.history.as_mut(),
173 &mut self.clipboard_subgraph,
174 &mut self.theme,
175 &mut emit,
176 &mut notify,
177 );
178
179 for plugin in &mut self.plugins_registry.plugins {
180 let result = plugin.on_event(&event, &mut ctx);
181 match result {
182 EventResult::Continue => {}
183 EventResult::Stop => break,
184 }
185 }
186 }
187
188 fn process_event_queue(&mut self, cx: &mut Context<Self>) {
189 while let Some(event) = self.event_queue.pop() {
190 let mut emit = |e| self.event_queue.push(e);
191
192 let mut notify = || {
193 cx.notify();
194 };
195
196 let mut ctx = PluginContext::new(
197 &mut self.graph,
198 &mut self.port_offset_cache,
199 &mut self.viewport,
200 &mut self.interaction,
201 &mut self.renderers,
202 &mut self.sync_plugin,
203 self.history.as_mut(),
204 &mut self.clipboard_subgraph,
205 &mut self.theme,
206 &mut emit,
207 &mut notify,
208 );
209
210 for plugin in &mut self.plugins_registry.plugins {
211 let result = plugin.on_event(&event, &mut ctx);
212 match result {
213 EventResult::Continue => {}
214 EventResult::Stop => break,
215 }
216 }
217 }
218 }
219
220 fn on_key_down(&mut self, ev: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {
221 self.handle_event(FlowEvent::Input(InputEvent::KeyDown(ev.clone())), cx);
222 self.process_event_queue(cx);
223 }
224
225 fn on_key_up(&mut self, ev: &KeyUpEvent, _: &mut Window, cx: &mut Context<Self>) {
226 self.handle_event(FlowEvent::Input(InputEvent::KeyUp(ev.clone())), cx);
227 self.process_event_queue(cx);
228 }
229
230 fn on_mouse_down(&mut self, ev: &MouseDownEvent, _: &mut Window, cx: &mut Context<Self>) {
231 self.handle_event(FlowEvent::Input(InputEvent::MouseDown(ev.clone())), cx);
232 self.process_event_queue(cx);
233 }
234
235 fn on_mouse_move(&mut self, ev: &MouseMoveEvent, _: &mut Window, cx: &mut Context<Self>) {
236 self.handle_event(FlowEvent::Input(InputEvent::MouseMove(ev.clone())), cx);
237 if let Some(sync_plugin) = &mut self.sync_plugin {
238 let world = self.viewport.screen_to_world(ev.position);
239 sync_plugin.on_mouse_move(ev, world);
240 }
241 self.process_event_queue(cx);
242 }
243
244 fn on_mouse_up(&mut self, ev: &MouseUpEvent, _: &mut Window, cx: &mut Context<Self>) {
245 self.handle_event(FlowEvent::Input(InputEvent::MouseUp(ev.clone())), cx);
246 self.process_event_queue(cx);
247 }
248
249 fn on_scroll_wheel(&mut self, ev: &ScrollWheelEvent, _: &mut Window, cx: &mut Context<Self>) {
250 self.handle_event(FlowEvent::Input(InputEvent::Wheel(ev.clone())), cx);
251 self.process_event_queue(cx);
252 }
253
254 fn on_canvas_hover(&mut self, hovered: &bool, _: &mut Window, cx: &mut Context<Self>) {
255 if !*hovered {
256 if let Some(sync_plugin) = &mut self.sync_plugin {
257 sync_plugin.on_mouse_leave();
258 }
259 cx.notify();
260 }
261 }
262}
263
264impl Render for FlowCanvas {
265 fn render(&mut self, window: &mut Window, this_cx: &mut Context<Self>) -> impl IntoElement {
266 self.viewport.sync_drawable_bounds(window);
267
268 let entity = this_cx.entity();
269
270 let graph = &mut self.graph;
271 let viewport = &self.viewport;
272 let renderder = &self.renderers;
273 let port_offset_cache = &mut self.port_offset_cache;
274 let theme = &self.theme;
275
276 let mut layers: Vec<Vec<AnyElement>> =
277 (0..RenderLayer::ALL.len()).map(|_| Vec::new()).collect();
278
279 let alignment_guides = self.interaction.alignment_guides.as_ref();
280
281 for plugin in self.plugins_registry.plugins.iter_mut() {
282 let layer = plugin.render_layer();
283
284 let mut ctx = RenderContext::new(
285 graph,
286 port_offset_cache,
287 viewport,
288 renderder,
289 window,
290 layer,
291 alignment_guides,
292 theme,
293 );
294
295 if let Some(el) = plugin.render(&mut ctx) {
296 layers[layer.index()].push(el);
297 }
298 }
299
300 if let Some(i) = self.interaction.handler.as_ref() {
301 let mut ctx = RenderContext::new(
302 graph,
303 port_offset_cache,
304 viewport,
305 renderder,
306 window,
307 RenderLayer::Interaction,
308 alignment_guides,
309 theme,
310 );
311
312 if let Some(el) = i.render(&mut ctx) {
313 layers[RenderLayer::Interaction.index()].push(el);
314 }
315 }
316
317 if let Some(sync_plugin) = &mut self.sync_plugin {
318 let mut ctx = RenderContext::new(
319 graph,
320 port_offset_cache,
321 viewport,
322 renderder,
323 window,
324 RenderLayer::Overlay,
325 alignment_guides,
326 theme,
327 );
328 let els = sync_plugin.render(&mut ctx);
329 for el in els {
330 layers[RenderLayer::Overlay.index()].push(el);
331 }
332 }
333
334 div()
335 .id("ferrum_flow_canvas")
336 .size_full()
337 .track_focus(&self.focus_handle)
338 .on_key_down(window.listener_for(&entity, Self::on_key_down))
339 .on_key_up(window.listener_for(&entity, Self::on_key_up))
340 .on_mouse_down(
341 MouseButton::Left,
342 window.listener_for(&entity, Self::on_mouse_down),
343 )
344 .on_mouse_down(
345 MouseButton::Right,
346 window.listener_for(&entity, Self::on_mouse_down),
347 )
348 .on_mouse_move(window.listener_for(&entity, Self::on_mouse_move))
349 .on_hover(window.listener_for(&entity, Self::on_canvas_hover))
350 .on_mouse_up(
351 MouseButton::Left,
352 window.listener_for(&entity, Self::on_mouse_up),
353 )
354 .on_scroll_wheel(window.listener_for(&entity, Self::on_scroll_wheel))
355 .children(RenderLayer::ALL.iter().map(|layer| {
356 div()
357 .absolute()
358 .size_full()
359 .children(layers[layer.index()].drain(..))
360 }))
361 }
362}
363
364pub struct FlowCanvasBuilder<'a, 'b> {
365 graph: Graph,
366 ctx: &'a mut Context<'b, FlowCanvas>,
367 window: &'a Window,
368
369 plugins: PluginRegistry,
370 renderers: RendererRegistry,
371 sync_plugin: Option<Box<dyn SyncPlugin + 'static>>,
372 theme: FlowTheme,
373}
374
375impl<'a, 'b> FlowCanvasBuilder<'a, 'b> {
376 pub fn plugin(mut self, plugin: impl Plugin + 'static) -> Self {
378 self.plugins = self.plugins.add(plugin);
379 self
380 }
381
382 pub fn plugins_core(mut self) -> Self {
391 self.plugins = self
392 .plugins
393 .add(BackgroundPlugin::new())
394 .add(SelectionPlugin::new())
395 .add(NodeInteractionPlugin::new())
396 .add(ViewportPlugin::new())
397 .add(NodePlugin::new())
398 .add(PortInteractionPlugin::new())
399 .add(EdgePlugin::new())
400 .add(DeletePlugin::new())
401 .add(HistoryPlugin::new());
402 self
403 }
404
405 pub fn sync_plugin(mut self, plugin: impl SyncPlugin + 'static) -> Self {
406 self.sync_plugin = Some(Box::new(plugin));
407 self
408 }
409
410 pub fn node_renderer<R>(mut self, name: impl Into<String>, renderer: R) -> Self
412 where
413 R: node_renderer::NodeRenderer + 'static,
414 {
415 self.renderers.register(name, renderer);
416 self
417 }
418
419 pub fn theme(mut self, theme: FlowTheme) -> Self {
421 self.theme = theme;
422 self
423 }
424
425 pub fn build(self) -> FlowCanvas {
426 let focus_handle = self.ctx.focus_handle();
427 let drawable_size = self.window.viewport_size();
428
429 let mut canvas = FlowCanvas {
430 graph: self.graph,
431 viewport: Viewport::new(),
432 plugins_registry: self.plugins,
433 sync_plugin: self.sync_plugin,
434 renderers: self.renderers,
435 focus_handle,
436 interaction: InteractionState::new(),
437 history: Box::new(LocalHistory::new()),
438 event_queue: vec![],
439 port_offset_cache: PortLayoutCache::new(),
440 clipboard_subgraph: None,
441 theme: self.theme,
442 };
443
444 if let Some(sync_plugin) = &mut canvas.sync_plugin {
445 let (change_sender, mut change_receiver) = mpsc::unbounded::<GraphChange>();
446
447 self.ctx
448 .spawn(async move |this, ctx| {
449 while let Some(change) = change_receiver.next().await {
450 let _ = this.update(ctx, |this, cx| {
451 this.graph.apply(change.kind);
452 cx.notify();
453 });
454 }
455 })
456 .detach();
457 sync_plugin.setup(change_sender);
458 }
459
460 canvas
461 .plugins_registry
462 .plugins
463 .sort_by_key(|p| -p.priority());
464
465 {
466 let mut ctx = InitPluginContext {
467 graph: &mut canvas.graph,
468 port_offset_cache: &mut canvas.port_offset_cache,
469 viewport: &mut canvas.viewport,
470 renderers: &mut canvas.renderers,
471 gpui_ctx: &self.ctx,
472 drawable_size,
473 theme: &mut canvas.theme,
474 };
476
477 for plugin in &mut canvas.plugins_registry.plugins {
478 plugin.setup(&mut ctx);
479 }
480 }
481
482 canvas
483 }
484}