|
@@ -1,15 +1,67 @@
|
|
|
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<Widget> children,
|
|
|
+ this.padding = EdgeInsets.zero,
|
|
|
+ this.mainAxisSpacing = 0,
|
|
|
+ this.background,
|
|
|
}) : super(key: key, children: children);
|
|
|
|
|
|
@override
|
|
|
- RenderObject createRenderObject(BuildContext context) {
|
|
|
- return _RenderTrapezoidBox();
|
|
|
+ _RenderTrapezoidBox createRenderObject(BuildContext context) {
|
|
|
+ return _RenderTrapezoidBox(padding, mainAxisSpacing, background);
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void updateRenderObject(
|
|
|
+ BuildContext context, covariant _RenderTrapezoidBox renderObject) {
|
|
|
+ renderObject
|
|
|
+ ..padding = padding
|
|
|
+ ..mainAxisSpacing = mainAxisSpacing
|
|
|
+ ..background = background;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -24,6 +76,39 @@ class _RenderTrapezoidBox extends RenderBox
|
|
|
with
|
|
|
ContainerRenderObjectMixin<RenderBox, TrapezoidParentData>,
|
|
|
RenderBoxContainerDefaultsMixin<RenderBox, TrapezoidParentData> {
|
|
|
+ _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) {
|
|
@@ -33,27 +118,136 @@ class _RenderTrapezoidBox extends RenderBox
|
|
|
|
|
|
@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, offset, child);
|
|
|
+ _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, Offset offset, RenderBox child) {
|
|
|
+ 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.c.dx, parentData.c.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();
|
|
|
}
|
|
|
}
|