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
// 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 flattened or "beveled" corners.
///
/// The line segments that connect the rectangle's four sides will
/// begin and at locations offset by the corresponding border radius,
/// but not farther than the side's center. If all the border radii
/// exceed the sides' half widths/heights the resulting shape is
/// diamond made by connecting the centers of the sides.
class BeveledRectangleBorder extends ShapeBorder {
/// Creates a border like a [RoundedRectangleBorder] except that the corners
/// are joined by straight lines instead of arcs.
///
/// The arguments must not be null.
const BeveledRectangleBorder({
this.side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
}) : assert(side != null),
assert(borderRadius != null);
/// The style of this border.
final BorderSide side;
/// The radii for each corner.
///
/// Each corner [Radius] defines the endpoints of a line segment that
/// spans the corner. The endpoints are located in the same place as
/// they would be for [RoundedRectangleBorder], but they're connected
/// by a straight line instead of an arc.
///
/// Negative radius values are clamped to 0.0 by [getInnerPath] and
/// [getOuterPath].
final BorderRadiusGeometry borderRadius;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
}
@override
ShapeBorder scale(double t) {
return BeveledRectangleBorder(
side: side.scale(t),
borderRadius: borderRadius * t,
);
}
@override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
assert(t != null);
if (a is BeveledRectangleBorder) {
return BeveledRectangleBorder(
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 BeveledRectangleBorder) {
return BeveledRectangleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
);
}
return super.lerpTo(b, t);
}
Path _getPath(RRect rrect) {
final Offset centerLeft = Offset(rrect.left, rrect.center.dy);
final Offset centerRight = Offset(rrect.right, rrect.center.dy);
final Offset centerTop = Offset(rrect.center.dx, rrect.top);
final Offset centerBottom = Offset(rrect.center.dx, rrect.bottom);
final double tlRadiusX = math.max(0.0, rrect.tlRadiusX);
final double tlRadiusY = math.max(0.0, rrect.tlRadiusY);
final double trRadiusX = math.max(0.0, rrect.trRadiusX);
final double trRadiusY = math.max(0.0, rrect.trRadiusY);
final double blRadiusX = math.max(0.0, rrect.blRadiusX);
final double blRadiusY = math.max(0.0, rrect.blRadiusY);
final double brRadiusX = math.max(0.0, rrect.brRadiusX);
final double brRadiusY = math.max(0.0, rrect.brRadiusY);
final List<Offset> vertices = <Offset>[
Offset(rrect.left, math.min(centerLeft.dy, rrect.top + tlRadiusY)),
Offset(math.min(centerTop.dx, rrect.left + tlRadiusX), rrect.top),
Offset(math.max(centerTop.dx, rrect.right -trRadiusX), rrect.top),
Offset(rrect.right, math.min(centerRight.dy, rrect.top + trRadiusY)),
Offset(rrect.right, math.max(centerRight.dy, rrect.bottom - brRadiusY)),
Offset(math.max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom),
Offset(math.min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom),
Offset(rrect.left, math.max(centerLeft.dy, rrect.bottom - blRadiusY)),
];
return Path()..addPolygon(vertices, true);
}
@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)
..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
canvas.drawPath(path, side.toPaint());
break;
}
}
@override
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType)
return false;
final BeveledRectangleBorder typedOther = other;
return side == typedOther.side
&& borderRadius == typedOther.borderRadius;
}
@override
int get hashCode => hashValues(side, borderRadius);
@override
String toString() {
return '$runtimeType($side, $borderRadius)';
}
}