Skip to main content

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::{Deserialize, Serialize, Serializer, de::Deserializer, ser::SerializeSeq};
10
11use anyhow::{Result, anyhow};
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      .scroll_source(egui::containers::scroll_area::ScrollSource {
600        scroll_bar: true,
601        drag: scrolling,
602        mouse_wheel: false,
603      })
604      .scroll_offset(scroll_offset);
605
606    // Show scroll area.
607    let out = scroll_area.show(ui, |ui| {
608      // Id for selecting nodes or dragging connections.
609      let id = ui.next_auto_id();
610
611      // Save old node style.
612      let old_node_style = NodeStyle::get(ui);
613
614      // Apply zoom to Ui style.
615      let node_style = NodeStyle::zoom_style(ui, zoom);
616
617      // Set node graph area.
618      ui.set_width(size.x);
619      ui.set_height(size.y);
620      // Need UI screen-space `min` to covert from graph-space to screen-space.
621      let ui_min = ui.min_rect().min.to_vec2();
622      let origin = origin + ui_min;
623      let state = self.ui_state.clone();
624      state.load(ui, origin, ui_min, zoom);
625
626      // Convert pointer position to graph-space.  (Used for adding new nodes).
627      let mut pointer_pos = emath::Pos2::default();
628      if let Some(pos) = ui.ctx().pointer_latest_pos() {
629        pointer_pos = pos;
630        if ui.ui_contains_pointer() {
631          self.editor.graph_pointer_pos = Some((pos - origin).to_vec2() / zoom);
632        }
633      }
634      // When not scrolling, detect click and drag to select nodes.
635      let mut area_resp = None;
636      let mut select_state = None;
637      if selecting {
638        let rect = ui.available_rect_before_wrap();
639        let resp = ui.interact(rect, id, egui::Sense::click_and_drag());
640        if resp.clicked() {
641          self.handle_clicked(clear_selected);
642        }
643        state.selecting_mut(|selecting| {
644          if resp.drag_started() {
645            selecting.drag_started(pointer_pos, clear_selected);
646            // Close the NodeFinder on clicks.
647            self.node_finder.close();
648          } else if resp.drag_stopped() {
649            selecting.drag_released();
650          } else {
651            selecting.update(pointer_pos);
652          }
653          select_state = Some(selecting.clone());
654        });
655        area_resp = Some(resp);
656      }
657
658      // Render groups.
659      let mut remove_group = None;
660      let mut resize_groups = BTreeSet::new();
661      let mut clicked_group = None;
662      for (group_id, group) in &mut self.groups.0 {
663        match state.render(ui, group) {
664          Some(NodeAction::Dragged(delta)) => {
665            let delta = delta / zoom;
666            for (_, node) in &mut self.nodes.0 {
667              if node.group_id == *group_id {
668                node.handle_move(delta);
669              }
670            }
671          }
672          Some(NodeAction::Clicked) => {
673            clicked_group = Some(*group_id);
674          }
675          Some(NodeAction::Delete(nodes)) => {
676            remove_group = Some((*group_id, nodes));
677          }
678          Some(NodeAction::JoinGroup(group_id)) => {
679            for node_id in self.ui_state.take_selected() {
680              if let Some(node) = self.nodes.0.get_mut(&node_id) {
681                node.group_id = group_id;
682              }
683            }
684            resize_groups.insert(group_id);
685          }
686          _ => (),
687        }
688      }
689      if let Some(group_id) = clicked_group {
690        self.handle_clicked(clear_selected);
691        self.select_node(group_id, true);
692      }
693      if let Some((group_id, remove_nodes)) = remove_group {
694        self.remove_group(group_id, remove_nodes);
695      }
696
697      // Draw connections.
698      let connection_style = NodeConnection::new(&node_style, ui_min);
699      self.render_connections(ui, id, &state, connection_style);
700
701      // Render nodes.
702      let mut remove_node = None;
703      let mut updated = false;
704      let mut clicked_node = None;
705      for (node_id, node) in &mut self.nodes.0 {
706        match state.render(ui, node) {
707          Some(NodeAction::Dragged(_) | NodeAction::Resize) => {
708            if !node.group_id.is_nil() {
709              resize_groups.insert(node.group_id);
710            }
711          }
712          Some(NodeAction::Clicked) => {
713            clicked_node = Some(*node_id);
714          }
715          Some(NodeAction::Delete(_)) => {
716            remove_node = Some(*node_id);
717          }
718          Some(NodeAction::LeaveGroup(group_id)) => {
719            resize_groups.insert(group_id);
720          }
721          _ => (),
722        }
723        updated |= node.updated;
724      }
725      if let Some(node_id) = clicked_node {
726        self.handle_clicked(clear_selected);
727        self.details_state.selected_node = Some(node_id);
728        self.select_node(node_id, true);
729      }
730
731      // Check for outputs that have changed their data types.
732      let outputs = state.take_updated_outputs();
733      if outputs.len() > 0 {
734        // Update any node that is connected to the changed outputs.
735        for (input, output) in self.connections.0.iter() {
736          if outputs.contains(output) {
737            if let Some(node) = self.nodes.0.get_mut(&input.node()) {
738              node.updated = true;
739            }
740          }
741        }
742      }
743      // Check if any of the nodes have been updated.
744      if updated {
745        self.updated();
746      }
747      // Handle node actions.
748      if let Some(node_id) = remove_node {
749        self.remove(node_id);
750      }
751      for group_id in resize_groups {
752        self.resize_group(group_id);
753      }
754
755      // Unload the graph state from egui.
756      state.unload(ui);
757
758      if let Some(selecting) = select_state {
759        selecting.ui(ui);
760      }
761
762      // Restore old NodeStyle and unzoom
763      old_node_style.unzoom_style(ui, zoom);
764
765      area_resp
766    });
767    // Save scroll offset and de-zoom it.
768    self.editor.scroll_offset = out.state.offset / zoom;
769
770    if let Some(resp) = out.inner {
771      resp.context_menu(|ui| self.context_menu(ui));
772      if !ui.ctx().is_popup_open() {
773        self.menu_state = None;
774      }
775    }
776  }
777
778  fn context_menu(&mut self, ui: &mut egui::Ui) {
779    let state = self
780      .menu_state
781      .get_or_insert_with(|| MenuState {
782        hover_connection: self.hover_connection,
783      })
784      .clone();
785    if ui.button("Create node").clicked() {
786      self.open_node_finder(ui);
787      ui.close_kind(egui::UiKind::Menu);
788    }
789    if self.has_selected() && ui.button("Group Nodes").clicked() {
790      self.group_selected_nodes();
791      ui.close_kind(egui::UiKind::Menu);
792    }
793    if let Some(input) = state.hover_connection {
794      if ui.button("Delete connection").clicked() {
795        if let Err(err) = self.disconnect(input) {
796          log::error!("Failed to delete connection: {err:?}");
797        }
798        ui.close_kind(egui::UiKind::Menu);
799      }
800    }
801  }
802
803  fn render_connections(
804    &mut self,
805    ui: &mut egui::Ui,
806    id: egui::Id,
807    state: &NodeGraphMeta,
808    conn: NodeConnection,
809  ) {
810    //let zoom = style.zoom;
811    // Check if a connection is being dragged.
812    state.drag_state_mut(|drag| {
813      // Handle connecting/disconnecting.
814      if ui.ctx().drag_stopped_id() == Some(id) {
815        ui.ctx().stop_dragging();
816        // The connection was dropped, take the sockets and check that they are compatible.
817        if let Some((src, dst)) = drag.take_sockets() {
818          if let Some((dst, dt)) = dst {
819            // Connect.
820            if let Err(err) = self.connect(src, dst, dt) {
821              log::warn!("Failed to connect input[{src:?}] to output[{dst:?}]: {err:?}");
822            }
823          } else {
824            // Disconnect
825            if let Err(err) = self.disconnect(src) {
826              log::warn!("Failed to disconnect input[{src:?}]: {err:?}");
827            }
828          }
829        }
830      } else if let Some(src) = &drag.src {
831        ui.ctx().set_dragged_id(id);
832        // Still dragging a connection.
833        let dst = if let Some(dst) = &drag.dst {
834          // Hovering over the destination socket.
835          ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
836          Some((conn.to_ui_pos(dst.center), dst.color))
837        } else if let Some(end) = ui.ctx().pointer_latest_pos() {
838          ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
839          // Try to scroll with the mouse pointer during the drag.
840          if let Some(last) = drag.pointer_last_pos {
841            let delta = (last - end) * 2.0;
842            ui.scroll_with_delta(delta);
843          }
844          drag.pointer_last_pos = Some(end);
845          Some((end, src.color))
846        } else {
847          None
848        };
849        if let Some((dst, color)) = dst {
850          let (start, end, color) = if let Some(input_id) = src.id.as_input_id() {
851            // If the dragged socket is an input, then remove it's current connection.
852            if let Err(err) = self.disconnect(input_id) {
853              log::warn!("Failed to disconnect input[{input_id:?}]: {err:?}");
854            }
855            (conn.to_ui_pos(src.center), dst, color)
856          } else {
857            // The dragged socket is an output.
858            (dst, conn.to_ui_pos(src.center), color)
859          };
860          conn.draw(ui, start, end, Some(color), false);
861        }
862      }
863    });
864
865    // Draw connections.
866    self.hover_connection = None;
867    for (input, output) in &self.connections.0 {
868      let meta = state.get_connection_meta(input, output);
869      if let Some((in_meta, out_meta)) = meta {
870        let start = conn.to_ui_pos(in_meta.center);
871        let end = conn.to_ui_pos(out_meta.center);
872        if conn
873          .draw(ui, start, end, Some(out_meta.color), true)
874          .is_some()
875        {
876          self.hover_connection = Some(*input);
877        }
878      }
879    }
880  }
881}
882
883#[derive(Clone)]
884#[cfg(feature = "egui")]
885pub struct NodeGraphEditor {
886  pub title: String,
887  pub size: emath::Vec2,
888  pub graph: NodeGraph,
889}
890
891#[cfg(feature = "egui")]
892impl Default for NodeGraphEditor {
893  fn default() -> Self {
894    Self {
895      title: "Graph editor".to_string(),
896      size: (900., 500.).into(),
897      graph: Default::default(),
898    }
899  }
900}
901
902#[cfg(feature = "egui")]
903impl NodeGraphEditor {
904  pub fn new() -> Self {
905    Self::default()
906  }
907
908  pub fn load<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
909    let path = path.as_ref();
910    let file = File::open(path)?;
911    self.graph = serde_json::from_reader(file)?;
912    Ok(())
913  }
914
915  pub fn show(&mut self, ctx: &egui::Context) {
916    egui::Window::new(&self.title)
917      .default_size(self.size)
918      .show(ctx, |ui| self.graph.show(ui));
919  }
920}