expandAll method
Expands all nodes in the tree.
Uses batch operations for better performance with large trees.
Implementation
void expandAll({bool animate = true, int? maxDepth}) {
if (animationDuration == Duration.zero) animate = false;
// Collect all nodes to expand, nodes to show, and nodes currently exiting
final nodesToExpand = <TKey>[];
final nodesToShow = <TKey>[];
final nodesToReverseExit = <TKey>[];
// Iterative DFS. Depth is recomputed per-visit via [_depthOfKey]
// (matching the original recursive implementation) so we do not
// need to carry depth along in a parallel stack.
final stack = <TKey>[];
for (final rootId in _roots) {
stack.add(rootId);
}
while (stack.isNotEmpty) {
final key = stack.removeLast();
if (_isPendingDeletion(key)) {
continue;
}
final children = _childListOf(key);
if (children == null || children.isEmpty) {
continue;
}
final depth = _depthOfKey(key);
final withinDepthLimit = maxDepth == null || depth < maxDepth;
if (withinDepthLimit && !_isExpandedKey(key)) {
nodesToExpand.add(key);
for (final childId in children) {
if (!_order.contains(childId)) {
nodesToShow.add(childId);
}
}
}
// Still check children for exiting animations regardless of depth.
for (final childId in children) {
// Check standalone exiting
final animation = _standaloneAt(childId);
if (animation != null && animation.type == AnimationType.exiting) {
if (!_isPendingDeletion(childId)) {
nodesToReverseExit.add(childId);
}
}
// Check operation group exiting (pendingRemoval)
final opGroupKey = _operationGroupOf(childId);
if (opGroupKey != null) {
final opGroup = _operationGroups[opGroupKey];
if (opGroup != null && opGroup.pendingRemoval.contains(childId)) {
if (!_isPendingDeletion(childId)) {
nodesToReverseExit.add(childId);
}
}
}
}
// Only descend into children if within depth limit.
if (withinDepthLimit) {
for (final childId in children) {
stack.add(childId);
}
}
}
if (nodesToExpand.isEmpty && nodesToReverseExit.isEmpty) {
return;
}
// Batch update expansion states. Skip per-call ancestors-expanded
// propagation — we rebuild it wholesale below in O(N).
for (final key in nodesToExpand) {
_setExpandedKey(key, true, propagate: false);
}
_rebuildAllAncestorsExpanded();
// Rebuild visible order from scratch (more efficient for bulk operations)
_markVisibleOrderDirty();
// Start animations for newly visible nodes and reverse exiting animations
if (animate) {
// Reverse collapsing operation groups
bool opGroupReversed = false;
for (final entry in _operationGroups.entries) {
final group = entry.value;
if (group.pendingRemoval.isNotEmpty) {
group.pendingRemoval.clear();
opGroupReversed = true;
// Restore each member's targetExtent to full so the reversal
// terminates at the correct natural size instead of at a
// captured mid-flight value.
for (final member in group.members.entries) {
member.value.targetExtent =
_fullExtentOf(member.key) ?? _unknownExtent;
}
group.controller.forward();
}
}
if (opGroupReversed) _bumpAnimGen();
// Check if there's a collapsing bulk animation we can reverse
if (_bulkAnimationGroup != null &&
_bulkAnimationGroup!.pendingRemoval.isNotEmpty) {
// Reverse the animation - nodes being removed will now expand
// Clear pending removal since we're expanding now
_clearBulkPending();
// Reverse standalone exit animations smoothly
for (final key in nodesToReverseExit) {
if (!_hasOperationGroup(key)) {
_startStandaloneEnterAnimation(key);
}
}
// Add any new nodes to the group (skip if already in an operation group)
for (final key in nodesToShow) {
if (_order.contains(key) && !_hasOperationGroup(key)) {
_addBulkMember(key);
}
}
// Reverse the controller direction
_bulkAnimationGroup!.controller.forward();
_bumpBulkGen();
} else {
// Dispose old group and create fresh to avoid status listener race
_disposeBulkAnimationGroup();
_bulkAnimationGroup = _createBulkAnimationGroup();
// Reverse standalone exit animations smoothly
for (final key in nodesToReverseExit) {
if (!_hasOperationGroup(key)) {
_startStandaloneEnterAnimation(key);
}
}
// Add new nodes to the bulk group (skip if already in an operation group)
for (final key in nodesToShow) {
if (_order.contains(key) && !_hasOperationGroup(key)) {
_addBulkMember(key);
}
}
// Start expanding (value 0 -> 1)
_bulkAnimationGroup!.controller.forward();
_bumpBulkGen();
}
} else {
// Remove animations if not animating
for (final key in nodesToReverseExit) {
_removeAnimation(key);
}
}
// Bulk expansion touches many ancestors' expansion state + every
// previously-collapsed node now flips its chevron. Enumerating the
// affected set precisely is error-prone; fall back to a full refresh.
_notifyStructural();
}