use serde::{Deserialize, Serialize};
use jellyflow_core::core::{CanvasPoint, CanvasRect, PortDirection};
use jellyflow_core::interaction::NodeGraphConnectionMode;
use crate::runtime::geometry::HandleBounds;
use super::{
ClosestConnectionHandleInput, ConnectionHandleCandidate, ConnectionHandleRef,
ConnectionHandleValidity, closest_connection_handle, connection_handle_validity,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ConnectionTargetHandle {
pub handle: ConnectionHandleRef,
pub connectable: bool,
pub connectable_end: bool,
}
impl ConnectionTargetHandle {
pub fn new(handle: ConnectionHandleRef, connectable: bool, connectable_end: bool) -> Self {
Self {
handle,
connectable,
connectable_end,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ConnectionTargetCandidate {
pub target: ConnectionTargetHandle,
pub node_rect: CanvasRect,
pub bounds: HandleBounds,
}
impl ConnectionTargetCandidate {
pub fn new(
target: ConnectionTargetHandle,
node_rect: CanvasRect,
bounds: HandleBounds,
) -> Self {
Self {
target,
node_rect,
bounds,
}
}
fn handle_candidate(self) -> ConnectionHandleCandidate {
ConnectionHandleCandidate::new(self.target.handle, self.node_rect, self.bounds)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ConnectionHandleConnection {
pub source: ConnectionHandleRef,
pub target: ConnectionHandleRef,
}
impl ConnectionHandleConnection {
pub fn from_start_and_target(from: ConnectionHandleRef, target: ConnectionHandleRef) -> Self {
if from.direction == PortDirection::In {
Self {
source: target,
target: from,
}
} else {
Self {
source: from,
target,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ConnectionTargetInput {
pub from: ConnectionHandleRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<ConnectionTargetHandle>,
pub mode: NodeGraphConnectionMode,
#[serde(default)]
pub is_inside_connection_radius: bool,
#[serde(default = "default_connection_validity")]
pub is_valid_connection: bool,
}
impl ConnectionTargetInput {
pub fn new(
from: ConnectionHandleRef,
target: Option<ConnectionTargetHandle>,
mode: NodeGraphConnectionMode,
is_inside_connection_radius: bool,
) -> Self {
Self {
from,
target,
mode,
is_inside_connection_radius,
is_valid_connection: true,
}
}
pub fn with_connection_validity(mut self, is_valid_connection: bool) -> Self {
self.is_valid_connection = is_valid_connection;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ConnectionTargetFromHandlesInput<'a> {
pub pointer: CanvasPoint,
pub radius: f32,
pub from: ConnectionHandleRef,
pub candidates: &'a [ConnectionTargetCandidate],
pub mode: NodeGraphConnectionMode,
pub is_valid_connection: bool,
}
impl<'a> ConnectionTargetFromHandlesInput<'a> {
pub fn new(
pointer: CanvasPoint,
radius: f32,
from: ConnectionHandleRef,
candidates: &'a [ConnectionTargetCandidate],
mode: NodeGraphConnectionMode,
) -> Self {
Self {
pointer,
radius,
from,
candidates,
mode,
is_valid_connection: true,
}
}
pub fn with_connection_validity(mut self, is_valid_connection: bool) -> Self {
self.is_valid_connection = is_valid_connection;
self
}
}
fn default_connection_validity() -> bool {
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ResolvedConnectionTarget {
pub target: Option<ConnectionTargetHandle>,
pub connection: Option<ConnectionHandleConnection>,
pub is_handle_valid: bool,
pub feedback: ConnectionHandleValidity,
}
pub fn resolve_connection_target(input: ConnectionTargetInput) -> ResolvedConnectionTarget {
let Some(target) = input.target else {
return ResolvedConnectionTarget {
target: None,
connection: None,
is_handle_valid: false,
feedback: connection_handle_validity(input.is_inside_connection_radius, false),
};
};
let connection = ConnectionHandleConnection::from_start_and_target(input.from, target.handle);
let mode_allows_target = match input.mode {
NodeGraphConnectionMode::Strict => input.from.direction != target.handle.direction,
NodeGraphConnectionMode::Loose => {
input.from.node != target.handle.node || input.from.port != target.handle.port
}
};
let is_handle_valid = target.connectable
&& target.connectable_end
&& mode_allows_target
&& input.is_valid_connection;
ResolvedConnectionTarget {
target: Some(target),
connection: Some(connection),
is_handle_valid,
feedback: connection_handle_validity(input.is_inside_connection_radius, is_handle_valid),
}
}
pub fn resolve_connection_target_from_handles(
input: ConnectionTargetFromHandlesInput<'_>,
) -> ResolvedConnectionTarget {
let handle_candidates = input
.candidates
.iter()
.copied()
.map(ConnectionTargetCandidate::handle_candidate)
.collect::<Vec<_>>();
let closest = closest_connection_handle(ClosestConnectionHandleInput::new(
input.pointer,
input.radius,
input.from,
&handle_candidates,
));
let target = closest.and_then(|closest| {
input
.candidates
.iter()
.find(|candidate| candidate.target.handle == closest.handle)
.map(|candidate| candidate.target)
});
resolve_connection_target(
ConnectionTargetInput::new(input.from, target, input.mode, closest.is_some())
.with_connection_validity(input.is_valid_connection),
)
}