1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::hash::Hash;
3use std::sync::{Arc, Mutex};
4
5#[cfg(feature = "layout")]
6pub use layout::layout;
7pub use node::{FramedResponse, NodeCtx, NodeId, NodeInteraction};
8pub use socket::layout::{grid::SocketGrid, SocketLayout};
9pub use socket::{SocketKind, SocketResponses};
10
11pub mod bezier;
12pub mod edge;
13#[cfg(feature = "layout")]
14pub mod layout;
15pub mod node;
16pub mod socket;
17
18pub struct Graph {
20 background: bool,
21 dot_grid: bool,
22 zoom_range: egui::Rangef,
23 max_inner_size: Option<egui::Vec2>,
24 center_view: bool,
25 id: egui::Id,
26 selected_nodes: Option<HashSet<NodeId>>,
28 immutable: bool,
36}
37
38#[derive(Clone, Default)]
40pub struct GraphTempMemory {
41 node_sizes: NodeSizes,
46 selection: Selection,
48 pressed: Option<Pressed>,
52 sockets: HashMap<NodeId, NodeSockets>,
56 closest_socket: Option<socket::Socket>,
60}
61
62type NodeSizes = HashMap<NodeId, egui::Vec2>;
63
64#[derive(Clone, Default)]
65struct Selection {
66 nodes: HashSet<NodeId>,
68 changed: bool,
70}
71
72#[derive(Clone, Debug)]
74struct Pressed {
75 over_selection_at_origin: bool,
82 origin_pos: egui::Pos2,
84 current_pos: egui::Pos2,
86 action: PressAction,
88}
89
90#[derive(Clone, Debug)]
91enum PressAction {
92 DragNodes {
94 node: Option<PressedNode>,
99 },
100 Select,
102 Socket(socket::Socket),
104}
105
106#[derive(Clone, Debug)]
107struct PressedNode {
108 id: NodeId,
110 position_at_origin: egui::Pos2,
112}
113
114#[derive(Debug, Clone, PartialEq)]
118#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
119pub struct View {
120 pub scene_rect: egui::Rect,
122 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_sorted_layout"))]
123 pub layout: Layout,
124}
125
126#[cfg(feature = "serde")]
127fn serialize_sorted_layout<S: serde::Serializer>(layout: &Layout, s: S) -> Result<S::Ok, S::Error> {
128 use serde::Serialize;
129 let sorted: BTreeMap<_, _> = layout.iter().collect();
130 sorted.serialize(s)
131}
132
133pub type Layout = HashMap<NodeId, egui::Pos2>;
135
136pub struct Show<'a> {
138 graph_id: egui::Id,
140 graph_rect: egui::Rect,
142 selection_rect: Option<egui::Rect>,
144 select: bool,
146 closest_socket: Option<socket::Socket>,
148 socket_press_released: Option<socket::Socket>,
150 visited: &'a mut HashSet<NodeId>,
154 layout: &'a mut Layout,
155 immutable: bool,
157}
158
159#[derive(Clone)]
161pub struct NodeSockets {
162 flow: egui::Direction,
163 inputs: BTreeMap<usize, egui::Pos2>,
164 outputs: BTreeMap<usize, egui::Pos2>,
165}
166
167pub struct NodesCtx<'a> {
169 pub graph_id: egui::Id,
170 graph_rect: egui::Rect,
171 selection_rect: Option<egui::Rect>,
172 select: bool,
173 socket_press_released: Option<socket::Socket>,
174 visited: &'a mut HashSet<NodeId>,
175 layout: &'a mut Layout,
176 pub immutable: bool,
178}
179
180pub struct EdgesCtx {
182 graph_id: egui::Id,
183 graph_rect: egui::Rect,
184 selection_rect: Option<egui::Rect>,
185 closest_socket: Option<socket::Socket>,
186 pub immutable: bool,
188}
189
190struct GraphInteraction {
193 pressed: Option<Pressed>,
194 socket_press_released: Option<socket::Socket>,
195 select: bool,
196 selection_rect: Option<egui::Rect>,
197 drag_nodes_delta: egui::Vec2,
198}
199
200pub struct GraphResponse<R> {
202 pub inner: R,
204 pub response: egui::Response,
206 pub selection_changed: Option<HashSet<NodeId>>,
208}
209
210impl Graph {
211 pub const DEFAULT_ZOOM_RANGE: egui::Rangef = egui::Rangef {
216 min: 0.25,
217 max: 1.0,
218 };
219 pub const DEFAULT_CENTER_VIEW: bool = false;
220
221 pub fn new(id_src: impl Hash) -> Self {
223 Self::from_id(id(id_src))
224 }
225
226 pub fn from_id(id: egui::Id) -> Self {
228 Self {
229 background: true,
230 dot_grid: true,
231 zoom_range: Self::DEFAULT_ZOOM_RANGE,
232 max_inner_size: None,
233 center_view: Self::DEFAULT_CENTER_VIEW,
234 id,
235 selected_nodes: None,
236 immutable: false,
237 }
238 }
239
240 pub fn background(mut self, show: bool) -> Self {
242 self.background = show;
243 self
244 }
245
246 pub fn dot_grid(mut self, show: bool) -> Self {
248 self.dot_grid = show;
249 self
250 }
251
252 pub fn zoom_range(mut self, zoom_range: impl Into<egui::Rangef>) -> Self {
258 self.zoom_range = zoom_range.into();
259 self
260 }
261
262 #[inline]
264 pub fn max_inner_size(mut self, max_inner_size: impl Into<egui::Vec2>) -> Self {
265 self.max_inner_size = Some(max_inner_size.into());
266 self
267 }
268
269 pub fn center_view(mut self, center_view: bool) -> Self {
273 self.center_view = center_view;
274 self
275 }
276
277 pub fn selected_nodes(mut self, nodes: HashSet<NodeId>) -> Self {
282 self.selected_nodes = Some(nodes);
283 self
284 }
285
286 pub fn immutable(mut self, immutable: bool) -> Self {
294 self.immutable = immutable;
295 self
296 }
297
298 pub fn show<R>(
303 mut self,
304 view: &mut View,
305 ui: &mut egui::Ui,
306 content: impl FnOnce(&mut egui::Ui, Show) -> R,
307 ) -> GraphResponse<R> {
308 let graph_rect = ui.available_rect_before_wrap();
310
311 let View {
312 ref mut scene_rect,
313 ref mut layout,
314 } = *view;
315
316 let mut scene = egui::containers::Scene::new()
318 .zoom_range(self.zoom_range.clone())
319 .drag_pan_buttons(egui::containers::DragPanButtons::MIDDLE);
320 if let Some(max_inner_size) = self.max_inner_size {
321 scene = scene.max_inner_size(max_inner_size);
322 }
323
324 let mut bounding_rect = None;
326
327 let scene_response = scene.show(ui, scene_rect, |ui| {
328 let mut selection_rect = None;
330 let mut select = false;
331 let mut closest_socket = None;
332 let mut socket_press_released = None;
333
334 let scene_response = ui.response();
336 let ptr_on_graph = scene_response.hovered();
337
338 let gmem_arc = memory(ui, self.id);
340 let mut gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
341
342 if let Some(nodes) = self.selected_nodes.take() {
344 gmem.selection.nodes = nodes;
345 }
346
347 gmem.selection.changed = false;
349
350 let pointer = ui.input(|i| i.pointer.clone());
358 if let Some(ptr_global) = pointer.interact_pos().or(pointer.hover_pos()) {
359 let ptr_graph = ui
360 .ctx()
361 .layer_transform_from_global(ui.layer_id())
362 .unwrap_or_default()
363 .mul_pos(ptr_global);
364
365 closest_socket = ui.response().hover_pos().and_then(|pos| {
367 find_closest_socket(pos, layout, &gmem, ui).map(|(socket, _dist_sqrd)| socket)
368 });
369
370 let closest_socket_for_interaction =
372 if self.immutable { None } else { closest_socket };
373
374 let interaction = graph_interaction(
376 layout,
377 &pointer,
378 closest_socket_for_interaction,
379 ptr_on_graph,
380 ptr_graph,
381 gmem.pressed.as_ref(),
382 );
383
384 if !self.immutable && interaction.drag_nodes_delta != egui::Vec2::ZERO {
386 if let Some(pressed) = gmem.pressed.as_ref() {
387 if let PressAction::DragNodes { .. } = pressed.action {
388 for &n_id in &gmem.selection.nodes {
389 if let Some(pos) = layout.get_mut(&n_id) {
390 *pos += interaction.drag_nodes_delta;
391 }
392 }
393 }
394 }
395 }
396
397 gmem.pressed = interaction.pressed;
398 gmem.closest_socket = closest_socket;
399 selection_rect = interaction.selection_rect;
400 select = interaction.select;
401 socket_press_released = interaction.socket_press_released;
402 }
403
404 let visible_rect = ui.clip_rect();
406 if self.background {
407 paint_background(visible_rect, ui);
408 }
409
410 if self.dot_grid {
412 paint_dot_grid(visible_rect, ui);
413 }
414
415 if let Some(sel_rect) = selection_rect {
418 paint_selection_area(sel_rect, ui);
419 }
420
421 let mut visited = HashSet::default();
422
423 let show = Show {
424 graph_id: self.id,
425 graph_rect,
426 selection_rect,
427 select,
428 closest_socket,
429 socket_press_released,
430 visited: &mut visited,
431 layout,
432 immutable: self.immutable,
433 };
434
435 std::mem::drop(gmem);
437
438 let output = content(ui, show);
439
440 prune_unused_nodes(self.id, &visited, ui);
441 bounding_rect = Some(ui.min_rect());
442
443 let gmem_arc = memory(ui, self.id);
445 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
446 let selection_changed = if gmem.selection.changed {
447 Some(gmem.selection.nodes.clone())
448 } else {
449 None
450 };
451
452 (output, selection_changed)
453 });
454
455 if self.center_view {
456 if let Some(rect) = bounding_rect {
457 view.scene_rect = rect.expand(rect.width() * 0.1);
458 }
459 }
460
461 let (inner, selection_changed) = scene_response.inner;
462 GraphResponse {
463 inner,
464 response: scene_response.response,
465 selection_changed,
466 }
467 }
468}
469
470impl GraphTempMemory {
471 pub fn node_sizes(&self) -> &NodeSizes {
473 &self.node_sizes
474 }
475}
476
477impl NodeSockets {
478 pub fn input(&self, ix: usize) -> Option<(egui::Pos2, egui::Vec2)> {
482 self.inputs
483 .get(&ix)
484 .map(|&pos| (pos, input_normal(self.flow)))
485 }
486
487 pub fn output(&self, ix: usize) -> Option<(egui::Pos2, egui::Vec2)> {
491 self.outputs
492 .get(&ix)
493 .map(|&pos| (pos, output_normal(self.flow)))
494 }
495
496 pub fn inputs(&self) -> impl Iterator<Item = (usize, egui::Pos2, egui::Vec2)> + '_ {
498 let norm = input_normal(self.flow);
499 self.inputs.iter().map(move |(&ix, &pos)| (ix, pos, norm))
500 }
501
502 pub fn outputs(&self) -> impl Iterator<Item = (usize, egui::Pos2, egui::Vec2)> + '_ {
504 let norm = output_normal(self.flow);
505 self.outputs.iter().map(move |(&ix, &pos)| (ix, pos, norm))
506 }
507}
508
509fn input_normal(flow: egui::Direction) -> egui::Vec2 {
510 match flow {
511 egui::Direction::LeftToRight => egui::Vec2::new(-1.0, 0.0),
512 egui::Direction::RightToLeft => egui::Vec2::new(1.0, 0.0),
513 egui::Direction::TopDown => egui::Vec2::new(0.0, -1.0),
514 egui::Direction::BottomUp => egui::Vec2::new(0.0, 1.0),
515 }
516}
517
518fn output_normal(flow: egui::Direction) -> egui::Vec2 {
519 match flow {
520 egui::Direction::LeftToRight => egui::Vec2::new(1.0, 0.0),
521 egui::Direction::RightToLeft => egui::Vec2::new(-1.0, 0.0),
522 egui::Direction::TopDown => egui::Vec2::new(0.0, 1.0),
523 egui::Direction::BottomUp => egui::Vec2::new(0.0, -1.0),
524 }
525}
526
527impl<'a> Show<'a> {
528 pub fn nodes(
530 mut self,
531 ui: &mut egui::Ui,
532 content: impl FnOnce(&mut NodesCtx, &mut egui::Ui),
533 ) -> Self {
534 {
535 let Self {
536 graph_id,
537 graph_rect,
538 selection_rect,
539 select,
540 socket_press_released,
541 ref mut visited,
542 ref mut layout,
543 immutable,
544 ..
545 } = self;
546 let mut ctx = NodesCtx {
547 graph_id,
548 graph_rect,
549 selection_rect,
550 select,
551 socket_press_released,
552 visited: &mut *visited,
553 layout: &mut *layout,
554 immutable,
555 };
556 content(&mut ctx, ui);
557 }
558 self
559 }
560
561 pub fn edges(
563 self,
564 ui: &mut egui::Ui,
565 content: impl FnOnce(&mut EdgesCtx, &mut egui::Ui),
566 ) -> Self {
567 {
568 let Self {
569 graph_rect,
570 graph_id,
571 selection_rect,
572 closest_socket,
573 immutable,
574 ..
575 } = self;
576 let mut ctx = EdgesCtx {
577 graph_id,
578 graph_rect,
579 selection_rect,
580 closest_socket,
581 immutable,
582 };
583 content(&mut ctx, ui);
584 }
585 self
586 }
587}
588
589fn prune_unused_nodes(graph_id: egui::Id, visited: &HashSet<NodeId>, ui: &mut egui::Ui) {
592 let gmem_arc = memory(ui, graph_id);
593 let mut gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
594 gmem.node_sizes.retain(|k, _| visited.contains(k));
595 gmem.selection.nodes.retain(|k| visited.contains(k));
596 if let Some(socket) = gmem.closest_socket.as_ref() {
597 if !visited.contains(&socket.node) {
598 gmem.closest_socket = None;
599 }
600 }
601 if let Some(pressed) = gmem.pressed.as_ref() {
602 match pressed.action {
603 PressAction::DragNodes {
604 node: Some(PressedNode { id: n, .. }),
605 }
606 | PressAction::Socket(socket::Socket { node: n, .. })
607 if !visited.contains(&n) =>
608 {
609 gmem.pressed = None
610 }
611 _ => (),
612 }
613 }
614}
615
616impl EdgesCtx {
617 pub fn input(
621 &self,
622 ui: &egui::Ui,
623 node: NodeId,
624 input: usize,
625 ) -> Option<(egui::Pos2, egui::Vec2)> {
626 let gmem_arc = crate::memory(ui, self.graph_id);
627 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
628 gmem.sockets
629 .get(&node)
630 .and_then(|sockets| sockets.input(input))
631 }
632
633 pub fn output(
637 &self,
638 ui: &egui::Ui,
639 node: NodeId,
640 output: usize,
641 ) -> Option<(egui::Pos2, egui::Vec2)> {
642 let gmem_arc = memory(ui, self.graph_id);
643 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
644 gmem.sockets
645 .get(&node)
646 .and_then(|sockets| sockets.output(output))
647 }
648
649 pub fn in_progress(&self, ui: &egui::Ui) -> Option<EdgeInProgress> {
651 let gmem_arc = memory(ui, self.graph_id);
652 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
653 let pressed = gmem.pressed.as_ref()?;
654 let start = match pressed.action {
655 PressAction::Socket(socket) => {
656 let sockets = gmem.sockets.get(&socket.node)?;
657 let (pos, normal) = match socket.kind {
658 socket::SocketKind::Input => sockets.input(socket.index)?,
659 socket::SocketKind::Output => sockets.output(socket.index)?,
660 };
661 socket::PositionedSocket {
662 socket,
663 pos,
664 normal,
665 }
666 }
667 _ => return None,
668 };
669 let (end_pos, end_socket) = match gmem.closest_socket {
670 Some(socket) if socket.kind != start.socket.kind => {
671 let sockets = gmem.sockets.get(&socket.node)?;
672 let (pos, normal) = match socket.kind {
673 socket::SocketKind::Input => sockets.input(socket.index)?,
674 socket::SocketKind::Output => sockets.output(socket.index)?,
675 };
676 (pos, Some((socket.kind, normal)))
677 }
678 _ => (pressed.current_pos, None),
679 };
680 Some(EdgeInProgress {
681 start,
682 end_pos,
683 end_socket,
684 })
685 }
686
687 pub fn graph_rect(&self) -> egui::Rect {
689 self.graph_rect
690 }
691}
692
693pub struct EdgeInProgress {
694 pub start: socket::PositionedSocket,
696 pub end_pos: egui::Pos2,
702 pub end_socket: Option<(socket::SocketKind, egui::Vec2)>,
706}
707
708impl EdgeInProgress {
709 pub fn bezier_cubic(&self) -> bezier::Cubic {
710 let start = (self.start.pos, self.start.normal);
711 let end_normal = self
712 .end_socket
713 .as_ref()
714 .map(|&(_, n)| n)
715 .unwrap_or(-self.start.normal);
716 let end = (self.end_pos, end_normal);
717 bezier::Cubic::from_edge_points(start, end)
718 }
719
720 pub fn show(&self, ui: &egui::Ui) {
726 let dist_per_pt = crate::edge::Edge::DEFAULT_DISTANCE_PER_POINT;
727 let bezier = self.bezier_cubic();
728 let pts = bezier.flatten(dist_per_pt).collect();
729 let stroke = ui.visuals().widgets.active.fg_stroke;
730 ui.painter().add(egui::Shape::line(pts, stroke));
731 }
732}
733
734impl Default for View {
735 fn default() -> Self {
736 Self {
737 scene_rect: egui::Rect::ZERO,
738 layout: Default::default(),
739 }
740 }
741}
742
743fn find_closest_socket(
747 pos_graph: egui::Pos2,
748 layout: &Layout,
749 gmem: &GraphTempMemory,
750 ui: &egui::Ui,
751) -> Option<(socket::Socket, f32)> {
752 let mut closest_socket = None;
755 let socket_radius = ui
756 .spacing()
757 .interact_size
758 .x
759 .min(ui.spacing().interact_size.y);
760 let visible_rect = ui.clip_rect();
761 let socket_radius_sq = socket_radius * socket_radius;
762 for (&n_id, &n_graph) in layout {
763 let n_screen = n_graph;
765 let size = match gmem.node_sizes.get(&n_id) {
766 None => continue,
767 Some(&size) => size,
768 };
769 let rect = egui::Rect::from_min_size(n_screen, size);
770 if !visible_rect.intersects(rect) {
771 continue;
772 }
773 let sockets = match gmem.sockets.get(&n_id) {
774 None => continue,
775 Some(sockets) => sockets,
776 };
777
778 for (ix, p, _) in sockets.inputs() {
780 let dist_sq = pos_graph.distance_sq(p);
781 if dist_sq < socket_radius_sq {
782 let socket = socket::Socket {
783 node: n_id,
784 kind: socket::SocketKind::Input,
785 index: ix,
786 };
787 closest_socket = match closest_socket {
788 None => Some((socket, dist_sq)),
789 Some((_, d_sq)) if dist_sq < d_sq => Some((socket, dist_sq)),
790 _ => closest_socket,
791 }
792 }
793 }
794
795 for (ix, p, _) in sockets.outputs() {
797 let dist_sq = pos_graph.distance_sq(p);
798 if dist_sq < socket_radius_sq {
799 let socket = socket::Socket {
800 node: n_id,
801 kind: socket::SocketKind::Output,
802 index: ix,
803 };
804 closest_socket = match closest_socket {
805 None => Some((socket, dist_sq)),
806 Some((_, d_sq)) if dist_sq < d_sq => Some((socket, dist_sq)),
807 _ => closest_socket,
808 }
809 }
810 }
811 }
812
813 closest_socket
814}
815
816fn graph_interaction(
818 layout: &Layout,
819 pointer: &egui::PointerState,
820 closest_socket: Option<socket::Socket>,
821 ptr_on_graph: bool,
822 ptr_graph: egui::Pos2,
823 pressed: Option<&Pressed>,
824) -> GraphInteraction {
825 let mut select = false;
826 let mut socket_press_released = None;
827 let mut drag_nodes_delta = egui::Vec2::ZERO;
828 let mut selection_rect = None;
829
830 let pressed: Option<Pressed> = if let Some(pressed) = pressed {
832 match pressed.action {
833 PressAction::DragNodes {
834 node: Some(ref node),
835 } => {
836 let delta = ptr_graph - pressed.origin_pos;
838 let target = node.position_at_origin + delta;
839 if let Some(current) = layout.get(&node.id) {
840 drag_nodes_delta = target - *current;
841 }
842 }
843 PressAction::Select => {
844 let min = pressed.origin_pos;
845 let max = ptr_graph;
846 selection_rect = Some(egui::Rect::from_two_pos(min, max));
847 }
848 _ => (),
849 }
850
851 if pointer.primary_released() {
853 match pressed.action {
854 PressAction::Select => select = true,
855 PressAction::Socket(socket) => socket_press_released = Some(socket),
856 _ => (),
857 }
858 None
859 } else {
860 Some(Pressed {
861 current_pos: ptr_graph,
862 ..pressed.clone()
863 })
864 }
865 } else if ptr_on_graph
867 && pointer.button_down(egui::PointerButton::Primary)
868 && pointer.button_pressed(egui::PointerButton::Primary)
869 {
870 let action = match closest_socket {
872 Some(socket) => PressAction::Socket(socket),
873 None => {
874 let min = ptr_graph;
875 let max = ptr_graph;
876 selection_rect = Some(egui::Rect::from_two_pos(min, max));
877 PressAction::Select
878 }
879 };
880
881 let pressed = Pressed {
882 over_selection_at_origin: false,
883 origin_pos: ptr_graph,
884 current_pos: ptr_graph,
885 action,
886 };
887 Some(pressed)
888
889 } else {
891 pressed.cloned()
892 };
893
894 GraphInteraction {
895 pressed,
896 socket_press_released,
897 select,
898 selection_rect,
899 drag_nodes_delta,
900 }
901}
902
903fn paint_dot_grid(visible_rect: egui::Rect, ui: &mut egui::Ui) {
905 let dot_step = ui.spacing().interact_size.y;
906 let vis = ui.style().noninteractive();
907 let x_dots = (visible_rect.min.x / dot_step) as i32..=(visible_rect.max.x / dot_step) as i32;
908 let y_dots = (visible_rect.min.y / dot_step) as i32..=(visible_rect.max.y / dot_step) as i32;
909 for x_dot in x_dots {
910 for y_dot in y_dots.clone() {
911 let x = x_dot as f32 * dot_step;
912 let y = y_dot as f32 * dot_step;
913 let r = egui::Rect::from_center_size([x, y].into(), [1.0; 2].into());
914 let color = vis.bg_stroke.color;
915 ui.painter().circle_filled(r.center(), 0.5, color);
916 }
917 }
918}
919
920fn paint_background(visible_rect: egui::Rect, ui: &mut egui::Ui) {
922 let vis = ui.style().noninteractive();
923 let stroke = egui::Stroke {
924 width: 0.0,
925 ..vis.bg_stroke
926 };
927 let fill = vis.bg_fill;
928 ui.painter()
929 .rect(visible_rect, 0.0, fill, stroke, egui::StrokeKind::Inside);
930}
931
932fn paint_selection_area(sel_rect: egui::Rect, ui: &mut egui::Ui) {
934 let color = ui.visuals().weak_text_color();
935 let fill = color.linear_multiply(0.125);
936 let width = 1.0;
937 let stroke = egui::Stroke { width, color };
938 ui.painter()
939 .rect(sel_rect, 0.0, fill, stroke, egui::StrokeKind::Inside);
940}
941
942pub fn id(id_src: impl Hash) -> egui::Id {
944 egui::Id::new((std::any::TypeId::of::<Graph>(), id_src))
945}
946
947pub fn with_graph_memory<R>(
952 ctx: &egui::Context,
953 graph_id: egui::Id,
954 f: impl FnOnce(&GraphTempMemory) -> R,
955) -> R {
956 let gmem_arc = ctx.data_mut(|d| {
957 d.get_temp_mut_or_default::<Arc<Mutex<GraphTempMemory>>>(graph_id)
958 .clone()
959 });
960 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
961 f(&*gmem)
962}
963
964pub fn is_node_selected(ui: &egui::Ui, graph_id: egui::Id, node_id: NodeId) -> bool {
966 let gmem_arc = memory(ui, graph_id);
967 let gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
968 gmem.selection.nodes.contains(&node_id)
969}
970
971fn memory(ui: &egui::Ui, graph_id: egui::Id) -> Arc<Mutex<GraphTempMemory>> {
973 ui.ctx().data_mut(|d| {
974 d.get_temp_mut_or_default::<Arc<Mutex<GraphTempMemory>>>(graph_id)
975 .clone()
976 })
977}