jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use crate::core::{
    Binding, BindingEndpoint, CanvasPoint, Edge, Graph, GraphLocalBindingTarget, Node, NodeId,
    Port, PortId,
};
use crate::core::{symbol_ref_node_data, symbol_ref_target_symbol_id};
use crate::ops::{GraphMutationBatchPlanner, GraphOp, GraphTransaction};

use super::super::{model::GraphFragment, remap::IdRemapper};
use super::remapped_ids::RemappedFragmentIds;
use super::tuning::PasteTuning;

pub(super) struct FragmentPastePlanner<'a> {
    fragment: &'a GraphFragment,
    ids: RemappedFragmentIds,
    tuning: PasteTuning,
    tx: GraphTransaction,
}

impl<'a> FragmentPastePlanner<'a> {
    pub(super) fn new(
        fragment: &'a GraphFragment,
        remapper: &'a IdRemapper,
        tuning: PasteTuning,
    ) -> Self {
        Self {
            fragment,
            ids: RemappedFragmentIds::new(fragment, remapper),
            tuning,
            tx: GraphTransaction::new(),
        }
    }

    pub(super) fn finish(mut self) -> GraphTransaction {
        self.push_imports();
        self.push_symbols();
        self.push_groups();
        self.push_batch_insert_ops();
        self.push_sticky_notes();
        self.push_bindings();
        self.tx
    }

    fn push_op(&mut self, op: GraphOp) {
        self.tx.push(op);
    }

    fn extend_ops(&mut self, ops: impl IntoIterator<Item = GraphOp>) {
        self.tx.extend(ops);
    }

    fn push_imports(&mut self) {
        for (id, import) in &self.fragment.imports {
            self.push_op(GraphOp::AddImport {
                id: *id,
                import: import.clone(),
            });
        }
    }

    fn push_symbols(&mut self) {
        for (old_id, old_symbol) in &self.fragment.symbols {
            self.push_op(GraphOp::AddSymbol {
                id: self.ids.symbol(*old_id),
                symbol: old_symbol.clone(),
            });
        }
    }

    fn push_groups(&mut self) {
        for (old_id, group) in &self.fragment.groups {
            self.push_op(GraphOp::AddGroup {
                id: self.ids.group(*old_id),
                group: group.clone(),
            });
        }
    }

    fn push_batch_insert_ops(&mut self) {
        let planning_graph = self.planning_graph();
        let mut batch = GraphMutationBatchPlanner::new(&planning_graph);
        self.stage_nodes(&mut batch);
        self.stage_edges(&mut batch);
        self.extend_ops(batch.into_ops());
    }

    fn planning_graph(&self) -> Graph {
        let mut planning_graph = Graph::default();
        for (old_id, group) in &self.fragment.groups {
            planning_graph
                .groups
                .insert(self.ids.group(*old_id), group.clone());
        }
        planning_graph
    }

    fn stage_nodes(&self, batch: &mut GraphMutationBatchPlanner<'_>) {
        for (old_id, old_node) in &self.fragment.nodes {
            batch
                .add_node_with_ports(
                    self.ids.node(*old_id),
                    self.remapped_node(*old_id, old_node),
                    self.remapped_node_ports(old_node, self.ids.node(*old_id)),
                )
                .expect("fragment paste should stage valid node and ports");
        }
    }

    fn stage_edges(&self, batch: &mut GraphMutationBatchPlanner<'_>) {
        for (old_edge_id, old_edge) in &self.fragment.edges {
            batch
                .add_edge(self.ids.edge(*old_edge_id), self.remapped_edge(old_edge))
                .expect("fragment paste should stage valid edges");
        }
    }

    fn push_sticky_notes(&mut self) {
        for (old_id, note) in &self.fragment.sticky_notes {
            self.push_op(GraphOp::AddStickyNote {
                id: self.ids.sticky_note(*old_id),
                note: note.clone(),
            });
        }
    }

