Skip to main content

jellyflow_runtime/io/view_state/
state.rs

1use serde::{Deserialize, Serialize};
2
3use jellyflow_core::core::{CanvasPoint, EdgeId, Graph, GroupId, NodeId};
4
5use super::default_zoom;
6use super::pure::NodeGraphPureViewState;
7
8/// Node graph editor view-state.
9///
10/// This is intentionally separate from graph semantics and may be stored per-user/per-project.
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct NodeGraphViewState {
13    /// Canvas pan in graph space.
14    #[serde(default)]
15    pub pan: CanvasPoint,
16    /// Zoom factor.
17    #[serde(default = "default_zoom")]
18    pub zoom: f32,
19    /// Selected nodes (optional).
20    #[serde(default, skip_serializing_if = "Vec::is_empty")]
21    pub selected_nodes: Vec<NodeId>,
22    /// Selected edges (optional).
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub selected_edges: Vec<EdgeId>,
25    /// Selected groups (optional).
26    #[serde(default, skip_serializing_if = "Vec::is_empty")]
27    pub selected_groups: Vec<GroupId>,
28    /// Explicit draw order (optional).
29    #[serde(default, skip_serializing_if = "Vec::is_empty")]
30    pub draw_order: Vec<NodeId>,
31    /// Explicit edge draw order (optional).
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub edge_draw_order: Vec<EdgeId>,
34    /// Explicit group draw order (optional).
35    #[serde(default, skip_serializing_if = "Vec::is_empty")]
36    pub group_draw_order: Vec<GroupId>,
37}
38
39impl Default for NodeGraphViewState {
40    fn default() -> Self {
41        Self {
42            pan: CanvasPoint::default(),
43            zoom: default_zoom(),
44            selected_nodes: Vec::new(),
45            selected_edges: Vec::new(),
46            selected_groups: Vec::new(),
47            draw_order: Vec::new(),
48            edge_draw_order: Vec::new(),
49            group_draw_order: Vec::new(),
50        }
51    }
52}
53
54impl NodeGraphViewState {
55    pub fn set_viewport(&mut self, pan: CanvasPoint, zoom: f32) {
56        self.pan = sanitize_pan(pan);
57        self.zoom = sanitize_zoom(zoom);
58    }
59
60    pub fn set_selection(&mut self, nodes: Vec<NodeId>, edges: Vec<EdgeId>, groups: Vec<GroupId>) {
61        self.selected_nodes = nodes;
62        self.selected_edges = edges;
63        self.selected_groups = groups;
64    }
65
66    /// Removes stale IDs (selection / draw order) that no longer exist in the target graph.
67    pub fn sanitize_for_graph(&mut self, graph: &Graph) {
68        self.sanitize_viewport();
69
70        let visible_node = |id: &NodeId| graph.nodes.get(id).is_some_and(|node| !node.hidden);
71
72        self.selected_nodes.retain(visible_node);
73        let visible_edge = |id: &EdgeId| {
74            let Some(edge) = graph.edges.get(id) else {
75                return false;
76            };
77            if edge.hidden {
78                return false;
79            }
80            let Some(from) = graph.ports.get(&edge.from) else {
81                return false;
82            };
83            let Some(to) = graph.ports.get(&edge.to) else {
84                return false;
85            };
86            visible_node(&from.node) && visible_node(&to.node)
87        };
88
89        self.selected_edges.retain(|id| visible_edge(id));
90        self.selected_groups
91            .retain(|id| graph.groups.contains_key(id));
92        self.draw_order.retain(visible_node);
93        self.edge_draw_order.retain(|id| visible_edge(id));
94        self.group_draw_order
95            .retain(|id| graph.groups.contains_key(id));
96    }
97
98    pub fn sanitize_viewport(&mut self) {
99        self.pan = sanitize_pan(self.pan);
100        self.zoom = sanitize_zoom(self.zoom);
101    }
102}
103
104fn sanitize_pan(pan: CanvasPoint) -> CanvasPoint {
105    if pan.is_finite() {
106        pan
107    } else {
108        CanvasPoint::default()
109    }
110}
111
112fn sanitize_zoom(zoom: f32) -> f32 {
113    if zoom.is_finite() && zoom > 0.0 {
114        zoom
115    } else {
116        default_zoom()
117    }
118}
119
120impl From<NodeGraphPureViewState> for NodeGraphViewState {
121    fn from(value: NodeGraphPureViewState) -> Self {
122        Self {
123            pan: value.pan,
124            zoom: value.zoom,
125            selected_nodes: value.selected_nodes,
126            selected_edges: value.selected_edges,
127            selected_groups: value.selected_groups,
128            draw_order: value.draw_order,
129            edge_draw_order: value.edge_draw_order,
130            group_draw_order: value.group_draw_order,
131        }
132    }
133}
134
135impl From<NodeGraphViewState> for NodeGraphPureViewState {
136    fn from(value: NodeGraphViewState) -> Self {
137        Self {
138            pan: value.pan,
139            zoom: value.zoom,
140            selected_nodes: value.selected_nodes,
141            selected_edges: value.selected_edges,
142            selected_groups: value.selected_groups,
143            draw_order: value.draw_order,
144            edge_draw_order: value.edge_draw_order,
145            group_draw_order: value.group_draw_order,
146        }
147    }
148}
149
150impl From<&NodeGraphViewState> for NodeGraphPureViewState {
151    fn from(value: &NodeGraphViewState) -> Self {
152        Self {
153            pan: value.pan,
154            zoom: value.zoom,
155            selected_nodes: value.selected_nodes.clone(),
156            selected_edges: value.selected_edges.clone(),
157            selected_groups: value.selected_groups.clone(),
158            draw_order: value.draw_order.clone(),
159            edge_draw_order: value.edge_draw_order.clone(),
160            group_draw_order: value.group_draw_order.clone(),
161        }
162    }
163}