collapseAll method
Collapses all nodes in the tree.
Uses batch operations for better performance with large trees.
Implementation
void collapseAll({bool animate = true, int? maxDepth}) {
if (animationDuration == Duration.zero) animate = false;
// Collect all expanded nodes and their visible descendants
final nodesToCollapse = <TKey>[];
final nodesToHide = <TKey>[];
for (final rootId in _roots) {
if (_isExpandedKey(rootId)) {
nodesToCollapse.add(rootId);
nodesToHide.addAll(_getVisibleDescendants(rootId));
}
}
// Also check for nodes that are entering (from an interrupted expandAll)
final nodesToHideSet = nodesToHide.toSet();
// Check standalone entering animations
for (final nid in _activeStandaloneNids) {
final state = _standaloneByNid[nid]!;
if (state.type != AnimationType.entering) continue;
final key = _nids.keyOfUnchecked(nid);
if (nodesToHideSet.contains(key)) continue;
if (_parentKeyOfKey(key) != null) {
nodesToHide.add(key);
nodesToHideSet.add(key);
}
}
// Check operation group members (expanding)
for (final group in _operationGroups.values) {
if (group.pendingRemoval.isEmpty) {
// Group is expanding
for (final key in group.members.keys) {
if (!nodesToHideSet.contains(key)) {
if (_parentKeyOfKey(key) != null) {
nodesToHide.add(key);
nodesToHideSet.add(key);
}
}
}
}
}
// Check bulk group members (expanding nodes)
if (_bulkAnimationGroup != null) {
for (final key in _bulkAnimationGroup!.members) {
if (!nodesToHideSet.contains(key)) {
if (_parentKeyOfKey(key) != null) {
nodesToHide.add(key);
nodesToHideSet.add(key);
}
}
}
}
if (nodesToHide.isEmpty) {
if (nodesToCollapse.isNotEmpty) {
_collapseAllInRegistry(maxDepth);
// Bulk expansion-state clear — see main collapseAll branch below.
_notifyStructural();
}
return;
}
// Clear expansion state for ALL nodes within depth limit,
// not just visible ones.
_collapseAllInRegistry(maxDepth);
_structureGeneration++;
if (animate) {
// Reverse expanding operation groups
bool opGroupReversed = false;
for (final entry in _operationGroups.entries) {
final group = entry.value;
if (group.pendingRemoval.isEmpty) {
// Group is expanding — reverse it
for (final nodeId in group.members.keys) {
if (!_isPendingDeletion(nodeId)) {
group.pendingRemoval.add(nodeId);
opGroupReversed = true;
}
}
// Normalize startExtent to 0 so the reversal terminates at
// zero instead of at a captured mid-flight start value.
for (final member in group.members.entries) {
member.value.startExtent = 0.0;
}
group.controller.reverse();
}
}
if (opGroupReversed) _bumpAnimGen();
// Check if there's an expanding bulk animation we can reverse
if (_bulkAnimationGroup != null &&
_bulkAnimationGroup!.members.isNotEmpty &&
_bulkAnimationGroup!.pendingRemoval.isEmpty) {
// Mark all members for removal when animation completes at 0
for (final key in _bulkAnimationGroup!.members) {
if (!_isPendingDeletion(key)) {
_addBulkPending(key);
}
}
// Handle additional nodes not in any group
for (final key in nodesToHide) {
if (_isPendingDeletion(key)) continue;
if (!_bulkAnimationGroup!.members.contains(key) &&
!_hasOperationGroup(key)) {
_startStandaloneExitAnimation(key);
}
}
// Reverse the controller direction
_bulkAnimationGroup!.controller.reverse();
_bumpBulkGen();
} else {
// Dispose old group and create fresh with value=1.0
_disposeBulkAnimationGroup();
_bulkAnimationGroup = _createBulkAnimationGroup(initialValue: 1.0);
// Add nodes to the bulk group, keeping individually-animating
// nodes on their own timeline for smooth transitions.
for (final key in nodesToHide) {
if (_isPendingDeletion(key)) continue;
if (_hasOperationGroup(key)) continue;
if (_hasStandalone(key)) {
// Reverse standalone animation smoothly
_startStandaloneExitAnimation(key);
} else {
_removeAnimation(key);
_addBulkMember(key);
_addBulkPending(key);
}
}
// Start collapsing (value 1 -> 0)
if (_bulkAnimationGroup!.members.isNotEmpty) {
_bulkAnimationGroup!.controller.reverse();
}
_bumpBulkGen();
}
} else {
// Remove immediately
final toRemove = <TKey>{};
for (final key in nodesToHide) {
if (!_isPendingDeletion(key)) {
toRemove.add(key);
_removeAnimation(key);
}
}
if (toRemove.isNotEmpty) {
_removeFromVisibleOrder(toRemove);
}
}
// Bulk expansion-state clear: every node whose isExpanded state flipped
// may render differently (chevron rotation, etc.). The set can span
// arbitrary subtrees; fall back to a full refresh.
_notifyStructural();
}