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