use serde::{Deserialize, Serialize};
use crate::runtime::events::{ConnectDragKind, ConnectEnd, ConnectEndOutcome, ConnectStart};
use crate::schema::MenuDescriptor;
use jellyflow_core::core::{CanvasPoint, PortId};
use jellyflow_core::interaction::NodeGraphConnectionMode;
use super::{ConnectionHandleConnection, ResolvedConnectionTarget};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
pub enum ConnectionEndIntent {
Complete,
Cancel,
DropOnPane {
pointer: CanvasPoint,
#[serde(default, skip_serializing_if = "Option::is_none")]
menu: Option<MenuDescriptor>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionLifecycleState {
Started,
HoverValid,
HoverInvalid,
HoverEmpty,
Committed,
Rejected,
Canceled,
DroppedOnPane,
NoOp,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ConnectionLifecycleResult {
pub start: ConnectStart,
pub state: ConnectionLifecycleState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hover: Option<ResolvedConnectionTarget>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connection: Option<ConnectionHandleConnection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<PortId>,
pub end: ConnectEnd,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dropped_at: Option<CanvasPoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dropped_wire_menu: Option<MenuDescriptor>,
}
impl ConnectionLifecycleResult {
pub fn for_hover(start: ConnectStart, hover: Option<ResolvedConnectionTarget>) -> Self {
let state = match hover {
Some(hover) if hover.is_handle_valid => ConnectionLifecycleState::HoverValid,
Some(hover) if hover.target.is_some() => ConnectionLifecycleState::HoverInvalid,
_ => ConnectionLifecycleState::HoverEmpty,
};
Self {
end: ConnectEnd {
kind: start.kind.clone(),
mode: start.mode,
target: hover.and_then(|hover| hover.target.map(|target| target.handle.port)),
outcome: ConnectEndOutcome::NoOp,
},
start,
state,
hover,
connection: hover.and_then(|hover| hover.connection),
target: hover.and_then(|hover| hover.target.map(|target| target.handle.port)),
dropped_at: None,
dropped_wire_menu: None,
}
}
pub fn did_commit(&self) -> bool {
matches!(self.state, ConnectionLifecycleState::Committed)
}
pub fn opens_dropped_wire_menu(&self) -> bool {
matches!(self.state, ConnectionLifecycleState::DroppedOnPane)
}
}
pub fn resolve_connection_lifecycle(
start: ConnectStart,
hover: Option<ResolvedConnectionTarget>,
intent: ConnectionEndIntent,
) -> ConnectionLifecycleResult {
let (state, connection, target, outcome, dropped_at, dropped_wire_menu) = match intent {
ConnectionEndIntent::Cancel => (
ConnectionLifecycleState::Canceled,
None,
None,
ConnectEndOutcome::Canceled,
None,
None,
),
ConnectionEndIntent::DropOnPane { pointer, menu } => (
ConnectionLifecycleState::DroppedOnPane,
None,
None,
ConnectEndOutcome::OpenInsertNodePicker,
Some(pointer),
menu,
),
ConnectionEndIntent::Complete => resolve_completed_lifecycle(hover),
};
let end = ConnectEnd {
kind: start.kind.clone(),
mode: start.mode,
target,
outcome,
};
ConnectionLifecycleResult {
start,
state,
hover,
connection,
target,
end,
dropped_at,
dropped_wire_menu,
}
}
fn resolve_completed_lifecycle(
hover: Option<ResolvedConnectionTarget>,
) -> (
ConnectionLifecycleState,
Option<ConnectionHandleConnection>,
Option<PortId>,
ConnectEndOutcome,
Option<CanvasPoint>,
Option<MenuDescriptor>,
) {
let Some(hover) = hover else {
return (
ConnectionLifecycleState::NoOp,
None,
None,
ConnectEndOutcome::NoOp,
None,
None,
);
};
if hover.is_handle_valid {
let connection = hover.connection;
let target = connection.map(|connection| connection.target.port);
return (
ConnectionLifecycleState::Committed,
connection,
target,
ConnectEndOutcome::Committed,
None,
None,
);
}
let target = hover.target.map(|target| target.handle.port);
let state = if hover.target.is_some() {
ConnectionLifecycleState::Rejected
} else {
ConnectionLifecycleState::NoOp
};
let outcome = if hover.target.is_some() {
ConnectEndOutcome::Rejected
} else {
ConnectEndOutcome::NoOp
};
(state, hover.connection, target, outcome, None, None)
}
pub fn new_connection_start(
from: PortId,
bundle: impl IntoIterator<Item = PortId>,
mode: NodeGraphConnectionMode,
) -> ConnectStart {
ConnectStart {
kind: ConnectDragKind::New {
from,
bundle: bundle.into_iter().collect(),
},
mode,
}
}