Unverified Commit 440e0e2c authored by Bernardo Ferrari's avatar Bernardo Ferrari Committed by GitHub

Add StrokeAlign to Border (#102112)

parent 893b58db
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment