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
167
168
169
170
171
172
173
174
175
176
// Copyright 2014 The Flutter 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 'package:flutter/foundation.dart';
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 snippet}
/// ```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 OutlinedBorder {
/// The arguments must not be null.
const ContinuousRectangleBorder({
super.side,
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;
@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 - brRadiusX)
..cubicTo(right, bottom, right, bottom, right - brRadiusY, bottom)
..lineTo(left + blRadiusX, bottom)
..cubicTo(left, bottom, left, bottom, left, bottom - blRadiusY)
..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
ContinuousRectangleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius }) {
return ContinuousRectangleBorder(
side: side ?? this.side,
borderRadius: borderRadius ?? this.borderRadius,
);
}
@override
void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
if (rect.isEmpty) {
return;
}
switch (side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
canvas.drawPath(
getOuterPath(rect, textDirection: textDirection),
side.toPaint(),
);
break;
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ContinuousRectangleBorder
&& other.side == side
&& other.borderRadius == borderRadius;
}
@override
int get hashCode => Object.hash(side, borderRadius);
@override
String toString() {
return '${objectRuntimeType(this, 'ContinuousRectangleBorder')}($side, $borderRadius)';
}
}