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 for plugin in self.plugins_registry.plugins.iter_mut() {
280 let layer = plugin.render_layer();
281
282 let mut ctx = RenderContext::new(
283 graph,
284 port_offset_cache,
285 viewport,
286 renderder,
287 window,
288 layer,
289 theme,
290 );
291
292 if let Some(el) = plugin.render(&mut ctx) {
293 layers[layer.index()].push(el);
294 }
295 }
296
297 if let Some(i) = self.interaction.handler.as_ref() {
298 let mut ctx = RenderContext::new(
299 graph,
300 port_offset_cache,
301 viewport,
302 renderder,
303 window,
304 RenderLayer::Interaction,
305 theme,
306 );
307
308 if let Some(el) = i.render(&mut ctx) {
309 layers[RenderLayer::Interaction.index()].push(el);
310 }
311 }
312
313 if let Some(sync_plugin) = &mut self.sync_plugin {
314 let mut ctx = RenderContext::new(
315 graph,
316 port_offset_cache,
317 viewport,
318 renderder,
319 window,
320 RenderLayer::Overlay,
321 theme,
322 );
323 let els = sync_plugin.render(&mut ctx);
324 for el in els {
325 layers[RenderLayer::Overlay.index()].push(el);
326 }
327 }
328
329 div()
330 .id("ferrum_flow_canvas")
331 .size_full()
332 .track_focus(&self.focus_handle)
333 .on_key_down(window.listener_for(&entity, Self::on_key_down))
334 .on_key_up(window.listener_for(&entity, Self::on_key_up))
335 .on_mouse_down(
336 MouseButton::Left,
337 window.listener_for(&entity, Self::on_mouse_down),
338 )
339 .on_mouse_down(
340 MouseButton::Right,
341 window.listener_for(&entity, Self::on_mouse_down),
342 )
343 .on_mouse_move(window.listener_for(&entity, Self::on_mouse_move))
344 .on_hover(window.listener_for(&entity, Self::on_canvas_hover))
345 .on_mouse_up(
346 MouseButton::Left,
347 window.listener_for(&entity, Self::on_mouse_up),
348 )
349 .on_scroll_wheel(window.listener_for(&entity, Self::on_scroll_wheel))
350 .children(RenderLayer::ALL.iter().map(|layer| {
351 div()
352 .absolute()
353 .size_full()
354 .children(layers[layer.index()].drain(..))
355 }))
356 }
357}
358
359pub struct FlowCanvasBuilder<'a, 'b> {
360 graph: Graph,
361 ctx: &'a mut Context<'b, FlowCanvas>,
362 window: &'a Window,
363
364 plugins: PluginRegistry,
365 renderers: RendererRegistry,
366 sync_plugin: Option<Box<dyn SyncPlugin + 'static>>,
367 theme: FlowTheme,
368}
369
370impl<'a, 'b> FlowCanvasBuilder<'a, 'b> {
371 pub fn plugin(mut self, plugin: impl Plugin + 'static) -> Self {
373 self.plugins = self.plugins.add(plugin);
374 self
375 }
376
377 pub fn plugins_core(mut self) -> Self {
386 self.plugins = self
387 .plugins
388 .add(BackgroundPlugin::new())
389 .add(SelectionPlugin::new())
390 .add(NodeInteractionPlugin::new())
391 .add(ViewportPlugin::new())
392 .add(NodePlugin::new())
393 .add(PortInteractionPlugin::new())
394 .add(EdgePlugin::new())
395 .add(DeletePlugin::new())
396 .add(HistoryPlugin::new());
397 self
398 }
399
400 pub fn sync_plugin(mut self, plugin: impl SyncPlugin + 'static) -> Self {
401 self.sync_plugin = Some(Box::new(plugin));
402 self
403 }
404
405 pub fn node_renderer<R>(mut self, name: impl Into<String>, renderer: R) -> Self
407 where
408 R: node_renderer::NodeRenderer + 'static,
409 {
410 self.renderers.register(name, renderer);
411 self
412 }
413
414 pub fn theme(mut self, theme: FlowTheme) -> Self {
416 self.theme = theme;
417 self
418 }
419
420 pub fn build(self) -> FlowCanvas {
421 let focus_handle = self.ctx.focus_handle();
422 let drawable_size = self.window.viewport_size();
423
424 let mut canvas = FlowCanvas {
425 graph: self.graph,
426 viewport: Viewport::new(),
427 plugins_registry: self.plugins,
428 sync_plugin: self.sync_plugin,
429 renderers: self.renderers,
430 focus_handle,
431 interaction: InteractionState::new(),
432 history: Box::new(LocalHistory::new()),
433 event_queue: vec![],
434 port_offset_cache: PortLayoutCache::new(),
435 clipboard_subgraph: None,
436 theme: self.theme,
437 };
438
439 if let Some(sync_plugin) = &mut canvas.sync_plugin {
440 let (change_sender, mut change_receiver) = mpsc::unbounded::<GraphChange>();
441
442 self.ctx
443 .spawn(async move |this, ctx| {
444 while let Some(change) = change_receiver.next().await {
445 let _ = this.update(ctx, |this, cx| {
446 this.graph.apply(change.kind);
447 cx.notify();
448 });
449 }
450 })
451 .detach();
452 sync_plugin.setup(change_sender);
453 }
454
455 canvas
456 .plugins_registry
457 .plugins
458 .sort_by_key(|p| -p.priority());
459
460 {
461 let mut ctx = InitPluginContext {
462 graph: &mut canvas.graph,
463 port_offset_cache: &mut canvas.port_offset_cache,
464 viewport: &mut canvas.viewport,
465 renderers: &mut canvas.renderers,
466 gpui_ctx: &self.ctx,
467 drawable_size,
468 theme: &mut canvas.theme,
469 };
471
472 for plugin in &mut canvas.plugins_registry.plugins {
473 plugin.setup(&mut ctx);
474 }
475 }
476
477 canvas
478 }
479}