mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-19 19:01:24 +08:00
* opt: linter * tweaks * opt: TopImage * update * remove repaintBoundary [skip ci] --------- Co-authored-by: dom <githubaccount56556@proton.me>
344 lines
8.9 KiB
Dart
344 lines
8.9 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
class SliverFixedWrap extends SliverMultiBoxAdaptorWidget {
|
|
final double mainAxisExtent;
|
|
final double spacing;
|
|
final double runSpacing;
|
|
|
|
const SliverFixedWrap({
|
|
super.key,
|
|
required super.delegate,
|
|
required this.mainAxisExtent,
|
|
this.spacing = 0,
|
|
this.runSpacing = 0,
|
|
});
|
|
|
|
@override
|
|
SliverWrapElement createElement() =>
|
|
SliverWrapElement(this, replaceMovedChildren: true);
|
|
|
|
@override
|
|
RenderSliverFixedWrap createRenderObject(BuildContext context) {
|
|
return RenderSliverFixedWrap(
|
|
childManager: context as SliverWrapElement,
|
|
mainAxisExtent: mainAxisExtent,
|
|
spacing: spacing,
|
|
runSpacing: runSpacing,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(
|
|
BuildContext context,
|
|
RenderSliverFixedWrap renderObject,
|
|
) {
|
|
renderObject
|
|
..mainAxisExtent = mainAxisExtent
|
|
..spacing = spacing
|
|
..runSpacing = runSpacing;
|
|
}
|
|
}
|
|
|
|
class SliverWrapParentData extends SliverMultiBoxAdaptorParentData {
|
|
double crossAxisOffset = 0.0;
|
|
|
|
@override
|
|
String toString() => 'crossAxisOffset=$crossAxisOffset; ${super.toString()}';
|
|
}
|
|
|
|
class _Row {
|
|
final int startIndex;
|
|
final int endIndex;
|
|
final List<double> childWidths;
|
|
|
|
_Row({
|
|
required this.startIndex,
|
|
required this.endIndex,
|
|
required this.childWidths,
|
|
});
|
|
}
|
|
|
|
class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
|
RenderSliverFixedWrap({
|
|
required super.childManager,
|
|
required double mainAxisExtent,
|
|
double spacing = 0.0,
|
|
double runSpacing = 0.0,
|
|
}) : _mainAxisExtent = mainAxisExtent,
|
|
_spacing = spacing,
|
|
_runSpacing = runSpacing {
|
|
assert(mainAxisExtent > 0.0 && mainAxisExtent.isFinite);
|
|
}
|
|
|
|
double _mainAxisExtent;
|
|
double get mainAxisExtent => _mainAxisExtent;
|
|
set mainAxisExtent(double value) {
|
|
if (_mainAxisExtent == value) return;
|
|
_mainAxisExtent = value;
|
|
markRowsDirty();
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double _spacing;
|
|
double get spacing => _spacing;
|
|
set spacing(double value) {
|
|
if (_spacing == value) return;
|
|
_spacing = value;
|
|
markRowsDirty();
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double _runSpacing;
|
|
double get runSpacing => _runSpacing;
|
|
set runSpacing(double value) {
|
|
if (_runSpacing == value) return;
|
|
_runSpacing = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
final List<_Row> _rows = [];
|
|
|
|
void markRowsDirty() {
|
|
_rows.clear();
|
|
}
|
|
|
|
@override
|
|
void setupParentData(RenderObject child) {
|
|
if (child.parentData is! SliverWrapParentData) {
|
|
child.parentData = SliverWrapParentData();
|
|
}
|
|
}
|
|
|
|
@override
|
|
double childCrossAxisPosition(RenderBox child) {
|
|
return (child.parentData as SliverWrapParentData).crossAxisOffset;
|
|
}
|
|
|
|
double _childCrossExtent(RenderBox child) {
|
|
assert(child.hasSize);
|
|
return switch (constraints.axis) {
|
|
Axis.horizontal => child.size.height,
|
|
Axis.vertical => child.size.width,
|
|
};
|
|
}
|
|
|
|
RenderBox _getOrCreateChildAtIndex(
|
|
int index,
|
|
BoxConstraints constraints,
|
|
RenderBox? child,
|
|
) {
|
|
assert(firstChild != null);
|
|
|
|
if (index < indexOf(firstChild!)) {
|
|
do {
|
|
child = insertAndLayoutLeadingChild(constraints, parentUsesSize: true);
|
|
assert(child != null);
|
|
} while (indexOf(child!) > index);
|
|
|
|
assert(indexOf(child) == index);
|
|
|
|
return child;
|
|
} else if (index > indexOf(lastChild!)) {
|
|
do {
|
|
child = insertAndLayoutChild(
|
|
constraints,
|
|
after: lastChild,
|
|
parentUsesSize: true,
|
|
);
|
|
assert(child != null);
|
|
} while (indexOf(child!) < index);
|
|
|
|
assert(indexOf(child) == index);
|
|
|
|
return child;
|
|
} else {
|
|
child = firstChild;
|
|
while (indexOf(child!) < index) {
|
|
child = childAfter(child);
|
|
}
|
|
if (indexOf(child) == index) {
|
|
child.layout(constraints, parentUsesSize: true);
|
|
return child;
|
|
}
|
|
throw RangeError.value(index, 'index', 'Value not included in children');
|
|
}
|
|
}
|
|
|
|
bool _buildNextRow(int start, BoxConstraints constraints) {
|
|
final int childCount = childManager.childCount;
|
|
|
|
if (start >= childCount) {
|
|
return false;
|
|
}
|
|
|
|
final crossAxisExtent = this.constraints.crossAxisExtent;
|
|
|
|
final List<double> widths = [];
|
|
int idx = start;
|
|
RenderBox? child;
|
|
for (var totalWidth = -_spacing; idx < childCount; idx++) {
|
|
child = _getOrCreateChildAtIndex(idx, constraints, child);
|
|
final childWidth = _childCrossExtent(child);
|
|
totalWidth += childWidth + _spacing;
|
|
|
|
if (totalWidth <= crossAxisExtent) {
|
|
widths.add(childWidth);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
_rows.add(_Row(startIndex: start, endIndex: idx - 1, childWidths: widths));
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
childManager
|
|
..didStartLayout()
|
|
..setDidUnderflow(false);
|
|
|
|
final constraints = this.constraints;
|
|
final childCount = childManager.childCount;
|
|
|
|
final rowHeight = _mainAxisExtent + _runSpacing;
|
|
|
|
final scrollOffset = constraints.scrollOffset;
|
|
|
|
final firstCacheOffset = scrollOffset + constraints.cacheOrigin;
|
|
final lastCacheOffset = scrollOffset + constraints.remainingCacheExtent;
|
|
|
|
final firstNeededRow = math.max(0, firstCacheOffset ~/ rowHeight);
|
|
final lastNeededRow = math.max(0, lastCacheOffset ~/ rowHeight);
|
|
|
|
final childConstraints = constraints.toFixedConstraints(_mainAxisExtent);
|
|
|
|
if (firstChild == null) {
|
|
if (!addInitialChild()) {
|
|
geometry = SliverGeometry.zero;
|
|
childManager.didFinishLayout();
|
|
return;
|
|
}
|
|
firstChild!.layout(childConstraints, parentUsesSize: true);
|
|
}
|
|
|
|
while (_rows.length <= lastNeededRow) {
|
|
final int startIndex = _rows.isEmpty ? 0 : _rows.last.endIndex + 1;
|
|
if (!_buildNextRow(startIndex, childConstraints)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(firstNeededRow >= 0);
|
|
|
|
final int firstKeptRow = firstNeededRow.clamp(0, _rows.length - 1);
|
|
final int lastKeptRow = lastNeededRow.clamp(0, _rows.length - 1);
|
|
|
|
final int firstKeptIndex = _rows[firstKeptRow].startIndex;
|
|
final int lastKeptIndex = _rows[lastKeptRow].endIndex;
|
|
|
|
collectGarbage(
|
|
calculateLeadingGarbage(firstIndex: firstKeptIndex),
|
|
calculateTrailingGarbage(lastIndex: lastKeptIndex),
|
|
);
|
|
|
|
RenderBox? child;
|
|
for (var r = firstKeptRow; r <= lastKeptRow; r++) {
|
|
final row = _rows[r];
|
|
final rowStartOffset = r * rowHeight;
|
|
double crossOffset = 0.0;
|
|
for (var i = row.startIndex; i <= row.endIndex; i++) {
|
|
child = _getOrCreateChildAtIndex(i, childConstraints, child);
|
|
(child.parentData as SliverWrapParentData)
|
|
..layoutOffset = rowStartOffset
|
|
..crossAxisOffset = crossOffset;
|
|
crossOffset += row.childWidths[i - row.startIndex] + _spacing;
|
|
}
|
|
}
|
|
|
|
final endOffset = _rows.last.endIndex == childCount - 1
|
|
? (_rows.length * rowHeight)
|
|
: (_rows.last.startIndex + 1) * rowHeight;
|
|
|
|
final double estimatedMaxScrollOffset;
|
|
if (_rows.length <= lastNeededRow || childCount == 0) {
|
|
estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
|
|
constraints,
|
|
firstIndex: firstKeptIndex,
|
|
lastIndex: lastKeptIndex,
|
|
leadingScrollOffset: firstKeptRow * rowHeight,
|
|
trailingScrollOffset: endOffset,
|
|
);
|
|
} else {
|
|
estimatedMaxScrollOffset = _rows.length * rowHeight;
|
|
}
|
|
|
|
final double paintExtent = calculatePaintOffset(
|
|
constraints,
|
|
from: firstKeptRow * rowHeight,
|
|
to: endOffset,
|
|
);
|
|
final double cacheExtent = calculateCacheOffset(
|
|
constraints,
|
|
from: firstCacheOffset,
|
|
to: lastCacheOffset,
|
|
);
|
|
|
|
geometry = SliverGeometry(
|
|
scrollExtent: estimatedMaxScrollOffset,
|
|
paintExtent: paintExtent,
|
|
cacheExtent: cacheExtent,
|
|
maxPaintExtent: estimatedMaxScrollOffset,
|
|
hasVisualOverflow:
|
|
endOffset >
|
|
constraints.scrollOffset + constraints.remainingPaintExtent,
|
|
);
|
|
|
|
if (estimatedMaxScrollOffset <= endOffset) {
|
|
childManager.setDidUnderflow(true);
|
|
}
|
|
|
|
childManager.didFinishLayout();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
markRowsDirty();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
class SliverWrapElement extends SliverMultiBoxAdaptorElement {
|
|
SliverWrapElement(SliverFixedWrap super.widget, {super.replaceMovedChildren});
|
|
|
|
@override
|
|
void performRebuild() {
|
|
(renderObject as RenderSliverFixedWrap).markRowsDirty();
|
|
super.performRebuild();
|
|
}
|
|
}
|
|
|
|
extension on SliverConstraints {
|
|
BoxConstraints toFixedConstraints(double mainAxisExtent) {
|
|
switch (axis) {
|
|
case Axis.horizontal:
|
|
return BoxConstraints(
|
|
minHeight: 0,
|
|
maxHeight: crossAxisExtent,
|
|
minWidth: mainAxisExtent,
|
|
maxWidth: mainAxisExtent,
|
|
);
|
|
case Axis.vertical:
|
|
return BoxConstraints(
|
|
minWidth: 0,
|
|
maxWidth: crossAxisExtent,
|
|
minHeight: mainAxisExtent,
|
|
maxHeight: mainAxisExtent,
|
|
);
|
|
}
|
|
}
|
|
}
|