Skip to main content

jellyflow_core/core/model/
edge.rs

1use serde::{Deserialize, Serialize};
2
3use crate::core::ids::PortId;
4
5use super::port::PortKind;
6
7fn is_false(v: &bool) -> bool {
8    !*v
9}
10
11/// Edge kind.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum EdgeKind {
15    /// Typed data flow.
16    Data,
17    /// Exec/control flow.
18    Exec,
19}
20
21impl EdgeKind {
22    pub fn port_kind(self) -> PortKind {
23        match self {
24            Self::Data => PortKind::Data,
25            Self::Exec => PortKind::Exec,
26        }
27    }
28}
29
30/// Edge between two ports.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct Edge {
33    /// Edge kind.
34    pub kind: EdgeKind,
35    /// Source port.
36    pub from: PortId,
37    /// Target port.
38    pub to: PortId,
39    /// Whether the edge is hidden (XyFlow `edge.hidden`).
40    ///
41    /// Hidden edges are excluded from derived selection and rendering surfaces.
42    #[serde(default, skip_serializing_if = "is_false")]
43    pub hidden: bool,
44
45    /// Whether the edge can be selected (XyFlow `edge.selectable`).
46    ///
47    /// When omitted, the global `NodeGraphInteractionState.edges_selectable` decides.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub selectable: Option<bool>,
50
51    /// Whether the edge can receive keyboard focus (XyFlow `edge.focusable`).
52    ///
53    /// When omitted, the global `NodeGraphInteractionState.edges_focusable` decides.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub focusable: Option<bool>,
56
57    /// Optional edge hit-test interaction width in logical pixels (XyFlow `edge.interactionWidth`).
58    ///
59    /// When omitted, the global `NodeGraphInteractionState.edge_interaction_width` decides.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub interaction_width: Option<f32>,
62
63    /// Whether the edge can be deleted via editor interactions (XyFlow `edge.deletable`).
64    ///
65    /// When omitted, the global `NodeGraphInteractionState.edges_deletable` decides.
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub deletable: Option<bool>,
68
69    /// Whether the edge can be reconnected via editor interactions (XyFlow `edge.reconnectable`).
70    ///
71    /// In XyFlow this field is a `boolean | 'source' | 'target'`. `true` enables reconnecting both
72    /// endpoints, `'source'` only enables reconnecting the source endpoint and `'target'` only
73    /// enables reconnecting the target endpoint.
74    ///
75    /// When omitted, the global `NodeGraphInteractionState.edges_reconnectable` decides.
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub reconnectable: Option<EdgeReconnectable>,
78}
79
80/// Per-edge reconnect enablement (XyFlow `edge.reconnectable`).
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(untagged)]
83pub enum EdgeReconnectable {
84    Bool(bool),
85    Endpoint(EdgeReconnectableEndpoint),
86}
87
88impl EdgeReconnectable {
89    pub fn allows_source(self) -> bool {
90        matches!(
91            self,
92            Self::Bool(true) | Self::Endpoint(EdgeReconnectableEndpoint::Source)
93        )
94    }
95
96    pub fn allows_target(self) -> bool {
97        matches!(
98            self,
99            Self::Bool(true) | Self::Endpoint(EdgeReconnectableEndpoint::Target)
100        )
101    }
102
103    pub fn endpoint_flags(self) -> (bool, bool) {
104        (self.allows_source(), self.allows_target())
105    }
106}
107
108/// Which endpoint is reconnectable (`'source' | 'target'`).
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum EdgeReconnectableEndpoint {
112    Source,
113    Target,
114}