import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'dart:math' show max; class Quadrilateral { final Offset a; final Offset b; final Offset c; final Offset d; Quadrilateral({ required this.a, required this.b, required this.c, required this.d, }); bool contains(Offset point) { final double px = point.dx; final double py = point.dy; final double cp1 = (b.dx - a.dx) * (py - a.dy) - (b.dy - a.dy) * (px - a.dx); final double cp2 = (c.dx - b.dx) * (py - b.dy) - (c.dy - b.dy) * (px - b.dx); final double cp3 = (d.dx - c.dx) * (py - c.dy) - (d.dy - c.dy) * (px - c.dx); final double cp4 = (a.dx - d.dx) * (py - d.dy) - (a.dy - d.dy) * (px - d.dx); if ((cp1 > 0 && cp2 > 0 && cp3 > 0 && cp4 > 0) || (cp1 < 0 && cp2 < 0 && cp3 < 0 && cp4 < 0)) { return true; } return false; } } class TrapezoidBox extends MultiChildRenderObjectWidget { final EdgeInsets padding; final double mainAxisSpacing; final Color? background; TrapezoidBox({ Key? key, required List children, this.padding = EdgeInsets.zero, this.mainAxisSpacing = 0, this.background, }) : super(key: key, children: children); @override _RenderTrapezoidBox createRenderObject(BuildContext context) { return _RenderTrapezoidBox(padding, mainAxisSpacing, background); } @override void updateRenderObject( BuildContext context, covariant _RenderTrapezoidBox renderObject) { renderObject ..padding = padding ..mainAxisSpacing = mainAxisSpacing ..background = background; } } class TrapezoidParentData extends ContainerBoxParentData { Offset a = Offset.zero; Offset b = Offset.zero; Offset c = Offset.zero; Offset d = Offset.zero; } class _RenderTrapezoidBox extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { _RenderTrapezoidBox( this._padding, this._mainAxisSpacing, this._background, ); EdgeInsets _padding; set padding(EdgeInsets value) { if (value != _padding) { _padding = value; markNeedsLayout(); } } double _mainAxisSpacing; set mainAxisSpacing(double value) { if (value != _mainAxisSpacing) { _mainAxisSpacing = value; markNeedsLayout(); } } Color? _background; set background(Color? value) { if (value != _background) { _background = value; markNeedsPaint(); } } @override void setupParentData(covariant RenderObject child) { if (child.parentData is! TrapezoidParentData) { child.parentData = TrapezoidParentData(); } } @override void performLayout() { var child = firstChild; final childConstraints = constraints.deflate(_padding); while (child != null) { assert(child is RenderBox); child.layout(childConstraints, parentUsesSize: true); child = childAfter(child); } child = firstChild; final double left = _padding.left; final double right = constraints.maxWidth - _padding.right; double start = _padding.top; int count = 0; while (child != null) { assert(child is RenderBox); count++; final parentData = child.parentData as TrapezoidParentData; final childSize = child.size; var end = start + childSize.height; if (count % 2 == 0) { parentData.a = Offset(left, start); parentData.b = Offset(right, start + 30); parentData.c = Offset(left, end); parentData.d = Offset(right, end - 30); parentData.offset = Offset(left, start); } else { parentData.a = Offset(left, start + 30); parentData.b = Offset(right, start); parentData.c = Offset(left, end - 30); parentData.d = Offset(right, end); parentData.offset = Offset(left, start); } start = end - 30 + _mainAxisSpacing; child = childAfter(child); } double height = _padding.top; child = firstChild; while (child != null) { height += child.size.height + _mainAxisSpacing; child = childAfter(child); } height += _padding.bottom - _mainAxisSpacing; height -= (childCount - 1) * 30; size = Size( constraints.biggest.width, height, ); } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { RenderBox? child = firstChild; while (child != null) { final TrapezoidParentData childParentData = child.parentData! as TrapezoidParentData; final Quadrilateral quadrilateral = Quadrilateral( a: childParentData.a, b: childParentData.b, c: childParentData.d, d: childParentData.c, ); bool isIn = quadrilateral.contains(position); if (isIn) { final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset? transformed) { assert(transformed == position - childParentData.offset); return child!.hitTest(result, position: transformed!); }, ); if (isHit) { return true; } } child = childParentData.nextSibling; } return false; } @override void paint(PaintingContext context, Offset offset) { context.canvas.save(); context.canvas.translate(offset.dx, offset.dy); _paintBackground(context.canvas); for (var child in getChildrenAsList()) { _paintChild(context, child); } context.canvas.restore(); } void _paintBackground(Canvas canvas) { if(_background != null) { Path path = Path(); path.moveTo(0, 30); path.lineTo(size.width, 0); path.lineTo(size.width, size.height); path.lineTo(0, size.height); path.close(); canvas.save(); canvas.clipPath(path); canvas.drawRect(Offset.zero&size, Paint()..color = _background!); canvas.restore(); } } void _paintChild(PaintingContext context, RenderBox child) { TrapezoidParentData parentData = child.parentData as TrapezoidParentData; context.canvas.save(); Path path = Path(); path.moveTo(parentData.a.dx, parentData.a.dy); path.lineTo(parentData.b.dx, parentData.b.dy); path.lineTo(parentData.d.dx, parentData.d.dy); path.lineTo(parentData.c.dx, parentData.c.dy); path.close(); context.canvas.clipPath(path); context.paintChild(child, parentData.offset); context.canvas.restore(); } }