TreeController<TKey, TData> class
Controller for a SliverTree widget.
Manages:
- Tree structure (nodes, parent/child relationships, depth)
- Visibility (which nodes are in the flattened visible list)
- Expansion state (which nodes are expanded)
- Animation state (which nodes are animating and their progress)
Uses an ECS-style architecture where components are stored separately for efficient iteration and memory usage.
The controller provides two notification channels:
- addListener / removeListener from ChangeNotifier: for structure changes
- addAnimationListener / removeAnimationListener: for animation ticks
This separation allows the render object to only do full relayout when structure changes, and just update geometry/repaint during animations.
- Inheritance
-
- Object
- ChangeNotifier
- TreeController
Constructors
-
TreeController({required TickerProvider vsync, TreeAnimationStyle animationStyle = const TreeAnimationStyle(), double indentWidth = 0.0, Comparator<
TreeNode< ? comparator})TKey, TData> > - Creates a tree controller.
Properties
-
anim
→ AnimationReader<
TKey> -
Public read-only accessor for the animation subsystem. Render-layer
hot paths bind to this via AnimationReader (the narrow read
interface implemented by AnimationCoordinator).
no setter
- animationStyle ↔ TreeAnimationStyle
-
Animation timing/easing for every family: expand/collapse,
enter/exit, reorder slides, make-room preview, drop settle.
getter/setter pair
- bulkAnimationGeneration → int
-
Monotonic counter that bumps whenever the bulk animation group is
created, destroyed, or its member set changes. The render object uses
this to detect when its cached per-position offset cumulatives are stale.
no setter
- bulkAnimationValue → double
-
Current animation value of the bulk animation group, or 0.0 if none.
no setter
-
comparator
→ Comparator<
TreeNode< ?TKey, TData> > -
Optional comparator for maintaining sorted order among siblings.
final
- composedSlideAbsDeltaBound → double
-
A true upper bound on the COMPOSED per-row delta, the SUM companion
of maxActiveSlideAbsDelta's max. getSlideDeltaNid SUMS the FLIP
engine and the make-room preview, and a row can carry both at once
(a drag started while the prior commit's FLIP is still animating),
reaching up to
slideMax + previewMax; a max-based bound under-estimates exactly there. Use THIS bound wherever a window must provably contain every composed painted position (render overreach widening, the bounded drop-target scan). Degenerates to the single engine's max whenever the other is idle (an idle engine's max is 0.0), so single-engine states pay no extra width. Both engine maxima are computed on demand, never stale.no setter -
currentlyAnimatingKeys
→ Set<
TKey> -
Live, read-only view of every key currently animating across
standalone, operation-group, and bulk sources. Backed by the lazy
AnimationCoordinator.ensureAnimatingKeyscache; do NOT mutate , same convention as orderNidsView. Iteration is stable within one frame.no setter -
debugAllKeys
→ Iterable<
TKey> -
All currently-live keys, in nid order. Debug-only accessor for tests
that need to pick a random key from the live set.
no setter
- debugDescendantWalkCount ↔ int
-
Cumulative count of structural-descendant walks (
_getDescendants). Perf pin companion to debugSlideBaselineStagingProbeCount: an idle expand must not materialize the descendant list at all (the staging row source is a thunk, invoked only past the gate), so the pair pins both halves of the idle-tap contract.getter/setter pair - debugIndexInParentIterationCount ↔ int
-
Debug-only: cumulative count of sibling-probe loop iterations
performed by the live-index cache refresh that backs
getIndexInParent. Counts iterations rather than calls so tests can
pin exact budgets: a cache-hit read costs ZERO iterations, and a
refresh costs the touched list's FULL raw length (it rebuilds every
live member's slot, not just the probed key's prefix). Never reset
internally; tests zero it directly.
getter/setter pair
- debugLastPreviewTargetIterationCount ↔ int
-
Debug-only: number of visible-order slots examined by the last
setReorderPreview target computation. -1 when the computation has
never run (calls that exit before the target loop leave it
unchanged). Pins the loop's O(visibleNodeCount) contract, the order
buffer is grow-only, so an unbounded loop would silently scan the
stale capacity tail after a high-water mark.
getter/setter pair
- debugOrderResetIndexAllCount → int
-
Number of full reverse-index resets (O(nidCapacity) memsets) the
order buffer has performed. Perf oracle for the contiguous-removal
fast path: incremental mutations must not trigger one.
no setter
- debugPreviewInstallCount ↔ int
-
Debug-only: number of full target-map computations performed by
setReorderPreview (memo misses). Never decremented. Pins the
geometry memo's contract: re-resolving an unchanged slot at pointer
frequency must not recompute the O(visibleNodeCount) target map.
getter/setter pair
- debugSlideBaselineStagingProbeCount ↔ int
-
Cumulative count of per-row slide-delta probes performed by
_stageSlideBaselineForBaseChange's any-live scan. Perf pin for the idle-tap contract: an expand/collapse with no composed slide activity anywhere performs ZERO probes (the O(1) hasActiveSlides gate returns first). Counts loop iterations, not calls. Tests reset it to 0 and assert an exact budget.getter/setter pair - debugVisibleDescendantsWalkCount ↔ int
-
Cumulative count of visible-descendant walks
(
_getVisibleDescendants). Perf pin for collapse's single-walk contract: one collapse performs exactly ONE walk, shared by baseline staging and the removal logic.getter/setter pair - hasActiveAnimations → bool
-
Whether any nodes are currently animating.
no setter
- hasActiveFlipSlides → bool
-
Internal-use-only: whether any FLIP slide is in flight, EXCLUDING a
held make-room preview.
no setter
- hasActiveSlides → bool
-
Whether any FLIP slide animations are currently active.
no setter
- hasActiveXSlides → bool
-
Whether any in-flight slide has a non-zero X-axis component
(depth-changing reparent). Hot-path render code uses this to skip
per-row X-delta reads when no X-axis work is in flight, the common
case, since most reorders are same-depth.
no setter
- hasComparator → bool
-
Whether this controller sorts siblings with a comparator.
no setter
- hashCode → int
-
The hash code for this object.
no setterinherited
- hasListeners → bool
-
Whether any listeners are currently registered.
no setterinherited
- hasOpGroupAnimations → bool
-
Whether any non-bulk animations (operation groups or standalone) are
currently active. When false and isBulkAnimating is true, the render
object can use its scalar-offset fast path for the whole frame.
no setter
- indentWidth ↔ double
-
Horizontal indent per depth level in logical pixels.
getter/setter pair
- isBulkAnimating → bool
-
True when a bulk animation group is currently active and has members
animating in either direction.
no setter
- liveRootCount → int
-
Number of live (non-pending-deletion) roots. Non-allocating variant
of
liveRootKeys.lengthfor hot paths (drop-zone boundary checks).no setter -
liveRootKeys
→ List<
TKey> -
Root keys that are not pending deletion.
no setter
- maxActiveSlideAbsDelta → double
-
Test-only: the LARGER of the two engines' maxima (|currentDelta|
ceilings), or 0.0 when both are idle.
no setter
- nidCapacity → int
-
The current high-water mark for allocated nids. Nid-indexed dense
arrays maintained externally should grow to at least this length.
no setter
- orderNidsView → Int32List
-
Read-only view over the visible-order nid buffer for hot-path
consumers that walk all visible positions and want to skip the
per-position visibleNidAt dispatch. The underlying buffer's
length may exceed visibleNodeCount, only the first N entries
are valid. The buffer itself is mutated in place by structural
changes; callers must not retain the reference across mutations.
no setter
- rootCount → int
-
no setter
-
rootKeys
→ List<
TKey> -
Root node IDs in order.
latefinal
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- slideClampOverhangViewports ↔ double
-
Padding past the viewport edge (in viewport-heights) used as the
start position (slide-IN) or end position (slide-OUT edge ghost) for
FLIP slides whose prior or current is off-screen. A small overhang
gives the row visible motion into / out of the viewport rather
than appearing/disappearing exactly at the edge.
getter/setter pair
- structureGeneration → int
-
no setter
- visibleNodeCount → int
-
Number of visible nodes.
no setter
-
visibleNodes
→ List<
TKey> -
The flattened list of visible node IDs in render order.
latefinal
Methods
-
addAnimationListener(
VoidCallback listener) → void - Registers a callback that fires on every animation tick.
-
addExpansionListener(
void listener(TKey key, bool isExpanded)) → void - Registers a callback fired when a node's expansion state changes.
-
addListener(
VoidCallback listener) → void -
Register a closure to be called when the object changes.
inherited
-
addNodeDataListener(
void listener(TKey key)) → void - Registers a callback that fires when a single node's data changes without any structural change (e.g. after updateNode).
-
addStructuralListener(
void listener(Set< TKey> ? affectedKeys)) → void -
Registers a callback that fires on structural mutations with an
optional set of affected keys. See
_structuralListenersfor the semantics of the argument. -
animateDropSettleGlide(
Map< TKey, ({double x, double y})> priorOffsets, Map<TKey, ({double x, double y})> currentOffsets, {required Duration duration, required Curve curve}) → void - Internal-use-only: installs a drop-settle glide (the drag proxy's cancel return / dead-commit-slide fallback).
-
animateScrollToKey(
TKey key, {required ScrollController scrollController, Duration duration = const Duration(milliseconds: 300), Curve curve = Curves.linear, double alignment = 0.0, AncestorExpansionMode ancestorExpansion = AncestorExpansionMode.immediate, double extentEstimator(TKey key)?, double sliverBaseOffset = 0.0}) → Future< bool> -
Animates
scrollControllerto revealkeyin its attached viewport. -
animateSlideFromOffsets(
Map< TKey, ({double x, double y})> priorOffsets, Map<TKey, ({double x, double y})> currentOffsets, {Duration? duration, Curve? curve, double maxSlideDistance = double.infinity}) → void -
Starts a FLIP slide animation for every visible node whose position in
scroll-space changed between
priorOffsets(pre-mutation) andcurrentOffsets(post-mutation). Produce both with RenderSliverTree.snapshotVisibleOffsets, the first before the structural mutation, the second from inside a WidgetsBinding.addPostFrameCallback after the mutation's layout has run. -
bulkAnimationData(
) → BulkAnimationData< TKey> - Captures every per-frame bulk-animation field the render object reads during a single layout into one snapshot. Forwards to AnimationCoordinator.bulkAnimationData which delegates to BulkAnimator.snapshot; the inactive case returns the const sentinel for zero per-call allocation.
-
captureOperationGroupToken(
TKey operationKey) → Object? -
Captures an opaque token identifying the current operation group at
operationKey, or null if no group is installed there. -
clearReorderPreview(
{required bool animate, Duration? duration, Curve? curve}) → void -
Ends the make-room preview: animate the shifted rows back
(
animatetrue, pointer left every valid slot, or the drag was cancelled) or drop the offsets instantly (animatefalse, the commit path, where the staged FLIP baseline has already captured the shifted painted positions and the mutation's layout takes over). -
collapse(
{required TKey key, bool animate = true}) → void - Collapses the given node, hiding its children.
-
collapseAll(
{bool animate = true, int? maxDepth}) → void - Collapses all nodes in the tree.
-
computeFirstAnimatingVisibleIndex(
) → int - Returns the smallest visible-order index among all currently-animating nodes, or visibleNodeCount when none are visible / none are animating.
-
debugAssertVisibleSubtreeSizeConsistency(
) → void - Debug-only forwarder to VisibleOrderBuffer.debugAssertSubtreeSizeConsistent, used by the fuzz and purge suites.
-
debugPrintBulkAnimationState(
) → void - Debug helper to print bulk animation state. Call this to verify animation is running correctly.
-
depthOfNid(
int nid) → int -
Depth for
nid(0 for roots). NoTKeyhash.nidmust be live. -
dispose(
) → void -
Discards any resources used by the object.
override
-
ensureAncestorsExpanded(
TKey key) → int -
Immediately expands every collapsed ancestor of
keyso thatkeybecomes part of the visible order. Expansion is synchronous (no animation) so a subsequent scrollOffsetOf call sees the updated structure. Returns the number of ancestors that were expanded. -
expand(
{required TKey key, bool animate = true}) → void - Expands the given node, revealing its children.
-
expandAll(
{bool animate = true, int? maxDepth}) → void - Expands all nodes in the tree.
-
extentOf(
TKey key, {double extentEstimator(TKey key)?}) → double -
Returns the best-known full (non-animated) extent for
key: the measured value if the node has ever been laid out, otherwiseextentEstimatorif supplied, otherwise defaultExtent. Matches the fallback chain used by scrollOffsetOf. -
getAnimatedExtent(
TKey key, double fullExtent) → double -
Gets the animated extent for a node.
If the node is animating, returns the interpolated extent.
Otherwise returns
fullExtent. -
getAnimationState(
TKey key) → AnimationState? - Gets the animation state for a node, or null if not animating. Returns the standalone state if present, a synthetic entering state for operation group members that are expanding, or null for bulk/ collapsing groups.
-
getChildCount(
TKey key) → int - Gets the number of children for the given node.
-
getChildren(
TKey key) → List< TKey> - Gets the ordered list of child keys for the given node.
-
getCurrentExtent(
TKey key) → double - Gets the current extent for a node, accounting for animation.
-
getCurrentExtentNid(
int nid) → double -
Current animated extent for the live
nid. Hot-path forwarder to the AnimationCoordinator's nid-keyed read. -
getDepth(
TKey key) → int - Gets the depth of the given node (0 for roots).
-
getDescendants(
TKey key) → List< TKey> -
Returns all descendants of
keyin pre-order (children, grandchildren, ...). Does not includekeyitself. Returns an empty list ifkeyhas no children or is not present. -
getEstimatedExtent(
TKey key) → double - Gets the estimated full extent for a node. Returns the cached measured extent if available, otherwise defaultExtent.
-
getEstimatedExtentNid(
int nid) → double -
Estimated full extent for the live
nid, measured value when available, defaultExtent otherwise. Hot-path equivalent of getEstimatedExtent that avoids the key-to-nid hash. Caller must guaranteenidis live and within range. -
getFlipSlideDeltaNid(
int nid) → double -
Internal-use-only: FLIP-only Y slide delta for the live
nid, EXCLUDING any held make-room preview offset. 0.0 when no FLIP slide is in flight fornid. -
getIndent(
TKey key) → double - Gets the horizontal indent for the given node.
-
getIndexInParent(
TKey key) → int -
Returns the zero-based index of
keywithin the live sibling list of its parent (or the live root list, ifkeyis a root). Returns -1 ifkeyis not present or is itself pending deletion. -
getLiveChildren(
TKey parent) → List< TKey> -
Children of
parentthat are not pending deletion. -
getMeasuredExtent(
TKey key) → double? -
The measured full extent for
key, or null ifkeyhas never been laid out. Distinct from getEstimatedExtent, which falls back to defaultExtent for unmeasured nodes. -
getNodeData(
TKey key) → TreeNode< TKey, TData> ? - Gets the node data for the given key, or null if not found.
-
getParent(
TKey key) → TKey? - Gets the parent key for the given node, or null if it is a root.
-
getSlideDelta(
TKey key) → double -
Current slide delta for
keyin scroll-space y, or 0.0 if the node is not currently sliding. Read by RenderSliverTree.paint, RenderSliverTree.applyPaintTransform, and the hit-test path on every frame (no caching, staleness-safe under tick-without-paint). -
getSlideDeltaNid(
int nid) → double -
Slide delta for the live
nid(paint-only FLIP offset), or 0.0 when the node is not currently sliding. Hot-path equivalent of getSlideDelta: read every paint, hit-test, and transform call for visible rows, so saving the key-to-nid hash matters. -
getSlideDeltaX(
TKey key) → double -
X-axis (cross-axis indent) slide delta for
key, or 0.0 when the node is not currently sliding (or not registered). -
getSlideDeltaXNid(
int nid) → double -
X-axis (cross-axis indent) slide delta for the live
nid, or 0.0 when the node is not currently sliding. Hot-path equivalent of getSlideDeltaX: read on every paint, hit-test, and transform call for visible rows during a depth-changing reparent. -
getVisibleIndex(
TKey key) → int - Gets the index of a node in the visible order, or -1 if not visible.
-
hasChildren(
TKey key) → bool - Whether the given node has children.
-
hasLiveChildren(
TKey parent) → bool -
Whether
parenthas at least one live (non-pending-deletion) child. -
insert(
{required TKey parentKey, required TreeNode< TKey, TData> node, int? index, bool animate = true, bool preservePendingSubtreeState = false}) → void - Inserts a new node as a child of the given parent.
-
insertRoot(
TreeNode< TKey, TData> node, {int? index, bool animate = true, bool preservePendingSubtreeState = false}) → void - Adds a new root node to the tree.
-
isAnimating(
TKey key) → bool - Whether the given node is currently animating. Forwards to the AnimationCoordinator's per-key check.
-
isAnimatingNid(
int nid) → bool -
Hot-path equivalent of isAnimating: O(1) array read via the
AnimationCoordinator's union mirror. Caller must guarantee
nidis live and within range. -
isBulkMember(
TKey key) → bool -
Whether
keyis a member of the bulk animation group (either active or pending removal at animation end). -
isExiting(
TKey key) → bool - Whether the given node is currently exiting (animating out).
-
isExitingNid(
int nid) → bool - Hot-path equivalent of isExiting.
-
isExpanded(
TKey key) → bool - Whether the given node is expanded.
-
isOperationGroupSame(
TKey operationKey, Object? token) → bool -
Whether the operation group at
operationKeyis identity-equal to the one captured by captureOperationGroupToken. -
isPendingDeletion(
TKey key) → bool -
Whether
keyis pending deletion, present in the structural maps but animating out and scheduled for purge once the animation settles. -
isVisible(
TKey key) → bool -
Whether
keyis currently in the flattened visible order, i.e. every ancestor (if any) is expanded AND the node itself exists. -
keyOfNid(
int nid) → TKey? -
Returns the key associated with
nid, or null if the nid has been released. O(1). Consumers that cache nid-indexed state can use this to detect stale entries after node removal. -
liveChildCount(
TKey parent) → int -
Number of live (non-pending-deletion) children of
parent. Non-allocating variant ofgetLiveChildren(parent).lengthfor hot paths (drop-zone boundary checks ask this per candidate level). -
liveSiblingAt(
TKey? parent, int index) → TKey? -
The live sibling at
indexunderparent(or among the roots whenparentis null), or null whenindexis out of range. -
markSlidePreserveProgress(
TKey key) → void -
Internal-use-only: marks the slide entry for
keyto bypass the engine's "un-touched re-baseline" branch on subsequent batch installs. -
moveNode(
TKey key, TKey? newParentKey, {int? index, bool animate = true, Duration? slideDuration, Curve? slideCurve}) → void -
Moves a node from its current parent to
newParentKey. -
nidOf(
TKey key) → int -
Returns the internal nid for
key, or noNid if the key isn't currently registered. O(1). -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
notifyAnimationListenersForScroll(
) → void -
Forwards to the coordinator's listener channel so the scroll
orchestrator's
AnimationController(scrollProgress) can fire ticks through the controller's animation-listener channel. -
notifyListeners(
) → void -
Call all the registered listeners.
inherited
-
registerRenderHost(
TreeRenderHost host) → void -
Registers a render host. Called by
RenderSliverTree.attach. Idempotent. -
remove(
{required TKey key, bool animate = true}) → void - Removes a node and all its descendants from the tree.
-
removeAnimationListener(
VoidCallback listener) → void - Removes a previously registered animation listener.
-
removeExpansionListener(
void listener(TKey key, bool isExpanded)) → void - Removes a previously registered expansion listener.
-
removeListener(
VoidCallback listener) → void -
Remove a previously registered closure from the list of closures that are
notified when the object changes.
inherited
-
removeNodeDataListener(
void listener(TKey key)) → void - Removes a previously registered node-data listener.
-
removeStructuralListener(
void listener(Set< TKey> ? affectedKeys)) → void - Removes a previously registered structural listener.
-
reorderChildren(
TKey parentKey, List< TKey> orderedKeys, {bool animate = true, Duration? slideDuration, Curve? slideCurve}) → void -
Reorders the children of
parentKeyto matchorderedKeys. -
reorderRoots(
List< TKey> orderedKeys, {bool animate = true, Duration? slideDuration, Curve? slideCurve}) → void -
Reorders the root nodes to match
orderedKeys. -
runBatch<
T> (T body()) → T -
Runs
bodywith structural notifications coalesced into a single notifyListeners call fired afterbodyreturns. -
scrollOffsetOf(
TKey key, {double extentEstimator(TKey key)?}) → double? -
Returns the sliver-space scroll offset of
key, or null ifkeyis not in the current visible order (e.g., ancestors collapsed, or key not registered). The offset corresponds to the node's top edge within the SliverTree's own scroll extent. -
setChildren(
TKey parentKey, List< TreeNode< children) → voidTKey, TData> > - Adds children to a node.
-
setFullExtent(
TKey key, double extent) → void - Stores the measured full extent for a node.
-
setReorderPreview(
{required TKey draggedKey, required TKey targetKey, required bool gapBelowTarget, Duration? duration, Curve? curve}) → void -
Opens the make-room preview at one of
targetKey's own row edges:gapBelowTarget: falsefor the edge above it,truefor the edge directly below it. -
setReorderPreviewAtIndex(
{required TKey draggedKey, required int gapVisibleIndex, Duration? duration, Curve? curve}) → void -
Opens (or re-targets) the paint-only make-room preview for a drag of
draggedKey, with the gap opening BEFORE the row atgapVisibleIndex: rows at/after that slot shift down by the dragged subtree's visible extent, rows after the vacated slot shift up, and the offsets are HELD until this is called again (new slot) or clearReorderPreview releases them. -
setRoots(
List< TreeNode< roots) → voidTKey, TData> > - Initializes the tree with the given root nodes.
-
takePendingExitPhantomAnchors(
) → Map< TKey, TKey> ? - Returns and clears the staged exit-phantom relationships. Companion to takePendingPhantomAnchors for the visible-to-hidden case.
-
takePendingPhantomAnchors(
) → Map< TKey, TKey> ? - Returns and clears the staged phantom-anchor relationships. Called by the render object during baseline consumption. Returns null when no relationships were staged (the common case, only animated reparents of hidden subtrees produce entries).
-
toggle(
{required TKey key, bool animate = true}) → void - Toggles the expansion state of the given node.
-
toString(
) → String -
A string representation of this object.
inherited
-
unregisterRenderHost(
TreeRenderHost host) → void -
Unregisters a render host. Called by
RenderSliverTree.detach. Tolerant of a host that was never registered (no-op). -
updateNode(
TreeNode< TKey, TData> node) → void - Updates the data payload for an existing node without structural changes.
-
visibleIndexOfNid(
int nid) → int -
Visible-order index for the live nid
nid, or VisibleOrderBuffer.kNotVisible whennidis not currently in the visible order. O(1) typed-data read; noTKeyhash. Hot-path equivalent of_order.indexByNid[nid]. -
visibleNidAt(
int visibleIndex) → int -
Returns the nid of the visible node at
visibleIndex. NoTKeyhash occurs. Panics (unchecked read) ifvisibleIndexis out of range. -
visibleSubtreeSize(
TKey key) → int -
Number of currently-visible rows in
key's subtree, includingkeyitself. O(1) read off the order buffer's cached subtree sizes.
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Properties
- debugFullConsistencyChecks ↔ bool
-
Opt-in: run the FULL cross-structure consistency sweep (whole order
walk, nid-table walks, every animation mirror) after every
incremental order mutation in debug builds. Off by default, the
sweep makes N sequential inserts O(N^2) in debug; the default is an
O(changed-range) order/reverse-index agreement check. The fuzz/purge
suites, which exist to exercise the invariants, enable this.
getter/setter pair
Constants
- defaultExtent → const double
- Default extent for nodes that haven't been measured yet.
- noNid → const int
- Sentinel returned by nidOf when the key isn't registered. Same value as the internal VisibleOrderBuffer.kNotVisible but exposed separately since callers should treat it as "unknown key".