Skip to content

Commit 5054b6e

Browse files
authored
Add Non-Uniform Border to Border. (flutter#121921)
Add Non-Uniform Border to Border.
1 parent 59c9d4e commit 5054b6e

File tree

3 files changed

+245
-56
lines changed

3 files changed

+245
-56
lines changed

packages/flutter/lib/src/painting/box_border.dart

Lines changed: 137 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ abstract class BoxBorder extends ShapeBorder {
212212
///
213213
/// See also:
214214
///
215-
/// * [paintBorder], which is used if the border is not uniform.
215+
/// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius.
216+
/// * [Border.paint], similar to this method, includes additional comments
217+
/// and provides more details on each parameter than described here.
216218
@override
217219
void paint(
218220
Canvas canvas,
@@ -240,6 +242,62 @@ abstract class BoxBorder extends ShapeBorder {
240242
}
241243
}
242244

245+
static void _paintNonUniformBorder(
246+
Canvas canvas,
247+
Rect rect, {
248+
required BorderRadius? borderRadius,
249+
required BoxShape shape,
250+
required TextDirection? textDirection,
251+
required BorderSide left,
252+
required BorderSide top,
253+
required BorderSide right,
254+
required BorderSide bottom,
255+
}) {
256+
final RRect borderRect;
257+
switch(shape) {
258+
case BoxShape.rectangle:
259+
borderRect = (borderRadius ?? BorderRadius.zero)
260+
.resolve(textDirection)
261+
.toRRect(rect);
262+
case BoxShape.circle:
263+
assert(borderRadius == null, 'A borderRadius cannot be given when shape is a BoxShape.circle.');
264+
borderRect = RRect.fromRectAndRadius(
265+
Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0),
266+
Radius.circular(rect.width),
267+
);
268+
}
269+
final Paint paint = Paint()..color = top.color;
270+
final RRect inner = _deflateRRect(borderRect, EdgeInsets.fromLTRB(left.strokeInset, top.strokeInset, right.strokeInset, bottom.strokeInset));
271+
final RRect outer = _inflateRRect(borderRect, EdgeInsets.fromLTRB(left.strokeOutset, top.strokeOutset, right.strokeOutset, bottom.strokeOutset));
272+
canvas.drawDRRect(outer, inner, paint);
273+
}
274+
275+
static RRect _inflateRRect(RRect rect, EdgeInsets insets) {
276+
return RRect.fromLTRBAndCorners(
277+
rect.left - insets.left,
278+
rect.top - insets.top,
279+
rect.right + insets.right,
280+
rect.bottom + insets.bottom,
281+
topLeft: (rect.tlRadius + Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
282+
topRight: (rect.trRadius + Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
283+
bottomRight: (rect.brRadius + Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
284+
bottomLeft: (rect.blRadius + Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
285+
);
286+
}
287+
288+
static RRect _deflateRRect(RRect rect, EdgeInsets insets) {
289+
return RRect.fromLTRBAndCorners(
290+
rect.left + insets.left,
291+
rect.top + insets.top,
292+
rect.right - insets.right,
293+
rect.bottom - insets.bottom,
294+
topLeft: (rect.tlRadius - Radius.elliptical(insets.left, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
295+
topRight: (rect.trRadius - Radius.elliptical(insets.right, insets.top)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
296+
bottomRight: (rect.brRadius - Radius.elliptical(insets.right, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
297+
bottomLeft:(rect.blRadius - Radius.elliptical(insets.left, insets.bottom)).clamp(minimum: Radius.zero), // ignore_clamp_double_lint
298+
);
299+
}
300+
243301
static void _paintUniformBorderWithCircle(Canvas canvas, Rect rect, BorderSide side) {
244302
assert(side.style != BorderStyle.none);
245303
final double radius = (rect.shortestSide + side.strokeOffset) / 2;
@@ -313,6 +371,10 @@ abstract class BoxBorder extends ShapeBorder {
313371
/// * [BorderSide], which is used to describe each side of the box.
314372
/// * [Theme], from the material layer, which can be queried to obtain appropriate colors
315373
/// to use for borders in a [MaterialApp], as shown in the "divider" sample above.
374+
/// * [paint], which explains the behavior of [BoxDecoration] parameters.
375+
/// * <https://pub.dev/packages/non_uniform_border>, a package that implements
376+
/// a Non-Uniform Border on ShapeBorder, which is used by Material Design
377+
/// buttons and other widgets, under the "shape" field.
316378
class Border extends BoxBorder {
317379
/// Creates a border.
318380
///
@@ -407,24 +469,24 @@ class Border extends BoxBorder {
407469

408470
bool get _colorIsUniform {
409471
final Color topColor = top.color;
410-
return right.color == topColor && bottom.color == topColor && left.color == topColor;
472+
return left.color == topColor && bottom.color == topColor && right.color == topColor;
411473
}
412474

413475
bool get _widthIsUniform {
414476
final double topWidth = top.width;
415-
return right.width == topWidth && bottom.width == topWidth && left.width == topWidth;
477+
return left.width == topWidth && bottom.width == topWidth && right.width == topWidth;
416478
}
417479

418480
bool get _styleIsUniform {
419481
final BorderStyle topStyle = top.style;
420-
return right.style == topStyle && bottom.style == topStyle && left.style == topStyle;
482+
return left.style == topStyle && bottom.style == topStyle && right.style == topStyle;
421483
}
422484

423485
bool get _strokeAlignIsUniform {
424486
final double topStrokeAlign = top.strokeAlign;
425-
return right.strokeAlign == topStrokeAlign
487+
return left.strokeAlign == topStrokeAlign
426488
&& bottom.strokeAlign == topStrokeAlign
427-
&& left.strokeAlign == topStrokeAlign;
489+
&& right.strokeAlign == topStrokeAlign;
428490
}
429491

430492
@override
@@ -491,14 +553,16 @@ class Border extends BoxBorder {
491553

492554
/// Paints the border within the given [Rect] on the given [Canvas].
493555
///
494-
/// Uniform borders are more efficient to paint than more complex borders.
556+
/// Uniform borders and non-uniform borders with similar colors and styles
557+
/// are more efficient to paint than more complex borders.
495558
///
496559
/// You can provide a [BoxShape] to draw the border on. If the `shape` in
497-
/// [BoxShape.circle], there is the requirement that the border [isUniform].
560+
/// [BoxShape.circle], there is the requirement that the border has uniform
561+
/// color and style.
498562
///
499563
/// If you specify a rectangular box shape ([BoxShape.rectangle]), then you
500564
/// may specify a [BorderRadius]. If a `borderRadius` is specified, there is
501-
/// the requirement that the border [isUniform].
565+
/// the requirement that the border has uniform color and style.
502566
///
503567
/// The [getInnerPath] and [getOuterPath] methods do not know about the
504568
/// `shape` and `borderRadius` arguments.
@@ -507,7 +571,10 @@ class Border extends BoxBorder {
507571
///
508572
/// See also:
509573
///
510-
/// * [paintBorder], which is used if the border is not uniform.
574+
/// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius.
575+
/// * <https://pub.dev/packages/non_uniform_border>, a package that implements
576+
/// a Non-Uniform Border on ShapeBorder, which is used by Material Design
577+
/// buttons and other widgets, under the "shape" field.
511578
@override
512579
void paint(
513580
Canvas canvas,
@@ -523,7 +590,7 @@ class Border extends BoxBorder {
523590
case BorderStyle.solid:
524591
switch (shape) {
525592
case BoxShape.circle:
526-
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
593+
assert(borderRadius == null, 'A borderRadius cannot be given when shape is a BoxShape.circle.');
527594
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
528595
case BoxShape.rectangle:
529596
if (borderRadius != null && borderRadius != BorderRadius.zero) {
@@ -536,36 +603,50 @@ class Border extends BoxBorder {
536603
}
537604
}
538605

606+
// Allow painting non-uniform borders if the color and style are uniform.
607+
if (_colorIsUniform && _styleIsUniform) {
608+
switch(top.style) {
609+
case BorderStyle.none:
610+
return;
611+
case BorderStyle.solid:
612+
BoxBorder._paintNonUniformBorder(canvas, rect,
613+
shape: shape,
614+
borderRadius: borderRadius,
615+
textDirection: textDirection,
616+
left: left,
617+
top: top,
618+
right: right,
619+
bottom: bottom);
620+
return;
621+
}
622+
}
623+
539624
assert(() {
540625
if (borderRadius != null) {
541626
throw FlutterError.fromParts(<DiagnosticsNode>[
542-
ErrorSummary('A borderRadius can only be given for a uniform Border.'),
627+
ErrorSummary('A borderRadius can only be given on borders with uniform colors and styles.'),
543628
ErrorDescription('The following is not uniform:'),
544629
if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
545-
if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
546630
if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
547-
if (!_strokeAlignIsUniform) ErrorDescription('BorderSide.strokeAlign'),
548631
]);
549632
}
550633
return true;
551634
}());
552635
assert(() {
553636
if (shape != BoxShape.rectangle) {
554637
throw FlutterError.fromParts(<DiagnosticsNode>[
555-
ErrorSummary('A Border can only be drawn as a circle if it is uniform.'),
638+
ErrorSummary('A Border can only be drawn as a circle on borders with uniform colors and styles.'),
556639
ErrorDescription('The following is not uniform:'),
557640
if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
558-
if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
559641
if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
560-
if (!_strokeAlignIsUniform) ErrorDescription('BorderSide.strokeAlign'),
561642
]);
562643
}
563644
return true;
564645
}());
565646
assert(() {
566647
if (!_strokeAlignIsUniform || top.strokeAlign != BorderSide.strokeAlignInside) {
567648
throw FlutterError.fromParts(<DiagnosticsNode>[
568-
ErrorSummary('A Border can only draw strokeAlign different than BorderSide.strokeAlignInside on uniform borders.'),
649+
ErrorSummary('A Border can only draw strokeAlign different than BorderSide.strokeAlignInside on borders with uniform colors and styles.'),
569650
]);
570651
}
571652
return true;
@@ -626,6 +707,9 @@ class Border extends BoxBorder {
626707
/// * [BorderSide], which is used to describe each side of the box.
627708
/// * [Theme], from the material layer, which can be queried to obtain appropriate colors
628709
/// to use for borders in a [MaterialApp], as shown in the "divider" sample above.
710+
/// * <https://pub.dev/packages/non_uniform_border>, a package that implements
711+
/// a Non-Uniform Border on ShapeBorder, which is used by Material Design
712+
/// buttons and other widgets, under the "shape" field.
629713
class BorderDirectional extends BoxBorder {
630714
/// Creates a border.
631715
///
@@ -698,33 +782,21 @@ class BorderDirectional extends BoxBorder {
698782
}
699783

700784
@override
701-
bool get isUniform {
785+
bool get isUniform => _colorIsUniform && _widthIsUniform && _styleIsUniform && _strokeAlignIsUniform;
786+
787+
bool get _colorIsUniform {
702788
final Color topColor = top.color;
703-
if (start.color != topColor ||
704-
end.color != topColor ||
705-
bottom.color != topColor) {
706-
return false;
707-
}
789+
return start.color == topColor && bottom.color == topColor && end.color == topColor;
790+
}
708791

792+
bool get _widthIsUniform {
709793
final double topWidth = top.width;
710-
if (start.width != topWidth ||
711-
end.width != topWidth ||
712-
bottom.width != topWidth) {
713-
return false;
714-
}
794+
return start.width == topWidth && bottom.width == topWidth && end.width == topWidth;
795+
}
715796

797+
bool get _styleIsUniform {
716798
final BorderStyle topStyle = top.style;
717-
if (start.style != topStyle ||
718-
end.style != topStyle ||
719-
bottom.style != topStyle) {
720-
return false;
721-
}
722-
723-
if (_strokeAlignIsUniform == false) {
724-
return false;
725-
}
726-
727-
return true;
799+
return start.style == topStyle && bottom.style == topStyle && end.style == topStyle;
728800
}
729801

730802
bool get _strokeAlignIsUniform {
@@ -850,7 +922,7 @@ class BorderDirectional extends BoxBorder {
850922
///
851923
/// See also:
852924
///
853-
/// * [paintBorder], which is used if the border is not uniform.
925+
/// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius.
854926
@override
855927
void paint(
856928
Canvas canvas,
@@ -866,7 +938,7 @@ class BorderDirectional extends BoxBorder {
866938
case BorderStyle.solid:
867939
switch (shape) {
868940
case BoxShape.circle:
869-
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
941+
assert(borderRadius == null, 'A borderRadius cannot be given when shape is a BoxShape.circle.');
870942
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
871943
case BoxShape.rectangle:
872944
if (borderRadius != null && borderRadius != BorderRadius.zero) {
@@ -879,10 +951,6 @@ class BorderDirectional extends BoxBorder {
879951
}
880952
}
881953

882-
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.');
883-
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.');
884-
assert(_strokeAlignIsUniform && top.strokeAlign == BorderSide.strokeAlignInside, 'A Border can only draw strokeAlign different than strokeAlignInside on uniform borders.');
885-
886954
final BorderSide left, right;
887955
assert(textDirection != null, 'Non-uniform BorderDirectional objects require a TextDirection when painting.');
888956
switch (textDirection!) {
@@ -893,6 +961,29 @@ class BorderDirectional extends BoxBorder {
893961
left = start;
894962
right = end;
895963
}
964+
965+
// Allow painting non-uniform borders if the color and style are uniform.
966+
if (_colorIsUniform && _styleIsUniform) {
967+
switch(top.style) {
968+
case BorderStyle.none:
969+
return;
970+
case BorderStyle.solid:
971+
BoxBorder._paintNonUniformBorder(canvas, rect,
972+
shape: shape,
973+
borderRadius: borderRadius,
974+
textDirection: textDirection,
975+
left: left,
976+
top: top,
977+
right: right,
978+
bottom: bottom);
979+
return;
980+
}
981+
}
982+
983+
assert(borderRadius == null, 'A borderRadius can only be given for borders with uniform colors and styles.');
984+
assert(shape == BoxShape.rectangle, 'A Border can only be drawn as a circle on borders with uniform colors and styles.');
985+
assert(_strokeAlignIsUniform && top.strokeAlign == BorderSide.strokeAlignInside, 'A Border can only draw strokeAlign different than strokeAlignInside on borders with uniform colors and styles.');
986+
896987
paintBorder(canvas, rect, top: top, left: left, bottom: bottom, right: right);
897988
}
898989

packages/flutter/test/material/data_table_test.dart

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,11 +1749,7 @@ void main() {
17491749
);
17501750
expect(
17511751
find.ancestor(of: find.byType(Table), matching: find.byType(Container)),
1752-
paints
1753-
..path(color: borderColor)
1754-
..path(color: borderColor)
1755-
..path(color: borderColor)
1756-
..path(color: borderColor),
1752+
paints..drrect(color: borderColor),
17571753
);
17581754
expect(
17591755
tester.getTopLeft(find.byType(Table)),

0 commit comments

Comments
 (0)