22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5- import 'dart:math ' as math ;
5+ import 'dart:ui ' as ui show lerpDouble ;
66
77import 'package:flutter/foundation.dart' ;
88
@@ -18,16 +18,32 @@ import 'edge_insets.dart';
1818/// When applied to a rectangular space, the border paints in the center of the
1919/// rectangle.
2020///
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+ ///
2127/// See also:
2228///
29+ /// * [OvalBorder] , which draws a Circle touching all the edges of the box.
2330/// * [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.
2632class CircleBorder extends OutlinedBorder {
2733 /// Create a circle border.
2834 ///
2935 /// 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;
3147
3248 @override
3349 EdgeInsetsGeometry get dimensions {
@@ -42,58 +58,56 @@ class CircleBorder extends OutlinedBorder {
4258 }
4359
4460 @override
45- ShapeBorder scale (double t) => CircleBorder (side: side.scale (t));
61+ ShapeBorder scale (double t) => CircleBorder (side: side.scale (t), eccentricity : eccentricity );
4662
4763 @override
4864 ShapeBorder ? lerpFrom (ShapeBorder ? a, double t) {
4965 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+ );
5170 }
5271 return super .lerpFrom (a, t);
5372 }
5473
5574 @override
5675 ShapeBorder ? lerpTo (ShapeBorder ? b, double t) {
5776 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+ );
5981 }
6082 return super .lerpTo (b, t);
6183 }
6284
6385 @override
6486 Path getInnerPath (Rect rect, { TextDirection ? textDirection }) {
65- final double radius = rect.shortestSide / 2.0 ;
66- final double adjustedRadius;
87+ final double delta;
6788 switch (side.strokeAlign) {
6889 case StrokeAlign .inside:
69- adjustedRadius = radius - side.width;
90+ delta = side.width;
7091 break ;
7192 case StrokeAlign .center:
72- adjustedRadius = radius - side.width / 2.0 ;
93+ delta = side.width / 2.0 ;
7394 break ;
7495 case StrokeAlign .outside:
75- adjustedRadius = radius ;
96+ delta = 0 ;
7697 break ;
7798 }
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);
83101 }
84102
85103 @override
86104 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));
92106 }
93107
94108 @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 );
97111 }
98112
99113 @override
@@ -102,19 +116,59 @@ class CircleBorder extends OutlinedBorder {
102116 case BorderStyle .none:
103117 break ;
104118 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 ());
116148 }
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+ );
118172 }
119173 }
120174
@@ -124,14 +178,18 @@ class CircleBorder extends OutlinedBorder {
124178 return false ;
125179 }
126180 return other is CircleBorder
127- && other.side == side;
181+ && other.side == side
182+ && other.eccentricity == eccentricity;
128183 }
129184
130185 @override
131- int get hashCode => side.hashCode ;
186+ int get hashCode => Object . hash (side, eccentricity) ;
132187
133188 @override
134189 String toString () {
190+ if (eccentricity != 0.0 ) {
191+ return '${objectRuntimeType (this , 'CircleBorder' )}($side , eccentricity: $eccentricity )' ;
192+ }
135193 return '${objectRuntimeType (this , 'CircleBorder' )}($side )' ;
136194 }
137195}
0 commit comments