1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'basic_types.dart';
import 'border_radius.dart';
import 'borders.dart';
import 'edge_insets.dart';
/// A rectangular border with smooth continuous transitions between the straight
/// sides and the rounded corners.
///
/// {@tool sample}
/// ```dart
/// Widget build(BuildContext context) {
/// return Material(
/// shape: ContinuousRectangleBorder(
/// borderRadius: BorderRadius.circular(28.0),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [RoundedRectangleBorder] Which creates rectangles with rounded corners,
/// however its straight sides change into a rounded corner with a circular
/// radius in a step function instead of gradually like the
/// [ContinuousRectangleBorder].
class ContinuousRectangleBorder extends ShapeBorder {
/// The arguments must not be null.
const ContinuousRectangleBorder({
this.side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
}) : assert(side != null),
assert(borderRadius != null);
/// The radius for each corner.
///
/// Negative radius values are clamped to 0.0 by [getInnerPath] and
/// [getOuterPath].
final BorderRadiusGeometry borderRadius;
/// The style of this border.
final BorderSide side;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);
@override
ShapeBorder scale(double t) {
return ContinuousRectangleBorder(
side: side.scale(t),
borderRadius: borderRadius * t,
);
}
@override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
assert(t != null);
if (a is ContinuousRectangleBorder) {
return ContinuousRectangleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder lerpTo(ShapeBorder b, double t) {
assert(t != null);
if (b is ContinuousRectangleBorder) {
return ContinuousRectangleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
);
}
return super.lerpTo(b, t);
}
double _clampToShortest(RRect rrect, double value) {
return value > rrect.shortestSide ? rrect.shortestSide : value;
}
Path _getPath(RRect rrect) {
final double left = rrect.left;
final double right = rrect.right;
final double top = rrect.top;
final double bottom = rrect.bottom;
// Radii will be clamped to the value of the shortest side
/// of [rrect] to avoid strange tie-fighter shapes.
final double tlRadiusX =
math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusX));
final double tlRadiusY =
math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusY));
final double trRadiusX =
math.max(0.0, _clampToShortest(rrect, rrect.trRadiusX));
final double trRadiusY =
math.max(0.0, _clampToShortest(rrect, rrect.trRadiusY));
final double blRadiusX =
math.max(0.0, _clampToShortest(rrect, rrect.blRadiusX));
final double blRadiusY =
math.max(0.0, _clampToShortest(rrect, rrect.blRadiusY));
final double brRadiusX =
math.max(0.0, _clampToShortest(rrect, rrect.brRadiusX));
final double brRadiusY =
math.max(0.0, _clampToShortest(rrect, rrect.brRadiusY));
return Path()
..moveTo(left, top + tlRadiusX)
..cubicTo(left, top, left, top, left + tlRadiusY, top)
..lineTo(right - trRadiusX, top)
..cubicTo(right, top, right, top, right, top + trRadiusY)
..lineTo(right, bottom - blRadiusX)
..cubicTo(right, bottom, right, bottom, right - blRadiusY, bottom)
..lineTo(left + brRadiusX, bottom)
..cubicTo(left, bottom, left, bottom, left, bottom - brRadiusY)
..close();
}
@override
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
}
@override
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
}
@override
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
if (rect.isEmpty)
return;
switch (side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
final Path path = getOuterPath(rect, textDirection: textDirection);
final Paint paint = side.toPaint();
canvas.drawPath(path, paint);
break;
}
}
@override
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType)
return false;
final ContinuousRectangleBorder typedOther = other;
return side == typedOther.side
&& borderRadius == typedOther.borderRadius;
}
@override
int get hashCode => hashValues(side, borderRadius);
@override
String toString() {
return '$runtimeType($side, $borderRadius)';
}
}