Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active November 19, 2025 14:43
Show Gist options
  • Select an option

  • Save PlugFox/73fdd4f3d366a8f3e8662768226af36f to your computer and use it in GitHub Desktop.

Select an option

Save PlugFox/73fdd4f3d366a8f3e8662768226af36f to your computer and use it in GitHub Desktop.
Superellipse Input Border for Flutter
/*
* Superellipse Input Border for Flutter
* https://gist.github.com/PlugFox/73fdd4f3d366a8f3e8662768226af36f
* https://dartpad.dev?id=73fdd4f3d366a8f3e8662768226af36f
* Mike Matiunin <[email protected]>, 19 November 2025
*/
import 'package:flutter/material.dart';
void main() => runApp(
const MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: DecoratedText(),
),
),
),
),
);
class DecoratedText extends StatelessWidget {
const DecoratedText({
super.key,
});
@override
Widget build(BuildContext context) => SizedBox(
width: 128,
height: 64,
child: InputDecorator(
decoration: InputDecoration(
border: ShapeInputBorder.superellipse(
borderRadius: const BorderRadius.all(Radius.circular(24)),
side: const BorderSide(color: Colors.blue, width: 2),
),
),
child: const Text('Hello, Superellipse!'),
),
);
}
class ShapeInputBorder extends InputBorder {
const ShapeInputBorder({
required this.shape,
super.borderSide,
});
ShapeInputBorder.superellipse({
BorderSide side = BorderSide.none,
BorderRadiusGeometry borderRadius = BorderRadius.zero,
}) : this(
shape: RoundedSuperellipseBorder(
side: side,
borderRadius: borderRadius,
),
borderSide: side,
);
final ShapeBorder shape;
@override
bool get isOutline => true;
@override
ShapeInputBorder copyWith({BorderSide? borderSide, OutlinedBorder? shape}) => ShapeInputBorder(
shape: shape ?? this.shape,
borderSide: borderSide ?? this.borderSide,
);
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(borderSide.width);
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) =>
shape.getInnerPath(rect, textDirection: textDirection);
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) =>
shape.getOuterPath(rect, textDirection: textDirection);
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double? gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
final paint = borderSide.toPaint();
final outerPath = shape.getOuterPath(rect, textDirection: textDirection);
if (gapExtent == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
canvas.drawPath(outerPath, paint);
return;
}
final extent = gapExtent + 5;
final start = gapStart ?? 2 - 2;
final gapRect = Rect.fromLTWH(
textDirection == TextDirection.rtl ? rect.width - start - extent : start,
rect.top - borderSide.width,
extent,
borderSide.width * 2,
);
final gapPath = Path()..addRect(gapRect);
final resultPath = Path.combine(
PathOperation.difference,
outerPath,
gapPath,
);
canvas.drawPath(resultPath, paint);
}
@override
ShapeInputBorder scale(double t) => ShapeInputBorder(
shape: shape.scale(t),
borderSide: borderSide.scale(t),
);
}
class RoundedSuperellipseInputBorder extends RoundedSuperellipseBorder implements InputBorder {
const RoundedSuperellipseInputBorder({
super.side = BorderSide.none,
super.borderRadius = BorderRadius.zero,
});
@override
BorderSide get borderSide => super.side;
@override
bool get isOutline => true;
RoundedSuperellipseInputBorder copyWith({
BorderSide? side,
BorderSide? borderSide,
BorderRadiusGeometry? borderRadius,
}) {
assert(
side == null || borderSide == null,
'Cannot provide both side and borderSide',
);
return RoundedSuperellipseInputBorder(
side: side ?? borderSide ?? this.side,
borderRadius: borderRadius ?? this.borderRadius,
);
}
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double? gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
final paint = borderSide.toPaint();
final outerPath = super.getOuterPath(rect, textDirection: textDirection);
if (gapExtent == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
canvas.drawPath(outerPath, paint);
return;
}
final extent = gapExtent + 5;
final start = gapStart ?? 2 - 2;
final gapRect = Rect.fromLTWH(
textDirection == TextDirection.rtl ? rect.width - start - extent : start,
rect.top - borderSide.width,
extent,
borderSide.width * 2,
);
final gapPath = Path()..addRect(gapRect);
final resultPath = Path.combine(
PathOperation.difference,
outerPath,
gapPath,
);
canvas.drawPath(resultPath, paint);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment