use serde::{Deserialize, Serialize};
use crate::io::{NodeGraphInteractionState, NodeGraphViewState};
use crate::runtime::drag::{
PointerGestureClaim, PointerGestureClaimInput, resolve_pointer_gesture_claim,
};
use crate::runtime::policy::resolve_node_interaction_policy;
use crate::runtime::store::NodeGraphStore;
use jellyflow_core::core::CanvasPoint;
use jellyflow_core::core::{EdgeId, Graph, GroupId, NodeId};
use super::types::SelectionModifier;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeDragStartSelectionInput {
pub node: NodeId,
pub modifier: SelectionModifier,
}
impl NodeDragStartSelectionInput {
pub fn new(node: NodeId, multi_selection_active: bool) -> Self {
Self {
node,
modifier: if multi_selection_active {
SelectionModifier::Additive
} else {
SelectionModifier::Replace
},
}
}
pub fn with_modifier(node: NodeId, modifier: SelectionModifier) -> Self {
Self { node, modifier }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeDragStartSelectionAction {
Unchanged,
Clear,
SelectOnly(NodeId),
Add(NodeId),
Remove(NodeId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodePointerDownDecision {
pub selection: NodeDragStartSelectionAction,
pub drag_claim: PointerGestureClaim,
}
impl NodePointerDownDecision {
pub fn new(selection: NodeDragStartSelectionAction, drag_claim: PointerGestureClaim) -> Self {
Self {
selection,
drag_claim,
}
}
pub fn apply_to_view_state(self, view_state: &mut NodeGraphViewState) {
self.selection.apply_to_view_state(view_state);
}
fn selection_after(
self,
view_state: &NodeGraphViewState,
) -> Option<(Vec<NodeId>, Vec<EdgeId>, Vec<GroupId>)> {
self.selection.selection_after(view_state)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct NodePointerDownInput {
pub node: NodeId,
#[serde(default)]
pub multi_selection_active: bool,
pub screen_delta: CanvasPoint,
}
impl NodePointerDownInput {
pub fn new(node: NodeId, multi_selection_active: bool, screen_delta: CanvasPoint) -> Self {
Self {
node,
multi_selection_active,
screen_delta,
}
}
}
impl NodeDragStartSelectionAction {
pub fn is_unchanged(self) -> bool {
self == Self::Unchanged
}
pub fn apply_to_view_state(self, view_state: &mut NodeGraphViewState) {
match self {
Self::Unchanged => {}
Self::Clear => view_state.set_selection(Vec::new(), Vec::new(), Vec::new()),
Self::SelectOnly(node) => view_state.set_selection(vec![node], Vec::new(), Vec::new()),
Self::Add(node) => {
if !view_state.selected_nodes.contains(&node) {
view_state.selected_nodes.push(node);
view_state.selected_nodes.sort();
view_state.selected_nodes.dedup();
}
}
Self::Remove(node) => {
view_state
.selected_nodes
.retain(|selected| *selected != node);
}
}
}
fn selection_after(
self,
view_state: &NodeGraphViewState,
) -> Option<(Vec<NodeId>, Vec<EdgeId>, Vec<GroupId>)> {
if self.is_unchanged() {
return None;
}
let mut next = view_state.clone();
self.apply_to_view_state(&mut next);
Some((
next.selected_nodes,
next.selected_edges,
next.selected_groups,
))
}
}
pub fn resolve_node_drag_start_selection(
graph: &Graph,
view_state: &NodeGraphViewState,
interaction: &NodeGraphInteractionState,
input: NodeDragStartSelectionInput,
) -> NodeDragStartSelectionAction {
let Some(node) = graph.nodes.get(&input.node) else {
return NodeDragStartSelectionAction::Unchanged;
};
if node.hidden {
return NodeDragStartSelectionAction::Unchanged;
}
let selected = view_state.selected_nodes.contains(&input.node);
let selectable = resolve_node_interaction_policy(node, interaction).selectable;
let selection = interaction.selection_interaction();
if (!selection.select_nodes_on_drag || !selectable) && !input.modifier.additive() {
return if selected {
NodeDragStartSelectionAction::Unchanged
} else {
NodeDragStartSelectionAction::Clear
};
}
if !selectable || !selection.select_nodes_on_drag {
return NodeDragStartSelectionAction::Unchanged;
}
if !selected {
if input.modifier.additive() {
NodeDragStartSelectionAction::Add(input.node)
} else {
NodeDragStartSelectionAction::SelectOnly(input.node)
}
} else if input.modifier.additive() {
NodeDragStartSelectionAction::Remove(input.node)
} else {
NodeDragStartSelectionAction::Unchanged
}
}
pub fn resolve_node_pointer_down(
graph: &Graph,
view_state: &NodeGraphViewState,
interaction: &NodeGraphInteractionState,
input: NodePointerDownInput,
) -> NodePointerDownDecision {
let selection = resolve_node_drag_start_selection(
graph,
view_state,
interaction,
NodeDragStartSelectionInput::new(input.node, input.multi_selection_active),
);
let drag_claim = resolve_pointer_gesture_claim(PointerGestureClaimInput::new(
input.screen_delta,
input.multi_selection_active,
false,
false,
interaction.node_drag_interaction().node_drag_threshold,
interaction.node_drag_interaction().node_drag_threshold,
));
NodePointerDownDecision::new(selection, drag_claim)
}
impl NodeGraphStore {
pub fn apply_node_pointer_down(
&mut self,
input: NodePointerDownInput,
) -> NodePointerDownDecision {
let interaction = self.resolved_interaction_state();
let decision =
resolve_node_pointer_down(self.graph(), self.view_state(), &interaction, input);
if let Some((nodes, edges, groups)) = decision.selection_after(self.view_state()) {
self.set_selection(nodes, edges, groups);
}
decision
}
pub fn apply_node_drag_start_selection(
&mut self,
input: NodeDragStartSelectionInput,
) -> NodeDragStartSelectionAction {
self.apply_node_pointer_down(NodePointerDownInput::new(
input.node,
input.modifier.additive(),
CanvasPoint::default(),
))
.selection
}
pub fn resolve_node_pointer_down(
&self,
input: NodePointerDownInput,
) -> NodePointerDownDecision {
let interaction = self.resolved_interaction_state();
resolve_node_pointer_down(self.graph(), self.view_state(), &interaction, input)
}
}