Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active August 13, 2021 17:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roipeker/9c21ecaee36ef3495cdfe5ba6da4343c to your computer and use it in GitHub Desktop.
Save roipeker/9c21ecaee36ef3495cdfe5ba6da4343c to your computer and use it in GitHub Desktop.
concept for Gradient input border for TextFields
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
/// Gradient Shader in outline input borders.
/// Doesn't merge solid color BorderSide and GradientBorderSide.
class OutlineInputBorderShader extends OutlineInputBorder {
const OutlineInputBorderShader({
GradientBorderSide borderSide = const GradientBorderSide(),
BorderRadius borderRadius = const BorderRadius.all(Radius.circular(4.0)),
double gapPadding = 4.0,
}) : super(
borderSide: borderSide,
borderRadius: borderRadius,
gapPadding: gapPadding,
);
Path _gapBorderPath(
Canvas canvas, RRect center, double start, double extent) {
// When the corner radii on any side add up to be greater than the
// given height, each radius has to be scaled to not exceed the
// size of the width/height of the RRect.
final RRect scaledRRect = center.scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.top,
scaledRRect.tlRadiusX * 2.0,
scaledRRect.tlRadiusY * 2.0,
);
final Rect trCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.trRadiusX * 2.0,
scaledRRect.top,
scaledRRect.trRadiusX * 2.0,
scaledRRect.trRadiusY * 2.0,
);
final Rect brCorner = Rect.fromLTWH(
scaledRRect.right - scaledRRect.brRadiusX * 2.0,
scaledRRect.bottom - scaledRRect.brRadiusY * 2.0,
scaledRRect.brRadiusX * 2.0,
scaledRRect.brRadiusY * 2.0,
);
final Rect blCorner = Rect.fromLTWH(
scaledRRect.left,
scaledRRect.bottom - scaledRRect.blRadiusY * 2.0,
scaledRRect.blRadiusX * 2.0,
scaledRRect.blRadiusX * 2.0,
);
const double cornerArcSweep = math.pi / 2.0;
final double tlCornerArcSweep = start < scaledRRect.tlRadiusX
? math.asin((start / scaledRRect.tlRadiusX).clamp(-1.0, 1.0))
: math.pi / 2.0;
final Path path = Path()
..addArc(tlCorner, math.pi, tlCornerArcSweep)
..moveTo(scaledRRect.left + scaledRRect.tlRadiusX, scaledRRect.top);
if (start > scaledRRect.tlRadiusX)
path.lineTo(scaledRRect.left + start, scaledRRect.top);
const double trCornerArcStart = (3 * math.pi) / 2.0;
const double trCornerArcSweep = cornerArcSweep;
if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) {
path
..relativeMoveTo(extent, 0.0)
..lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top)
..addArc(trCorner, trCornerArcStart, trCornerArcSweep);
} else if (start + extent < scaledRRect.width) {
final double dx = scaledRRect.width - (start + extent);
final double sweep = math.acos(dx / scaledRRect.trRadiusX);
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
}
return path
..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY)
..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY)
..addArc(brCorner, 0.0, cornerArcSweep)
..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom)
..addArc(blCorner, math.pi / 2.0, cornerArcSweep)
..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY);
}
Paint _resolvePaint(Rect rect) {
return (borderSide as GradientBorderSide).toShaderPaint(rect);
// if (borderSide is GradientBorderSide) {
// return (borderSide as GradientBorderSide).toShaderPaint(rect);
// } else {
// return borderSide.toPaint();
// }
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is OutlineInputBorderShader) {
final OutlineInputBorderShader outline = a;
return OutlineInputBorderShader(
borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t)!,
borderSide: GradientBorderSide.lerp(
outline.borderSide as GradientBorderSide,
borderSide as GradientBorderSide,
t,
),
gapPadding: outline.gapPadding,
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is OutlineInputBorderShader) {
final OutlineInputBorderShader outline = b;
return OutlineInputBorderShader(
borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t)!,
borderSide: GradientBorderSide.lerp(
borderSide as GradientBorderSide,
outline.borderSide as GradientBorderSide,
t,
),
gapPadding: outline.gapPadding,
);
}
return super.lerpTo(b, t);
}
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
final RRect outer = borderRadius.toRRect(rect);
final RRect center = outer.deflate(borderSide.width / 2.0);
if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
canvas.drawRRect(center, _resolvePaint(center.outerRect));
} else {
final double extent =
lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!;
switch (textDirection!) {
case TextDirection.rtl:
final Path path = _gapBorderPath(canvas, center,
math.max(0.0, gapStart + gapPadding - extent), extent);
canvas.drawPath(path, _resolvePaint(path.getBounds()));
break;
case TextDirection.ltr:
final Path path = _gapBorderPath(
canvas, center, math.max(0.0, gapStart - gapPadding), extent);
canvas.drawPath(path, _resolvePaint(path.getBounds()));
break;
}
}
}
}
/// Maybe can change implementation to take ui.Image as well to make the
/// shader generic.
class GradientBorderSide extends BorderSide {
static const GradientBorderSide none =
GradientBorderSide(width: 0.0, style: BorderStyle.none);
final Gradient gradient;
const GradientBorderSide({
this.gradient = const LinearGradient(colors: [Color(0x0)]),
double width = 1.0,
BorderStyle style = BorderStyle.solid,
}) : super(style: style, width: width);
Paint toShaderPaint(Rect rect) {
final paint = Paint();
if (style == BorderStyle.none) {
return paint;
}
paint.shader = gradient.createShader(rect);
paint.style = PaintingStyle.stroke;
paint.strokeWidth = width;
return paint;
}
/// Linearly interpolate between two border sides.
///
/// The arguments must not be null.
///
/// {@macro dart.ui.shadow.lerp}
static GradientBorderSide lerp(
GradientBorderSide a, GradientBorderSide b, double t) {
if (t == 0.0) return a;
if (t == 1.0) return b;
final double width = lerpDouble(a.width, b.width, t)!;
if (width < 0.0) return GradientBorderSide.none;
if (a.style == b.style) {
final grad = a.gradient.lerpTo(b.gradient, t)!;
return GradientBorderSide(
gradient: grad,
width: width,
style: a.style, // == b.style
);
}
return a;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment