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? 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? 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, RenderBoxContainerDefaultsMixin { 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 { int? crossAxisCellCount; num? mainAxisCellCount; double? realMainAxisExtent; @override String toString() => 'crossAxisCellCount=$crossAxisCellCount; mainAxisCellCount=$mainAxisCellCount;'; } class GridItem extends ParentDataWidget { 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 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; }