2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
- import 'dart:math ' as math ;
5
+ import 'dart:ui ' as ui show lerpDouble ;
6
6
7
7
import 'package:flutter/foundation.dart' ;
8
8
@@ -18,16 +18,32 @@ import 'edge_insets.dart';
18
18
/// When applied to a rectangular space, the border paints in the center of the
19
19
/// rectangle.
20
20
///
21
+ /// The [eccentricity] parameter describes how much a circle will deform to
22
+ /// fit the rectangle it is a border for. A value of zero implies no
23
+ /// deformation (a circle touching at least two sides of the rectangle), a
24
+ /// value of one implies full deformation (an oval touching all sides of the
25
+ /// rectangle).
26
+ ///
21
27
/// See also:
22
28
///
29
+ /// * [OvalBorder] , which draws a Circle touching all the edges of the box.
23
30
/// * [BorderSide] , which is used to describe each side of the box.
24
- /// * [Border] , which, when used with [BoxDecoration] , can also
25
- /// describe a circle.
31
+ /// * [Border] , which, when used with [BoxDecoration] , can also describe a circle.
26
32
class CircleBorder extends OutlinedBorder {
27
33
/// Create a circle border.
28
34
///
29
35
/// The [side] argument must not be null.
30
- const CircleBorder ({ super .side }) : assert (side != null );
36
+ const CircleBorder ({ super .side, this .eccentricity = 0.0 })
37
+ : assert (side != null ),
38
+ assert (eccentricity != null ),
39
+ assert (eccentricity >= 0.0 , 'The eccentricity argument $eccentricity is not greater than or equal to zero.' ),
40
+ assert (eccentricity <= 1.0 , 'The eccentricity argument $eccentricity is not less than or equal to one.' );
41
+
42
+ /// Defines the ratio (0.0-1.0) from which the border will deform
43
+ /// to fit a rectangle.
44
+ /// When 0.0, it draws a circle touching at least two sides of the rectangle.
45
+ /// When 1.0, it draws an oval touching all sides of the rectangle.
46
+ final double eccentricity;
31
47
32
48
@override
33
49
EdgeInsetsGeometry get dimensions {
@@ -42,58 +58,56 @@ class CircleBorder extends OutlinedBorder {
42
58
}
43
59
44
60
@override
45
- ShapeBorder scale (double t) => CircleBorder (side: side.scale (t));
61
+ ShapeBorder scale (double t) => CircleBorder (side: side.scale (t), eccentricity : eccentricity );
46
62
47
63
@override
48
64
ShapeBorder ? lerpFrom (ShapeBorder ? a, double t) {
49
65
if (a is CircleBorder ) {
50
- return CircleBorder (side: BorderSide .lerp (a.side, side, t));
66
+ return CircleBorder (
67
+ side: BorderSide .lerp (a.side, side, t),
68
+ eccentricity: clampDouble (ui.lerpDouble (a.eccentricity, eccentricity, t)! , 0.0 , 1.0 ),
69
+ );
51
70
}
52
71
return super .lerpFrom (a, t);
53
72
}
54
73
55
74
@override
56
75
ShapeBorder ? lerpTo (ShapeBorder ? b, double t) {
57
76
if (b is CircleBorder ) {
58
- return CircleBorder (side: BorderSide .lerp (side, b.side, t));
77
+ return CircleBorder (
78
+ side: BorderSide .lerp (side, b.side, t),
79
+ eccentricity: clampDouble (ui.lerpDouble (eccentricity, b.eccentricity, t)! , 0.0 , 1.0 ),
80
+ );
59
81
}
60
82
return super .lerpTo (b, t);
61
83
}
62
84
63
85
@override
64
86
Path getInnerPath (Rect rect, { TextDirection ? textDirection }) {
65
- final double radius = rect.shortestSide / 2.0 ;
66
- final double adjustedRadius;
87
+ final double delta;
67
88
switch (side.strokeAlign) {
68
89
case StrokeAlign .inside:
69
- adjustedRadius = radius - side.width;
90
+ delta = side.width;
70
91
break ;
71
92
case StrokeAlign .center:
72
- adjustedRadius = radius - side.width / 2.0 ;
93
+ delta = side.width / 2.0 ;
73
94
break ;
74
95
case StrokeAlign .outside:
75
- adjustedRadius = radius ;
96
+ delta = 0 ;
76
97
break ;
77
98
}
78
- return Path ()
79
- ..addOval (Rect .fromCircle (
80
- center: rect.center,
81
- radius: math.max (0.0 , adjustedRadius),
82
- ));
99
+ final Rect adjustedRect = _adjustRect (rect).deflate (delta);
100
+ return Path ()..addOval (adjustedRect);
83
101
}
84
102
85
103
@override
86
104
Path getOuterPath (Rect rect, { TextDirection ? textDirection }) {
87
- return Path ()
88
- ..addOval (Rect .fromCircle (
89
- center: rect.center,
90
- radius: rect.shortestSide / 2.0 ,
91
- ));
105
+ return Path ()..addOval (_adjustRect (rect));
92
106
}
93
107
94
108
@override
95
- CircleBorder copyWith ({ BorderSide ? side }) {
96
- return CircleBorder (side: side ?? this .side);
109
+ CircleBorder copyWith ({ BorderSide ? side, double ? eccentricity }) {
110
+ return CircleBorder (side: side ?? this .side, eccentricity : eccentricity ?? this .eccentricity );
97
111
}
98
112
99
113
@override
@@ -102,19 +116,59 @@ class CircleBorder extends OutlinedBorder {
102
116
case BorderStyle .none:
103
117
break ;
104
118
case BorderStyle .solid:
105
- final double radius;
106
- switch (side.strokeAlign) {
107
- case StrokeAlign .inside:
108
- radius = (rect.shortestSide - side.width) / 2.0 ;
109
- break ;
110
- case StrokeAlign .center:
111
- radius = rect.shortestSide / 2.0 ;
112
- break ;
113
- case StrokeAlign .outside:
114
- radius = (rect.shortestSide + side.width) / 2.0 ;
115
- break ;
119
+ if (eccentricity != 0.0 ) {
120
+ final Rect borderRect = _adjustRect (rect);
121
+ final Rect adjustedRect;
122
+ switch (side.strokeAlign) {
123
+ case StrokeAlign .inside:
124
+ adjustedRect = borderRect.deflate (side.width / 2.0 );
125
+ break ;
126
+ case StrokeAlign .center:
127
+ adjustedRect = borderRect;
128
+ break ;
129
+ case StrokeAlign .outside:
130
+ adjustedRect = borderRect.inflate (side.width / 2.0 );
131
+ break ;
132
+ }
133
+ canvas.drawOval (adjustedRect, side.toPaint ());
134
+ } else {
135
+ final double radius;
136
+ switch (side.strokeAlign) {
137
+ case StrokeAlign .inside:
138
+ radius = (rect.shortestSide - side.width) / 2.0 ;
139
+ break ;
140
+ case StrokeAlign .center:
141
+ radius = rect.shortestSide / 2.0 ;
142
+ break ;
143
+ case StrokeAlign .outside:
144
+ radius = (rect.shortestSide + side.width) / 2.0 ;
145
+ break ;
146
+ }
147
+ canvas.drawCircle (rect.center, radius, side.toPaint ());
116
148
}
117
- canvas.drawCircle (rect.center, radius, side.toPaint ());
149
+ }
150
+ }
151
+
152
+ Rect _adjustRect (Rect rect) {
153
+ if (eccentricity == 0.0 || rect.width == rect.height) {
154
+ return Rect .fromCircle (center: rect.center, radius: rect.shortestSide / 2.0 );
155
+ }
156
+ if (rect.width < rect.height) {
157
+ final double delta = (1.0 - eccentricity) * (rect.height - rect.width) / 2.0 ;
158
+ return Rect .fromLTRB (
159
+ rect.left,
160
+ rect.top + delta,
161
+ rect.right,
162
+ rect.bottom - delta,
163
+ );
164
+ } else {
165
+ final double delta = (1.0 - eccentricity) * (rect.width - rect.height) / 2.0 ;
166
+ return Rect .fromLTRB (
167
+ rect.left + delta,
168
+ rect.top,
169
+ rect.right - delta,
170
+ rect.bottom,
171
+ );
118
172
}
119
173
}
120
174
@@ -124,14 +178,18 @@ class CircleBorder extends OutlinedBorder {
124
178
return false ;
125
179
}
126
180
return other is CircleBorder
127
- && other.side == side;
181
+ && other.side == side
182
+ && other.eccentricity == eccentricity;
128
183
}
129
184
130
185
@override
131
- int get hashCode => side.hashCode ;
186
+ int get hashCode => Object . hash (side, eccentricity) ;
132
187
133
188
@override
134
189
String toString () {
190
+ if (eccentricity != 0.0 ) {
191
+ return '${objectRuntimeType (this , 'CircleBorder' )}($side , eccentricity: $eccentricity )' ;
192
+ }
135
193
return '${objectRuntimeType (this , 'CircleBorder' )}($side )' ;
136
194
}
137
195
}
0 commit comments