jellyflow_core/core/model/node.rs
1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::core::ids::{GroupId, NodeKindKey, PortId};
5
6use super::geometry::{CanvasPoint, CanvasRect, CanvasSize};
7
8fn is_false(v: &bool) -> bool {
9 !*v
10}
11
12/// Node instance.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Node {
15 /// Node kind identifier.
16 pub kind: NodeKindKey,
17 /// Node kind version (for per-kind migrations).
18 pub kind_version: u32,
19 /// Top-left position in canvas space.
20 pub pos: CanvasPoint,
21
22 /// Optional node origin override (XyFlow `node.origin`).
23 ///
24 /// When omitted, runtime uses the global `NodeGraphInteractionState.node_origin`.
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub origin: Option<NodeOrigin>,
27
28 /// Whether the node can be selected (XyFlow `node.selectable`).
29 ///
30 /// When omitted, the global `NodeGraphInteractionState.elements_selectable` decides.
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub selectable: Option<bool>,
33
34 /// Whether the node can receive keyboard focus (XyFlow `node.focusable`).
35 ///
36 /// When omitted, the global `NodeGraphInteractionState.nodes_focusable` decides.
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub focusable: Option<bool>,
39
40 /// Whether the node can be dragged with pointer interactions (XyFlow `node.draggable`).
41 ///
42 /// When omitted, the global `NodeGraphInteractionState.nodes_draggable` decides.
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub draggable: Option<bool>,
45
46 /// Whether the node can be used for creating connections via editor interactions (XyFlow
47 /// `node.connectable`).
48 ///
49 /// When omitted, the global `NodeGraphInteractionState.nodes_connectable` decides.
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub connectable: Option<bool>,
52
53 /// Whether the node can be deleted via editor interactions (XyFlow `node.deletable`).
54 ///
55 /// When omitted, the global `NodeGraphInteractionState.nodes_deletable` decides.
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub deletable: Option<bool>,
58
59 /// Optional group container id (subflow / parent frame).
60 ///
61 /// This is an editor-structure concept (XyFlow `parentId` mental model) and is intentionally
62 /// orthogonal to semantic subgraphs (see ADR 0126).
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub parent: Option<GroupId>,
65
66 /// Optional per-node movement/resize extent override.
67 ///
68 /// This mirrors XyFlow's `node.extent` concept. It is an editor-structure constraint (UI-facing),
69 /// not a semantic graph rule.
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub extent: Option<NodeExtent>,
72
73 /// Whether moving/resizing this node can expand its parent container (if any).
74 ///
75 /// This mirrors XyFlow's `node.expandParent` behavior.
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub expand_parent: Option<bool>,
78
79 /// Optional explicit node size in logical px at zoom=1 (semantic sizing).
80 ///
81 /// The editor converts this into canvas space by dividing by the current zoom so node content
82 /// remains readable under semantic zoom.
83 ///
84 /// When `None`, the editor derives the size from measured geometry or style defaults.
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub size: Option<CanvasSize>,
87
88 /// Whether the node is hidden (XyFlow `node.hidden`).
89 ///
90 /// Hidden nodes are excluded from derived geometry (hit-testing, rendering, fit-view).
91 #[serde(default, skip_serializing_if = "is_false")]
92 pub hidden: bool,
93
94 /// Whether the node is collapsed.
95 #[serde(default)]
96 pub collapsed: bool,
97
98 /// Stable port ordering for this node (UI-facing).
99 #[serde(default, skip_serializing_if = "Vec::is_empty")]
100 pub ports: Vec<PortId>,
101
102 /// Opaque node payload (domain-owned).
103 ///
104 /// This must be preserved for unknown node kinds.
105 #[serde(default)]
106 pub data: Value,
107}
108
109/// Per-node origin override, expressed as a normalized fraction of the node rect.
110#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
111pub struct NodeOrigin {
112 pub x: f32,
113 pub y: f32,
114}
115
116impl NodeOrigin {
117 pub fn is_finite(self) -> bool {
118 self.x.is_finite() && self.y.is_finite()
119 }
120
121 pub fn as_tuple(self) -> (f32, f32) {
122 (self.x, self.y)
123 }
124}
125
126/// Per-node movement/resize extent.
127#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
128#[serde(tag = "kind", rename_all = "snake_case")]
129pub enum NodeExtent {
130 /// Constrain to the node's parent container (if any).
131 Parent,
132 /// Constrain to the given rect in canvas space.
133 Rect { rect: CanvasRect },
134}