Commit 9d59fb0c authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Split TableBorder from Border (#12104)

This will enable both to be RTL'ed.

Also, factor out common border painting code into paintBorder.
Also, make Border paint uniform non-rounded borders using drawRect.
Also, add some documentation about an issue that wasted an hour of my life.
Also, factor out all the border painting code into TableBorder.paint.
parent b5448d46
......@@ -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';
......
......@@ -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);
if (shape == BoxShape.circle) {
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
_paintUniformBorderWithCircle(canvas, rect);
return;
}
if (shape == BoxShape.circle) {
assert(borderRadius == null);
_paintBorderWithCircle(canvas, rect);
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(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;
}
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.');
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;
}
}
......@@ -580,7 +580,7 @@ Iterable<Rect> _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<Rect> _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,
......
......@@ -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
......
// 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<double> rows,
@required List<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.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)';
}
......@@ -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))',
),
);
});
......
......@@ -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.
......
......@@ -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.
......
......@@ -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 = <int>[];
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, <int>[1,1,1]);
});
testWidgets('Can hit test on BoxDecoration circle', (WidgetTester tester) async {
......@@ -132,7 +135,7 @@ void main() {
Widget buildFrame(Border border) {
itemsTapped = <int>[];
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, <int>[1,1]);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment