Last active
August 13, 2021 17:06
-
-
Save roipeker/9c21ecaee36ef3495cdfe5ba6da4343c to your computer and use it in GitHub Desktop.
concept for Gradient input border for TextFields
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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