-
Notifications
You must be signed in to change notification settings - Fork 31.1k
Expand file tree
/
Copy pathsliver.dart
More file actions
2119 lines (1997 loc) · 85.1 KB
/
Copy pathsliver.dart
File metadata and controls
2119 lines (1997 loc) · 85.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'package:flutter/material.dart';
///
/// @docImport 'proxy_box.dart';
/// @docImport 'proxy_sliver.dart';
/// @docImport 'sliver_fill.dart';
/// @docImport 'sliver_grid.dart';
/// @docImport 'sliver_group.dart';
/// @docImport 'sliver_list.dart';
/// @docImport 'sliver_padding.dart';
/// @docImport 'sliver_persistent_header.dart';
library;
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'box.dart';
import 'debug.dart';
import 'object.dart';
import 'viewport.dart';
import 'viewport_offset.dart';
// CORE TYPES FOR SLIVERS
// The RenderSliver base class and its helper types.
/// Called to get the item extent by the index of item.
///
/// Should return null if asked to build an item extent with a greater index than
/// exists.
///
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
typedef ItemExtentBuilder = double? Function(int index, SliverLayoutDimensions dimensions);
/// Relates the dimensions of the [RenderSliver] during layout.
///
/// Used by [ListView.itemExtentBuilder] and [SliverVariedExtentList.itemExtentBuilder].
@immutable
class SliverLayoutDimensions {
/// Constructs a [SliverLayoutDimensions] with the specified parameters.
const SliverLayoutDimensions({
required this.scrollOffset,
required this.precedingScrollExtent,
required this.viewportMainAxisExtent,
required this.crossAxisExtent,
});
/// {@macro flutter.rendering.SliverConstraints.scrollOffset}
final double scrollOffset;
/// {@macro flutter.rendering.SliverConstraints.precedingScrollExtent}
final double precedingScrollExtent;
/// The number of pixels the viewport can display in the main axis.
///
/// For a vertical list, this is the height of the viewport.
final double viewportMainAxisExtent;
/// The number of pixels in the cross-axis.
///
/// For a vertical list, this is the width of the sliver.
final double crossAxisExtent;
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is! SliverLayoutDimensions) {
return false;
}
return other.scrollOffset == scrollOffset &&
other.precedingScrollExtent == precedingScrollExtent &&
other.viewportMainAxisExtent == viewportMainAxisExtent &&
other.crossAxisExtent == crossAxisExtent;
}
@override
String toString() {
return 'scrollOffset: $scrollOffset'
' precedingScrollExtent: $precedingScrollExtent'
' viewportMainAxisExtent: $viewportMainAxisExtent'
' crossAxisExtent: $crossAxisExtent';
}
@override
int get hashCode =>
Object.hash(scrollOffset, precedingScrollExtent, viewportMainAxisExtent, crossAxisExtent);
}
/// The direction in which a sliver's contents are ordered, relative to the
/// scroll offset axis.
///
/// For example, a vertical alphabetical list that is going [AxisDirection.down]
/// with a [GrowthDirection.forward] would have the A at the top and the Z at
/// the bottom, with the A adjacent to the origin, as would such a list going
/// [AxisDirection.up] with a [GrowthDirection.reverse]. On the other hand, a
/// vertical alphabetical list that is going [AxisDirection.down] with a
/// [GrowthDirection.reverse] would have the Z at the top (at scroll offset
/// zero) and the A below it.
///
/// {@template flutter.rendering.GrowthDirection.sample}
/// Most scroll views by default are ordered [GrowthDirection.forward].
/// Changing the default values of [ScrollView.anchor],
/// [ScrollView.center], or both, can configure a scroll view for
/// [GrowthDirection.reverse].
///
/// {@tool dartpad}
/// This sample shows a [CustomScrollView], with [Radio] buttons in the
/// [AppBar.bottom] that change the [AxisDirection] to illustrate different
/// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
/// properties are also set to have the 0 scroll offset positioned in the middle
/// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
/// illustrated on either side. The sliver that shares the
/// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
///
/// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
/// {@end-tool}
/// {@endtemplate}
///
/// See also:
///
/// * [applyGrowthDirectionToAxisDirection], which returns the direction in
/// which the scroll offset increases.
enum GrowthDirection {
/// This sliver's contents are ordered in the same direction as the
/// [AxisDirection]. For example, a vertical alphabetical list that is going
/// [AxisDirection.down] with a [GrowthDirection.forward] would have the A at
/// the top and the Z at the bottom, with the A adjacent to the origin.
///
/// See also:
///
/// * [applyGrowthDirectionToAxisDirection], which returns the direction in
/// which the scroll offset increases.
forward,
/// This sliver's contents are ordered in the opposite direction of the
/// [AxisDirection].
///
/// See also:
///
/// * [applyGrowthDirectionToAxisDirection], which returns the direction in
/// which the scroll offset increases.
reverse,
}
/// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
///
/// Specifically, returns `axisDirection` if `growthDirection` is
/// [GrowthDirection.forward], otherwise returns [flipAxisDirection] applied to
/// `axisDirection`.
///
/// This function is useful in [RenderSliver] subclasses that are given both an
/// [AxisDirection] and a [GrowthDirection] and wish to compute the
/// [AxisDirection] in which growth will occur.
AxisDirection applyGrowthDirectionToAxisDirection(
AxisDirection axisDirection,
GrowthDirection growthDirection,
) {
return switch (growthDirection) {
GrowthDirection.forward => axisDirection,
GrowthDirection.reverse => flipAxisDirection(axisDirection),
};
}
/// Flips the [ScrollDirection] if the [GrowthDirection] is
/// [GrowthDirection.reverse].
///
/// Specifically, returns `scrollDirection` if `scrollDirection` is
/// [GrowthDirection.forward], otherwise returns [flipScrollDirection] applied
/// to `scrollDirection`.
///
/// This function is useful in [RenderSliver] subclasses that are given both an
/// [ScrollDirection] and a [GrowthDirection] and wish to compute the
/// [ScrollDirection] in which growth will occur.
ScrollDirection applyGrowthDirectionToScrollDirection(
ScrollDirection scrollDirection,
GrowthDirection growthDirection,
) {
return switch (growthDirection) {
GrowthDirection.forward => scrollDirection,
GrowthDirection.reverse => flipScrollDirection(scrollDirection),
};
}
/// Immutable layout constraints for [RenderSliver] layout.
///
/// The [SliverConstraints] describe the current scroll state of the viewport
/// from the point of view of the sliver receiving the constraints. For example,
/// a [scrollOffset] of zero means that the leading edge of the sliver is
/// visible in the viewport, not that the viewport itself has a zero scroll
/// offset.
class SliverConstraints extends Constraints {
/// Creates sliver constraints with the given information.
const SliverConstraints({
required this.axisDirection,
required this.growthDirection,
required this.userScrollDirection,
required this.scrollOffset,
required this.precedingScrollExtent,
required this.overlap,
required this.remainingPaintExtent,
required this.crossAxisExtent,
required this.crossAxisDirection,
required this.viewportMainAxisExtent,
required this.remainingCacheExtent,
required this.cacheOrigin,
});
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
SliverConstraints copyWith({
AxisDirection? axisDirection,
GrowthDirection? growthDirection,
ScrollDirection? userScrollDirection,
double? scrollOffset,
double? precedingScrollExtent,
double? overlap,
double? remainingPaintExtent,
double? crossAxisExtent,
AxisDirection? crossAxisDirection,
double? viewportMainAxisExtent,
double? remainingCacheExtent,
double? cacheOrigin,
}) {
return SliverConstraints(
axisDirection: axisDirection ?? this.axisDirection,
growthDirection: growthDirection ?? this.growthDirection,
userScrollDirection: userScrollDirection ?? this.userScrollDirection,
scrollOffset: scrollOffset ?? this.scrollOffset,
precedingScrollExtent: precedingScrollExtent ?? this.precedingScrollExtent,
overlap: overlap ?? this.overlap,
remainingPaintExtent: remainingPaintExtent ?? this.remainingPaintExtent,
crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent,
crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection,
viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent,
remainingCacheExtent: remainingCacheExtent ?? this.remainingCacheExtent,
cacheOrigin: cacheOrigin ?? this.cacheOrigin,
);
}
/// The direction in which the [scrollOffset] and [remainingPaintExtent]
/// increase.
///
/// {@tool dartpad}
/// This sample shows a [CustomScrollView], with [Radio] buttons in the
/// [AppBar.bottom] that change the [AxisDirection] to illustrate different
/// configurations.
///
/// ** See code in examples/api/lib/painting/axis_direction/axis_direction.0.dart **
/// {@end-tool}
final AxisDirection axisDirection;
/// The direction in which the contents of slivers are ordered, relative to
/// the [axisDirection].
///
/// For example, if the [axisDirection] is [AxisDirection.up], and the
/// [growthDirection] is [GrowthDirection.forward], then an alphabetical list
/// will have A at the bottom, then B, then C, and so forth, with Z at the
/// top, with the bottom of the A at scroll offset zero, and the top of the Z
/// at the highest scroll offset.
///
/// If a viewport has an overall [AxisDirection] of [AxisDirection.down], then
/// slivers above the absolute zero offset will have an axis of
/// [AxisDirection.up] and a growth direction of [GrowthDirection.reverse],
/// while slivers below the absolute zero offset will have the same axis
/// direction as the viewport and a growth direction of
/// [GrowthDirection.forward]. (The slivers with a reverse growth direction
/// still see only positive scroll offsets; the scroll offsets are reversed as
/// well, with zero at the absolute zero point, and positive numbers going
/// away from there.)
///
/// Normally, the absolute zero offset is determined by the viewport's
/// [RenderViewport.center] and [RenderViewport.anchor] properties.
///
/// {@macro flutter.rendering.GrowthDirection.sample}
final GrowthDirection growthDirection;
/// The direction in which the user is attempting to scroll, relative to the
/// [axisDirection] and [growthDirection].
///
/// For example, if [growthDirection] is [GrowthDirection.forward] and
/// [axisDirection] is [AxisDirection.down], then a
/// [ScrollDirection.reverse] means that the user is scrolling down, in the
/// positive [scrollOffset] direction.
///
/// If the _user_ is not scrolling, this will return [ScrollDirection.idle]
/// even if there is (for example) a [ScrollActivity] currently animating the
/// position.
///
/// This is used by some slivers to determine how to react to a change in
/// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will
/// only expand a floating app bar when the [userScrollDirection] is in the
/// positive scroll offset direction.
///
/// {@macro flutter.rendering.ScrollDirection.sample}
final ScrollDirection userScrollDirection;
/// {@template flutter.rendering.SliverConstraints.scrollOffset}
/// The scroll offset, in this sliver's coordinate system, that corresponds to
/// the earliest visible part of this sliver in the [AxisDirection] if
/// [SliverConstraints.growthDirection] is [GrowthDirection.forward] or in the opposite
/// [AxisDirection] direction if [SliverConstraints.growthDirection] is [GrowthDirection.reverse].
///
/// For example, if [AxisDirection] is [AxisDirection.down] and [SliverConstraints.growthDirection]
/// is [GrowthDirection.forward], then scroll offset is the amount the top of
/// the sliver has been scrolled past the top of the viewport.
///
/// This value is typically used to compute whether this sliver should still
/// protrude into the viewport via [SliverGeometry.paintExtent] and
/// [SliverGeometry.layoutExtent] considering how far the beginning of the
/// sliver is above the beginning of the viewport.
///
/// For slivers whose top is not past the top of the viewport, the
/// [scrollOffset] is `0` when [AxisDirection] is [AxisDirection.down] and
/// [SliverConstraints.growthDirection] is [GrowthDirection.forward]. The set of slivers with
/// [scrollOffset] `0` includes all the slivers that are below the bottom of the
/// viewport.
///
/// [SliverConstraints.remainingPaintExtent] is typically used to accomplish
/// the same goal of computing whether scrolled out slivers should still
/// partially 'protrude in' from the bottom of the viewport.
///
/// Whether this corresponds to the beginning or the end of the sliver's
/// contents depends on the [SliverConstraints.growthDirection].
/// {@endtemplate}
final double scrollOffset;
/// {@template flutter.rendering.SliverConstraints.precedingScrollExtent}
/// The scroll distance that has been consumed by all [RenderSliver]s that
/// came before this [RenderSliver].
///
/// # Edge Cases
///
/// [RenderSliver]s often lazily create their internal content as layout
/// occurs, e.g., [SliverList]. In this case, when [RenderSliver]s exceed the
/// viewport, their children are built lazily, and the [RenderSliver] does not
/// have enough information to estimate its total extent,
/// [precedingScrollExtent] will be [double.infinity] for all [RenderSliver]s
/// that appear after the lazily constructed child. This is because a total
/// [SliverGeometry.scrollExtent] cannot be calculated unless all inner
/// children have been created and sized, or the number of children and
/// estimated extents are provided. The infinite [SliverGeometry.scrollExtent]
/// will become finite as soon as enough information is available to estimate
/// the overall extent of all children within the given [RenderSliver].
///
/// [RenderSliver]s may legitimately be infinite, meaning that they can scroll
/// content forever without reaching the end. For any [RenderSliver]s that
/// appear after the infinite [RenderSliver], the [precedingScrollExtent] will
/// be [double.infinity].
/// {@endtemplate}
final double precedingScrollExtent;
/// The number of pixels from where the pixels corresponding to the
/// [scrollOffset] will be painted up to the first pixel that has not yet been
/// painted on by an earlier sliver, in the [axisDirection].
///
/// For example, if the previous sliver had a [SliverGeometry.paintExtent] of
/// 100.0 pixels but a [SliverGeometry.layoutExtent] of only 50.0 pixels,
/// then the [overlap] of this sliver will be 50.0.
///
/// This is typically ignored unless the sliver is itself going to be pinned
/// or floating and wants to avoid doing so under the previous sliver.
final double overlap;
/// The number of pixels of content that the sliver should consider providing.
/// (Providing more pixels than this is inefficient.)
///
/// The actual number of pixels provided should be specified in the
/// [RenderSliver.geometry] as [SliverGeometry.paintExtent].
///
/// This value may be infinite, for example if the viewport is an
/// unconstrained [RenderShrinkWrappingViewport].
///
/// This value may be 0.0, for example if the sliver is scrolled off the
/// bottom of a downwards vertical viewport.
final double remainingPaintExtent;
/// The number of pixels in the cross-axis.
///
/// For a vertical list, this is the width of the sliver.
final double crossAxisExtent;
/// The direction in which children should be placed in the cross axis.
///
/// Typically used in vertical lists to describe whether the ambient
/// [TextDirection] is [TextDirection.rtl] or [TextDirection.ltr].
final AxisDirection crossAxisDirection;
/// The number of pixels the viewport can display in the main axis.
///
/// For a vertical list, this is the height of the viewport.
final double viewportMainAxisExtent;
/// Where the cache area starts relative to the [scrollOffset].
///
/// Slivers that fall into the cache area located before the leading edge and
/// after the trailing edge of the viewport should still render content
/// because they are about to become visible when the user scrolls.
///
/// The [cacheOrigin] describes where the [remainingCacheExtent] starts relative
/// to the [scrollOffset]. A cache origin of 0 means that the sliver does not
/// have to provide any content before the current [scrollOffset]. A
/// [cacheOrigin] of -250.0 means that even though the first visible part of
/// the sliver will be at the provided [scrollOffset], the sliver should
/// render content starting 250.0 before the [scrollOffset] to fill the
/// cache area of the viewport.
///
/// The [cacheOrigin] is always negative or zero and will never exceed
/// -[scrollOffset]. In other words, a sliver is never asked to provide
/// content before its zero [scrollOffset].
///
/// See also:
///
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double cacheOrigin;
/// Describes how much content the sliver should provide starting from the
/// [cacheOrigin].
///
/// Not all content in the [remainingCacheExtent] will be visible as some
/// of it might fall into the cache area of the viewport.
///
/// Each sliver should start laying out content at the [cacheOrigin] and
/// try to provide as much content as the [remainingCacheExtent] allows.
///
/// The [remainingCacheExtent] is always larger or equal to the
/// [remainingPaintExtent]. Content, that falls in the [remainingCacheExtent],
/// but is outside of the [remainingPaintExtent] is currently not visible
/// in the viewport.
///
/// See also:
///
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double remainingCacheExtent;
/// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured.
Axis get axis => axisDirectionToAxis(axisDirection);
/// Return what the [growthDirection] would be if the [axisDirection] was
/// either [AxisDirection.down] or [AxisDirection.right].
///
/// This is the same as [growthDirection] unless the [axisDirection] is either
/// [AxisDirection.up] or [AxisDirection.left], in which case it is the
/// opposite growth direction.
///
/// This can be useful in combination with [axis] to view the [axisDirection]
/// and [growthDirection] in different terms.
GrowthDirection get normalizedGrowthDirection {
if (axisDirectionIsReversed(axisDirection)) {
return switch (growthDirection) {
GrowthDirection.forward => GrowthDirection.reverse,
GrowthDirection.reverse => GrowthDirection.forward,
};
}
return growthDirection;
}
@override
bool get isTight => false;
@override
bool get isNormalized {
return scrollOffset >= 0.0 &&
crossAxisExtent >= 0.0 &&
axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection) &&
viewportMainAxisExtent >= 0.0 &&
remainingPaintExtent >= 0.0;
}
/// Returns [BoxConstraints] that reflects the sliver constraints.
///
/// The `minExtent` and `maxExtent` are used as the constraints in the main
/// axis. If non-null, the given `crossAxisExtent` is used as a tight
/// constraint in the cross axis. Otherwise, the [crossAxisExtent] from this
/// object is used as a constraint in the cross axis.
///
/// Useful for slivers that have [RenderBox] children.
BoxConstraints asBoxConstraints({
double minExtent = 0.0,
double maxExtent = double.infinity,
double? crossAxisExtent,
}) {
crossAxisExtent ??= this.crossAxisExtent;
switch (axis) {
case Axis.horizontal:
return BoxConstraints(
minHeight: crossAxisExtent,
maxHeight: crossAxisExtent,
minWidth: minExtent,
maxWidth: maxExtent,
);
case Axis.vertical:
return BoxConstraints(
minWidth: crossAxisExtent,
maxWidth: crossAxisExtent,
minHeight: minExtent,
maxHeight: maxExtent,
);
}
}
@override
bool debugAssertIsValid({
bool isAppliedConstraint = false,
InformationCollector? informationCollector,
}) {
assert(() {
var hasErrors = false;
final errorMessage = StringBuffer('\n');
void verify(bool check, String message) {
if (check) {
return;
}
hasErrors = true;
errorMessage.writeln(' $message');
}
void verifyDouble(
double property,
String name, {
bool mustBePositive = false,
bool mustBeNegative = false,
}) {
if (property.isNaN) {
var additional = '.';
if (mustBePositive) {
additional = ', expected greater than or equal to zero.';
} else if (mustBeNegative) {
additional = ', expected less than or equal to zero.';
}
verify(false, 'The "$name" is NaN$additional');
} else if (mustBePositive) {
verify(property >= 0.0, 'The "$name" is negative.');
} else if (mustBeNegative) {
verify(property <= 0.0, 'The "$name" is positive.');
}
}
verifyDouble(scrollOffset, 'scrollOffset');
verifyDouble(overlap, 'overlap');
verifyDouble(crossAxisExtent, 'crossAxisExtent');
verifyDouble(scrollOffset, 'scrollOffset', mustBePositive: true);
verify(
axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection),
'The "axisDirection" and the "crossAxisDirection" are along the same axis.',
);
verifyDouble(viewportMainAxisExtent, 'viewportMainAxisExtent', mustBePositive: true);
verifyDouble(remainingPaintExtent, 'remainingPaintExtent', mustBePositive: true);
verifyDouble(remainingCacheExtent, 'remainingCacheExtent', mustBePositive: true);
verifyDouble(cacheOrigin, 'cacheOrigin', mustBeNegative: true);
verifyDouble(precedingScrollExtent, 'precedingScrollExtent', mustBePositive: true);
verify(
isNormalized,
'The constraints are not normalized.',
); // should be redundant with earlier checks
if (hasErrors) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$runtimeType is not valid: $errorMessage'),
if (informationCollector != null) ...informationCollector(),
DiagnosticsProperty<SliverConstraints>(
'The offending constraints were',
this,
style: DiagnosticsTreeStyle.errorProperty,
),
]);
}
return true;
}());
return true;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is! SliverConstraints) {
return false;
}
assert(other.debugAssertIsValid());
return other.axisDirection == axisDirection &&
other.growthDirection == growthDirection &&
other.userScrollDirection == userScrollDirection &&
other.scrollOffset == scrollOffset &&
other.precedingScrollExtent == precedingScrollExtent &&
other.overlap == overlap &&
other.remainingPaintExtent == remainingPaintExtent &&
other.crossAxisExtent == crossAxisExtent &&
other.crossAxisDirection == crossAxisDirection &&
other.viewportMainAxisExtent == viewportMainAxisExtent &&
other.remainingCacheExtent == remainingCacheExtent &&
other.cacheOrigin == cacheOrigin;
}
@override
int get hashCode => Object.hash(
axisDirection,
growthDirection,
userScrollDirection,
scrollOffset,
precedingScrollExtent,
overlap,
remainingPaintExtent,
crossAxisExtent,
crossAxisDirection,
viewportMainAxisExtent,
remainingCacheExtent,
cacheOrigin,
);
@override
String toString() {
final properties = <String>[
'$axisDirection',
'$growthDirection',
'$userScrollDirection',
'scrollOffset: ${scrollOffset.toStringAsFixed(1)}',
'precedingScrollExtent: ${precedingScrollExtent.toStringAsFixed(1)}',
'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}',
if (overlap != 0.0) 'overlap: ${overlap.toStringAsFixed(1)}',
'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}',
'crossAxisDirection: $crossAxisDirection',
'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}',
'remainingCacheExtent: ${remainingCacheExtent.toStringAsFixed(1)}',
'cacheOrigin: ${cacheOrigin.toStringAsFixed(1)}',
];
return 'SliverConstraints(${properties.join(', ')})';
}
}
/// Describes the amount of space occupied by a [RenderSliver].
///
/// A sliver can occupy space in several different ways, which is why this class
/// contains multiple values.
@immutable
class SliverGeometry with Diagnosticable {
/// Creates an object that describes the amount of space occupied by a sliver.
///
/// If the [layoutExtent] argument is null, [layoutExtent] defaults to the
/// [paintExtent]. If the [hitTestExtent] argument is null, [hitTestExtent]
/// defaults to the [paintExtent]. If [visible] is null, [visible] defaults to
/// whether [paintExtent] is greater than zero.
const SliverGeometry({
this.scrollExtent = 0.0,
this.paintExtent = 0.0,
this.paintOrigin = 0.0,
double? layoutExtent,
this.maxPaintExtent = 0.0,
this.maxScrollObstructionExtent = 0.0,
this.crossAxisExtent,
double? hitTestExtent,
bool? visible,
this.hasVisualOverflow = false,
this.scrollOffsetCorrection,
double? cacheExtent,
}) : assert(scrollOffsetCorrection != 0.0),
layoutExtent = layoutExtent ?? paintExtent,
hitTestExtent = hitTestExtent ?? paintExtent,
cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent,
visible = visible ?? paintExtent > 0.0;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
SliverGeometry copyWith({
double? scrollExtent,
double? paintExtent,
double? paintOrigin,
double? layoutExtent,
double? maxPaintExtent,
double? maxScrollObstructionExtent,
double? crossAxisExtent,
double? hitTestExtent,
bool? visible,
bool? hasVisualOverflow,
double? cacheExtent,
}) {
return SliverGeometry(
scrollExtent: scrollExtent ?? this.scrollExtent,
paintExtent: paintExtent ?? this.paintExtent,
paintOrigin: paintOrigin ?? this.paintOrigin,
layoutExtent: layoutExtent ?? this.layoutExtent,
maxPaintExtent: maxPaintExtent ?? this.maxPaintExtent,
maxScrollObstructionExtent: maxScrollObstructionExtent ?? this.maxScrollObstructionExtent,
crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent,
hitTestExtent: hitTestExtent ?? this.hitTestExtent,
visible: visible ?? this.visible,
hasVisualOverflow: hasVisualOverflow ?? this.hasVisualOverflow,
cacheExtent: cacheExtent ?? this.cacheExtent,
);
}
/// A sliver that occupies no space at all.
static const SliverGeometry zero = SliverGeometry();
/// The (estimated) total scrollable extent that this sliver has content for.
///
/// This is the amount of scrolling the user needs to do to get from the
/// beginning of this sliver to the end of this sliver.
///
/// The value is used to calculate the [SliverConstraints.scrollOffset] of
/// all slivers in the scrollable and thus should be provided whether the
/// sliver is currently in the viewport or not.
///
/// In a typical scrolling scenario, the [scrollExtent] is constant for a
/// sliver throughout the scrolling while [paintExtent] and [layoutExtent]
/// will progress from `0` when offscreen to between `0` and [scrollExtent]
/// as the sliver scrolls partially into and out of the screen and is
/// equal to [scrollExtent] while the sliver is entirely on screen. However,
/// these relationships can be customized to achieve more special effects.
///
/// This value must be accurate if the [paintExtent] is less than the
/// [SliverConstraints.remainingPaintExtent] provided during layout.
final double scrollExtent;
/// The visual location of the first visible part of this sliver relative to
/// its layout position.
///
/// For example, if the sliver wishes to paint visually before its layout
/// position, the [paintOrigin] is negative. The coordinate system this sliver
/// uses for painting is relative to this [paintOrigin]. In other words,
/// when [RenderSliver.paint] is called, the (0, 0) position of the [Offset]
/// given to it is at this [paintOrigin].
///
/// The coordinate system used for the [paintOrigin] itself is relative
/// to the start of this sliver's layout position rather than relative to
/// its current position on the viewport. In other words, in a typical
/// scrolling scenario, [paintOrigin] remains constant at 0.0 rather than
/// tracking from 0.0 to [SliverConstraints.viewportMainAxisExtent] as the
/// sliver scrolls past the viewport.
///
/// This value does not affect the layout of subsequent slivers. The next
/// sliver is still placed at [layoutExtent] after this sliver's layout
/// position. This value does affect where the [paintExtent] extent is
/// measured from when computing the [SliverConstraints.overlap] for the next
/// sliver.
///
/// Defaults to 0.0, which means slivers start painting at their layout
/// position by default.
final double paintOrigin;
/// The amount of currently visible visual space that was taken by the sliver
/// to render the subset of the sliver that covers all or part of the
/// [SliverConstraints.remainingPaintExtent] in the current viewport.
///
/// This value does not affect how the next sliver is positioned. In other
/// words, if this value was 100 and [layoutExtent] was 0, typical slivers
/// placed after it would end up drawing in the same 100 pixel space while
/// painting.
///
/// This must be between zero and [SliverConstraints.remainingPaintExtent].
///
/// This value is typically 0 when outside of the viewport and grows or
/// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the
/// viewport unless the sliver wants to achieve a special effect and paint
/// even when scrolled away.
///
/// This contributes to the calculation for the next sliver's
/// [SliverConstraints.overlap].
final double paintExtent;
/// The distance from the first visible part of this sliver to the first
/// visible part of the next sliver, assuming the next sliver's
/// [SliverConstraints.scrollOffset] is zero.
///
/// This must be between zero and [paintExtent]. It defaults to [paintExtent].
///
/// This value is typically 0 when outside of the viewport and grows or
/// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the
/// viewport unless the sliver wants to achieve a special effect and push
/// down the layout start position of subsequent slivers before the sliver is
/// even scrolled into the viewport.
final double layoutExtent;
/// The (estimated) total paint extent that this sliver would be able to
/// provide if the [SliverConstraints.remainingPaintExtent] was infinite.
///
/// This is used by viewports that implement shrink-wrapping.
///
/// By definition, this cannot be less than [paintExtent].
final double maxPaintExtent;
/// The maximum extent by which this sliver can reduce the area in which
/// content can scroll if the sliver were pinned at the edge.
///
/// Slivers that never get pinned at the edge, should return zero.
///
/// A pinned app bar is an example for a sliver that would use this setting:
/// When the app bar is pinned to the top, the area in which content can
/// actually scroll is reduced by the height of the app bar.
final double maxScrollObstructionExtent;
/// The distance from where this sliver started painting to the bottom of
/// where it should accept hits.
///
/// This must be between zero and [paintExtent]. It defaults to [paintExtent].
final double hitTestExtent;
/// Whether this sliver should be painted.
///
/// By default, this is true if [paintExtent] is greater than zero, and
/// false if [paintExtent] is zero.
final bool visible;
/// Whether this sliver has visual overflow.
///
/// By default, this is false, which means the viewport does not need to clip
/// its children. If any slivers have visual overflow, the viewport will apply
/// a clip to its children.
final bool hasVisualOverflow;
/// If this is non-zero after [RenderSliver.performLayout] returns, the scroll
/// offset will be adjusted by the parent and then the entire layout of the
/// parent will be rerun.
///
/// When the value is non-zero, the [RenderSliver] does not need to compute
/// the rest of the values when constructing the [SliverGeometry] or call
/// [RenderObject.layout] on its children since [RenderSliver.performLayout]
/// will be called again on this sliver in the same frame after the
/// [SliverConstraints.scrollOffset] correction has been applied, when the
/// proper [SliverGeometry] and layout of its children can be computed.
///
/// If the parent is also a [RenderSliver], it must propagate this value
/// in its own [RenderSliver.geometry] property until a viewport which adjusts
/// its offset based on this value.
final double? scrollOffsetCorrection;
/// How many pixels the sliver has consumed in the
/// [SliverConstraints.remainingCacheExtent].
///
/// This value should be equal to or larger than the [layoutExtent] because
/// the sliver always consumes at least the [layoutExtent] from the
/// [SliverConstraints.remainingCacheExtent] and possibly more if it falls
/// into the cache area of the viewport.
///
/// See also:
///
/// * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
final double cacheExtent;
/// The amount of space allocated to the cross axis.
///
/// This value will be typically null unless it is different from
/// [SliverConstraints.crossAxisExtent]. If null, then the cross axis extent of
/// the sliver is assumed to be the same as the [SliverConstraints.crossAxisExtent].
/// This is because slivers typically consume all of the extent that is available
/// in the cross axis.
///
/// See also:
///
/// * [SliverConstrainedCrossAxis] for an example of a sliver which takes up
/// a smaller cross axis extent than the provided constraint.
/// * [SliverCrossAxisGroup] for an example of a sliver which makes use of this
/// [crossAxisExtent] to lay out their children.
final double? crossAxisExtent;
/// Asserts that this geometry is internally consistent.
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugAssertIsValid({InformationCollector? informationCollector}) {
assert(() {
void verify(bool check, String summary, {List<DiagnosticsNode>? details}) {
if (check) {
return;
}
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('${objectRuntimeType(this, 'SliverGeometry')} is not valid: $summary'),
...?details,
if (informationCollector != null) ...informationCollector(),
]);
}
verify(scrollExtent >= 0.0, 'The "scrollExtent" is negative.');
verify(paintExtent >= 0.0, 'The "paintExtent" is negative.');
verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.');
verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.');
if (layoutExtent > paintExtent) {
verify(
false,
'The "layoutExtent" exceeds the "paintExtent".',
details: _debugCompareFloats('paintExtent', paintExtent, 'layoutExtent', layoutExtent),
);
}
// If the paintExtent is slightly more than the maxPaintExtent, but the difference is still less
// than precisionErrorTolerance, we will not throw the assert below.
if (paintExtent - maxPaintExtent > precisionErrorTolerance) {
verify(
false,
'The "maxPaintExtent" is less than the "paintExtent".',
details: _debugCompareFloats('maxPaintExtent', maxPaintExtent, 'paintExtent', paintExtent)
..add(
ErrorDescription(
"By definition, a sliver can't paint more than the maximum that it can paint!",
),
),
);
}
verify(hitTestExtent >= 0.0, 'The "hitTestExtent" is negative.');
verify(scrollOffsetCorrection != 0.0, 'The "scrollOffsetCorrection" is zero.');
return true;
}());
return true;
}
@override
String toStringShort() => objectRuntimeType(this, 'SliverGeometry');
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('scrollExtent', scrollExtent));
if (paintExtent > 0.0) {
properties.add(
DoubleProperty('paintExtent', paintExtent, unit: visible ? null : ' but not painting'),
);
} else if (paintExtent == 0.0) {
if (visible) {
properties.add(
DoubleProperty('paintExtent', paintExtent, unit: visible ? null : ' but visible'),
);
}
properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden'));
} else {
// Negative paintExtent!
properties.add(DoubleProperty('paintExtent', paintExtent, tooltip: '!'));
}
properties.add(DoubleProperty('paintOrigin', paintOrigin, defaultValue: 0.0));
properties.add(DoubleProperty('layoutExtent', layoutExtent, defaultValue: paintExtent));
properties.add(DoubleProperty('maxPaintExtent', maxPaintExtent));
properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
properties.add(
DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false),
);
properties.add(
DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null),
);
properties.add(DoubleProperty('cacheExtent', cacheExtent, defaultValue: 0.0));
}
}
/// Method signature for hit testing a [RenderSliver].
///
/// Used by [SliverHitTestResult.addWithAxisOffset] to hit test [RenderSliver]
/// children.
///
/// See also:
///
/// * [RenderSliver.hitTest], which documents more details around hit testing
/// [RenderSliver]s.
typedef SliverHitTest =
bool Function(
SliverHitTestResult result, {
required double mainAxisPosition,
required double crossAxisPosition,
});
/// The result of performing a hit test on [RenderSliver]s.
///
/// An instance of this class is provided to [RenderSliver.hitTest] to record
/// the result of the hit test.
class SliverHitTestResult extends HitTestResult {
/// Creates an empty hit test result for hit testing on [RenderSliver].
SliverHitTestResult() : super();
/// Wraps `result` to create a [HitTestResult] that implements the
/// [SliverHitTestResult] protocol for hit testing on [RenderSliver]s.
///
/// This method is used by [RenderObject]s that adapt between the
/// [RenderSliver]-world and the non-[RenderSliver]-world to convert a
/// (subtype of) [HitTestResult] to a [SliverHitTestResult] for hit testing on
/// [RenderSliver]s.
///
/// The [HitTestEntry] instances added to the returned [SliverHitTestResult]
/// are also added to the wrapped `result` (both share the same underlying
/// data structure to store [HitTestEntry] instances).
///
/// See also:
///
/// * [HitTestResult.wrap], which turns a [SliverHitTestResult] back into a
/// generic [HitTestResult].
/// * [BoxHitTestResult.wrap], which turns a [SliverHitTestResult] into a
/// [BoxHitTestResult] for hit testing on [RenderBox] children.
SliverHitTestResult.wrap(super.result) : super.wrap();
/// Transforms `mainAxisPosition` and `crossAxisPosition` to the local
/// coordinate system of a child for hit-testing the child.
///
/// The actual hit testing of the child needs to be implemented in the
/// provided `hitTest` callback, which is invoked with the transformed
/// `position` as argument.
///
/// For the transform `mainAxisOffset` is subtracted from `mainAxisPosition`
/// and `crossAxisOffset` is subtracted from `crossAxisPosition`.
///
/// The `paintOffset` describes how the paint position of a point painted at
/// the provided `mainAxisPosition` and `crossAxisPosition` would change after