node_engine/
graph.rs

1use std::fs::File;
2use std::path::Path;
3
4use std::collections::BTreeSet;
5use uuid::Uuid;
6
7use indexmap::IndexMap;
8
9use serde::{de::Deserializer, ser::SerializeSeq, Deserialize, Serialize, Serializer};
10
11use anyhow::{anyhow, Result};
12
13#[cfg(feature = "egui")]
14use crate::ui::*;
15use crate::*;
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct EditorState {
19  size: emath::Vec2,
20  origin: emath::Vec2,
21  zoom: f32,
22  scroll_offset: emath::Vec2,
23  #[serde(skip)]
24  graph_pointer_pos: Option<emath::Vec2>,
25  #[serde(skip)]
26  add_node_at: Option<emath::Vec2>,
27}
28
29impl Default for EditorState {
30  fn default() -> Self {
31    let size = emath::vec2(10000.0, 10000.0);
32    let origin = size / 2.0;
33    Self {
34      size,
35      origin,
36      zoom: 0.5,
37      scroll_offset: origin - emath::vec2(450., 250.),
38      graph_pointer_pos: None,
39      add_node_at: None,
40    }
41  }
42}
43
44#[cfg(feature = "egui")]
45impl EditorState {
46  fn get_zoomed(&self) -> (emath::Vec2, emath::Vec2, emath::Vec2, f32) {
47    let mut size = self.size;
48    let mut origin = self.origin;
49    let mut scroll_offset = self.scroll_offset;
50    size.zoom(self.zoom);
51    origin.zoom(self.zoom);
52    scroll_offset.zoom(self.zoom);
53    (size, origin, scroll_offset, self.zoom)
54  }
55}
56
57pub trait GetId {
58  fn id(&self) -> Uuid;
59}
60
61#[derive(Clone, Debug)]
62struct IdMap<V>(pub(crate) IndexMap<Uuid, V>);
63
64impl<V> Default for IdMap<V> {
65  fn default() -> Self {
66    Self(Default::default())
67  }
68}
69
70impl<V> Serialize for IdMap<V>
71where
72  V: Serialize,
73{
74  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75  where
76    S: Serializer,
77  {
78    let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
79    for n in self.0.values() {
80      seq.serialize_element(n)?;
81    }
82    seq.end()
83  }
84}
85
86impl<'de, V> Deserialize<'de> for IdMap<V>
87where
88  V: GetId + serde::de::DeserializeOwned,
89{
90  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91  where
92    D: Deserializer<'de>,
93  {
94    let nodes = Vec::<V>::deserialize(deserializer)?;
95    Ok(Self(nodes.into_iter().map(|n| (n.id(), n)).collect()))
96  }
97}
98
99#[derive(Clone, Default, Debug)]
100struct ConnectionMap(pub(crate) IndexMap<InputId, OutputId>);
101
102impl Serialize for ConnectionMap {
103  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104  where
105    S: Serializer,
106  {
107    #[derive(Serialize)]
108    struct Connection<'a> {
109      input: &'a InputId,
110      output: &'a OutputId,
111    }
112    let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
113    for (input, output) in &self.0 {
114      seq.serialize_element(&Connection { input, output })?;
115    }
116    seq.end()
117  }
118}
119
120impl<'de> Deserialize<'de> for ConnectionMap {
121  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122  where
123    D: Deserializer<'de>,
124  {
125    #[derive(Deserialize)]
126    struct Connection {
127      input: InputId,
128      output: OutputId,
129    }
130    let connections = Vec::<Connection>::deserialize(deserializer)?;
131    Ok(Self(
132      connections
133        .into_iter()
134        .map(|c| (c.input, c.output))
135        .collect(),
136    ))
137  }
138}
139
140#[derive(Clone, Default, Debug, Serialize, Deserialize)]
141pub struct NodeGraphProperty {
142  id: Uuid,
143  name: String,
144  description: String,
145  value: Value,
146}
147
148impl GetId for NodeGraphProperty {
149  fn id(&self) -> Uuid {
150    self.id
151  }
152}
153
154#[derive(Clone, Debug)]
155pub struct NodeFinder {
156  pub registry: NodeRegistry,
157  pub node_filter: NodeFilter,
158  open: bool,
159  open_at: Option<emath::Pos2>,
160}
161
162impl Default for NodeFinder {
163  fn default() -> Self {
164    Self {
165      registry: NodeRegistry::build(),
166      node_filter: Default::default(),
167      open: false,
168      open_at: None,
169    }
170  }
171}
172
173impl NodeFinder {
174  pub fn open_at(&mut self, pos: emath::Pos2) {
175    self.open_at = Some(pos);
176    self.open = true;
177  }
178
179  pub fn close(&mut self) {
180    self.open = false;
181    self.open_at = None;
182  }
183
184  pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Node> {
185    if !self.open {
186      return None;
187    }
188
189    let mut area = egui::Area::new("NodeFinder".into());
190    if let Some(pos) = self.open_at.take() {
191      area = area.current_pos(pos);
192    }
193    let node = area.show(ui.ctx(), |ui| self.frame_ui(ui)).inner;
194    if node.is_some() {
195      // A node was selected close the finder.
196      self.close();
197    }
198    node
199  }
200
201  // Draw a frame around the node finder UI.
202  fn frame_ui(&mut self, ui: &mut egui::Ui) -> Option<Node> {
203    // Window-style frame.
204    let style = ui.style();
205    let mut frame = egui::Frame::window(style);
206    frame.shadow = Default::default();
207
208    let mut node = None;
209    frame.show(ui, |ui| {
210      ui.vertical(|ui| {
211        // Title bar.
212        ui.label("Create node");
213        // Node filter UI.
214        self.node_filter.ui(ui);
215        // Show available nodes from registry.
216        node = self.registry.ui(ui, &self.node_filter);
217      });
218    });
219    node
220  }
221}
222
223#[derive(Clone, Default, Debug, Serialize, Deserialize)]
224struct MenuState {
225  hover_connection: Option<InputId>,
226}
227
228#[derive(Clone, Default, Debug, Serialize, Deserialize)]
229struct DetailPanelState {
230  pub selected_node: Option<NodeId>,
231}
232
233#[derive(Clone, Default, Debug, Serialize, Deserialize)]
234pub struct NodeGraph {
235  id: Uuid,
236  editor: EditorState,
237  properties: IdMap<NodeGraphProperty>,
238  nodes: IdMap<Node>,
239  groups: IdMap<NodeGroup>,
240  connections: ConnectionMap,
241  output: Option<NodeId>,
242  #[serde(skip)]
243  changed: usize,
244  #[serde(skip)]
245  hover_connection: Option<InputId>,
246  #[serde(skip)]
247  menu_state: Option<MenuState>,
248  #[serde(skip)]
249  details_state: DetailPanelState,
250  #[serde(skip)]
251  #[cfg(feature = "egui")]
252  ui_state: NodeGraphMeta,
253  #[serde(skip)]
254  node_finder: NodeFinder,
255}
256
257impl NodeGraph {
258  pub fn new() -> Self {
259    Self {
260      id: Uuid::new_v4(),
261      ..Self::default()
262    }
263  }
264
265  pub fn add_group(&mut self, mut group: NodeGroup) -> NodeGroupId {
266    // Check for duplicate node group ids.
267    if self.groups.0.contains_key(&group.id) {
268      group.id = Uuid::new_v4();
269    }
270    let id = group.id;
271    self.groups.0.insert(id, group);
272    id
273  }
274
275  pub fn remove_group(&mut self, group_id: NodeGroupId, delete_nodes: bool) {
276    self.groups.0.shift_remove(&group_id);
277    if delete_nodes {
278      let mut nodes = Vec::new();
279      for (node_id, node) in &mut self.nodes.0 {
280        if node.group_id == group_id {
281          nodes.push(*node_id);
282        }
283      }
284      for node_id in nodes {
285        self.remove(node_id);
286      }
287    } else {
288      for (_, node) in &mut self.nodes.0 {
289        if node.group_id == group_id {
290          node.group_id = Uuid::nil();
291        }
292      }
293    }
294  }
295
296  pub fn resize_group(&mut self, group_id: NodeGroupId) {
297    if let Some(group) = self.groups.0.get_mut(&group_id) {
298      let mut area = emath::Rect::NOTHING;
299      for (_, node) in &mut self.nodes.0 {
300        if node.group_id == group_id {
301          area = area.union(node.rect());
302        }
303      }
304      group.set_area(area);
305    }
306  }
307
308  /// Returns the `changed` counter to detect when the graph needs to be recompiled.
309  pub fn changed_counter(&self) -> usize {
310    self.changed
311  }
312
313  // Inc. the `changed` counter to detect when the graph needs to be recompiled.
314  fn updated(&mut self) {
315    self.changed += 1;
316  }
317
318  pub fn add(&mut self, mut node: Node) -> NodeId {
319    self.updated();
320    if let Some(position) = &self.editor.add_node_at {
321      node.set_position(*position);
322    }
323    // Check for duplicate node ids.
324    if self.contains(node.id()) {
325      node.new_id();
326    }
327    let id = node.id();
328    self.nodes.0.insert(id, node);
329    id
330  }
331
332  pub fn remove(&mut self, id: NodeId) -> Option<Node> {
333    self.updated();
334    // Remove all connections to the node.
335    self.connections.0.retain(|input, output| {
336      if output.node() == id {
337        // Need to disconnect inputs from the nodes outputs.
338        let node = self.nodes.0.get_mut(&input.node());
339        if let Some(node) = node {
340          if let Err(err) = node.set_input(*input, Input::Disconnect) {
341            log::warn!("Failed to disconnect from input node: {err:?}");
342          }
343        }
344        false
345      } else if input.node() == id {
346        // We can just remove the nodes own inputs.
347        false
348      } else {
349        // Keep
350        true
351      }
352    });
353    #[cfg(feature = "egui")]
354    {
355      // Remove all UI state for the node
356      self.ui_state.remove_node(id);
357    }
358    // Remove node.
359    self.nodes.0.shift_remove(&id)
360  }
361
362  pub fn contains(&self, id: NodeId) -> bool {
363    self.nodes.0.contains_key(&id)
364  }
365
366  pub fn get_input_id<I: Into<InputKey>>(&self, id: NodeId, idx: I) -> Result<InputId> {
367    let node = self.get(id)?;
368    let idx = node.get_input_idx(&idx.into())?;
369    Ok(InputId::new(id, idx))
370  }
371
372  pub fn get_node_input<I: Into<InputKey>>(&self, id: NodeId, idx: I) -> Result<Input> {
373    self.get(id).and_then(|n| n.get_input(idx.into()))
374  }
375
376  pub fn set_node_input<I: Into<InputKey>>(
377    &mut self,
378    id: NodeId,
379    key: I,
380    value: Input,
381  ) -> Result<Option<OutputId>> {
382    let key = key.into();
383    // Get node.
384    let node = self
385      .nodes
386      .0
387      .get_mut(&id)
388      .ok_or_else(|| anyhow!("Missing node: {id:?}"))?;
389    // Convert Input key to id.
390    let input_id = node.get_input_idx(&key).map(|idx| InputId::new(id, idx))?;
391    // Update connections.
392    match &value {
393      Input::Disconnect => {
394        if self.connections.0.shift_remove(&input_id).is_none() {
395          // The input was already disconnected.  No change.
396          return Ok(None);
397        }
398      }
399      Input::Connect(output_id, _) => {
400        self.connections.0.insert(input_id, *output_id);
401      }
402      _ => {}
403    }
404    // Set the node input.
405    let old = node.set_input(key, value.clone())?;
406    // Mark graph as updated.
407    self.updated();
408    Ok(old)
409  }
410
411  pub fn set_input(&mut self, input_id: InputId, value: Input) -> Result<Option<OutputId>> {
412    self.set_node_input(input_id.node(), input_id, value)
413  }
414
415  pub fn disconnect(&mut self, input: InputId) -> Result<()> {
416    self.set_input(input, Input::Disconnect)?;
417    Ok(())
418  }
419
420  pub fn connect(&mut self, input: InputId, output: OutputId, dt: DataType) -> Result<()> {
421    self.set_input(input, Input::Connect(output, Some(dt)))?;
422    Ok(())
423  }
424
425  pub fn get(&self, id: NodeId) -> Result<&Node> {
426    self
427      .nodes
428      .0
429      .get(&id)
430      .ok_or_else(|| anyhow!("Missing node: {id:?}"))
431  }
432
433  pub fn get_mut(&mut self, id: NodeId) -> Result<&mut Node> {
434    self.updated();
435    self
436      .nodes
437      .0
438      .get_mut(&id)
439      .ok_or_else(|| anyhow!("Missing node: {id:?}"))
440  }
441
442  pub fn set_output(&mut self, output: Option<NodeId>) {
443    self.updated();
444    self.output = output;
445  }
446
447  pub fn output(&self) -> Option<NodeId> {
448    self.output
449  }
450}
451
452#[cfg(feature = "egui")]
453impl NodeGraph {
454  /// Returns true if there are selected nodes.
455  pub fn has_selected(&self) -> bool {
456    self.ui_state.has_selected()
457  }
458
459  /// Is the pointer hover a connection.
460  pub fn hover_connection(&self) -> Option<InputId> {
461    self.hover_connection
462  }
463
464  pub fn open_node_finder(&mut self, ui: &egui::Ui) {
465    if let Some(pos) = ui.ctx().pointer_latest_pos() {
466      self.editor.add_node_at = self.editor.graph_pointer_pos;
467      self.node_finder.open_at(pos);
468    }
469  }
470
471  pub fn group_selected_nodes(&mut self) -> Option<NodeGroupId> {
472    let mut group = NodeGroup::new();
473
474    let mut empty = true;
475    for node_id in self.ui_state.take_selected() {
476      if let Some(node) = self.nodes.0.get_mut(&node_id) {
477        group.add_node(node);
478        empty = false;
479      }
480    }
481
482    if empty {
483      None
484    } else {
485      let id = group.id;
486      self.groups.0.insert(id, group);
487      Some(id)
488    }
489  }
490
491  pub fn select_node(&mut self, id: NodeId, select: bool) {
492    self.ui_state.frame_state_mut(id, |frame| {
493      frame.selected = select;
494    });
495  }
496
497  pub fn show(&mut self, ui: &mut egui::Ui) {
498    self.show_details(ui);
499    self.show_graph(ui);
500  }
501
502  pub fn show_details(&mut self, ui: &mut egui::Ui) {
503    // Note: Without this side panel, the central panel will not work with a ScrollArea.
504    egui::SidePanel::right("graph_details_panel")
505      .min_width(150.0)
506      .resizable(false)
507      .show_inside(ui, |ui| self.details_ui(ui));
508  }
509
510  pub fn details_ui(&mut self, ui: &mut egui::Ui) {
511    let mut updated = false;
512    if let Some(id) = self.details_state.selected_node {
513      if let Some(node) = self.nodes.0.get_mut(&id) {
514        ui.vertical(|ui| {
515          ui.horizontal(|ui| {
516            ui.label("Name:");
517            ui.text_edit_singleline(&mut node.name);
518          });
519          if node.details_ui(ui, id) {
520            updated = true;
521          }
522        });
523      }
524    } else {
525      // Show tips.
526      ui.label("Click node to view/edit details");
527    }
528    if updated {
529      self.updated();
530    }
531  }
532
533  pub fn show_graph(&mut self, ui: &mut egui::Ui) {
534    egui::CentralPanel::default().show_inside(ui, |ui| self.graph_ui(ui));
535  }
536
537  fn handle_clicked(&mut self, clear_on_click: bool) {
538    if clear_on_click {
539      self.details_state.selected_node = None;
540      self.ui_state.clear_selected();
541    }
542  }
543
544  pub fn graph_ui(&mut self, ui: &mut egui::Ui) {
545    // Show the node finder if it is open.
546    if let Some(node) = self.node_finder.ui(ui) {
547      self.add(node);
548    }
549
550    let mut scrolling = true;
551    let mut selecting = true;
552    let mut clear_selected = true;
553    // Detect drag mode.
554    // * Select nodes only in dragged area - Primary mouse button and no modifiers.
555    // * Add nodes in dragged area to selected set - Primary mouse button + SHIFT.
556    // * Scroll - Primary mouse button + CTRL.
557    ui.input(|i| {
558      // Enable scrolling when CTRL is down.
559      if i.modifiers.ctrl {
560        selecting = false;
561      }
562      // Don't scroll from secondary mouse button.
563      if i.pointer.secondary_down() {
564        // Don't clear selected when opening the Context menu.
565        clear_selected = false;
566        scrolling = false;
567      }
568      // When SHIFT is down keep current selected nodes.
569      if i.modifiers.shift {
570        clear_selected = false;
571      }
572    });
573
574    if ui.ui_contains_pointer() {
575      // Use mouse wheel for zoom instead of scrolling.
576      // Mouse wheel + ctrl scrolling left/right.
577      // Multitouch (pinch gesture) zoom.
578      let z_delta = ui.input(|i| {
579        // Use up/down mouse wheel for zoom.
580        let scroll_delta = i.raw_scroll_delta.y;
581        if scroll_delta > 0.1 {
582          0.01
583        } else if scroll_delta < -0.1 {
584          -0.01
585        } else {
586          // For Multitouch devices (pinch gesture).
587          i.zoom_delta() - 1.0
588        }
589      });
590      if z_delta != 0.0 {
591        let zoom = (self.editor.zoom + z_delta).clamp(0.1, 1.0);
592        self.editor.zoom = zoom;
593        scrolling = false;
594      }
595    }
596    let (size, origin, scroll_offset, zoom) = self.editor.get_zoomed();
597    // Create scroll area and restore zoomed scroll offset.
598    let scroll_area = egui::ScrollArea::both()
599      .enable_scrolling(scrolling)
600      .scroll_offset(scroll_offset);
601
602    // Show scroll area.
603    let out = scroll_area.show(ui, |ui| {
604      // Id for selecting nodes or dragging connections.
605      let id = ui.next_auto_id();
606
607      // Save old node style.
608      let old_node_style = NodeStyle::get(ui);
609
610      // Apply zoom to Ui style.
611      let node_style = NodeStyle::zoom_style(ui, zoom);
612
613      // Set node graph area.
614      ui.set_width(size.x);
615      ui.set_height(size.y);
616      // Need UI screen-space `min` to covert from graph-space to screen-space.
617      let ui_min = ui.min_rect().min.to_vec2();
618      let origin = origin + ui_min;
619      let state = self.ui_state.clone();
620      state.load(ui, origin, ui_min, zoom);
621
622      // Convert pointer position to graph-space.  (Used for adding new nodes).
623      let mut pointer_pos = emath::Pos2::default();
624      if let Some(pos) = ui.ctx().pointer_latest_pos() {
625        pointer_pos = pos;
626        if ui.ui_contains_pointer() {
627          self.editor.graph_pointer_pos = Some((pos - origin).to_vec2() / zoom);
628        }
629      }
630      // When not scrolling, detect click and drag to select nodes.
631      let mut area_resp = None;
632      let mut select_state = None;
633      if selecting {
634        let rect = ui.available_rect_before_wrap();
635        let resp = ui.interact(rect, id, egui::Sense::click_and_drag());
636        if resp.clicked() {
637          self.handle_clicked(clear_selected);
638        }
639        state.selecting_mut(|selecting| {
640          if resp.drag_started() {
641            selecting.drag_started(pointer_pos, clear_selected);
642            // Close the NodeFinder on clicks.
643            self.node_finder.close();
644          } else if resp.drag_stopped() {
645            selecting.drag_released();
646          } else {
647            selecting.update(pointer_pos);
648          }
649          select_state = Some(selecting.clone());
650        });
651        area_resp = Some(resp);
652      }
653
654      // Render groups.
655      let mut remove_group = None;
656      let mut resize_groups = BTreeSet::new();
657      let mut clicked_group = None;
658      for (group_id, group) in &mut self.groups.0 {
659        match state.render(ui, group) {
660          Some(NodeAction::Dragged(delta)) => {
661            let delta = delta / zoom;
662            for (_, node) in &mut self.nodes.0 {
663              if node.group_id == *group_id {
664                node.handle_move(delta);
665              }
666            }
667          }
668          Some(NodeAction::Clicked) => {
669            clicked_group = Some(*group_id);
670          }
671          Some(NodeAction::Delete(nodes)) => {
672            remove_group = Some((*group_id, nodes));
673          }
674          Some(NodeAction::JoinGroup(group_id)) => {
675            for node_id in self.ui_state.take_selected() {
676              if let Some(node) = self.nodes.0.get_mut(&node_id) {
677                node.group_id = group_id;
678              }
679            }
680            resize_groups.insert(group_id);
681          }
682          _ => (),
683        }
684      }
685      if let Some(group_id) = clicked_group {
686        self.handle_clicked(clear_selected);
687        self.select_node(group_id, true);
688      }
689      if let Some((group_id, remove_nodes)) = remove_group {
690        self.remove_group(group_id, remove_nodes);
691      }
692
693      // Draw connections.
694      let connection_style = NodeConnection::new(&node_style, ui_min);
695      self.render_connections(ui, id, &state, connection_style);
696
697      // Render nodes.
698      let mut remove_node = None;
699      let mut updated = false;
700      let mut clicked_node = None;
701      for (node_id, node) in &mut self.nodes.0 {
702        match state.render(ui, node) {
703          Some(NodeAction::Dragged(_) | NodeAction::Resize) => {
704            if !node.group_id.is_nil() {
705              resize_groups.insert(node.group_id);
706            }
707          }
708          Some(NodeAction::Clicked) => {
709            clicked_node = Some(*node_id);
710          }
711          Some(NodeAction::Delete(_)) => {
712            remove_node = Some(*node_id);
713          }
714          Some(NodeAction::LeaveGroup(group_id)) => {
715            resize_groups.insert(group_id);
716          }
717          _ => (),
718        }
719        updated |= node.updated;
720      }
721      if let Some(node_id) = clicked_node {
722        self.handle_clicked(clear_selected);
723        self.details_state.selected_node = Some(node_id);
724        self.select_node(node_id, true);
725      }
726
727      // Check for outputs that have changed their data types.
728      let outputs = state.take_updated_outputs();
729      if outputs.len() > 0 {
730        // Update any node that is connected to the changed outputs.
731        for (input, output) in self.connections.0.iter() {
732          if outputs.contains(output) {
733            if let Some(node) = self.nodes.0.get_mut(&input.node()) {
734              node.updated = true;
735            }
736          }
737        }
738      }
739      // Check if any of the nodes have been updated.
740      if updated {
741        self.updated();
742      }
743      // Handle node actions.
744      if let Some(node_id) = remove_node {
745        self.remove(node_id);
746      }
747      for group_id in resize_groups {
748        self.resize_group(group_id);
749      }
750
751      // Unload the graph state from egui.
752      state.unload(ui);
753
754      if let Some(selecting) = select_state {
755        selecting.ui(ui);
756      }
757
758      // Restore old NodeStyle and unzoom
759      old_node_style.unzoom_style(ui, zoom);
760
761      area_resp
762    });
763    // Save scroll offset and de-zoom it.
764    self.editor.scroll_offset = out.state.offset / zoom;
765
766    if let Some(resp) = out.inner {
767      resp.context_menu(|ui| self.context_menu(ui));
768      if !ui.ctx().is_context_menu_open() {
769        self.menu_state = None;
770      }
771    }
772  }
773
774  fn context_menu(&mut self, ui: &mut egui::Ui) {
775    let state = self
776      .menu_state
777      .get_or_insert_with(|| MenuState {
778        hover_connection: self.hover_connection,
779      })
780      .clone();
781    if ui.button("Create node").clicked() {
782      self.open_node_finder(ui);
783      ui.close_menu();
784    }
785    if self.has_selected() && ui.button("Group Nodes").clicked() {
786      self.group_selected_nodes();
787      ui.close_menu();
788    }
789    if let Some(input) = state.hover_connection {
790      if ui.button("Delete connection").clicked() {
791        if let Err(err) = self.disconnect(input) {
792          log::error!("Failed to delete connection: {err:?}");
793        }
794        ui.close_menu();
795      }
796    }
797  }
798
799  fn render_connections(
800    &mut self,
801    ui: &mut egui::Ui,
802    id: egui::Id,
803    state: &NodeGraphMeta,
804    conn: NodeConnection,
805  ) {
806    //let zoom = style.zoom;
807    // Check if a connection is being dragged.
808    state.drag_state_mut(|drag| {
809      // Handle connecting/disconnecting.
810      if ui.ctx().drag_stopped_id() == Some(id) {
811        ui.ctx().stop_dragging();
812        // The connection was dropped, take the sockets and check that they are compatible.
813        if let Some((src, dst)) = drag.take_sockets() {
814          if let Some((dst, dt)) = dst {
815            // Connect.
816            if let Err(err) = self.connect(src, dst, dt) {
817              log::warn!("Failed to connect input[{src:?}] to output[{dst:?}]: {err:?}");
818            }
819          } else {
820            // Disconnect
821            if let Err(err) = self.disconnect(src) {
822              log::warn!("Failed to disconnect input[{src:?}]: {err:?}");
823            }
824          }
825        }
826      } else if let Some(src) = &drag.src {
827        ui.ctx().set_dragged_id(id);
828        // Still dragging a connection.
829        let dst = if let Some(dst) = &drag.dst {
830          // Hovering over the destination socket.
831          ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
832          Some((conn.to_ui_pos(dst.center), dst.color))
833        } else if let Some(end) = ui.ctx().pointer_latest_pos() {
834          ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
835          // Try to scroll with the mouse pointer during the drag.
836          if let Some(last) = drag.pointer_last_pos {
837            let delta = (last - end) * 2.0;
838            ui.scroll_with_delta(delta);
839          }
840          drag.pointer_last_pos = Some(end);
841          Some((end, src.color))
842        } else {
843          None
844        };
845        if let Some((dst, color)) = dst {
846          let (start, end, color) = if let Some(input_id) = src.id.as_input_id() {
847            // If the dragged socket is an input, then remove it's current connection.
848            if let Err(err) = self.disconnect(input_id) {
849              log::warn!("Failed to disconnect input[{input_id:?}]: {err:?}");
850            }
851            (conn.to_ui_pos(src.center), dst, color)
852          } else {
853            // The dragged socket is an output.
854            (dst, conn.to_ui_pos(src.center), color)
855          };
856          conn.draw(ui, start, end, Some(color), false);
857        }
858      }
859    });
860
861    // Draw connections.
862    self.hover_connection = None;
863    for (input, output) in &self.connections.0 {
864      let meta = state.get_connection_meta(input, output);
865      if let Some((in_meta, out_meta)) = meta {
866        let start = conn.to_ui_pos(in_meta.center);
867        let end = conn.to_ui_pos(out_meta.center);
868        if conn
869          .draw(ui, start, end, Some(out_meta.color), true)
870          .is_some()
871        {
872          self.hover_connection = Some(*input);
873        }
874      }
875    }
876  }
877}
878
879#[derive(Clone)]
880#[cfg(feature = "egui")]
881pub struct NodeGraphEditor {
882  pub title: String,
883  pub size: emath::Vec2,
884  pub graph: NodeGraph,
885}
886
887#[cfg(feature = "egui")]
888impl Default for NodeGraphEditor {
889  fn default() -> Self {
890    Self {
891      title: "Graph editor".to_string(),
892      size: (900., 500.).into(),
893      graph: Default::default(),
894    }
895  }
896}
897
898#[cfg(feature = "egui")]
899impl NodeGraphEditor {
900  pub fn new() -> Self {
901    Self::default()
902  }
903
904  pub fn load<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
905    let path = path.as_ref();
906    let file = File::open(path)?;
907    self.graph = serde_json::from_reader(file)?;
908    Ok(())
909  }
910
911  pub fn show(&mut self, ctx: &egui::Context) {
912    egui::Window::new(&self.title)
913      .default_size(self.size)
914      .show(ctx, |ui| self.graph.show(ui));
915  }
916}