1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:vector_math/vector_math_64.dart';
7
8import 'box.dart';
9import 'object.dart';
10import 'sliver.dart';
11
12/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
13///
14/// [RenderSliverMultiBoxAdaptor] objects reify their children lazily to avoid
15/// spending resources on children that are not visible in the viewport. This
16/// delegate lets these objects create and remove children as well as estimate
17/// the total scroll offset extent occupied by the full child list.
18abstract class RenderSliverBoxChildManager {
19 /// Called during layout when a new child is needed. The child should be
20 /// inserted into the child list in the appropriate position, after the
21 /// `after` child (at the start of the list if `after` is null). Its index and
22 /// scroll offsets will automatically be set appropriately.
23 ///
24 /// The `index` argument gives the index of the child to show. It is possible
25 /// for negative indices to be requested. For example: if the user scrolls
26 /// from child 0 to child 10, and then those children get much smaller, and
27 /// then the user scrolls back up again, this method will eventually be asked
28 /// to produce a child for index -1.
29 ///
30 /// If no child corresponds to `index`, then do nothing.
31 ///
32 /// Which child is indicated by index zero depends on the [GrowthDirection]
33 /// specified in the `constraints` of the [RenderSliverMultiBoxAdaptor]. For
34 /// example if the children are the alphabet, then if
35 /// [SliverConstraints.growthDirection] is [GrowthDirection.forward] then
36 /// index zero is A, and index 25 is Z. On the other hand if
37 /// [SliverConstraints.growthDirection] is [GrowthDirection.reverse] then
38 /// index zero is Z, and index 25 is A.
39 ///
40 /// During a call to [createChild] it is valid to remove other children from
41 /// the [RenderSliverMultiBoxAdaptor] object if they were not created during
42 /// this frame and have not yet been updated during this frame. It is not
43 /// valid to add any other children to this render object.
44 void createChild(int index, { required RenderBox? after });
45
46 /// Remove the given child from the child list.
47 ///
48 /// Called by [RenderSliverMultiBoxAdaptor.collectGarbage], which itself is
49 /// called from [RenderSliverMultiBoxAdaptor]'s `performLayout`.
50 ///
51 /// The index of the given child can be obtained using the
52 /// [RenderSliverMultiBoxAdaptor.indexOf] method, which reads it from the
53 /// [SliverMultiBoxAdaptorParentData.index] field of the child's
54 /// [RenderObject.parentData].
55 void removeChild(RenderBox child);
56
57 /// Called to estimate the total scrollable extents of this object.
58 ///
59 /// Must return the total distance from the start of the child with the
60 /// earliest possible index to the end of the child with the last possible
61 /// index.
62 double estimateMaxScrollOffset(
63 SliverConstraints constraints, {
64 int? firstIndex,
65 int? lastIndex,
66 double? leadingScrollOffset,
67 double? trailingScrollOffset,
68 });
69
70 /// Called to obtain a precise measure of the total number of children.
71 ///
72 /// Must return the number that is one greater than the greatest `index` for
73 /// which `createChild` will actually create a child.
74 ///
75 /// This is used when [createChild] cannot add a child for a positive `index`,
76 /// to determine the precise dimensions of the sliver. It must return an
77 /// accurate and precise non-null value. It will not be called if
78 /// [createChild] is always able to create a child (e.g. for an infinite
79 /// list).
80 int get childCount;
81
82 /// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
83 /// [RenderSliverMultiBoxAdaptor.move].
84 ///
85 /// Subclasses must ensure that the [SliverMultiBoxAdaptorParentData.index]
86 /// field of the child's [RenderObject.parentData] accurately reflects the
87 /// child's index in the child list after this function returns.
88 void didAdoptChild(RenderBox child);
89
90 /// Called during layout to indicate whether this object provided insufficient
91 /// children for the [RenderSliverMultiBoxAdaptor] to fill the
92 /// [SliverConstraints.remainingPaintExtent].
93 ///
94 /// Typically called unconditionally at the start of layout with false and
95 /// then later called with true when the [RenderSliverMultiBoxAdaptor]
96 /// fails to create a child required to fill the
97 /// [SliverConstraints.remainingPaintExtent].
98 ///
99 /// Useful for subclasses to determine whether newly added children could
100 /// affect the visible contents of the [RenderSliverMultiBoxAdaptor].
101 void setDidUnderflow(bool value);
102
103 /// Called at the beginning of layout to indicate that layout is about to
104 /// occur.
105 void didStartLayout() { }
106
107 /// Called at the end of layout to indicate that layout is now complete.
108 void didFinishLayout() { }
109
110 /// In debug mode, asserts that this manager is not expecting any
111 /// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
112 ///
113 /// This function always returns true.
114 ///
115 /// The manager is not required to track whether it is expecting modifications
116 /// to the [RenderSliverMultiBoxAdaptor]'s child list and can return
117 /// true without making any assertions.
118 bool debugAssertChildListLocked() => true;
119}
120
121/// Parent data structure used by [RenderSliverWithKeepAliveMixin].
122mixin KeepAliveParentDataMixin implements ParentData {
123 /// Whether to keep the child alive even when it is no longer visible.
124 bool keepAlive = false;
125
126 /// Whether the widget is currently being kept alive, i.e. has [keepAlive] set
127 /// to true and is offscreen.
128 bool get keptAlive;
129}
130
131/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
132///
133/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
134/// a parentData class that uses the right mixin or whatever is appropriate.
135mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
136 /// Alerts the developer that the child's parentData needs to be of type
137 /// [KeepAliveParentDataMixin].
138 @override
139 void setupParentData(RenderObject child) {
140 assert(child.parentData is KeepAliveParentDataMixin);
141 }
142}
143
144/// Parent data structure used by [RenderSliverMultiBoxAdaptor].
145class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox>, KeepAliveParentDataMixin {
146 /// The index of this child according to the [RenderSliverBoxChildManager].
147 int? index;
148
149 @override
150 bool get keptAlive => _keptAlive;
151 bool _keptAlive = false;
152
153 @override
154 String toString() => 'index=$index; ${keepAlive ? "keepAlive; " : ""}${super.toString()}';
155}
156
157/// A sliver with multiple box children.
158///
159/// [RenderSliverMultiBoxAdaptor] is a base class for slivers that have multiple
160/// box children. The children are managed by a [RenderSliverBoxChildManager],
161/// which lets subclasses create children lazily during layout. Typically
162/// subclasses will create only those children that are actually needed to fill
163/// the [SliverConstraints.remainingPaintExtent].
164///
165/// The contract for adding and removing children from this render object is
166/// more strict than for normal render objects:
167///
168/// * Children can be removed except during a layout pass if they have already
169/// been laid out during that layout pass.
170/// * Children cannot be added except during a call to [childManager], and
171/// then only if there is no child corresponding to that index (or the child
172/// corresponding to that index was first removed).
173///
174/// See also:
175///
176/// * [RenderSliverToBoxAdapter], which has a single box child.
177/// * [RenderSliverList], which places its children in a linear
178/// array.
179/// * [RenderSliverFixedExtentList], which places its children in a linear
180/// array with a fixed extent in the main axis.
181/// * [RenderSliverGrid], which places its children in arbitrary positions.
182abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
183 with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
184 RenderSliverHelpers, RenderSliverWithKeepAliveMixin {
185
186 /// Creates a sliver with multiple box children.
187 RenderSliverMultiBoxAdaptor({
188 required RenderSliverBoxChildManager childManager,
189 }) : _childManager = childManager {
190 assert(() {
191 _debugDanglingKeepAlives = <RenderBox>[];
192 return true;
193 }());
194 }
195
196 @override
197 void setupParentData(RenderObject child) {
198 if (child.parentData is! SliverMultiBoxAdaptorParentData) {
199 child.parentData = SliverMultiBoxAdaptorParentData();
200 }
201 }
202
203 /// The delegate that manages the children of this object.
204 ///
205 /// Rather than having a concrete list of children, a
206 /// [RenderSliverMultiBoxAdaptor] uses a [RenderSliverBoxChildManager] to
207 /// create children during layout in order to fill the
208 /// [SliverConstraints.remainingPaintExtent].
209 @protected
210 RenderSliverBoxChildManager get childManager => _childManager;
211 final RenderSliverBoxChildManager _childManager;
212
213 /// The nodes being kept alive despite not being visible.
214 final Map<int, RenderBox> _keepAliveBucket = <int, RenderBox>{};
215
216 late List<RenderBox> _debugDanglingKeepAlives;
217
218 /// Indicates whether integrity check is enabled.
219 ///
220 /// Setting this property to true will immediately perform an integrity check.
221 ///
222 /// The integrity check consists of:
223 ///
224 /// 1. Verify that the children index in childList is in ascending order.
225 /// 2. Verify that there is no dangling keepalive child as the result of [move].
226 bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
227 bool _debugChildIntegrityEnabled = true;
228 set debugChildIntegrityEnabled(bool enabled) {
229 assert(() {
230 _debugChildIntegrityEnabled = enabled;
231 return _debugVerifyChildOrder() &&
232 (!_debugChildIntegrityEnabled || _debugDanglingKeepAlives.isEmpty);
233 }());
234 }
235
236 @override
237 void adoptChild(RenderObject child) {
238 super.adoptChild(child);
239 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
240 if (!childParentData._keptAlive) {
241 childManager.didAdoptChild(child as RenderBox);
242 }
243 }
244
245 bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();
246
247 /// Verify that the child list index is in strictly increasing order.
248 ///
249 /// This has no effect in release builds.
250 bool _debugVerifyChildOrder() {
251 if (_debugChildIntegrityEnabled) {
252 RenderBox? child = firstChild;
253 int index;
254 while (child != null) {
255 index = indexOf(child);
256 child = childAfter(child);
257 assert(child == null || indexOf(child) > index);
258 }
259 }
260 return true;
261 }
262
263 @override
264 void insert(RenderBox child, { RenderBox? after }) {
265 assert(!_keepAliveBucket.containsValue(child));
266 super.insert(child, after: after);
267 assert(firstChild != null);
268 assert(_debugVerifyChildOrder());
269 }
270
271 @override
272 void move(RenderBox child, { RenderBox? after }) {
273 // There are two scenarios:
274 //
275 // 1. The child is not keptAlive.
276 // The child is in the childList maintained by ContainerRenderObjectMixin.
277 // We can call super.move and update parentData with the new slot.
278 //
279 // 2. The child is keptAlive.
280 // In this case, the child is no longer in the childList but might be stored in
281 // [_keepAliveBucket]. We need to update the location of the child in the bucket.
282 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
283 if (!childParentData.keptAlive) {
284 super.move(child, after: after);
285 childManager.didAdoptChild(child); // updates the slot in the parentData
286 // Its slot may change even if super.move does not change the position.
287 // In this case, we still want to mark as needs layout.
288 markNeedsLayout();
289 } else {
290 // If the child in the bucket is not current child, that means someone has
291 // already moved and replaced current child, and we cannot remove this child.
292 if (_keepAliveBucket[childParentData.index] == child) {
293 _keepAliveBucket.remove(childParentData.index);
294 }
295 assert(() {
296 _debugDanglingKeepAlives.remove(child);
297 return true;
298 }());
299 // Update the slot and reinsert back to _keepAliveBucket in the new slot.
300 childManager.didAdoptChild(child);
301 // If there is an existing child in the new slot, that mean that child will
302 // be moved to other index. In other cases, the existing child should have been
303 // removed by updateChild. Thus, it is ok to overwrite it.
304 assert(() {
305 if (_keepAliveBucket.containsKey(childParentData.index)) {
306 _debugDanglingKeepAlives.add(_keepAliveBucket[childParentData.index]!);
307 }
308 return true;
309 }());
310 _keepAliveBucket[childParentData.index!] = child;
311 }
312 }
313
314 @override
315 void remove(RenderBox child) {
316 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
317 if (!childParentData._keptAlive) {
318 super.remove(child);
319 return;
320 }
321 assert(_keepAliveBucket[childParentData.index] == child);
322 assert(() {
323 _debugDanglingKeepAlives.remove(child);
324 return true;
325 }());
326 _keepAliveBucket.remove(childParentData.index);
327 dropChild(child);
328 }
329
330 @override
331 void removeAll() {
332 super.removeAll();
333 _keepAliveBucket.values.forEach(dropChild);
334 _keepAliveBucket.clear();
335 }
336
337 void _createOrObtainChild(int index, { required RenderBox? after }) {
338 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
339 assert(constraints == this.constraints);
340 if (_keepAliveBucket.containsKey(index)) {
341 final RenderBox child = _keepAliveBucket.remove(index)!;
342 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
343 assert(childParentData._keptAlive);
344 dropChild(child);
345 child.parentData = childParentData;
346 insert(child, after: after);
347 childParentData._keptAlive = false;
348 } else {
349 _childManager.createChild(index, after: after);
350 }
351 });
352 }
353
354 void _destroyOrCacheChild(RenderBox child) {
355 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
356 if (childParentData.keepAlive) {
357 assert(!childParentData._keptAlive);
358 remove(child);
359 _keepAliveBucket[childParentData.index!] = child;
360 child.parentData = childParentData;
361 super.adoptChild(child);
362 childParentData._keptAlive = true;
363 } else {
364 assert(child.parent == this);
365 _childManager.removeChild(child);
366 assert(child.parent == null);
367 }
368 }
369
370 @override
371 void attach(PipelineOwner owner) {
372 super.attach(owner);
373 for (final RenderBox child in _keepAliveBucket.values) {
374 child.attach(owner);
375 }
376 }
377
378 @override
379 void detach() {
380 super.detach();
381 for (final RenderBox child in _keepAliveBucket.values) {
382 child.detach();
383 }
384 }
385
386 @override
387 void redepthChildren() {
388 super.redepthChildren();
389 _keepAliveBucket.values.forEach(redepthChild);
390 }
391
392 @override
393 void visitChildren(RenderObjectVisitor visitor) {
394 super.visitChildren(visitor);
395 _keepAliveBucket.values.forEach(visitor);
396 }
397
398 @override
399 void visitChildrenForSemantics(RenderObjectVisitor visitor) {
400 super.visitChildren(visitor);
401 // Do not visit children in [_keepAliveBucket].
402 }
403
404 /// Called during layout to create and add the child with the given index and
405 /// scroll offset.
406 ///
407 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
408 /// the child if necessary. The child may instead be obtained from a cache;
409 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
410 ///
411 /// Returns false if there was no cached child and `createChild` did not add
412 /// any child, otherwise returns true.
413 ///
414 /// Does not layout the new child.
415 ///
416 /// When this is called, there are no visible children, so no children can be
417 /// removed during the call to `createChild`. No child should be added during
418 /// that call either, except for the one that is created and returned by
419 /// `createChild`.
420 @protected
421 bool addInitialChild({ int index = 0, double layoutOffset = 0.0 }) {
422 assert(_debugAssertChildListLocked());
423 assert(firstChild == null);
424 _createOrObtainChild(index, after: null);
425 if (firstChild != null) {
426 assert(firstChild == lastChild);
427 assert(indexOf(firstChild!) == index);
428 final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
429 firstChildParentData.layoutOffset = layoutOffset;
430 return true;
431 }
432 childManager.setDidUnderflow(true);
433 return false;
434 }
435
436 /// Called during layout to create, add, and layout the child before
437 /// [firstChild].
438 ///
439 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
440 /// the child if necessary. The child may instead be obtained from a cache;
441 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
442 ///
443 /// Returns the new child or null if no child was obtained.
444 ///
445 /// The child that was previously the first child, as well as any subsequent
446 /// children, may be removed by this call if they have not yet been laid out
447 /// during this layout pass. No child should be added during that call except
448 /// for the one that is created and returned by `createChild`.
449 @protected
450 RenderBox? insertAndLayoutLeadingChild(
451 BoxConstraints childConstraints, {
452 bool parentUsesSize = false,
453 }) {
454 assert(_debugAssertChildListLocked());
455 final int index = indexOf(firstChild!) - 1;
456 _createOrObtainChild(index, after: null);
457 if (indexOf(firstChild!) == index) {
458 firstChild!.layout(childConstraints, parentUsesSize: parentUsesSize);
459 return firstChild;
460 }
461 childManager.setDidUnderflow(true);
462 return null;
463 }
464
465 /// Called during layout to create, add, and layout the child after
466 /// the given child.
467 ///
468 /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
469 /// the child if necessary. The child may instead be obtained from a cache;
470 /// see [SliverMultiBoxAdaptorParentData.keepAlive].
471 ///
472 /// Returns the new child. It is the responsibility of the caller to configure
473 /// the child's scroll offset.
474 ///
475 /// Children after the `after` child may be removed in the process. Only the
476 /// new child may be added.
477 @protected
478 RenderBox? insertAndLayoutChild(
479 BoxConstraints childConstraints, {
480 required RenderBox? after,
481 bool parentUsesSize = false,
482 }) {
483 assert(_debugAssertChildListLocked());
484 assert(after != null);
485 final int index = indexOf(after!) + 1;
486 _createOrObtainChild(index, after: after);
487 final RenderBox? child = childAfter(after);
488 if (child != null && indexOf(child) == index) {
489 child.layout(childConstraints, parentUsesSize: parentUsesSize);
490 return child;
491 }
492 childManager.setDidUnderflow(true);
493 return null;
494 }
495
496 /// Called after layout with the number of children that can be garbage
497 /// collected at the head and tail of the child list.
498 ///
499 /// Children whose [SliverMultiBoxAdaptorParentData.keepAlive] property is
500 /// set to true will be removed to a cache instead of being dropped.
501 ///
502 /// This method also collects any children that were previously kept alive but
503 /// are now no longer necessary. As such, it should be called every time
504 /// [performLayout] is run, even if the arguments are both zero.
505 @protected
506 void collectGarbage(int leadingGarbage, int trailingGarbage) {
507 assert(_debugAssertChildListLocked());
508 assert(childCount >= leadingGarbage + trailingGarbage);
509 invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
510 while (leadingGarbage > 0) {
511 _destroyOrCacheChild(firstChild!);
512 leadingGarbage -= 1;
513 }
514 while (trailingGarbage > 0) {
515 _destroyOrCacheChild(lastChild!);
516 trailingGarbage -= 1;
517 }
518 // Ask the child manager to remove the children that are no longer being
519 // kept alive. (This should cause _keepAliveBucket to change, so we have
520 // to prepare our list ahead of time.)
521 _keepAliveBucket.values.where((RenderBox child) {
522 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
523 return !childParentData.keepAlive;
524 }).toList().forEach(_childManager.removeChild);
525 assert(_keepAliveBucket.values.where((RenderBox child) {
526 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
527 return !childParentData.keepAlive;
528 }).isEmpty);
529 });
530 }
531
532 /// Returns the index of the given child, as given by the
533 /// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
534 int indexOf(RenderBox child) {
535 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
536 assert(childParentData.index != null);
537 return childParentData.index!;
538 }
539
540 /// Returns the dimension of the given child in the main axis, as given by the
541 /// child's [RenderBox.size] property. This is only valid after layout.
542 @protected
543 double paintExtentOf(RenderBox child) {
544 assert(child.hasSize);
545 switch (constraints.axis) {
546 case Axis.horizontal:
547 return child.size.width;
548 case Axis.vertical:
549 return child.size.height;
550 }
551 }
552
553 @override
554 bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
555 RenderBox? child = lastChild;
556 final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
557 while (child != null) {
558 if (hitTestBoxChild(boxResult, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition)) {
559 return true;
560 }
561 child = childBefore(child);
562 }
563 return false;
564 }
565
566 @override
567 double childMainAxisPosition(RenderBox child) {
568 return childScrollOffset(child)! - constraints.scrollOffset;
569 }
570
571 @override
572 double? childScrollOffset(RenderObject child) {
573 assert(child.parent == this);
574 final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
575 return childParentData.layoutOffset;
576 }
577
578 @override
579 bool paintsChild(RenderBox child) {
580 final SliverMultiBoxAdaptorParentData? childParentData = child.parentData as SliverMultiBoxAdaptorParentData?;
581 return childParentData?.index != null &&
582 !_keepAliveBucket.containsKey(childParentData!.index);
583 }
584
585 @override
586 void applyPaintTransform(RenderBox child, Matrix4 transform) {
587 if (!paintsChild(child)) {
588 // This can happen if some child asks for the global transform even though
589 // they are not getting painted. In that case, the transform sets set to
590 // zero since [applyPaintTransformForBoxChild] would end up throwing due
591 // to the child not being configured correctly for applying a transform.
592 // There's no assert here because asking for the paint transform is a
593 // valid thing to do even if a child would not be painted, but there is no
594 // meaningful non-zero matrix to use in this case.
595 transform.setZero();
596 } else {
597 applyPaintTransformForBoxChild(child, transform);
598 }
599 }
600
601 @override
602 void paint(PaintingContext context, Offset offset) {
603 if (firstChild == null) {
604 return;
605 }
606 // offset is to the top-left corner, regardless of our axis direction.
607 // originOffset gives us the delta from the real origin to the origin in the axis direction.
608 final Offset mainAxisUnit, crossAxisUnit, originOffset;
609 final bool addExtent;
610 switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
611 case AxisDirection.up:
612 mainAxisUnit = const Offset(0.0, -1.0);
613 crossAxisUnit = const Offset(1.0, 0.0);
614 originOffset = offset + Offset(0.0, geometry!.paintExtent);
615 addExtent = true;
616 case AxisDirection.right:
617 mainAxisUnit = const Offset(1.0, 0.0);
618 crossAxisUnit = const Offset(0.0, 1.0);
619 originOffset = offset;
620 addExtent = false;
621 case AxisDirection.down:
622 mainAxisUnit = const Offset(0.0, 1.0);
623 crossAxisUnit = const Offset(1.0, 0.0);
624 originOffset = offset;
625 addExtent = false;
626 case AxisDirection.left:
627 mainAxisUnit = const Offset(-1.0, 0.0);
628 crossAxisUnit = const Offset(0.0, 1.0);
629 originOffset = offset + Offset(geometry!.paintExtent, 0.0);
630 addExtent = true;
631 }
632 RenderBox? child = firstChild;
633 while (child != null) {
634 final double mainAxisDelta = childMainAxisPosition(child);
635 final double crossAxisDelta = childCrossAxisPosition(child);
636 Offset childOffset = Offset(
637 originOffset.dx + mainAxisUnit.dx * mainAxisDelta + crossAxisUnit.dx * crossAxisDelta,
638 originOffset.dy + mainAxisUnit.dy * mainAxisDelta + crossAxisUnit.dy * crossAxisDelta,
639 );
640 if (addExtent) {
641 childOffset += mainAxisUnit * paintExtentOf(child);
642 }
643
644 // If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child))
645 // does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden.
646 if (mainAxisDelta < constraints.remainingPaintExtent && mainAxisDelta + paintExtentOf(child) > 0) {
647 context.paintChild(child, childOffset);
648 }
649
650 child = childAfter(child);
651 }
652 }
653
654 @override
655 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
656 super.debugFillProperties(properties);
657 properties.add(DiagnosticsNode.message(firstChild != null ? 'currently live children: ${indexOf(firstChild!)} to ${indexOf(lastChild!)}' : 'no children current live'));
658 }
659
660 /// Asserts that the reified child list is not empty and has a contiguous
661 /// sequence of indices.
662 ///
663 /// Always returns true.
664 bool debugAssertChildListIsNonEmptyAndContiguous() {
665 assert(() {
666 assert(firstChild != null);
667 int index = indexOf(firstChild!);
668 RenderBox? child = childAfter(firstChild!);
669 while (child != null) {
670 index += 1;
671 assert(indexOf(child) == index);
672 child = childAfter(child);
673 }
674 return true;
675 }());
676 return true;
677 }
678
679 @override
680 List<DiagnosticsNode> debugDescribeChildren() {
681 final List<DiagnosticsNode> children = <DiagnosticsNode>[];
682 if (firstChild != null) {
683 RenderBox? child = firstChild;
684 while (true) {
685 final SliverMultiBoxAdaptorParentData childParentData = child!.parentData! as SliverMultiBoxAdaptorParentData;
686 children.add(child.toDiagnosticsNode(name: 'child with index ${childParentData.index}'));
687 if (child == lastChild) {
688 break;
689 }
690 child = childParentData.nextSibling;
691 }
692 }
693 if (_keepAliveBucket.isNotEmpty) {
694 final List<int> indices = _keepAliveBucket.keys.toList()..sort();
695 for (final int index in indices) {
696 children.add(_keepAliveBucket[index]!.toDiagnosticsNode(
697 name: 'child with index $index (kept alive but not laid out)',
698 style: DiagnosticsTreeStyle.offstage,
699 ));
700 }
701 }
702 return children;
703 }
704}
705