Persist diagram routing with atomic edits and grouped movement

This commit is contained in:
2026-09-05 20:18:55 +03:00
parent fa096cefd3
commit 4e5a88dc4d
8 changed files with 943 additions and 3 deletions
+53
View File
@@ -180,6 +180,59 @@ pub fn descendant_nodes(p: &Project, diagram: Id, node: Id) -> Vec<Id> {
})
.collect()
}
/// Actual endpoint-owner placements before folding promotes a hidden endpoint
/// to an ancestor. Ports belong to their placed function/participant; they do not
/// acquire independent placement identities. Missing placements are not inferred.
pub fn edge_endpoint_placements(p: &Project, d: &Diagram, edge: Id) -> Option<(Id, Id)> {
let (source, target) = if let Some(r) = p.relations.get(&edge) {
(r.source(), r.target())
} else {
let trace = p.traces.get(&edge)?;
(trace.oa, trace.sa)
};
let placement = |id| {
let element = p.elements.get(&id)?;
let owner = if element.kind() == Kind::Port {
element.parent?
} else {
id
};
d.nodes.values().find(|n| n.element == owner).map(|n| n.id)
};
Some((placement(source)?, placement(target)?))
}
/// Route updates for a simultaneous placement movement. `deltas` maps placement
/// identities to diagram-space displacement. Only routes with two actual owners
/// moving by the same nonzero delta translate; one-ended moves keep world pins.
/// Returns changed records only, for the caller to apply with its node edits in
/// one transaction. This pure helper also drives reflow and drag previews.
pub fn translated_routes(
p: &Project,
d: &Diagram,
deltas: &BTreeMap<Id, Position>,
) -> Vec<EdgeRoute> {
d.routes
.iter()
.filter_map(|route| {
let (source, target) = edge_endpoint_placements(p, d, route.edge)?;
let source_delta = deltas.get(&source)?;
let target_delta = deltas.get(&target)?;
if source_delta != target_delta
|| (source_delta.x == 0. && source_delta.y == 0.)
|| route.waypoints.is_empty()
{
return None;
}
let mut translated = route.clone();
for point in &mut translated.waypoints {
point.x += source_delta.x;
point.y += source_delta.y;
}
Some(translated)
})
.collect()
}
/// One projection drives drawing, hit testing, Fit, SVG, and grouped movement.
pub fn project_view(p: &Project, diagram: Id) -> ProjectedView {
let Some(d) = p.diagrams.get(&diagram) else {