| // 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 'package:flutter/foundation.dart'; |
| import 'package:flutter/painting.dart' hide Border; |
| |
| /// Border specification for [Table] widgets. |
| /// |
| /// This is like [Border], with the addition of two sides: the inner horizontal |
| /// borders between rows and the inner vertical borders between columns. |
| /// |
| /// The sides are represented by [BorderSide] objects. |
| @immutable |
| class TableBorder { |
| /// Creates a border for a table. |
| /// |
| /// All the sides of the border default to [BorderSide.none]. |
| const TableBorder({ |
| this.top = BorderSide.none, |
| this.right = BorderSide.none, |
| this.bottom = BorderSide.none, |
| this.left = BorderSide.none, |
| this.horizontalInside = BorderSide.none, |
| this.verticalInside = BorderSide.none, |
| }); |
| |
| /// A uniform border with all sides the same color and width. |
| /// |
| /// The sides default to black solid borders, one logical pixel wide. |
| factory TableBorder.all({ |
| Color color = const Color(0xFF000000), |
| double width = 1.0, |
| BorderStyle style = BorderStyle.solid, |
| }) { |
| final BorderSide side = BorderSide(color: color, width: width, style: style); |
| return TableBorder(top: side, right: side, bottom: side, left: side, horizontalInside: side, verticalInside: side); |
| } |
| |
| /// Creates a border for a table where all the interior sides use the same |
| /// styling and all the exterior sides use the same styling. |
| factory TableBorder.symmetric({ |
| BorderSide inside = BorderSide.none, |
| BorderSide outside = BorderSide.none, |
| }) { |
| return TableBorder( |
| top: outside, |
| right: outside, |
| bottom: outside, |
| left: outside, |
| horizontalInside: inside, |
| verticalInside: inside, |
| ); |
| } |
| |
| /// The top side of this border. |
| final BorderSide top; |
| |
| /// The right side of this border. |
| final BorderSide right; |
| |
| /// The bottom side of this border. |
| final BorderSide bottom; |
| |
| /// The left side of this border. |
| final BorderSide left; |
| |
| /// The horizontal interior sides of this border. |
| final BorderSide horizontalInside; |
| |
| /// The vertical interior sides of this border. |
| final BorderSide verticalInside; |
| |
| /// The widths of the sides of this border represented as an [EdgeInsets]. |
| /// |
| /// This can be used, for example, with a [Padding] widget to inset a box by |
| /// the size of these borders. |
| EdgeInsets get dimensions { |
| return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width); |
| } |
| |
| /// Whether all the sides of the border (outside and inside) are identical. |
| /// Uniform borders are typically more efficient to paint. |
| bool get isUniform { |
| assert(top != null); |
| assert(right != null); |
| assert(bottom != null); |
| assert(left != null); |
| assert(horizontalInside != null); |
| assert(verticalInside != null); |
| |
| final Color topColor = top.color; |
| if (right.color != topColor || |
| bottom.color != topColor || |
| left.color != topColor || |
| horizontalInside.color != topColor || |
| verticalInside.color != topColor) |
| return false; |
| |
| final double topWidth = top.width; |
| if (right.width != topWidth || |
| bottom.width != topWidth || |
| left.width != topWidth || |
| horizontalInside.width != topWidth || |
| verticalInside.width != topWidth) |
| return false; |
| |
| final BorderStyle topStyle = top.style; |
| if (right.style != topStyle || |
| bottom.style != topStyle || |
| left.style != topStyle || |
| horizontalInside.style != topStyle || |
| verticalInside.style != topStyle) |
| return false; |
| |
| return true; |
| } |
| |
| /// Creates a copy of this border but with the widths scaled by the factor `t`. |
| /// |
| /// The `t` argument represents the multiplicand, or the position on the |
| /// timeline for an interpolation from nothing to `this`, with 0.0 meaning |
| /// that the object returned should be the nil variant of this object, 1.0 |
| /// meaning that no change should be applied, returning `this` (or something |
| /// equivalent to `this`), and other values meaning that the object should be |
| /// multiplied by `t`. Negative values are treated like zero. |
| /// |
| /// Values for `t` are usually obtained from an [Animation<double>], such as |
| /// an [AnimationController]. |
| /// |
| /// See also: |
| /// |
| /// * [BorderSide.scale], which is used to implement this method. |
| TableBorder scale(double t) { |
| return TableBorder( |
| top: top.scale(t), |
| right: right.scale(t), |
| bottom: bottom.scale(t), |
| left: left.scale(t), |
| horizontalInside: horizontalInside.scale(t), |
| verticalInside: verticalInside.scale(t), |
| ); |
| } |
| |
| /// Linearly interpolate between two table borders. |
| /// |
| /// If a border is null, it is treated as having only [BorderSide.none] |
| /// borders. |
| /// |
| /// {@macro dart.ui.shadow.lerp} |
| static TableBorder lerp(TableBorder a, TableBorder b, double t) { |
| assert(t != null); |
| if (a == null && b == null) |
| return null; |
| if (a == null) |
| return b.scale(t); |
| if (b == null) |
| return a.scale(1.0 - t); |
| return TableBorder( |
| top: BorderSide.lerp(a.top, b.top, t), |
| right: BorderSide.lerp(a.right, b.right, t), |
| bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
| left: BorderSide.lerp(a.left, b.left, t), |
| horizontalInside: BorderSide.lerp(a.horizontalInside, b.horizontalInside, t), |
| verticalInside: BorderSide.lerp(a.verticalInside, b.verticalInside, t), |
| ); |
| } |
| |
| /// Paints the border around the given [Rect] on the given [Canvas], with the |
| /// given rows and columns. |
| /// |
| /// Uniform borders are more efficient to paint than more complex borders. |
| /// |
| /// The `rows` argument specifies the vertical positions between the rows, |
| /// relative to the given rectangle. For example, if the table contained two |
| /// rows of height 100.0 each, then `rows` would contain a single value, |
| /// 100.0, which is the vertical position between the two rows (relative to |
| /// the top edge of `rect`). |
| /// |
| /// The `columns` argument specifies the horizontal positions between the |
| /// columns, relative to the given rectangle. For example, if the table |
| /// contained two columns of height 100.0 each, then `columns` would contain a |
| /// single value, 100.0, which is the vertical position between the two |
| /// columns (relative to the left edge of `rect`). |
| /// |
| /// The [verticalInside] border is only drawn if there are at least two |
| /// columns. The [horizontalInside] border is only drawn if there are at least |
| /// two rows. The horizontal borders are drawn after the vertical borders. |
| /// |
| /// The outer borders (in the order [top], [right], [bottom], [left], with |
| /// [left] above the others) are painted after the inner borders. |
| /// |
| /// The paint order is particularly notable in the case of |
| /// partially-transparent borders. |
| void paint( |
| Canvas canvas, |
| Rect rect, { |
| @required Iterable<double> rows, |
| @required Iterable<double> columns, |
| }) { |
| // properties can't be null |
| assert(top != null); |
| assert(right != null); |
| assert(bottom != null); |
| assert(left != null); |
| assert(horizontalInside != null); |
| assert(verticalInside != null); |
| |
| // arguments can't be null |
| assert(canvas != null); |
| assert(rect != null); |
| assert(rows != null); |
| assert(rows.isEmpty || (rows.first >= 0.0 && rows.last <= rect.height)); |
| assert(columns != null); |
| assert(columns.isEmpty || (columns.first >= 0.0 && columns.last <= rect.width)); |
| |
| if (columns.isNotEmpty || rows.isNotEmpty) { |
| final Paint paint = Paint(); |
| final Path path = Path(); |
| |
| if (columns.isNotEmpty) { |
| switch (verticalInside.style) { |
| case BorderStyle.solid: |
| paint |
| ..color = verticalInside.color |
| ..strokeWidth = verticalInside.width |
| ..style = PaintingStyle.stroke; |
| path.reset(); |
| for (final double x in columns) { |
| path.moveTo(rect.left + x, rect.top); |
| path.lineTo(rect.left + x, rect.bottom); |
| } |
| canvas.drawPath(path, paint); |
| break; |
| case BorderStyle.none: |
| break; |
| } |
| } |
| |
| if (rows.isNotEmpty) { |
| switch (horizontalInside.style) { |
| case BorderStyle.solid: |
| paint |
| ..color = horizontalInside.color |
| ..strokeWidth = horizontalInside.width |
| ..style = PaintingStyle.stroke; |
| path.reset(); |
| for (final double y in rows) { |
| path.moveTo(rect.left, rect.top + y); |
| path.lineTo(rect.right, rect.top + y); |
| } |
| canvas.drawPath(path, paint); |
| break; |
| case BorderStyle.none: |
| break; |
| } |
| } |
| } |
| paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); |
| } |
| |
| @override |
| bool operator ==(Object other) { |
| if (identical(this, other)) |
| return true; |
| if (other.runtimeType != runtimeType) |
| return false; |
| return other is TableBorder |
| && other.top == top |
| && other.right == right |
| && other.bottom == bottom |
| && other.left == left |
| && other.horizontalInside == horizontalInside |
| && other.verticalInside == verticalInside; |
| } |
| |
| @override |
| int get hashCode => hashValues(top, right, bottom, left, horizontalInside, verticalInside); |
| |
| @override |
| String toString() => 'TableBorder($top, $right, $bottom, $left, $horizontalInside, $verticalInside)'; |
| } |