Skip to content

Instantly share code, notes, and snippets.

@pskink
Last active May 26, 2022 10:48
Show Gist options
  • Select an option

  • Save pskink/314911dc93fffc39e5855a90b1ca27cb to your computer and use it in GitHub Desktop.

Select an option

Save pskink/314911dc93fffc39e5855a90b1ca27cb to your computer and use it in GitHub Desktop.
import 'dart:ui';
import 'dart:math';
import 'package:flutter/material.dart';
/*
class CornerDecorationTest extends StatefulWidget {
@override
_CornerDecorationTestState createState() => _CornerDecorationTestState();
}
class _CornerDecorationTestState extends State<CornerDecorationTest> {
bool checked = false;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 350),
decoration: CornerDecoration(
strokeWidth: checked? 4 : 1,
strokeColor: checked? Colors.orange : Colors.black,
insets: checked? const EdgeInsets.all(16) : const EdgeInsets.symmetric(vertical: 48 * 2.0, horizontal: 48),
cornerSide: checked? CornerSide.all(16, 48, Radius.circular(8)) : CornerSide.vertical(top: const Size.square(16)),
fillTop: checked? 0.0 : 1.0,
fillBottom: checked? 0.0 : 1.0,
),
child: Container(
constraints: const BoxConstraints.expand(),
decoration: const FlutterLogoDecoration(style: FlutterLogoStyle.stacked),
child: OutlinedButton(
onPressed: () => setState(() => checked = !checked),
child: const Align(alignment: Alignment.centerLeft, child: Text('press me')),
),
),
);
}
}
*/
// ============================================================================
// ============================================================================
// ============================================================================
class CornerDecoration extends Decoration {
final double fillLeft;
final double fillTop;
final double fillRight;
final double fillBottom;
final double strokeWidth;
final Color strokeColor;
final EdgeInsets insets;
final double position;
final CornerSide cornerSide;
CornerDecoration({
this.fillLeft = 0.0,
this.fillTop = 0.0,
this.fillRight = 0.0,
this.fillBottom = 0.0,
this.strokeWidth = 3,
required this.strokeColor,
EdgeInsets? insets,
this.position = 0.5,
this.cornerSide = const CornerSide._all(Size.square(16), Radius.zero),
}) : insets = insets ?? EdgeInsets.all(strokeWidth),
assert(0 <= fillLeft && fillLeft <= 1),
assert(0 <= fillTop && fillTop <= 1),
assert(0 <= fillRight && fillRight <= 1),
assert(0 <= fillBottom && fillBottom <= 1);
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) =>
_CornerBoxPainter(fillLeft, fillTop, fillRight, fillBottom, strokeWidth, strokeColor, insets, position, cornerSide);
@override
EdgeInsetsGeometry get padding => insets;
@override
Decoration? lerpFrom(Decoration? a, double t) {
if (a is CornerDecoration) {
return CornerDecoration(
fillLeft: lerpDouble(a.fillLeft, fillLeft, t)!,
fillTop: lerpDouble(a.fillTop, fillTop, t)!,
fillRight: lerpDouble(a.fillRight, fillRight, t)!,
fillBottom: lerpDouble(a.fillBottom, fillBottom, t)!,
strokeWidth: lerpDouble(a.strokeWidth, strokeWidth, t)!,
strokeColor: Color.lerp(a.strokeColor, strokeColor, t)!,
insets: EdgeInsets.lerp(a.insets, insets, t),
position: lerpDouble(a.position, position, t)!,
cornerSide: CornerSide.lerp(a.cornerSide, cornerSide, t),
);
}
return super.lerpFrom(a, t) as BoxDecoration?;
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is CornerDecoration
&& other.fillLeft == fillLeft
&& other.fillTop == fillTop
&& other.fillRight == fillRight
&& other.fillBottom == fillBottom
&& other.strokeWidth == strokeWidth
&& other.strokeColor == strokeColor
&& other.insets == insets
&& other.position == position
&& other.cornerSide == cornerSide;
}
@override
int get hashCode {
return hashValues(
fillLeft,
fillTop,
fillRight,
fillBottom,
strokeWidth,
strokeColor,
insets,
position,
cornerSide,
);
}
}
class CornerSide {
CornerSide.all(double x, [double? y, Radius radius = Radius.zero]) : this._all(Size(x, y ?? x), radius);
CornerSide.vertical({
Size top = Size.zero,
Size bottom = Size.zero,
Radius topRadius = Radius.zero,
Radius bottomRadius = Radius.zero,
}) : this.only(
topLeft: top,
topRight: top,
bottomLeft: bottom,
bottomRight: bottom,
topLeftRadius: topRadius,
topRightRadius: topRadius,
bottomLeftRadius: bottomRadius,
bottomRightRadius: bottomRadius,
);
CornerSide.horizontal({
Size left = Size.zero,
Size right = Size.zero,
Radius leftRadius = Radius.zero,
Radius rightRadius = Radius.zero,
}) : this.only(
topLeft: left,
topRight: right,
bottomLeft: left,
bottomRight: right,
topLeftRadius: leftRadius,
topRightRadius: rightRadius,
bottomLeftRadius: leftRadius,
bottomRightRadius: rightRadius,
);
const CornerSide.only({
this.topLeft = Size.zero,
this.topRight = Size.zero,
this.bottomLeft = Size.zero,
this.bottomRight = Size.zero,
this.topLeftRadius = Radius.zero,
this.topRightRadius = Radius.zero,
this.bottomLeftRadius = Radius.zero,
this.bottomRightRadius = Radius.zero,
});
const CornerSide._all(Size size, Radius radius) : this.only(
topLeft: size,
topRight: size,
bottomLeft: size,
bottomRight: size,
topLeftRadius: radius,
topRightRadius: radius,
bottomLeftRadius: radius,
bottomRightRadius: radius,
);
final Size topLeft;
final Size topRight;
final Size bottomLeft;
final Size bottomRight;
final Radius topLeftRadius;
final Radius topRightRadius;
final Radius bottomLeftRadius;
final Radius bottomRightRadius;
static CornerSide lerp(CornerSide a, CornerSide b, double t) {
return CornerSide.only(
topLeft: Size.lerp(a.topLeft, b.topLeft, t)!,
topRight: Size.lerp(a.topRight, b.topRight, t)!,
bottomRight: Size.lerp(a.bottomRight, b.bottomRight, t)!,
bottomLeft: Size.lerp(a.bottomLeft, b.bottomLeft, t)!,
topLeftRadius: Radius.lerp(a.topLeftRadius, b.topLeftRadius, t)!,
topRightRadius: Radius.lerp(a.topRightRadius, b.topRightRadius, t)!,
bottomRightRadius: Radius.lerp(a.bottomRightRadius, b.bottomRightRadius, t)!,
bottomLeftRadius: Radius.lerp(a.bottomLeftRadius, b.bottomLeftRadius, t)!,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is CornerSide
&& other.topLeft == topLeft
&& other.topRight == topRight
&& other.bottomLeft == bottomLeft
&& other.bottomRight == bottomRight
&& other.topLeftRadius == topLeftRadius
&& other.topRightRadius == topRightRadius
&& other.bottomLeftRadius == bottomLeftRadius
&& other.bottomRightRadius == bottomRightRadius;
}
@override
int get hashCode {
return hashValues(
topLeft,
topRight,
bottomLeft,
bottomRight,
topLeftRadius,
topRightRadius,
bottomLeftRadius,
bottomRightRadius,
);
}
}
class _CornerBoxPainter extends BoxPainter {
final double fillLeft;
final double fillTop;
final double fillRight;
final double fillBottom;
final double strokeWidth;
final EdgeInsets insets;
final double position;
final CornerSide cornerSide;
late Paint _p;
_CornerBoxPainter(this.fillLeft, this.fillTop, this.fillRight, this.fillBottom, this.strokeWidth, strokeColor, this.insets, this.position, this.cornerSide) {
_p = Paint()
..color = strokeColor
..style = PaintingStyle.stroke
// ..strokeCap = StrokeCap.square
..strokeWidth = strokeWidth;
}
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final _rect = offset & configuration.size!;
final a = _rect.deflate(strokeWidth / 2);
final b = insets.deflateRect(_rect).inflate(strokeWidth / 2);
final rect = Rect.lerp(a, b, position)!;
Path _path = Path();
final topLeftCornerRect = Alignment.topLeft.inscribe(cornerSide.topLeft, rect);
final topRightCornerRect = Alignment.topRight.inscribe(cornerSide.topRight, rect);
final bottomRightCornerRect = Alignment.bottomRight.inscribe(cornerSide.bottomRight, rect);
final bottomLeftCornerRect = Alignment.bottomLeft.inscribe(cornerSide.bottomLeft, rect);
final i = <dynamic>[
Offset.lerp(topLeftCornerRect.bottomLeft, rect.centerLeft, fillLeft)!,
_midPoint(rect, cornerSide.topLeftRadius, Alignment.topLeft, -pi),
Offset.lerp(topLeftCornerRect.topRight, rect.topCenter, fillTop)!,
Offset.lerp(topRightCornerRect.topLeft, rect.topCenter, fillTop)!,
_midPoint(rect, cornerSide.topRightRadius, Alignment.topRight, -pi / 2),
Offset.lerp(topRightCornerRect.bottomRight, rect.centerRight, fillRight)!,
Offset.lerp(bottomRightCornerRect.topRight, rect.centerRight, fillRight)!,
_midPoint(rect, cornerSide.bottomRightRadius, Alignment.bottomRight, 0),
Offset.lerp(bottomRightCornerRect.bottomLeft, rect.bottomCenter, fillBottom)!,
Offset.lerp(bottomLeftCornerRect.bottomRight, rect.bottomCenter, fillBottom)!,
_midPoint(rect, cornerSide.bottomLeftRadius, Alignment.bottomLeft, pi / 2),
Offset.lerp(bottomLeftCornerRect.topLeft, rect.centerLeft, fillLeft)!,
].iterator;
while (i.moveNext()) {
final point0 = i.current as Offset;
_path.moveTo(point0.dx, point0.dy); i.moveNext();
final arcEntry = i.current as _ArcEntry;
arcEntry.addToPath(_path); i.moveNext();
final point1 = i.current as Offset;
_path.lineTo(point1.dx, point1.dy);
}
canvas.drawPath(_path, _p);
}
_ArcEntry _midPoint(Rect rect, Radius radius, Alignment alignmnet, double startAngle) {
return radius != Radius.zero?
_ArcEntry.arc(alignmnet.inscribe(Size(radius.x * 2, radius.y * 2), rect), startAngle) :
_ArcEntry.point(alignmnet.withinRect(rect));
}
}
class _ArcEntry {
final bool sharp;
final Offset point;
final Rect rect;
final double startAngle;
_ArcEntry.point(this.point) : sharp = true, rect = Rect.zero, startAngle = 0;
_ArcEntry.arc(this.rect, this.startAngle) : sharp = false, point = Offset.zero;
void addToPath(Path path) {
if (sharp) {
path.lineTo(point.dx, point.dy);
} else {
path.arcTo(rect, startAngle, pi / 2, false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment