1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'rounded_rectangle_border.dart'; |
6 | library; |
7 | |
8 | import 'dart:math' as math; |
9 | |
10 | import 'package:flutter/foundation.dart'; |
11 | |
12 | import 'basic_types.dart'; |
13 | import 'border_radius.dart'; |
14 | import 'borders.dart'; |
15 | |
16 | /// A rectangular border with flattened or "beveled" corners. |
17 | /// |
18 | /// The line segments that connect the rectangle's four sides will |
19 | /// begin and at locations offset by the corresponding border radius, |
20 | /// but not farther than the side's center. If all the border radii |
21 | /// exceed the sides' half widths/heights the resulting shape is |
22 | /// diamond made by connecting the centers of the sides. |
23 | class BeveledRectangleBorder extends OutlinedBorder { |
24 | /// Creates a border like a [RoundedRectangleBorder] except that the corners |
25 | /// are joined by straight lines instead of arcs. |
26 | const BeveledRectangleBorder({super.side, this.borderRadius = BorderRadius.zero}); |
27 | |
28 | /// The radii for each corner. |
29 | /// |
30 | /// Each corner [Radius] defines the endpoints of a line segment that |
31 | /// spans the corner. The endpoints are located in the same place as |
32 | /// they would be for [RoundedRectangleBorder], but they're connected |
33 | /// by a straight line instead of an arc. |
34 | /// |
35 | /// Negative radius values are clamped to 0.0 by [getInnerPath] and |
36 | /// [getOuterPath]. |
37 | final BorderRadiusGeometry borderRadius; |
38 | |
39 | @override |
40 | ShapeBorder scale(double t) { |
41 | return BeveledRectangleBorder(side: side.scale(t), borderRadius: borderRadius * t); |
42 | } |
43 | |
44 | @override |
45 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
46 | if (a is BeveledRectangleBorder) { |
47 | return BeveledRectangleBorder( |
48 | side: BorderSide.lerp(a.side, side, t), |
49 | borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!, |
50 | ); |
51 | } |
52 | return super.lerpFrom(a, t); |
53 | } |
54 | |
55 | @override |
56 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
57 | if (b is BeveledRectangleBorder) { |
58 | return BeveledRectangleBorder( |
59 | side: BorderSide.lerp(side, b.side, t), |
60 | borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!, |
61 | ); |
62 | } |
63 | return super.lerpTo(b, t); |
64 | } |
65 | |
66 | /// Returns a copy of this RoundedRectangleBorder with the given fields |
67 | /// replaced with the new values. |
68 | @override |
69 | BeveledRectangleBorder copyWith({BorderSide? side, BorderRadiusGeometry? borderRadius}) { |
70 | return BeveledRectangleBorder( |
71 | side: side ?? this.side, |
72 | borderRadius: borderRadius ?? this.borderRadius, |
73 | ); |
74 | } |
75 | |
76 | Path _getPath(RRect rrect) { |
77 | final Offset centerLeft = Offset(rrect.left, rrect.center.dy); |
78 | final Offset centerRight = Offset(rrect.right, rrect.center.dy); |
79 | final Offset centerTop = Offset(rrect.center.dx, rrect.top); |
80 | final Offset centerBottom = Offset(rrect.center.dx, rrect.bottom); |
81 | |
82 | final double tlRadiusX = math.max(0.0, rrect.tlRadiusX); |
83 | final double tlRadiusY = math.max(0.0, rrect.tlRadiusY); |
84 | final double trRadiusX = math.max(0.0, rrect.trRadiusX); |
85 | final double trRadiusY = math.max(0.0, rrect.trRadiusY); |
86 | final double blRadiusX = math.max(0.0, rrect.blRadiusX); |
87 | final double blRadiusY = math.max(0.0, rrect.blRadiusY); |
88 | final double brRadiusX = math.max(0.0, rrect.brRadiusX); |
89 | final double brRadiusY = math.max(0.0, rrect.brRadiusY); |
90 | |
91 | final List<Offset> vertices = <Offset>[ |
92 | Offset(rrect.left, math.min(centerLeft.dy, rrect.top + tlRadiusY)), |
93 | Offset(math.min(centerTop.dx, rrect.left + tlRadiusX), rrect.top), |
94 | Offset(math.max(centerTop.dx, rrect.right - trRadiusX), rrect.top), |
95 | Offset(rrect.right, math.min(centerRight.dy, rrect.top + trRadiusY)), |
96 | Offset(rrect.right, math.max(centerRight.dy, rrect.bottom - brRadiusY)), |
97 | Offset(math.max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom), |
98 | Offset(math.min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom), |
99 | Offset(rrect.left, math.max(centerLeft.dy, rrect.bottom - blRadiusY)), |
100 | ]; |
101 | |
102 | return Path()..addPolygon(vertices, true); |
103 | } |
104 | |
105 | @override |
106 | Path getInnerPath(Rect rect, {TextDirection? textDirection}) { |
107 | return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.strokeInset)); |
108 | } |
109 | |
110 | @override |
111 | Path getOuterPath(Rect rect, {TextDirection? textDirection}) { |
112 | return _getPath(borderRadius.resolve(textDirection).toRRect(rect)); |
113 | } |
114 | |
115 | @override |
116 | void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { |
117 | if (rect.isEmpty) { |
118 | return; |
119 | } |
120 | switch (side.style) { |
121 | case BorderStyle.none: |
122 | break; |
123 | case BorderStyle.solid: |
124 | final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect); |
125 | final RRect adjustedRect = borderRect.inflate(side.strokeOutset); |
126 | final Path path = _getPath(adjustedRect) |
127 | ..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero); |
128 | canvas.drawPath(path, side.toPaint()); |
129 | } |
130 | } |
131 | |
132 | @override |
133 | bool operator ==(Object other) { |
134 | if (other.runtimeType != runtimeType) { |
135 | return false; |
136 | } |
137 | return other is BeveledRectangleBorder && |
138 | other.side == side && |
139 | other.borderRadius == borderRadius; |
140 | } |
141 | |
142 | @override |
143 | int get hashCode => Object.hash(side, borderRadius); |
144 | |
145 | @override |
146 | String toString() { |
147 | return ' ${objectRuntimeType(this, 'BeveledRectangleBorder' )}( $side, $borderRadius)' ; |
148 | } |
149 | } |
150 | |