From 375241be37582b964db57c8d2d00503c53802dc5 Mon Sep 17 00:00:00 2001 From: Andy Horn Date: Wed, 27 May 2026 06:57:51 -0700 Subject: [PATCH] refactor: convert _distributeAvailableSpaceDelta to iterative water-filling Replaces the recursive implementation that rebuilt changeable indices, maximum allowances, and full index lists per recursion. The iterative loop reuses a single mutable working-sizes buffer and saturates at least one child per pass, bounding total work at O(n^2) with much smaller constants. Closes #118 --- lib/src/resizable_controller.dart | 70 ++++++++++++++----------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/lib/src/resizable_controller.dart b/lib/src/resizable_controller.dart index e8a4592..7c0c32e 100644 --- a/lib/src/resizable_controller.dart +++ b/lib/src/resizable_controller.dart @@ -369,53 +369,47 @@ class ResizableController with ChangeNotifier { required double delta, required List sizes, }) { - final indices = List.generate(_children.length, (i) => i); - final changeableIndices = _getChangeableIndices(delta < 0 ? -1 : 1, sizes); - - if (changeableIndices.isEmpty) { - return List.filled(sizes.length, 0.0); - } + final changes = List.filled(sizes.length, 0.0); + final workingSizes = List.of(sizes); + var remainingDelta = delta; - final changePerItem = delta / changeableIndices.length; + // Iterative water-filling: each pass distributes `remainingDelta` equally + // amongst changeable children, capping each at its allowable change. A + // child capped below the equal share is saturated and drops out of the + // next pass, so the loop terminates in at most 2n iterations (the + // expand-priority set, then the fallback set). + while (remainingDelta != 0.0) { + final changeableIndices = _getChangeableIndices( + remainingDelta < 0 ? -1 : 1, + workingSizes, + ); - final maximums = indices.map((i) { - if (changeableIndices.contains(i)) { - return _getAllowableChange(delta: delta, index: i, sizes: sizes); + if (changeableIndices.isEmpty) { + break; } - return 0.0; - }).toList(); - - final changes = indices.map((index) { - if (!changeableIndices.contains(index)) { - return 0.0; - } + final changePerItem = remainingDelta / changeableIndices.length; + var allocated = 0.0; - final max = maximums[index]; + for (final index in changeableIndices) { + final allowable = _getAllowableChange( + delta: remainingDelta, + index: index, + sizes: workingSizes, + ); + final change = + allowable.abs() < changePerItem.abs() ? allowable : changePerItem; - if (max.abs() < changePerItem.abs()) { - return max; + changes[index] += change; + workingSizes[index] += change; + allocated += change; } - return changePerItem; - }).toList(); - - final changesSum = changes.sum(); - final remainingChange = delta - changesSum; - - if (remainingChange.abs() > 0) { - final adjustedSizes = indices.map( - (index) => sizes[index] + changes[index], - ); - - final redistributed = _distributeAvailableSpaceDelta( - delta: remainingChange, - sizes: adjustedSizes.toList(), - ); - - for (var i = 0; i < changes.length; i++) { - changes[i] += redistributed[i]; + if (allocated == 0.0) { + break; } + + remainingDelta -= allocated; } return changes;