diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart index 91548a4222..3e3a809576 100644 --- a/packages/flutter/lib/rendering.dart +++ b/packages/flutter/lib/rendering.dart @@ -60,6 +60,7 @@ export 'src/rendering/sliver_padding.dart'; export 'src/rendering/sliver_persistent_header.dart'; export 'src/rendering/stack.dart'; export 'src/rendering/table.dart'; +export 'src/rendering/table_border.dart'; export 'src/rendering/tweens.dart'; export 'src/rendering/view.dart'; export 'src/rendering/viewport.dart'; diff --git a/packages/flutter/lib/src/painting/border.dart b/packages/flutter/lib/src/painting/border.dart index 5865dc8c32..3a3d1c54bb 100644 --- a/packages/flutter/lib/src/painting/border.dart +++ b/packages/flutter/lib/src/painting/border.dart @@ -337,114 +337,36 @@ class Border { /// If you specify a rectangular box shape (BoxShape.rectangle), then you may /// specify a [BorderRadius]. If a border radius is specified, there is the /// requirement that the border [isUniform]. + /// + /// See also: + /// + /// * [paintBorder], which is used if the border is not uniform. void paint(Canvas canvas, Rect rect, { BoxShape shape: BoxShape.rectangle, BorderRadius borderRadius, }) { if (isUniform) { - if (borderRadius != null) { - assert(shape == BoxShape.rectangle, 'A borderRadius can only be given for rectangular boxes.'); - _paintBorderWithRadius(canvas, rect, borderRadius); - return; - } if (shape == BoxShape.circle) { - assert(borderRadius == null); - _paintBorderWithCircle(canvas, rect); + assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.'); + _paintUniformBorderWithCircle(canvas, rect); return; } + if (borderRadius != null) { + _paintUniformBorderWithRadius(canvas, rect, borderRadius); + return; + } + _paintUniformBorderWithRectangle(canvas, rect); + return; } - assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); // TODO(abarth): Support non-uniform rounded borders. - assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); // TODO(ianh): Support non-uniform borders on circles. + assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); + assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); - assert(top != null); - assert(right != null); - assert(bottom != null); - assert(left != null); - - final Paint paint = new Paint() - ..strokeWidth = 0.0; // used for hairline borders - Path path; - - switch (top.style) { - case BorderStyle.solid: - paint.color = top.color; - path = new Path(); - path.moveTo(rect.left, rect.top); - path.lineTo(rect.right, rect.top); - if (top.width == 0.0) { - paint.style = PaintingStyle.stroke; - } else { - paint.style = PaintingStyle.fill; - path.lineTo(rect.right - right.width, rect.top + top.width); - path.lineTo(rect.left + left.width, rect.top + top.width); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: - break; - } - - switch (right.style) { - case BorderStyle.solid: - paint.color = right.color; - path = new Path(); - path.moveTo(rect.right, rect.top); - path.lineTo(rect.right, rect.bottom); - if (right.width == 0.0) { - paint.style = PaintingStyle.stroke; - } else { - paint.style = PaintingStyle.fill; - path.lineTo(rect.right - right.width, rect.bottom - bottom.width); - path.lineTo(rect.right - right.width, rect.top + top.width); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: - break; - } - - switch (bottom.style) { - case BorderStyle.solid: - paint.color = bottom.color; - path = new Path(); - path.moveTo(rect.right, rect.bottom); - path.lineTo(rect.left, rect.bottom); - if (bottom.width == 0.0) { - paint.style = PaintingStyle.stroke; - } else { - paint.style = PaintingStyle.fill; - path.lineTo(rect.left + left.width, rect.bottom - bottom.width); - path.lineTo(rect.right - right.width, rect.bottom - bottom.width); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: - break; - } - - switch (left.style) { - case BorderStyle.solid: - paint.color = left.color; - path = new Path(); - path.moveTo(rect.left, rect.bottom); - path.lineTo(rect.left, rect.top); - if (left.width == 0.0) { - paint.style = PaintingStyle.stroke; - } else { - paint.style = PaintingStyle.fill; - path.lineTo(rect.left + left.width, rect.top + top.width); - path.lineTo(rect.left + left.width, rect.bottom - bottom.width); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: - break; - } + paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); } - void _paintBorderWithRadius(Canvas canvas, Rect rect, - BorderRadius borderRadius) { + void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, + BorderRadius borderRadius) { assert(isUniform); final Paint paint = new Paint() ..color = top.color; @@ -461,7 +383,7 @@ class Border { } } - void _paintBorderWithCircle(Canvas canvas, Rect rect) { + void _paintUniformBorderWithCircle(Canvas canvas, Rect rect) { assert(isUniform); final double width = top.width; final Paint paint = new Paint() @@ -472,6 +394,16 @@ class Border { canvas.drawCircle(rect.center, radius, paint); } + void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect) { + assert(isUniform); + final double width = top.width; + final Paint paint = new Paint() + ..color = top.color + ..strokeWidth = width + ..style = PaintingStyle.stroke; + canvas.drawRect(rect.inflate(width / 2.0), paint); + } + @override bool operator ==(dynamic other) { if (identical(this, other)) @@ -489,5 +421,124 @@ class Border { int get hashCode => hashValues(top, right, bottom, left); @override - String toString() => 'Border($top, $right, $bottom, $left)'; + String toString() { + if (isUniform) + return 'Border.all($top)'; + return 'Border($top, $right, $bottom, $left)'; + } +} + +/// Paints a border around the given rectangle on the canvas. +/// +/// The four sides can be independently specified. They are painted in the order +/// top, right, bottom, left. This is only notable if the widths of the borders +/// and the size of the given rectangle are such that the border sides will +/// overlap each other. No effort is made to optimize the rendering of uniform +/// borders (where all the borders have the same configuration); to render a +/// uniform border, consider using [Canvas.drawRect] directly. +/// +/// The arguments must not be null. +/// +/// See also: +/// +/// * [paintImage], which paints an image in a rectangle on a canvas. +/// * [Border], which uses this function to paint its border when the border is +/// not uniform. +/// * [BoxDecoration], which describes its border using the [Border] class. +void paintBorder(Canvas canvas, Rect rect, { + BorderSide top: BorderSide.none, + BorderSide right: BorderSide.none, + BorderSide bottom: BorderSide.none, + BorderSide left: BorderSide.none, +}) { + assert(canvas != null); + assert(rect != null); + assert(top != null); + assert(right != null); + assert(bottom != null); + assert(left != null); + + // We draw the borders as filled shapes, unless the borders are hairline + // borders, in which case we use PaintingStyle.stroke, with the stroke width + // specified here. + final Paint paint = new Paint() + ..strokeWidth = 0.0; + + final Path path = new Path(); + + switch (top.style) { + case BorderStyle.solid: + paint.color = top.color; + path.reset(); + path.moveTo(rect.left, rect.top); + path.lineTo(rect.right, rect.top); + if (top.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.right - right.width, rect.top + top.width); + path.lineTo(rect.left + left.width, rect.top + top.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } + + switch (right.style) { + case BorderStyle.solid: + paint.color = right.color; + path.reset(); + path.moveTo(rect.right, rect.top); + path.lineTo(rect.right, rect.bottom); + if (right.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.right - right.width, rect.bottom - bottom.width); + path.lineTo(rect.right - right.width, rect.top + top.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } + + switch (bottom.style) { + case BorderStyle.solid: + paint.color = bottom.color; + path.reset(); + path.moveTo(rect.right, rect.bottom); + path.lineTo(rect.left, rect.bottom); + if (bottom.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.left + left.width, rect.bottom - bottom.width); + path.lineTo(rect.right - right.width, rect.bottom - bottom.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } + + switch (left.style) { + case BorderStyle.solid: + paint.color = left.color; + path.reset(); + path.moveTo(rect.left, rect.bottom); + path.lineTo(rect.left, rect.top); + if (left.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.left + left.width, rect.top + top.width); + path.lineTo(rect.left + left.width, rect.bottom - bottom.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } } diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index 7fce46d22f..2cde632ba2 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -580,7 +580,7 @@ Iterable _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im } } -/// Paints an image into the given rectangle in the canvas. +/// Paints an image into the given rectangle on the canvas. /// /// * `canvas`: The canvas onto which the image will be painted. /// * `rect`: The region of the canvas into which the image will be painted. @@ -612,6 +612,12 @@ Iterable _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im /// `alignment` is [FractionalOffset.bottomRight], the image will be as large /// as possible within `rect` and placed with its bottom right corner at the /// bottom right corner of `rect`. +/// +/// See also: +/// +/// * [paintBorder], which paints a border around a rectangle on a canvas. +/// * [DecorationImage], which holds a configuration for calling this function. +/// * [BoxDecoration], which uses this function to paint a [DecorationImage]. void paintImage({ @required Canvas canvas, @required Rect rect, diff --git a/packages/flutter/lib/src/rendering/table.dart b/packages/flutter/lib/src/rendering/table.dart index 7794d888b1..8ee958cbe9 100644 --- a/packages/flutter/lib/src/rendering/table.dart +++ b/packages/flutter/lib/src/rendering/table.dart @@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; import 'box.dart'; import 'object.dart'; +import 'table_border.dart'; /// Parent data used by [RenderTable] for its children. class TableCellParentData extends BoxParentData { @@ -311,130 +312,6 @@ class MinColumnWidth extends TableColumnWidth { String toString() => '$runtimeType($a, $b)'; } -/// Border specification for [RenderTable]. -/// -/// This is like [Border], with the addition of two sides: the inner -/// horizontal borders and the inner vertical borders. -class TableBorder extends Border { - /// Creates a border for a table. - /// - /// All the sides of the border default to [BorderSide.none]. - const TableBorder({ - BorderSide top: BorderSide.none, - BorderSide right: BorderSide.none, - BorderSide bottom: BorderSide.none, - BorderSide left: BorderSide.none, - this.horizontalInside: BorderSide.none, - this.verticalInside: BorderSide.none - }) : super( - top: top, - right: right, - bottom: bottom, - left: left - ); - - /// A uniform border with all sides the same color and width. - factory TableBorder.all({ - Color color: const Color(0xFF000000), - double width: 1.0 - }) { - final BorderSide side = new BorderSide(color: color, width: width); - return new 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 new TableBorder( - top: outside, - right: outside, - bottom: outside, - left: outside, - horizontalInside: inside, - verticalInside: inside - ); - } - - /// The horizontal interior sides of this border. - final BorderSide horizontalInside; - - /// The vertical interior sides of this border. - final BorderSide verticalInside; - - @override - bool get isUniform { - assert(horizontalInside != null); - assert(verticalInside != null); - if (!super.isUniform) - return false; - - final Color topColor = top.color; - if (horizontalInside.color != topColor || - verticalInside.color != topColor) - return false; - - final double topWidth = top.width; - if (horizontalInside.width != topWidth || - verticalInside.width != topWidth) - return false; - - final BorderStyle topStyle = top.style; - if (horizontalInside.style != topStyle || - verticalInside.style != topStyle) - return false; - - return true; - } - - @override - TableBorder scale(double t) { - return new TableBorder( - top: top.copyWith(width: t * top.width), - right: right.copyWith(width: t * right.width), - bottom: bottom.copyWith(width: t * bottom.width), - left: left.copyWith(width: t * left.width), - horizontalInside: horizontalInside.copyWith(width: t * horizontalInside.width), - verticalInside: verticalInside.copyWith(width: t * verticalInside.width) - ); - } - - /// Linearly interpolate between two table borders. - static TableBorder lerp(TableBorder a, TableBorder b, double t) { - if (a == null && b == null) - return null; - if (a == null) - return b.scale(t); - if (b == null) - return a.scale(1.0 - t); - return new 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) - ); - } - - @override - bool operator ==(dynamic other) { - if (super != other) - return false; - final TableBorder typedOther = other; - return horizontalInside == typedOther.horizontalInside && - verticalInside == typedOther.verticalInside; - } - - @override - int get hashCode => hashValues(super.hashCode, horizontalInside, verticalInside); - - @override - String toString() => 'TableBorder($top, $right, $bottom, $left, $horizontalInside, $verticalInside)'; -} - /// Vertical alignment options for cells in [RenderTable] objects. /// /// This is specified using [TableCellParentData] objects on the @@ -1240,41 +1117,9 @@ class RenderTable extends RenderBox { context.paintChild(child, childParentData.offset + offset); } } - canvas = context.canvas; - final Rect bounds = offset & size; - if (border != null) { - switch (border.verticalInside.style) { - case BorderStyle.solid: - final Paint paint = new Paint() - ..color = border.verticalInside.color - ..strokeWidth = border.verticalInside.width - ..style = PaintingStyle.stroke; - final Path path = new Path(); - for (int x = 1; x < columns; x += 1) { - path.moveTo(bounds.left + _columnLefts[x], bounds.top); - path.lineTo(bounds.left + _columnLefts[x], bounds.bottom); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: break; - } - switch (border.horizontalInside.style) { - case BorderStyle.solid: - final Paint paint = new Paint() - ..color = border.horizontalInside.color - ..strokeWidth = border.horizontalInside.width - ..style = PaintingStyle.stroke; - final Path path = new Path(); - for (int y = 1; y < rows; y += 1) { - path.moveTo(bounds.left, bounds.top + _rowTops[y]); - path.lineTo(bounds.right, bounds.top + _rowTops[y]); - } - canvas.drawPath(path, paint); - break; - case BorderStyle.none: break; - } - border.paint(canvas, bounds); - } + assert(_rows == _rowTops.length - 1); + assert(_columns == _columnLefts.length); + border?.paint(context.canvas, offset & size, rows: _rowTops, columns: _columnLefts); } @override diff --git a/packages/flutter/lib/src/rendering/table_border.dart b/packages/flutter/lib/src/rendering/table_border.dart new file mode 100644 index 0000000000..7f8a981287 --- /dev/null +++ b/packages/flutter/lib/src/rendering/table_border.dart @@ -0,0 +1,265 @@ +// Copyright 2015 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 '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 = new BorderSide(color: color, width: width, style: style); + return new 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 new 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 new EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width); + } + + /// Whether all four sides of the border 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 new border with the widths of this border multiplied by `t`. + TableBorder scale(double t) { + return new TableBorder( + top: top.copyWith(width: t * top.width), + right: right.copyWith(width: t * right.width), + bottom: bottom.copyWith(width: t * bottom.width), + left: left.copyWith(width: t * left.width), + horizontalInside: horizontalInside.copyWith(width: t * horizontalInside.width), + verticalInside: verticalInside.copyWith(width: t * verticalInside.width) + ); + } + + /// Linearly interpolate between two table borders. + /// + /// If a border is null, it is treated as having only [BorderSide.none] + /// borders. + static TableBorder lerp(TableBorder a, TableBorder b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b.scale(t); + if (b == null) + return a.scale(1.0 - t); + return new 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 of the rows, + /// specified in terms of the top of each row, in order from top to bottom, + /// relative to the given rectangle, with an additional entry for the bottom + /// of the last row (so the first entry should be zero and the last entry + /// should be `rect.height`). + /// + /// The `columns` argument has slightly different semantics; it specifies the + /// horizontal positions of the columns, specified in terms of the _left_ edge + /// of each row, relative to the given rectangle, in left-to-right order (so + /// the first entry should be zero). There is no extra entry for the right + /// edge of the last column. + /// + /// There must be at least one row and at least one column, so the `rows` list + /// should at a minimum have two values, and the `columns` argument one value. + /// + /// The [verticalInside] border is only drawn if there are at least columns + /// rows. The [horizontalInside] border is only drawn if there are at least + /// two rows. The vertical borders are drawn below the horizontal borders. + /// + /// The outer borders (in the order [top], [right], [bottom], [left], with + /// [left] above the others) are painted above the inner borders. + /// + /// The paint order is particularly notable in the case of + /// partially-transparent borders. + void paint(Canvas canvas, Rect rect, { + @required List rows, + @required List 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.length >= 2); + assert(rows.first == 0.0); + assert(rows.last == rect.height); + assert(columns != null); + assert(columns.length >= 2); + assert(columns.first == 0.0); + assert(columns.last == rect.width); + + final Paint paint = new Paint(); + final Path path = new Path(); + + switch (verticalInside.style) { + case BorderStyle.solid: + paint + ..color = verticalInside.color + ..strokeWidth = verticalInside.width + ..style = PaintingStyle.stroke; + path.reset(); + for (int x = 1; x < columns.length; x += 1) { + path.moveTo(rect.left + columns[x], rect.top); + path.lineTo(rect.left + columns[x], rect.bottom); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } + + switch (horizontalInside.style) { + case BorderStyle.solid: + paint + ..color = horizontalInside.color + ..strokeWidth = horizontalInside.width + ..style = PaintingStyle.stroke; + path.reset(); + for (int y = 1; y < rows.length; y += 1) { + path.moveTo(rect.left, rect.top + rows[y]); + path.lineTo(rect.right, rect.top + rows[y]); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: + break; + } + + paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + if (runtimeType != other.runtimeType) + return false; + final TableBorder typedOther = other; + return top == typedOther.top + && right == typedOther.right + && bottom == typedOther.bottom + && left == typedOther.left + && horizontalInside == typedOther.horizontalInside + && verticalInside == typedOther.verticalInside; + } + + @override + int get hashCode => hashValues(top, right, bottom, left, horizontalInside, verticalInside); + + @override + String toString() => 'TableBorder($top, $right, $bottom, $left, $horizontalInside, $verticalInside)'; +} diff --git a/packages/flutter/test/painting/box_painter_test.dart b/packages/flutter/test/painting/box_painter_test.dart index f1d901dc63..1900ce9634 100644 --- a/packages/flutter/test/painting/box_painter_test.dart +++ b/packages/flutter/test/painting/box_painter_test.dart @@ -70,7 +70,18 @@ void main() { expect( new Border.all(width: 4.0).toString(), equals( - 'Border(BorderSide(Color(0xff000000), 4.0, BorderStyle.solid), BorderSide(Color(0xff000000), 4.0, BorderStyle.solid), BorderSide(Color(0xff000000), 4.0, BorderStyle.solid), BorderSide(Color(0xff000000), 4.0, BorderStyle.solid))', + 'Border.all(BorderSide(Color(0xff000000), 4.0, BorderStyle.solid))', + ), + ); + expect( + const Border( + top: const BorderSide(width: 3.0), + right: const BorderSide(width: 3.0), + bottom: const BorderSide(width: 3.0), + left: const BorderSide(width: 3.0), + ).toString(), + equals( + 'Border.all(BorderSide(Color(0xff000000), 3.0, BorderStyle.solid))', ), ); }); diff --git a/packages/flutter/test/rendering/mock_canvas.dart b/packages/flutter/test/rendering/mock_canvas.dart index 8d7d18d8e5..da22c26193 100644 --- a/packages/flutter/test/rendering/mock_canvas.dart +++ b/packages/flutter/test/rendering/mock_canvas.dart @@ -138,6 +138,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawRect] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void rect({ Rect rect, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that a rounded rectangle clip is expected next. @@ -162,6 +168,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawRRect] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void rrect({ RRect rrect, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that a circle is expected next. @@ -174,6 +186,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawCircle] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void circle({ double x, double y, double radius, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that a path is expected next. @@ -189,6 +207,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawPath] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void path({ Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that a line is expected next. @@ -201,6 +225,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawLine] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void line({ Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that an arc is expected next. @@ -213,6 +243,12 @@ abstract class PaintPattern { /// /// Any calls made between the last matched call (if any) and the /// [Canvas.drawArc] call are ignored. + /// + /// The [Paint]-related arguments (`color`, `strokeWidth`, `hasMaskFilter`, + /// `style`) are compared against the state of the [Paint] object after the + /// painting has completed, not at the time of the call. If the same [Paint] + /// object is reused multiple times, then this may not match the actual + /// arguments as they were seen by the method. void arc({ Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }); /// Indicates that a paragraph is expected next. diff --git a/packages/flutter/test/rendering/recording_canvas.dart b/packages/flutter/test/rendering/recording_canvas.dart index df704e750d..ed046b32ae 100644 --- a/packages/flutter/test/rendering/recording_canvas.dart +++ b/packages/flutter/test/rendering/recording_canvas.dart @@ -13,6 +13,12 @@ class RecordedInvocation { const RecordedInvocation(this.invocation, { this.stack }); /// The method that was called and its arguments. + /// + /// The arguments preserve identity, but not value. Thus, if two invocations + /// were made with the same [Paint] object, but with that object configured + /// differently each time, then they will both have the same object as their + /// argument, and inspecting that object will return the object's current + /// values (mostly likely those passed to the second call). final Invocation invocation; /// The stack trace at the time of the method call. diff --git a/packages/flutter/test/widgets/box_decoration_test.dart b/packages/flutter/test/widgets/box_decoration_test.dart index 667652b416..abf831c265 100644 --- a/packages/flutter/test/widgets/box_decoration_test.dart +++ b/packages/flutter/test/widgets/box_decoration_test.dart @@ -58,19 +58,13 @@ void main() { await tester.pumpWidget(buildFrame(new Border.all())); expect(find.byKey(key), paints - ..path(color: black, style: PaintingStyle.fill) - ..path(color: black, style: PaintingStyle.fill) - ..path(color: black, style: PaintingStyle.fill) - ..path(color: black, style: PaintingStyle.fill)); + ..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 1.0)); await tester.pumpWidget(buildFrame(new Border.all(width: 0.0))); expect(find.byKey(key), paints - ..path(color: black, style: PaintingStyle.stroke) - ..path(color: black, style: PaintingStyle.stroke) - ..path(color: black, style: PaintingStyle.stroke) - ..path(color: black, style: PaintingStyle.stroke)); + ..rect(color: black, style: PaintingStyle.stroke, strokeWidth: 0.0)); - final Color green = const Color(0xFF000000); + final Color green = const Color(0xFF00FF00); final BorderSide greenSide = new BorderSide(color: green, width: 10.0); await tester.pumpWidget(buildFrame(new Border(top: greenSide))); @@ -84,6 +78,15 @@ void main() { await tester.pumpWidget(buildFrame(new Border(bottom: greenSide))); expect(find.byKey(key), paints..path(color: green, style: PaintingStyle.fill)); + + final Color blue = const Color(0xFF0000FF); + final BorderSide blueSide = new BorderSide(color: blue, width: 0.0); + + await tester.pumpWidget(buildFrame(new Border(top: blueSide, right: greenSide, bottom: greenSide))); + expect(find.byKey(key), paints + ..path() // There's not much point checking the arguments to these calls because paintBorder + ..path() // reuses the same Paint object each time, configured differently, and so they will + ..path()); // all appear to have the same settings here (that of the last call). }); @@ -95,9 +98,9 @@ void main() { Widget buildFrame(Border border) { itemsTapped = []; return new Center( - child: new GestureDetector( - behavior: HitTestBehavior.deferToChild, - child: new Container( + child: new GestureDetector( + behavior: HitTestBehavior.deferToChild, + child: new Container( key: key, width: 100.0, height: 50.0, @@ -121,7 +124,7 @@ void main() { await tester.tapAt(const Offset(449.0, 324.0)); expect(itemsTapped, [1,1,1]); - + }); testWidgets('Can hit test on BoxDecoration circle', (WidgetTester tester) async { @@ -132,7 +135,7 @@ void main() { Widget buildFrame(Border border) { itemsTapped = []; return new Center( - child: new GestureDetector( + child: new GestureDetector( behavior: HitTestBehavior.deferToChild, child: new Container( key: key, @@ -161,7 +164,7 @@ void main() { await tester.tap(find.byKey(key)); expect(itemsTapped, [1,1]); - + }); }