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 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/painting.dart' hide Border; |
7 | |
8 | /// Border specification for [Table] widgets. |
9 | /// |
10 | /// This is like [Border], with the addition of two sides: the inner horizontal |
11 | /// borders between rows and the inner vertical borders between columns. |
12 | /// |
13 | /// The sides are represented by [BorderSide] objects. |
14 | @immutable |
15 | class TableBorder { |
16 | /// Creates a border for a table. |
17 | /// |
18 | /// All the sides of the border default to [BorderSide.none]. |
19 | const TableBorder({ |
20 | this.top = BorderSide.none, |
21 | this.right = BorderSide.none, |
22 | this.bottom = BorderSide.none, |
23 | this.left = BorderSide.none, |
24 | this.horizontalInside = BorderSide.none, |
25 | this.verticalInside = BorderSide.none, |
26 | this.borderRadius = BorderRadius.zero, |
27 | }); |
28 | |
29 | /// A uniform border with all sides the same color and width. |
30 | /// |
31 | /// The sides default to black solid borders, one logical pixel wide. |
32 | factory TableBorder.all({ |
33 | Color color = const Color(0xFF000000), |
34 | double width = 1.0, |
35 | BorderStyle style = BorderStyle.solid, |
36 | BorderRadius borderRadius = BorderRadius.zero, |
37 | }) { |
38 | final BorderSide side = BorderSide(color: color, width: width, style: style); |
39 | return TableBorder(top: side, right: side, bottom: side, left: side, horizontalInside: side, verticalInside: side, borderRadius: borderRadius); |
40 | } |
41 | |
42 | /// Creates a border for a table where all the interior sides use the same |
43 | /// styling and all the exterior sides use the same styling. |
44 | factory TableBorder.symmetric({ |
45 | BorderSide inside = BorderSide.none, |
46 | BorderSide outside = BorderSide.none, |
47 | }) { |
48 | return TableBorder( |
49 | top: outside, |
50 | right: outside, |
51 | bottom: outside, |
52 | left: outside, |
53 | horizontalInside: inside, |
54 | verticalInside: inside, |
55 | ); |
56 | } |
57 | |
58 | /// The top side of this border. |
59 | final BorderSide top; |
60 | |
61 | /// The right side of this border. |
62 | final BorderSide right; |
63 | |
64 | /// The bottom side of this border. |
65 | final BorderSide bottom; |
66 | |
67 | /// The left side of this border. |
68 | final BorderSide left; |
69 | |
70 | /// The horizontal interior sides of this border. |
71 | final BorderSide horizontalInside; |
72 | |
73 | /// The vertical interior sides of this border. |
74 | final BorderSide verticalInside; |
75 | |
76 | /// The [BorderRadius] to use when painting the corners of this border. |
77 | /// |
78 | /// It is also applied to [DataTable]'s [Material]. |
79 | final BorderRadius borderRadius; |
80 | |
81 | /// The widths of the sides of this border represented as an [EdgeInsets]. |
82 | /// |
83 | /// This can be used, for example, with a [Padding] widget to inset a box by |
84 | /// the size of these borders. |
85 | EdgeInsets get dimensions { |
86 | return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width); |
87 | } |
88 | |
89 | /// Whether all the sides of the border (outside and inside) are identical. |
90 | /// Uniform borders are typically more efficient to paint. |
91 | bool get isUniform { |
92 | |
93 | final Color topColor = top.color; |
94 | if (right.color != topColor || |
95 | bottom.color != topColor || |
96 | left.color != topColor || |
97 | horizontalInside.color != topColor || |
98 | verticalInside.color != topColor) { |
99 | return false; |
100 | } |
101 | |
102 | final double topWidth = top.width; |
103 | if (right.width != topWidth || |
104 | bottom.width != topWidth || |
105 | left.width != topWidth || |
106 | horizontalInside.width != topWidth || |
107 | verticalInside.width != topWidth) { |
108 | return false; |
109 | } |
110 | |
111 | final BorderStyle topStyle = top.style; |
112 | if (right.style != topStyle || |
113 | bottom.style != topStyle || |
114 | left.style != topStyle || |
115 | horizontalInside.style != topStyle || |
116 | verticalInside.style != topStyle) { |
117 | return false; |
118 | } |
119 | |
120 | return true; |
121 | } |
122 | |
123 | /// Creates a copy of this border but with the widths scaled by the factor `t`. |
124 | /// |
125 | /// The `t` argument represents the multiplicand, or the position on the |
126 | /// timeline for an interpolation from nothing to `this`, with 0.0 meaning |
127 | /// that the object returned should be the nil variant of this object, 1.0 |
128 | /// meaning that no change should be applied, returning `this` (or something |
129 | /// equivalent to `this`), and other values meaning that the object should be |
130 | /// multiplied by `t`. Negative values are treated like zero. |
131 | /// |
132 | /// Values for `t` are usually obtained from an [Animation<double>], such as |
133 | /// an [AnimationController]. |
134 | /// |
135 | /// See also: |
136 | /// |
137 | /// * [BorderSide.scale], which is used to implement this method. |
138 | TableBorder scale(double t) { |
139 | return TableBorder( |
140 | top: top.scale(t), |
141 | right: right.scale(t), |
142 | bottom: bottom.scale(t), |
143 | left: left.scale(t), |
144 | horizontalInside: horizontalInside.scale(t), |
145 | verticalInside: verticalInside.scale(t), |
146 | ); |
147 | } |
148 | |
149 | /// Linearly interpolate between two table borders. |
150 | /// |
151 | /// If a border is null, it is treated as having only [BorderSide.none] |
152 | /// borders. |
153 | /// |
154 | /// {@macro dart.ui.shadow.lerp} |
155 | static TableBorder? lerp(TableBorder? a, TableBorder? b, double t) { |
156 | if (identical(a, b)) { |
157 | return a; |
158 | } |
159 | if (a == null) { |
160 | return b!.scale(t); |
161 | } |
162 | if (b == null) { |
163 | return a.scale(1.0 - t); |
164 | } |
165 | return TableBorder( |
166 | top: BorderSide.lerp(a.top, b.top, t), |
167 | right: BorderSide.lerp(a.right, b.right, t), |
168 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
169 | left: BorderSide.lerp(a.left, b.left, t), |
170 | horizontalInside: BorderSide.lerp(a.horizontalInside, b.horizontalInside, t), |
171 | verticalInside: BorderSide.lerp(a.verticalInside, b.verticalInside, t), |
172 | ); |
173 | } |
174 | |
175 | /// Paints the border around the given [Rect] on the given [Canvas], with the |
176 | /// given rows and columns. |
177 | /// |
178 | /// Uniform borders are more efficient to paint than more complex borders. |
179 | /// |
180 | /// The `rows` argument specifies the vertical positions between the rows, |
181 | /// relative to the given rectangle. For example, if the table contained two |
182 | /// rows of height 100.0 each, then `rows` would contain a single value, |
183 | /// 100.0, which is the vertical position between the two rows (relative to |
184 | /// the top edge of `rect`). |
185 | /// |
186 | /// The `columns` argument specifies the horizontal positions between the |
187 | /// columns, relative to the given rectangle. For example, if the table |
188 | /// contained two columns of height 100.0 each, then `columns` would contain a |
189 | /// single value, 100.0, which is the vertical position between the two |
190 | /// columns (relative to the left edge of `rect`). |
191 | /// |
192 | /// The [verticalInside] border is only drawn if there are at least two |
193 | /// columns. The [horizontalInside] border is only drawn if there are at least |
194 | /// two rows. The horizontal borders are drawn after the vertical borders. |
195 | /// |
196 | /// The outer borders (in the order [top], [right], [bottom], [left], with |
197 | /// [left] above the others) are painted after the inner borders. |
198 | /// |
199 | /// The paint order is particularly notable in the case of |
200 | /// partially-transparent borders. |
201 | void paint( |
202 | Canvas canvas, |
203 | Rect rect, { |
204 | required Iterable<double> rows, |
205 | required Iterable<double> columns, |
206 | }) { |
207 | // properties can't be null |
208 | |
209 | // arguments can't be null |
210 | assert(rows.isEmpty || (rows.first >= 0.0 && rows.last <= rect.height)); |
211 | assert(columns.isEmpty || (columns.first >= 0.0 && columns.last <= rect.width)); |
212 | |
213 | if (columns.isNotEmpty || rows.isNotEmpty) { |
214 | final Paint paint = Paint(); |
215 | final Path path = Path(); |
216 | |
217 | if (columns.isNotEmpty) { |
218 | switch (verticalInside.style) { |
219 | case BorderStyle.solid: |
220 | paint |
221 | ..color = verticalInside.color |
222 | ..strokeWidth = verticalInside.width |
223 | ..style = PaintingStyle.stroke; |
224 | path.reset(); |
225 | for (final double x in columns) { |
226 | path.moveTo(rect.left + x, rect.top); |
227 | path.lineTo(rect.left + x, rect.bottom); |
228 | } |
229 | canvas.drawPath(path, paint); |
230 | case BorderStyle.none: |
231 | break; |
232 | } |
233 | } |
234 | |
235 | if (rows.isNotEmpty) { |
236 | switch (horizontalInside.style) { |
237 | case BorderStyle.solid: |
238 | paint |
239 | ..color = horizontalInside.color |
240 | ..strokeWidth = horizontalInside.width |
241 | ..style = PaintingStyle.stroke; |
242 | path.reset(); |
243 | for (final double y in rows) { |
244 | path.moveTo(rect.left, rect.top + y); |
245 | path.lineTo(rect.right, rect.top + y); |
246 | } |
247 | canvas.drawPath(path, paint); |
248 | case BorderStyle.none: |
249 | break; |
250 | } |
251 | } |
252 | } |
253 | if (!isUniform || borderRadius == BorderRadius.zero) { |
254 | paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); |
255 | } else { |
256 | final RRect outer = borderRadius.toRRect(rect); |
257 | final RRect inner = outer.deflate(top.width); |
258 | final Paint paint = Paint()..color = top.color; |
259 | canvas.drawDRRect(outer, inner, paint); |
260 | } |
261 | } |
262 | |
263 | @override |
264 | bool operator ==(Object other) { |
265 | if (identical(this, other)) { |
266 | return true; |
267 | } |
268 | if (other.runtimeType != runtimeType) { |
269 | return false; |
270 | } |
271 | return other is TableBorder |
272 | && other.top == top |
273 | && other.right == right |
274 | && other.bottom == bottom |
275 | && other.left == left |
276 | && other.horizontalInside == horizontalInside |
277 | && other.verticalInside == verticalInside |
278 | && other.borderRadius == borderRadius; |
279 | } |
280 | |
281 | @override |
282 | int get hashCode => Object.hash(top, right, bottom, left, horizontalInside, verticalInside, borderRadius); |
283 | |
284 | @override |
285 | String toString() => 'TableBorder( $top, $right, $bottom, $left, $horizontalInside, $verticalInside, $borderRadius)' ; |
286 | } |
287 | |