    fn push_bindings(&mut self) {
        for (old_id, binding) in &self.fragment.bindings {
            if let Some(binding) = self.remapped_binding(binding) {
                self.push_op(GraphOp::AddBinding {
                    id: self.ids.binding(*old_id),
                    binding,
                });
            }
        }
    }

    fn remapped_node(&self, old_id: NodeId, old_node: &Node) -> Node {
        let mut node = old_node.clone();

        if let Ok(Some(old_symbol_id)) = symbol_ref_target_symbol_id(old_id, old_node)
            && let Some(new_symbol_id) = self.ids.maybe_symbol(old_symbol_id)
        {
            node.data = symbol_ref_node_data(new_symbol_id);
        }

        node.pos = CanvasPoint {
            x: node.pos.x + self.tuning.offset.x,
            y: node.pos.y + self.tuning.offset.y,
        };
        node.parent = node
            .parent
            .and_then(|old_parent| self.ids.maybe_group(old_parent));
        node
    }

    fn remapped_node_ports(&self, old_node: &Node, new_id: NodeId) -> Vec<(PortId, Port)> {
        let mut ports: Vec<(PortId, Port)> = Vec::new();
        for old_port_id in &old_node.ports {
            if let Some(old_port) = self.fragment.ports.get(old_port_id) {
                let mut port = old_port.clone();
                port.node = new_id;
                ports.push((self.ids.port(*old_port_id), port));
            }
        }
        ports
    }

    fn remapped_edge(&self, old_edge: &Edge) -> Edge {
        Edge {
            kind: old_edge.kind,
            from: self.ids.port(old_edge.from),
            to: self.ids.port(old_edge.to),
            hidden: old_edge.hidden,
            selectable: old_edge.selectable,
            focusable: old_edge.focusable,
            interaction_width: old_edge.interaction_width,
            deletable: old_edge.deletable,
            reconnectable: old_edge.reconnectable,
        }
    }

    fn remapped_binding(&self, binding: &Binding) -> Option<Binding> {
        Some(Binding {
            subject: self.remapped_binding_endpoint(&binding.subject)?,
            target: self.remapped_binding_endpoint(&binding.target)?,
            kind: binding.kind.clone(),
            meta: binding.meta.clone(),
        })
    }

    fn remapped_binding_endpoint(&self, endpoint: &BindingEndpoint) -> Option<BindingEndpoint> {
        match endpoint {
            BindingEndpoint::Source { anchor } => Some(BindingEndpoint::Source {
                anchor: anchor.clone(),
            }),
            BindingEndpoint::GraphLocal { target } => Some(BindingEndpoint::GraphLocal {
                target: self.remapped_graph_local_target(*target)?,
            }),
        }
    }

    fn remapped_graph_local_target(
        &self,
        target: GraphLocalBindingTarget,
    ) -> Option<GraphLocalBindingTarget> {
        match target {
            GraphLocalBindingTarget::Graph => Some(GraphLocalBindingTarget::Graph),
            GraphLocalBindingTarget::Node { id } => self
                .ids
                .maybe_node(id)
                .map(|id| GraphLocalBindingTarget::Node { id }),
            GraphLocalBindingTarget::Port { id } => self
                .ids
                .maybe_port(id)
                .map(|id| GraphLocalBindingTarget::Port { id }),
            GraphLocalBindingTarget::Edge { id } => self
                .ids
                .maybe_edge(id)
                .map(|id| GraphLocalBindingTarget::Edge { id }),
            GraphLocalBindingTarget::Group { id } => self
                .ids
                .maybe_group(id)
                .map(|id| GraphLocalBindingTarget::Group { id }),
            GraphLocalBindingTarget::StickyNote { id } => self
                .ids
                .maybe_sticky_note(id)
                .map(|id| GraphLocalBindingTarget::StickyNote { id }),
        }
    }
}