Files
MWClash/lib/widgets/grid.dart
chen08209 ef97ef40a1 Remake dashboard
Optimize theme

Optimize more details

Update flutter version
2025-01-09 10:10:06 +08:00

416 lines
12 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
typedef WrapBuilder = Widget Function(Widget child);
class Grid extends MultiChildRenderObjectWidget {
final double mainAxisSpacing;
final double crossAxisSpacing;
final double? mainAxisExtent;
final int crossAxisCount;
final AxisDirection axisDirection;
final TextDirection textDirection;
const Grid({
super.key,
this.mainAxisSpacing = 0,
this.crossAxisSpacing = 0,
int? crossAxisCount,
AxisDirection? axisDirection,
TextDirection? textDirection,
this.mainAxisExtent,
List<Widget>? children,
}) : crossAxisCount = crossAxisCount ?? 1,
axisDirection = axisDirection ?? AxisDirection.down,
textDirection = textDirection ?? TextDirection.ltr,
super(children: children ?? const []);
const Grid.baseGap({
Key? key,
double mainAxisSpacing = 8.0,
double crossAxisSpacing = 8.0,
int? crossAxisCount,
AxisDirection? axisDirection,
TextDirection? textDirection,
double? mainAxisExtent,
List<Widget>? children,
}) : this(
key: key,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
crossAxisCount: crossAxisCount,
axisDirection: axisDirection,
textDirection: textDirection,
mainAxisExtent: mainAxisExtent,
children: children,
);
@override
RenderObject createRenderObject(BuildContext context) {
return RenderGrid(
textDirection: textDirection,
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
axisDirection: axisDirection,
mainAxisExtent: mainAxisExtent,
);
}
@override
void updateRenderObject(
BuildContext context,
RenderGrid renderObject,
) {
renderObject
..mainAxisSpacing = mainAxisSpacing
..mainAxisExtent = mainAxisExtent
..crossAxisSpacing = crossAxisSpacing
..textDirection = textDirection
..axisDirection = axisDirection
..crossAxisCount = crossAxisCount;
}
}
class RenderGrid extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, GridParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
RenderGrid({
required double mainAxisSpacing,
required double crossAxisSpacing,
required int crossAxisCount,
required AxisDirection axisDirection,
required TextDirection textDirection,
double? mainAxisExtent,
}) : _crossAxisCount = crossAxisCount,
_crossAxisSpacing = crossAxisSpacing,
_mainAxisSpacing = mainAxisSpacing,
_axisDirection = axisDirection,
_textDirection = textDirection,
_mainAxisExtent = mainAxisExtent;
int _crossAxisCount;
int get crossAxisCount => _crossAxisCount;
set crossAxisCount(int value) {
if (_crossAxisCount != value) {
_crossAxisCount = value;
markNeedsLayout();
}
}
double? _mainAxisExtent;
double? get mainAxisExtent => _mainAxisExtent;
set mainAxisExtent(double? value) {
if (_mainAxisExtent != value) {
_mainAxisExtent = value;
markNeedsLayout();
}
}
double _mainAxisSpacing;
double get mainAxisSpacing => _mainAxisSpacing;
set mainAxisSpacing(double value) {
if (_mainAxisSpacing != value) {
_mainAxisSpacing = value;
markNeedsLayout();
}
}
double _crossAxisSpacing;
double get crossAxisSpacing => _crossAxisSpacing;
set crossAxisSpacing(double value) {
if (_crossAxisSpacing != value) {
_crossAxisSpacing = value;
markNeedsLayout();
}
}
AxisDirection _axisDirection;
AxisDirection get axisDirection => _axisDirection;
set axisDirection(AxisDirection value) {
if (_axisDirection != value) {
_axisDirection = value;
markNeedsLayout();
}
}
TextDirection _textDirection;
TextDirection get textDirection => _textDirection;
set textDirection(TextDirection value) {
if (_textDirection != value) {
_textDirection = value;
markNeedsLayout();
}
}
Axis get mainAxis => axisDirectionToAxis(_axisDirection);
@override
void setupParentData(RenderObject child) {
if (child.parentData is! GridParentData) {
child.parentData = GridParentData();
}
}
@override
void performLayout() {
final requestedSize = _computeSize(constraints: constraints);
size = constraints.constrain(requestedSize);
_hasOverflow = size != requestedSize;
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
if (_hasOverflow) {
context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
defaultPaint,
);
} else {
defaultPaint(context, offset);
}
}
GridParentData _getParentData(RenderBox child) {
return child.parentData as GridParentData;
}
void _layoutChild(
RenderBox child,
BoxConstraints constraints, {
bool parentUsesSize = false,
}) {
child.layout(constraints, parentUsesSize: parentUsesSize);
}
int _computeCrossAxisCellCount(
GridParentData childParentData,
int crossAxisCount,
) {
return math.min(
childParentData.crossAxisCellCount ?? 1,
crossAxisCount,
);
}
Size _computeSize({
required BoxConstraints constraints,
}) {
final crossAxisExtent = mainAxis == Axis.vertical
? constraints.maxWidth
: constraints.maxHeight;
final stride = (crossAxisExtent + crossAxisSpacing) / crossAxisCount;
final offsets = List.filled(crossAxisCount, 0.0);
RenderBox? child = firstChild;
while (child != null) {
final childParentData = _getParentData(child);
final crossAxisCellCount = _computeCrossAxisCellCount(
childParentData,
crossAxisCount,
);
final crossAxisExtent = stride * crossAxisCellCount - crossAxisSpacing;
final shouldFitContent = childParentData.mainAxisCellCount == null;
double mainAxisExtent = 0;
if (shouldFitContent) {
final childConstraints = mainAxis == Axis.vertical
? BoxConstraints.tightFor(width: crossAxisExtent)
: BoxConstraints.tightFor(height: crossAxisExtent);
_layoutChild(child, childConstraints, parentUsesSize: true);
mainAxisExtent =
mainAxis == Axis.vertical ? child.size.height : child.size.width;
} else {
final mainAxisCellCount = childParentData.mainAxisCellCount ?? 1;
mainAxisExtent = (this.mainAxisExtent ?? stride) * mainAxisCellCount -
mainAxisSpacing;
childParentData.realMainAxisExtent = mainAxisExtent;
final childSize = mainAxis == Axis.vertical
? Size(crossAxisExtent, mainAxisExtent)
: Size(mainAxisExtent, crossAxisExtent);
final childConstraints = BoxConstraints.tight(childSize);
_layoutChild(child, childConstraints);
}
final origin = _getOrigin(offsets, crossAxisCellCount);
final mainAxisOffset = origin.mainAxisOffset;
final crossAxisOffset = origin.crossAxisIndex * stride;
final offset = mainAxis == Axis.vertical
? Offset(crossAxisOffset, mainAxisOffset)
: Offset(mainAxisOffset, crossAxisOffset);
childParentData.offset = offset;
final nextOffset = mainAxisOffset + mainAxisExtent + mainAxisSpacing;
for (int i = 0; i < crossAxisCellCount; i++) {
offsets[origin.crossAxisIndex + i] = nextOffset;
}
child = childAfter(child);
}
final mainAxisExtent = offsets.reduce(math.max) - mainAxisSpacing;
if (axisDirectionIsReversed(axisDirection)) {
child = firstChild;
while (child != null) {
final childParentData = _getParentData(child);
final offset = childParentData.offset;
final crossAxisOffset = offset.getCrossAxisOffset(mainAxis);
final mainAxisOffset = mainAxisExtent -
offset.getMainAxisOffset(mainAxis) -
childParentData.realMainAxisExtent!;
final newOffset = mainAxis == Axis.vertical
? Offset(crossAxisOffset, mainAxisOffset)
: Offset(mainAxisOffset, crossAxisOffset);
childParentData.offset = newOffset;
child = childAfter(child);
}
}
if (mainAxis == Axis.vertical && textDirection == TextDirection.rtl) {
child = firstChild;
while (child != null) {
final childParentData = _getParentData(child);
final crossAxisCellCount = crossAxisCount;
final crossAxisCellExtent =
stride * crossAxisCellCount - crossAxisSpacing;
final offset = childParentData.offset;
final crossAxisOffset =
crossAxisExtent - offset.dx - crossAxisCellExtent;
final mainAxisOffset = offset.dy;
final newOffset = Offset(crossAxisOffset, mainAxisOffset);
childParentData.offset = newOffset;
child = childAfter(child);
}
}
return mainAxis == Axis.vertical
? Size(crossAxisExtent, mainAxisExtent)
: Size(mainAxisExtent, crossAxisExtent);
}
bool _hasOverflow = false;
}
class GridParentData extends ContainerBoxParentData<RenderBox> {
int? crossAxisCellCount;
num? mainAxisCellCount;
double? realMainAxisExtent;
@override
String toString() =>
'crossAxisCellCount=$crossAxisCellCount; mainAxisCellCount=$mainAxisCellCount;';
}
class GridItem extends ParentDataWidget<GridParentData> {
final int crossAxisCellCount;
final num? mainAxisCellCount;
const GridItem({
super.key,
required super.child,
this.mainAxisCellCount,
this.crossAxisCellCount = 1,
});
@override
void applyParentData(RenderObject renderObject) {
final parentData = renderObject.parentData;
if (parentData is GridParentData) {
bool needsLayout = false;
if (parentData.crossAxisCellCount != crossAxisCellCount) {
parentData.crossAxisCellCount = crossAxisCellCount;
needsLayout = true;
}
if (parentData.mainAxisCellCount != mainAxisCellCount) {
parentData.mainAxisCellCount = mainAxisCellCount;
needsLayout = true;
}
if (needsLayout) {
final targetParent = renderObject.parent;
if (targetParent is RenderGrid) {
targetParent.markNeedsLayout();
}
}
}
}
@override
Type get debugTypicalAncestorWidgetClass => GridItem;
GridItem wrap({
required WrapBuilder builder,
}) {
return GridItem(
mainAxisCellCount: mainAxisCellCount,
crossAxisCellCount: crossAxisCellCount,
child: builder(
child,
),
);
}
}
class _Origin {
final int crossAxisIndex;
final double mainAxisOffset;
const _Origin(this.crossAxisIndex, this.mainAxisOffset);
}
_Origin _getOrigin(List<double> offsets, int crossAxisCount) {
final length = offsets.length;
_Origin origin = const _Origin(0, double.infinity);
for (int i = 0; i < length; i++) {
final offset = offsets[i];
if (offset.moreOrEqual(origin.mainAxisOffset)) {
continue;
}
int start = 0;
int span = 0;
for (int j = 0;
span < crossAxisCount &&
j < length &&
length - j >= crossAxisCount - span;
j++) {
if (offset.moreOrEqual(offsets[j])) {
span++;
if (span == crossAxisCount) {
origin = _Origin(start, offset);
}
} else {
start = j + 1;
span = 0;
}
}
}
return origin;
}