Add StrokeAlign to Border (#102112)
This commit is contained in:
@@ -42,7 +42,14 @@ class BeveledRectangleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return EdgeInsets.all(side.width);
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsets.all(side.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsets.all(side.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -118,7 +125,21 @@ class BeveledRectangleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
|
||||
final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
}
|
||||
|
||||
return _getPath(adjustedRect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -134,7 +155,20 @@ class BeveledRectangleBorder extends OutlinedBorder {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final Path path = getOuterPath(rect, textDirection: textDirection)
|
||||
final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.inflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect.inflate(side.width);
|
||||
break;
|
||||
}
|
||||
final Path path = _getPath(adjustedRect)
|
||||
..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
|
||||
canvas.drawPath(path, side.toPaint());
|
||||
break;
|
||||
|
||||
@@ -21,6 +21,27 @@ enum BorderStyle {
|
||||
// if you add more, think about how they will lerp
|
||||
}
|
||||
|
||||
/// The relative position of the stroke on a [BorderSide] in a [Border] or [OutlinedBorder].
|
||||
/// When set to [inside], the stroke is drawn completely inside the widget.
|
||||
/// For [center] and [outside], a property such as [Container.clipBehavior]
|
||||
/// can be used in an outside widget to clip it.
|
||||
/// If [Container.decoration] has a border, the container may incorporate
|
||||
/// [BorderSide.width] as additional padding:
|
||||
/// - [inside] provides padding with full [BorderSide.width].
|
||||
/// - [center] provides padding with half [BorderSide.width].
|
||||
/// - [outside] provides zero padding, as stroke is drawn entirely outside.
|
||||
enum StrokeAlign {
|
||||
/// The border is drawn on the inside of the border path.
|
||||
inside,
|
||||
|
||||
/// The border is drawn on the center of the border path, with half of the
|
||||
/// [BorderSide.width] on the inside, and the other half on the outside of the path.
|
||||
center,
|
||||
|
||||
/// The border is drawn on the outside of the border path.
|
||||
outside,
|
||||
}
|
||||
|
||||
/// A side of a border of a box.
|
||||
///
|
||||
/// A [Border] consists of four [BorderSide] objects: [Border.top],
|
||||
@@ -66,6 +87,7 @@ class BorderSide {
|
||||
this.color = const Color(0xFF000000),
|
||||
this.width = 1.0,
|
||||
this.style = BorderStyle.solid,
|
||||
this.strokeAlign = StrokeAlign.inside,
|
||||
}) : assert(color != null),
|
||||
assert(width != null),
|
||||
assert(width >= 0.0),
|
||||
@@ -126,6 +148,9 @@ class BorderSide {
|
||||
/// A hairline black border that is not rendered.
|
||||
static const BorderSide none = BorderSide(width: 0.0, style: BorderStyle.none);
|
||||
|
||||
/// The direction of where the border will be drawn relative to the container.
|
||||
final StrokeAlign strokeAlign;
|
||||
|
||||
/// Creates a copy of this border but with the given fields replaced with the new values.
|
||||
BorderSide copyWith({
|
||||
Color? color,
|
||||
@@ -200,7 +225,8 @@ class BorderSide {
|
||||
(b.style == BorderStyle.none && b.width == 0.0))
|
||||
return true;
|
||||
return a.style == b.style
|
||||
&& a.color == b.color;
|
||||
&& a.color == b.color
|
||||
&& a.strokeAlign == b.strokeAlign;
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two border sides.
|
||||
@@ -219,14 +245,15 @@ class BorderSide {
|
||||
final double width = ui.lerpDouble(a.width, b.width, t)!;
|
||||
if (width < 0.0)
|
||||
return BorderSide.none;
|
||||
if (a.style == b.style) {
|
||||
if (a.style == b.style && a.strokeAlign == b.strokeAlign) {
|
||||
return BorderSide(
|
||||
color: Color.lerp(a.color, b.color, t)!,
|
||||
width: width,
|
||||
style: a.style, // == b.style
|
||||
strokeAlign: a.strokeAlign, // == b.strokeAlign
|
||||
);
|
||||
}
|
||||
Color colorA, colorB;
|
||||
final Color colorA, colorB;
|
||||
switch (a.style) {
|
||||
case BorderStyle.solid:
|
||||
colorA = a.color;
|
||||
@@ -243,9 +270,20 @@ class BorderSide {
|
||||
colorB = b.color.withAlpha(0x00);
|
||||
break;
|
||||
}
|
||||
if (a.strokeAlign != b.strokeAlign) {
|
||||
// When strokeAlign changes, lerp to 0, then from 0 to the target width.
|
||||
// All StrokeAlign values share a common zero width state.
|
||||
final StrokeAlign strokeAlign = t > 0.5 ? b.strokeAlign : a.strokeAlign;
|
||||
return BorderSide(
|
||||
color: Color.lerp(colorA, colorB, t)!,
|
||||
width: t > 0.5 ? ui.lerpDouble(0, b.width, t * 2 - 1)! : ui.lerpDouble(a.width, 0, t * 2)!,
|
||||
strokeAlign: strokeAlign,
|
||||
);
|
||||
}
|
||||
return BorderSide(
|
||||
color: Color.lerp(colorA, colorB, t)!,
|
||||
width: width,
|
||||
strokeAlign: a.strokeAlign, // == b.strokeAlign
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,14 +296,20 @@ class BorderSide {
|
||||
return other is BorderSide
|
||||
&& other.color == color
|
||||
&& other.width == width
|
||||
&& other.style == style;
|
||||
&& other.style == style
|
||||
&& other.strokeAlign == strokeAlign;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(color, width, style);
|
||||
int get hashCode => Object.hash(color, width, style, strokeAlign);
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'BorderSide')}($color, ${width.toStringAsFixed(1)}, $style)';
|
||||
String toString() {
|
||||
if (strokeAlign == StrokeAlign.inside) {
|
||||
return '${objectRuntimeType(this, 'BorderSide')}($color, ${width.toStringAsFixed(1)}, $style)';
|
||||
}
|
||||
return '${objectRuntimeType(this, 'BorderSide')}($color, ${width.toStringAsFixed(1)}, $style, $strokeAlign)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Base class for shape outlines.
|
||||
|
||||
@@ -210,16 +210,29 @@ abstract class BoxBorder extends ShapeBorder {
|
||||
assert(side.style != BorderStyle.none);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
final RRect outer = borderRadius.toRRect(rect);
|
||||
final double width = side.width;
|
||||
if (width == 0.0) {
|
||||
paint
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.0;
|
||||
canvas.drawRRect(outer, paint);
|
||||
canvas.drawRRect(borderRadius.toRRect(rect), paint);
|
||||
} else {
|
||||
final RRect inner = outer.deflate(width);
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
if (side.strokeAlign == StrokeAlign.inside) {
|
||||
final RRect outer = borderRadius.toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
} else {
|
||||
final Rect inner;
|
||||
final Rect outer;
|
||||
if (side.strokeAlign == StrokeAlign.center) {
|
||||
inner = rect.deflate(width / 2);
|
||||
outer = rect.inflate(width / 2);
|
||||
} else {
|
||||
inner = rect;
|
||||
outer = rect.inflate(width);
|
||||
}
|
||||
canvas.drawDRRect(borderRadius.toRRect(outer), borderRadius.toRRect(inner), paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +240,18 @@ abstract class BoxBorder extends ShapeBorder {
|
||||
assert(side.style != BorderStyle.none);
|
||||
final double width = side.width;
|
||||
final Paint paint = side.toPaint();
|
||||
final double radius = (rect.shortestSide - width) / 2.0;
|
||||
final double radius;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
radius = (rect.shortestSide - width) / 2.0;
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
radius = rect.shortestSide / 2.0;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
radius = (rect.shortestSide + width) / 2.0;
|
||||
break;
|
||||
}
|
||||
canvas.drawCircle(rect.center, radius, paint);
|
||||
}
|
||||
|
||||
@@ -235,7 +259,20 @@ abstract class BoxBorder extends ShapeBorder {
|
||||
assert(side.style != BorderStyle.none);
|
||||
final double width = side.width;
|
||||
final Paint paint = side.toPaint();
|
||||
canvas.drawRect(rect.deflate(width / 2.0), paint);
|
||||
final Rect rectToBeDrawn;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
rectToBeDrawn = rect.deflate(width / 2.0);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
rectToBeDrawn = rect;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
rectToBeDrawn = rect.inflate(width / 2.0);
|
||||
break;
|
||||
}
|
||||
|
||||
canvas.drawRect(rectToBeDrawn, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,8 +386,9 @@ class Border extends BoxBorder {
|
||||
Color color = const Color(0xFF000000),
|
||||
double width = 1.0,
|
||||
BorderStyle style = BorderStyle.solid,
|
||||
StrokeAlign strokeAlign = StrokeAlign.inside,
|
||||
}) {
|
||||
final BorderSide side = BorderSide(color: color, width: width, style: style);
|
||||
final BorderSide side = BorderSide(color: color, width: width, style: style, strokeAlign: strokeAlign);
|
||||
return Border.fromBorderSide(side);
|
||||
}
|
||||
|
||||
@@ -390,11 +428,21 @@ class Border extends BoxBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
if (isUniform) {
|
||||
switch (top.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsets.all(top.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsets.all(top.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isUniform => _colorIsUniform && _widthIsUniform && _styleIsUniform;
|
||||
bool get isUniform => _colorIsUniform && _widthIsUniform && _styleIsUniform && _strokeAlignIsUniform;
|
||||
|
||||
bool get _colorIsUniform {
|
||||
final Color topColor = top.color;
|
||||
@@ -411,6 +459,13 @@ class Border extends BoxBorder {
|
||||
return right.style == topStyle && bottom.style == topStyle && left.style == topStyle;
|
||||
}
|
||||
|
||||
bool get _strokeAlignIsUniform {
|
||||
final StrokeAlign topStrokeAlign = top.strokeAlign;
|
||||
return right.strokeAlign == topStrokeAlign
|
||||
&& bottom.strokeAlign == topStrokeAlign
|
||||
&& left.strokeAlign == topStrokeAlign;
|
||||
}
|
||||
|
||||
@override
|
||||
Border? add(ShapeBorder other, { bool reversed = false }) {
|
||||
if (other is Border &&
|
||||
@@ -526,6 +581,7 @@ class Border extends BoxBorder {
|
||||
if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
|
||||
if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
|
||||
if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
|
||||
if (!_strokeAlignIsUniform) ErrorDescription('BorderSide.strokeAlign'),
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
@@ -533,11 +589,20 @@ class Border extends BoxBorder {
|
||||
assert(() {
|
||||
if (shape != BoxShape.rectangle) {
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
ErrorSummary('A Border can only be drawn as a circle if it is uniform'),
|
||||
ErrorSummary('A Border can only be drawn as a circle if it is uniform.'),
|
||||
ErrorDescription('The following is not uniform:'),
|
||||
if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
|
||||
if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
|
||||
if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
|
||||
if (!_strokeAlignIsUniform) ErrorDescription('BorderSide.strokeAlign'),
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
assert(() {
|
||||
if (!_strokeAlignIsUniform || top.strokeAlign != StrokeAlign.inside) {
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
ErrorSummary('A Border can only draw strokeAlign different than StrokeAlign.inside on uniform borders.'),
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
@@ -665,6 +730,16 @@ class BorderDirectional extends BoxBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
if (isUniform) {
|
||||
switch (top.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsetsDirectional.all(top.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsetsDirectional.all(top.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsetsDirectional.zero;
|
||||
}
|
||||
}
|
||||
return EdgeInsetsDirectional.fromSTEB(start.width, top.width, end.width, bottom.width);
|
||||
}
|
||||
|
||||
@@ -688,9 +763,19 @@ class BorderDirectional extends BoxBorder {
|
||||
bottom.style != topStyle)
|
||||
return false;
|
||||
|
||||
if (_strokeAlignIsUniform == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get _strokeAlignIsUniform {
|
||||
final StrokeAlign topStrokeAlign = top.strokeAlign;
|
||||
return start.strokeAlign == topStrokeAlign
|
||||
&& bottom.strokeAlign == topStrokeAlign
|
||||
&& end.strokeAlign == topStrokeAlign;
|
||||
}
|
||||
|
||||
@override
|
||||
BoxBorder? add(ShapeBorder other, { bool reversed = false }) {
|
||||
if (other is BorderDirectional) {
|
||||
@@ -834,8 +919,9 @@ class BorderDirectional extends BoxBorder {
|
||||
|
||||
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.');
|
||||
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.');
|
||||
assert(_strokeAlignIsUniform && top.strokeAlign == StrokeAlign.inside, 'A Border can only draw strokeAlign different than StrokeAlign.inside on uniform borders.');
|
||||
|
||||
BorderSide left, right;
|
||||
final BorderSide left, right;
|
||||
assert(textDirection != null, 'Non-uniform BorderDirectional objects require a TextDirection when painting.');
|
||||
switch (textDirection!) {
|
||||
case TextDirection.rtl:
|
||||
|
||||
@@ -31,7 +31,14 @@ class CircleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return EdgeInsets.all(side.width);
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsets.all(side.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsets.all(side.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -53,10 +60,23 @@ class CircleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
final double radius = rect.shortestSide / 2.0;
|
||||
final double adjustedRadius;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRadius = radius - side.width;
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRadius = radius - side.width / 2.0;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRadius = radius;
|
||||
break;
|
||||
}
|
||||
return Path()
|
||||
..addOval(Rect.fromCircle(
|
||||
center: rect.center,
|
||||
radius: math.max(0.0, rect.shortestSide / 2.0 - side.width),
|
||||
radius: math.max(0.0, adjustedRadius),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -80,7 +100,19 @@ class CircleBorder extends OutlinedBorder {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
canvas.drawCircle(rect.center, (rect.shortestSide - side.width) / 2.0, side.toPaint());
|
||||
final double radius;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
radius = (rect.shortestSide - side.width) / 2.0;
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
radius = rect.shortestSide / 2.0;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
radius = (rect.shortestSide + side.width) / 2.0;
|
||||
break;
|
||||
}
|
||||
canvas.drawCircle(rect.center, radius, side.toPaint());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,14 @@ class RoundedRectangleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return EdgeInsets.all(side.width);
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsets.all(side.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsets.all(side.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -100,8 +107,21 @@ class RoundedRectangleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
}
|
||||
return Path()
|
||||
..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
|
||||
..addRRect(adjustedRect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -120,12 +140,26 @@ class RoundedRectangleBorder extends OutlinedBorder {
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
if (side.strokeAlign == StrokeAlign.inside) {
|
||||
final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
} else {
|
||||
final Rect inner;
|
||||
final Rect outer;
|
||||
if (side.strokeAlign == StrokeAlign.center) {
|
||||
inner = rect.deflate(width / 2);
|
||||
outer = rect.inflate(width / 2);
|
||||
} else {
|
||||
inner = rect;
|
||||
outer = rect.inflate(width);
|
||||
}
|
||||
final BorderRadius borderRadiusResolved = borderRadius.resolve(textDirection);
|
||||
canvas.drawDRRect(borderRadiusResolved.toRRect(outer), borderRadiusResolved.toRRect(inner), paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,8 +292,21 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
final RRect borderRect = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
}
|
||||
return Path()
|
||||
..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)).deflate(side.width));
|
||||
..addRRect(adjustedRect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -287,11 +334,20 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
final RRect borderRect = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(width / 2);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect.inflate(width / 2);
|
||||
break;
|
||||
}
|
||||
canvas.drawRRect(adjustedRect, side.toPaint());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,14 @@ class StadiumBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return EdgeInsets.all(side.width);
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
return EdgeInsets.all(side.width);
|
||||
case StrokeAlign.center:
|
||||
return EdgeInsets.all(side.width / 2);
|
||||
case StrokeAlign.outside:
|
||||
return EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -88,8 +95,21 @@ class StadiumBorder extends OutlinedBorder {
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
final Radius radius = Radius.circular(rect.shortestSide / 2.0);
|
||||
final RRect borderRect = RRect.fromRectAndRadius(rect, radius);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
}
|
||||
return Path()
|
||||
..addRRect(RRect.fromRectAndRadius(rect, radius).deflate(side.width));
|
||||
..addRRect(adjustedRect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -106,8 +126,21 @@ class StadiumBorder extends OutlinedBorder {
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final Radius radius = Radius.circular(rect.shortestSide / 2.0);
|
||||
final RRect borderRect = RRect.fromRectAndRadius(rect, radius);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect.inflate(side.width /2);
|
||||
break;
|
||||
}
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, radius).deflate(side.width / 2.0),
|
||||
adjustedRect,
|
||||
side.toPaint(),
|
||||
);
|
||||
}
|
||||
@@ -257,11 +290,20 @@ class _StadiumToCircleBorder extends OutlinedBorder {
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = _adjustBorderRadius(rect).toRRect(_adjustRect(rect));
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
final RRect borderRect = _adjustBorderRadius(rect).toRRect(_adjustRect(rect));
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(width / 2);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect.inflate(width / 2);
|
||||
break;
|
||||
}
|
||||
canvas.drawRRect(adjustedRect, side.toPaint());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,8 +419,21 @@ class _StadiumToRoundedRectangleBorder extends OutlinedBorder {
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
|
||||
final RRect borderRect = _adjustBorderRadius(rect).toRRect(rect);
|
||||
final RRect adjustedRect;
|
||||
switch (side.strokeAlign) {
|
||||
case StrokeAlign.inside:
|
||||
adjustedRect = borderRect.deflate(side.width);
|
||||
break;
|
||||
case StrokeAlign.center:
|
||||
adjustedRect = borderRect.deflate(side.width / 2);
|
||||
break;
|
||||
case StrokeAlign.outside:
|
||||
adjustedRect = borderRect;
|
||||
break;
|
||||
}
|
||||
return Path()
|
||||
..addRRect(_adjustBorderRadius(rect).toRRect(rect).deflate(side.width));
|
||||
..addRRect(adjustedRect);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -406,11 +461,21 @@ class _StadiumToRoundedRectangleBorder extends OutlinedBorder {
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(_adjustBorderRadius(rect).toRRect(rect), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = _adjustBorderRadius(rect).toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
if (side.strokeAlign == StrokeAlign.inside) {
|
||||
final RRect outer = _adjustBorderRadius(rect).toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
} else {
|
||||
final RRect outer;
|
||||
if (side.strokeAlign == StrokeAlign.center) {
|
||||
outer = _adjustBorderRadius(rect).toRRect(rect);
|
||||
} else {
|
||||
outer = _adjustBorderRadius(rect.inflate(width)).toRRect(rect.inflate(width / 2));
|
||||
}
|
||||
canvas.drawRRect(outer, side.toPaint());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +106,29 @@ void main() {
|
||||
expect(border.getOuterPath(rect,textDirection: TextDirection.rtl), looksLikeRectRtl);
|
||||
expect(border.getInnerPath(rect,textDirection: TextDirection.rtl), looksLikeRectRtl);
|
||||
});
|
||||
|
||||
test('BeveledRectangleBorder with StrokeAlign', () {
|
||||
const BorderRadius borderRadius = BorderRadius.all(Radius.circular(10));
|
||||
const BeveledRectangleBorder inside = BeveledRectangleBorder(side: BorderSide(width: 10.0), borderRadius: borderRadius);
|
||||
const BeveledRectangleBorder center = BeveledRectangleBorder(side: BorderSide(width: 10.0, strokeAlign: StrokeAlign.center), borderRadius: borderRadius);
|
||||
const BeveledRectangleBorder outside = BeveledRectangleBorder(side: BorderSide(width: 10.0, strokeAlign: StrokeAlign.outside), borderRadius: borderRadius);
|
||||
expect(inside.dimensions, const EdgeInsets.all(10.0));
|
||||
expect(center.dimensions, const EdgeInsets.all(5.0));
|
||||
expect(outside.dimensions, EdgeInsets.zero);
|
||||
|
||||
const Rect rect = Rect.fromLTWH(0.0, 0.0, 120.0, 40.0);
|
||||
|
||||
expect(inside.getInnerPath(rect), isPathThat(
|
||||
includes: const <Offset>[ Offset(10, 20), Offset(100, 10), Offset(50, 30), Offset(50, 20) ],
|
||||
excludes: const <Offset>[ Offset(9, 9), Offset(100, 0), Offset(110, 31), Offset(9, 31) ],
|
||||
));
|
||||
expect(center.getInnerPath(rect), isPathThat(
|
||||
includes: const <Offset>[ Offset(9, 9), Offset(100, 10), Offset(110, 31), Offset(9, 31) ],
|
||||
excludes: const <Offset>[ Offset(4, 4), Offset(100, 0), Offset(116, 31), Offset(4, 31) ],
|
||||
));
|
||||
expect(outside.getInnerPath(rect), isPathThat(
|
||||
includes: const <Offset>[ Offset(5, 5), Offset(110, 0), Offset(116, 31), Offset(4, 31) ],
|
||||
excludes: const <Offset>[ Offset.zero, Offset(120, 0), Offset(120, 31), Offset(0, 31) ],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,4 +122,18 @@ void main() {
|
||||
'BorderSide(Color(0xffaabbcc), 1.2, BorderStyle.solid)',
|
||||
);
|
||||
});
|
||||
|
||||
test('BorderSide - lerp with strokeAlign', () {
|
||||
const BorderSide side0 = BorderSide(width: 2.0, strokeAlign: StrokeAlign.center);
|
||||
const BorderSide side1 = BorderSide(width: 2.0, strokeAlign: StrokeAlign.outside);
|
||||
expect(BorderSide.lerp(side0, side1, 0), const BorderSide(width: 2.0, strokeAlign: StrokeAlign.center));
|
||||
expect(BorderSide.lerp(side0, side1, 0.5), const BorderSide(width: 0.0, strokeAlign: StrokeAlign.center));
|
||||
expect(BorderSide.lerp(side0, side1, 1), const BorderSide(width: 2.0, strokeAlign: StrokeAlign.outside));
|
||||
|
||||
const BorderSide side2 = BorderSide(width: 2.0);
|
||||
const BorderSide side3 = BorderSide(width: 2.0, strokeAlign: StrokeAlign.center);
|
||||
expect(BorderSide.lerp(side2, side3, 0), const BorderSide(width: 2.0));
|
||||
expect(BorderSide.lerp(side2, side3, 0.5), const BorderSide(width: 0.0));
|
||||
expect(BorderSide.lerp(side2, side3, 1), const BorderSide(width: 2.0, strokeAlign: StrokeAlign.center));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,9 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart' show FlutterError;
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class TestCanvas implements Canvas {
|
||||
final List<Invocation> invocations = <Invocation>[];
|
||||
|
||||
@override
|
||||
void noSuchMethod(Invocation invocation) {
|
||||
invocations.add(invocation);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('Border.uniform constructor', () {
|
||||
const BorderSide side = BorderSide();
|
||||
@@ -189,6 +199,14 @@ void main() {
|
||||
).isUniform,
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
const Border(
|
||||
left: BorderSide(),
|
||||
top: BorderSide(strokeAlign: StrokeAlign.center),
|
||||
right: BorderSide(strokeAlign: StrokeAlign.outside),
|
||||
).isUniform,
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
const Border().isUniform,
|
||||
true,
|
||||
@@ -238,4 +256,48 @@ void main() {
|
||||
expect(Border.lerp(null, visualWithTop10, 2.0), const Border(top: BorderSide(width: 20.0)));
|
||||
expect(Border.lerp(at0, at100, 2.0), at200);
|
||||
});
|
||||
|
||||
test('Border - throws correct exception with strokeAlign', () {
|
||||
late FlutterError error;
|
||||
try {
|
||||
final TestCanvas canvas = TestCanvas();
|
||||
// Border.all supports all StrokeAlign values.
|
||||
// Border() supports StrokeAlign.inside only.
|
||||
const Border(
|
||||
left: BorderSide(strokeAlign: StrokeAlign.center),
|
||||
right: BorderSide(strokeAlign: StrokeAlign.outside),
|
||||
).paint(canvas, const Rect.fromLTWH(10.0, 20.0, 30.0, 40.0));
|
||||
} on FlutterError catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error, isNotNull);
|
||||
expect(error.diagnostics.length, 1);
|
||||
expect(
|
||||
error.diagnostics[0].toStringDeep(),
|
||||
'A Border can only draw strokeAlign different than\nStrokeAlign.inside on uniform borders.\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('Border.dimension', () {
|
||||
final Border insideBorder = Border.all(width: 10);
|
||||
expect(insideBorder.dimensions, const EdgeInsets.all(10));
|
||||
|
||||
final Border centerBorder = Border.all(width: 10, strokeAlign: StrokeAlign.center);
|
||||
expect(centerBorder.dimensions, const EdgeInsets.all(5));
|
||||
|
||||
final Border outsideBorder = Border.all(width: 10, strokeAlign: StrokeAlign.outside);
|
||||
expect(outsideBorder.dimensions, EdgeInsets.zero);
|
||||
|
||||
const BorderSide insideSide = BorderSide(width: 10);
|
||||
const BorderDirectional insideBorderDirectional = BorderDirectional(top: insideSide, bottom: insideSide, start: insideSide, end: insideSide);
|
||||
expect(insideBorderDirectional.dimensions, const EdgeInsetsDirectional.all(10));
|
||||
|
||||
const BorderSide centerSide = BorderSide(width: 10, strokeAlign: StrokeAlign.center);
|
||||
const BorderDirectional centerBorderDirectional = BorderDirectional(top: centerSide, bottom: centerSide, start: centerSide, end: centerSide);
|
||||
expect(centerBorderDirectional.dimensions, const EdgeInsetsDirectional.all(5));
|
||||
|
||||
const BorderSide outsideSide = BorderSide(width: 10, strokeAlign: StrokeAlign.outside);
|
||||
const BorderDirectional outsideBorderDirectional = BorderDirectional(top: outsideSide, bottom: outsideSide, start: outsideSide, end: outsideSide);
|
||||
expect(outsideBorderDirectional.dimensions, EdgeInsetsDirectional.zero);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,4 +130,24 @@ void main() {
|
||||
expect(direct50.hashCode, indirect50.hashCode);
|
||||
expect(direct50.toString(), indirect50.toString());
|
||||
});
|
||||
|
||||
test('RoundedRectangleBorder.dimensions and CircleBorder.dimensions', () {
|
||||
const RoundedRectangleBorder insideRoundedRectangleBorder = RoundedRectangleBorder(side: BorderSide(width: 10));
|
||||
expect(insideRoundedRectangleBorder.dimensions, const EdgeInsets.all(10));
|
||||
|
||||
const RoundedRectangleBorder centerRoundedRectangleBorder = RoundedRectangleBorder(side: BorderSide(width: 10, strokeAlign: StrokeAlign.center));
|
||||
expect(centerRoundedRectangleBorder.dimensions, const EdgeInsets.all(5));
|
||||
|
||||
const RoundedRectangleBorder outsideRoundedRectangleBorder = RoundedRectangleBorder(side: BorderSide(width: 10, strokeAlign: StrokeAlign.outside));
|
||||
expect(outsideRoundedRectangleBorder.dimensions, EdgeInsets.zero);
|
||||
|
||||
const CircleBorder insideCircleBorder = CircleBorder(side: BorderSide(width: 10));
|
||||
expect(insideCircleBorder.dimensions, const EdgeInsets.all(10));
|
||||
|
||||
const CircleBorder centerCircleBorder = CircleBorder(side: BorderSide(width: 10, strokeAlign: StrokeAlign.center));
|
||||
expect(centerCircleBorder.dimensions, const EdgeInsets.all(5));
|
||||
|
||||
const CircleBorder outsideCircleBorder = CircleBorder(side: BorderSide(width: 10, strokeAlign: StrokeAlign.outside));
|
||||
expect(outsideCircleBorder.dimensions, EdgeInsets.zero);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,6 +47,33 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('StadiumBorder with StrokeAlign', () {
|
||||
const StadiumBorder center = StadiumBorder(side: BorderSide(width: 10.0, strokeAlign: StrokeAlign.center));
|
||||
const StadiumBorder outside = StadiumBorder(side: BorderSide(width: 10.0, strokeAlign: StrokeAlign.outside));
|
||||
expect(center.dimensions, const EdgeInsets.all(5.0));
|
||||
expect(outside.dimensions, EdgeInsets.zero);
|
||||
|
||||
const Rect rect = Rect.fromLTRB(10.0, 20.0, 100.0, 200.0);
|
||||
|
||||
expect(
|
||||
(Canvas canvas) => center.paint(canvas, rect),
|
||||
paints
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(rect, Radius.circular(rect.shortestSide / 2.0)),
|
||||
strokeWidth: 10.0,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
(Canvas canvas) => outside.paint(canvas, rect),
|
||||
paints
|
||||
..rrect(
|
||||
rrect: RRect.fromRectAndRadius(rect, Radius.circular(rect.shortestSide / 2.0)).inflate(5.0),
|
||||
strokeWidth: 10.0,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('StadiumBorder and CircleBorder', () {
|
||||
const StadiumBorder stadium = StadiumBorder();
|
||||
const CircleBorder circle = CircleBorder();
|
||||
|
||||
Reference in New Issue
Block a user