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 self.close();
197 }
198 node
199 }
200
201 fn frame_ui(&mut self, ui: &mut egui::Ui) -> Option<Node> {
203 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 ui.label("Create node");
213 self.node_filter.ui(ui);
215 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 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 pub fn changed_counter(&self) -> usize {
310 self.changed
311 }
312
313 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 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 self.connections.0.retain(|input, output| {
336 if output.node() == id {
337 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 false
348 } else {
349 true
351 }
352 });
353 #[cfg(feature = "egui")]
354 {
355 self.ui_state.remove_node(id);
357 }
358 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 let node = self
385 .nodes
386 .0
387 .get_mut(&id)
388 .ok_or_else(|| anyhow!("Missing node: {id:?}"))?;
389 let input_id = node.get_input_idx(&key).map(|idx| InputId::new(id, idx))?;
391 match &value {
393 Input::Disconnect => {
394 if self.connections.0.shift_remove(&input_id).is_none() {
395 return Ok(None);
397 }
398 }
399 Input::Connect(output_id, _) => {
400 self.connections.0.insert(input_id, *output_id);
401 }
402 _ => {}
403 }
404 let old = node.set_input(key, value.clone())?;
406 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 pub fn has_selected(&self) -> bool {
456 self.ui_state.has_selected()
457 }
458
459 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 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 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 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 ui.input(|i| {
558 if i.modifiers.ctrl {
560 selecting = false;
561 }
562 if i.pointer.secondary_down() {
564 clear_selected = false;
566 scrolling = false;
567 }
568 if i.modifiers.shift {
570 clear_selected = false;
571 }
572 });
573
574 if ui.ui_contains_pointer() {
575 let z_delta = ui.input(|i| {
579 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 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 let scroll_area = egui::ScrollArea::both()
599 .enable_scrolling(scrolling)
600 .scroll_offset(scroll_offset);
601
602 let out = scroll_area.show(ui, |ui| {
604 let id = ui.next_auto_id();
606
607 let old_node_style = NodeStyle::get(ui);
609
610 let node_style = NodeStyle::zoom_style(ui, zoom);
612
613 ui.set_width(size.x);
615 ui.set_height(size.y);
616 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 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 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 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 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 let connection_style = NodeConnection::new(&node_style, ui_min);
695 self.render_connections(ui, id, &state, connection_style);
696
697 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 let outputs = state.take_updated_outputs();
729 if outputs.len() > 0 {
730 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 if updated {
741 self.updated();
742 }
743 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 state.unload(ui);
753
754 if let Some(selecting) = select_state {
755 selecting.ui(ui);
756 }
757
758 old_node_style.unzoom_style(ui, zoom);
760
761 area_resp
762 });
763 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 state.drag_state_mut(|drag| {
809 if ui.ctx().drag_stopped_id() == Some(id) {
811 ui.ctx().stop_dragging();
812 if let Some((src, dst)) = drag.take_sockets() {
814 if let Some((dst, dt)) = dst {
815 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 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 let dst = if let Some(dst) = &drag.dst {
830 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 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 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 (dst, conn.to_ui_pos(src.center), color)
855 };
856 conn.draw(ui, start, end, Some(color), false);
857 }
858 }
859 });
860
861 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}