handlePortCollapseThrough method
Handle recursive port collapse (Shift+click on a boundary port).
Reverse of handlePortExpandThrough: removes the wire and connected
trivial gates recursively across module boundaries.
Implements smart focus recovery: if collapsing is the last connection to the original port, finds the closest non-constant connected gate to focus on after collapse, similar to port collapse behavior.
Implementation
Future<SchematicLayoutResult?> handlePortCollapseThrough(
String nodeId,
String portId,
) async {
if (isToggling) {
return null;
}
final engine = _layoutEngine;
if (engine == null || schematicAdapter == null) {
return null;
}
setState(() {
isToggling = true;
togglingLabel = 'Collapsing...';
pendingToggleNodeId = nodeId;
});
final completer = Completer<void>();
WidgetsBinding.instance.addPostFrameCallback((_) {
completer.complete();
});
await completer.future;
SchedulerBinding.instance.ensureVisualUpdate();
try {
// Before collapsing, check if this is the last connection to the port
// and find the closest non-constant connected gate for focus recovery
String? closestConnectedGateId;
final oldEdges = layout?.edges ?? <SchematicEdgeData>[];
// Count connections specifically to THIS PORT being collapsed
var connectionsFromThisPort = 0;
String? otherPortOnConnection;
for (final edge in oldEdges) {
// Check if this edge connects to the specific port being collapsed
if (edge.sourcePort == portId) {
connectionsFromThisPort++;
otherPortOnConnection = edge.targetPort;
} else if (edge.targetPort == portId) {
connectionsFromThisPort++;
otherPortOnConnection = edge.sourcePort;
}
}
// Helper function to check if a gate is a constant
bool isConstantGate(String? gateId) {
if (gateId == null) {
return false;
}
final node = schematicAdapter?.schematic.nodeMap[gateId];
if (node == null) {
return false;
}
return isConstantName(node.hwMeta.name);
}
// Helper function to find the closest non-constant gate by tracing
// connections
String? findNonConstantConnectedGate(String startPortId) {
final visited = <String>{};
final queue = <String>[startPortId];
while (queue.isNotEmpty) {
final currentPortId = queue.removeAt(0);
if (visited.contains(currentPortId)) {
continue;
}
visited.add(currentPortId);
// Find the gate for this port
String? currentGateId;
try {
final port = layout!.ports.firstWhere((p) => p.id == currentPortId);
currentGateId = port.instanceId;
} on Object catch (_) {
continue;
}
// If this gate is not constant, return it
if (!isConstantGate(currentGateId)) {
return currentGateId;
}
// If it's constant, queue its connected ports
for (final edge in oldEdges) {
if (edge.sourcePort == currentPortId &&
edge.targetPort != null &&
!visited.contains(edge.targetPort)) {
queue.add(edge.targetPort!);
} else if (edge.targetPort == currentPortId &&
edge.sourcePort != null &&
!visited.contains(edge.sourcePort)) {
queue.add(edge.sourcePort!);
}
}
}
return null; // No non-constant gate found
}
// If this port has only 1 connection, find the closest non-constant gate
if (connectionsFromThisPort == 1 && otherPortOnConnection != null) {
closestConnectedGateId = findNonConstantConnectedGate(
otherPortOnConnection,
);
}
final collapsed = schematicAdapter!.collapsePortRecursive(nodeId, portId);
if (!collapsed) {
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
final elkGraph = schematicAdapter!.schematic.toJsGraph();
final newLayout = await engine.computeLayoutFromElkGraph(
elkGraph,
sessionId: _sessionId,
);
if (newLayout.hasError || !mounted) {
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
// Determine focus point for pan after collapse. Case 1: Original port
// still exists → focus on it Case 2: Was last connection and found a
// close non-constant gate → focus there Case 3: No good focus point →
// keep viewport where it was
String? newFocusPortId;
final portStillExists = newLayout.ports.any((p) => p.id == portId);
if (portStillExists) {
// Port still exists—keep it in focus
newFocusPortId = portId;
} else if (closestConnectedGateId != null) {
// Port was deleted but we found a non-constant gate to focus on
// Find a port on that gate to focus on
try {
final gatePort = newLayout.ports.firstWhere(
(p) => p.instanceId == closestConnectedGateId,
);
newFocusPortId = gatePort.id;
} on Object catch (_) {
// Port not found in closest gate
}
}
setState(() {
layout = newLayout;
isToggling = false;
pendingToggleNodeId = null;
if (newFocusPortId != null) {
focusPortId = newFocusPortId;
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
focusPortId = null;
});
}
});
return newLayout;
} on Exception {
if (mounted) {
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
}
return null;
}
}