Unverified Commit 22cb06b5 authored by Bernardo Ferrari's avatar Bernardo Ferrari Committed by GitHub

Add OvalBorder and BoxShape.oval (#103833)

parent c8c2e3d3
......@@ -49,6 +49,7 @@ export 'src/painting/image_stream.dart';
export 'src/painting/inline_span.dart';
export 'src/painting/matrix_utils.dart';
export 'src/painting/notched_shapes.dart';
export 'src/painting/oval_border.dart';
export 'src/painting/paint_utilities.dart';
export 'src/painting/placeholder_span.dart';
export 'src/painting/rounded_rectangle_border.dart';
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
......@@ -18,16 +18,32 @@ import 'edge_insets.dart';
/// When applied to a rectangular space, the border paints in the center of the
/// rectangle.
///
/// The [eccentricity] parameter describes how much a circle will deform to
/// fit the rectangle it is a border for. A value of zero implies no
/// deformation (a circle touching at least two sides of the rectangle), a
/// value of one implies full deformation (an oval touching all sides of the
/// rectangle).
///
/// See also:
///
/// * [OvalBorder], which draws a Circle touching all the edges of the box.
/// * [BorderSide], which is used to describe each side of the box.
/// * [Border], which, when used with [BoxDecoration], can also
/// describe a circle.
/// * [Border], which, when used with [BoxDecoration], can also describe a circle.
class CircleBorder extends OutlinedBorder {
/// Create a circle border.
///
/// The [side] argument must not be null.
const CircleBorder({ super.side }) : assert(side != null);
const CircleBorder({ super.side, this.eccentricity = 0.0 })
: assert(side != null),
assert(eccentricity != null),
assert(eccentricity >= 0.0, 'The eccentricity argument $eccentricity is not greater than or equal to zero.'),
assert(eccentricity <= 1.0, 'The eccentricity argument $eccentricity is not less than or equal to one.');
/// Defines the ratio (0.0-1.0) from which the border will deform
/// to fit a rectangle.
/// When 0.0, it draws a circle touching at least two sides of the rectangle.
/// When 1.0, it draws an oval touching all sides of the rectangle.
final double eccentricity;
@override
EdgeInsetsGeometry get dimensions {
......@@ -42,12 +58,15 @@ class CircleBorder extends OutlinedBorder {
}
@override
ShapeBorder scale(double t) => CircleBorder(side: side.scale(t));
ShapeBorder scale(double t) => CircleBorder(side: side.scale(t), eccentricity: eccentricity);
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is CircleBorder) {
return CircleBorder(side: BorderSide.lerp(a.side, side, t));
return CircleBorder(
side: BorderSide.lerp(a.side, side, t),
eccentricity: clampDouble(ui.lerpDouble(a.eccentricity, eccentricity, t)!, 0.0, 1.0),
);
}
return super.lerpFrom(a, t);
}
......@@ -55,45 +74,40 @@ class CircleBorder extends OutlinedBorder {
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is CircleBorder) {
return CircleBorder(side: BorderSide.lerp(side, b.side, t));
return CircleBorder(
side: BorderSide.lerp(side, b.side, t),
eccentricity: clampDouble(ui.lerpDouble(eccentricity, b.eccentricity, t)!, 0.0, 1.0),
);
}
return super.lerpTo(b, t);
}
@override
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
final double radius = rect.shortestSide / 2.0;
final double adjustedRadius;
final double delta;
switch (side.strokeAlign) {
case StrokeAlign.inside:
adjustedRadius = radius - side.width;
delta = side.width;
break;
case StrokeAlign.center:
adjustedRadius = radius - side.width / 2.0;
delta = side.width / 2.0;
break;
case StrokeAlign.outside:
adjustedRadius = radius;
delta = 0;
break;
}
return Path()
..addOval(Rect.fromCircle(
center: rect.center,
radius: math.max(0.0, adjustedRadius),
));
final Rect adjustedRect = _adjustRect(rect).deflate(delta);
return Path()..addOval(adjustedRect);
}
@override
Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addOval(Rect.fromCircle(
center: rect.center,
radius: rect.shortestSide / 2.0,
));
return Path()..addOval(_adjustRect(rect));
}
@override
CircleBorder copyWith({ BorderSide? side }) {
return CircleBorder(side: side ?? this.side);
CircleBorder copyWith({ BorderSide? side, double? eccentricity }) {
return CircleBorder(side: side ?? this.side, eccentricity: eccentricity ?? this.eccentricity);
}
@override
......@@ -102,19 +116,59 @@ class CircleBorder extends OutlinedBorder {
case BorderStyle.none:
break;
case BorderStyle.solid:
final double radius;
switch (side.strokeAlign) {
case StrokeAlign.inside:
radius = (rect.shortestSide - side.width) / 2.0;
break;
case StrokeAlign.center:
radius = rect.shortestSide / 2.0;
break;
case StrokeAlign.outside:
radius = (rect.shortestSide + side.width) / 2.0;
break;
if (eccentricity != 0.0) {
final Rect borderRect = _adjustRect(rect);
final Rect adjustedRect;
switch (side.strokeAlign) {
case StrokeAlign.inside:
adjustedRect = borderRect.deflate(side.width / 2.0);
break;
case StrokeAlign.center:
adjustedRect = borderRect;
break;
case StrokeAlign.outside:
adjustedRect = borderRect.inflate(side.width / 2.0);
break;
}
canvas.drawOval(adjustedRect, side.toPaint());
} else {
final double radius;
switch (side.strokeAlign) {
case StrokeAlign.inside:
radius = (rect.shortestSide - side.width) / 2.0;
break;
case StrokeAlign.center:
radius = rect.shortestSide / 2.0;
break;
case StrokeAlign.outside:
radius = (rect.shortestSide + side.width) / 2.0;
break;
}
canvas.drawCircle(rect.center, radius, side.toPaint());
}
canvas.drawCircle(rect.center, radius, side.toPaint());
}
}
Rect _adjustRect(Rect rect) {
if (eccentricity == 0.0 || rect.width == rect.height) {
return Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0);
}
if (rect.width < rect.height) {
final double delta = (1.0 - eccentricity) * (rect.height - rect.width) / 2.0;
return Rect.fromLTRB(
rect.left,
rect.top + delta,
rect.right,
rect.bottom - delta,
);
} else {
final double delta = (1.0 - eccentricity) * (rect.width - rect.height) / 2.0;
return Rect.fromLTRB(
rect.left + delta,
rect.top,
rect.right - delta,
rect.bottom,
);
}
}
......@@ -124,14 +178,18 @@ class CircleBorder extends OutlinedBorder {
return false;
}
return other is CircleBorder
&& other.side == side;
&& other.side == side
&& other.eccentricity == eccentricity;
}
@override
int get hashCode => side.hashCode;
int get hashCode => Object.hash(side, eccentricity);
@override
String toString() {
if (eccentricity != 0.0) {
return '${objectRuntimeType(this, 'CircleBorder')}($side, eccentricity: $eccentricity)';
}
return '${objectRuntimeType(this, 'CircleBorder')}($side)';
}
}
// 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 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'borders.dart';
import 'circle_border.dart';
/// A border that fits an elliptical shape.
///
/// Typically used with [ShapeDecoration] to draw an oval. Instead of centering
/// the [Border] to a square, like [CircleBorder], it fills the available space,
/// such that it touches the edges of the box. There is no difference between
/// `CircleBorder(eccentricity = 1.0)` and `OvalBorder()`. [OvalBorder] works as
/// an alias for users to discover this feature.
///
/// See also:
///
/// * [CircleBorder], which draws a circle, centering when the box is rectangular.
/// * [Border], which, when used with [BoxDecoration], can also describe an oval.
class OvalBorder extends CircleBorder {
/// Create an oval border.
const OvalBorder({ super.side, super.eccentricity = 1.0 });
@override
ShapeBorder scale(double t) => OvalBorder(side: side.scale(t), eccentricity: eccentricity);
@override
OvalBorder copyWith({ BorderSide? side, double? eccentricity }) {
return OvalBorder(side: side ?? this.side, eccentricity: eccentricity ?? this.eccentricity);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is OvalBorder) {
return OvalBorder(
side: BorderSide.lerp(a.side, side, t),
eccentricity: clampDouble(ui.lerpDouble(a.eccentricity, eccentricity, t)!, 0.0, 1.0),
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is OvalBorder) {
return OvalBorder(
side: BorderSide.lerp(side, b.side, t),
eccentricity: clampDouble(ui.lerpDouble(eccentricity, b.eccentricity, t)!, 0.0, 1.0),
);
}
return super.lerpTo(b, t);
}
@override
String toString() {
if (eccentricity != 1.0) {
return '${objectRuntimeType(this, 'OvalBorder')}($side, eccentricity: $eccentricity)';
}
return '${objectRuntimeType(this, 'OvalBorder')}($side)';
}
}
......@@ -71,6 +71,7 @@ class RoundedRectangleBorder extends OutlinedBorder {
side: BorderSide.lerp(a.side, side, t),
borderRadius: borderRadius,
circleness: 1.0 - t,
eccentricity: a.eccentricity,
);
}
return super.lerpFrom(a, t);
......@@ -90,6 +91,7 @@ class RoundedRectangleBorder extends OutlinedBorder {
side: BorderSide.lerp(side, b.side, t),
borderRadius: borderRadius,
circleness: t,
eccentricity: b.eccentricity,
);
}
return super.lerpTo(b, t);
......@@ -187,17 +189,25 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
super.side,
this.borderRadius = BorderRadius.zero,
required this.circleness,
required this.eccentricity,
}) : assert(side != null),
assert(borderRadius != null),
assert(circleness != null);
final BorderRadiusGeometry borderRadius;
final double circleness;
final double eccentricity;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
switch (side.strokeAlign) {
case StrokeAlign.inside:
return EdgeInsets.all(side.width);
case StrokeAlign.center:
return EdgeInsets.all(side.width / 2);
case StrokeAlign.outside:
return EdgeInsets.zero;
}
}
@override
......@@ -206,6 +216,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: side.scale(t),
borderRadius: borderRadius * t,
circleness: t,
eccentricity: eccentricity,
);
}
......@@ -217,6 +228,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
circleness: circleness * t,
eccentricity: eccentricity,
);
}
if (a is CircleBorder) {
......@@ -224,6 +236,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(a.side, side, t),
borderRadius: borderRadius,
circleness: circleness + (1.0 - circleness) * (1.0 - t),
eccentricity: a.eccentricity,
);
}
if (a is _RoundedRectangleToCircleBorder) {
......@@ -231,6 +244,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
circleness: ui.lerpDouble(a.circleness, circleness, t)!,
eccentricity: eccentricity,
);
}
return super.lerpFrom(a, t);
......@@ -243,6 +257,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
circleness: circleness * (1.0 - t),
eccentricity: eccentricity,
);
}
if (b is CircleBorder) {
......@@ -250,6 +265,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(side, b.side, t),
borderRadius: borderRadius,
circleness: circleness + (1.0 - circleness) * t,
eccentricity: b.eccentricity,
);
}
if (b is _RoundedRectangleToCircleBorder) {
......@@ -257,6 +273,7 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
circleness: ui.lerpDouble(circleness, b.circleness, t)!,
eccentricity: eccentricity,
);
}
return super.lerpTo(b, t);
......@@ -267,7 +284,8 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
return rect;
}
if (rect.width < rect.height) {
final double delta = circleness * (rect.height - rect.width) / 2.0;
final double partialDelta = (rect.height - rect.width) / 2;
final double delta = circleness * partialDelta * (1.0 - eccentricity);
return Rect.fromLTRB(
rect.left,
rect.top + delta,
......@@ -275,7 +293,8 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
rect.bottom - delta,
);
} else {
final double delta = circleness * (rect.width - rect.height) / 2.0;
final double partialDelta = (rect.width - rect.height) / 2;
final double delta = circleness * partialDelta * (1.0 - eccentricity);
return Rect.fromLTRB(
rect.left + delta,
rect.top,
......@@ -290,7 +309,22 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
if (circleness == 0.0) {
return resolvedRadius;
}
return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), circleness);
if (eccentricity != 0.0) {
if (rect.width < rect.height) {
return BorderRadius.lerp(
resolvedRadius,
BorderRadius.all(Radius.elliptical(rect.width / 2, (0.5 + eccentricity / 2) * rect.height / 2)),
circleness,
)!;
} else {
return BorderRadius.lerp(
resolvedRadius,
BorderRadius.all(Radius.elliptical((0.5 + eccentricity / 2) * rect.width / 2, rect.height / 2)),
circleness,
)!;
}
}
return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2), circleness);
}
@override
......@@ -319,11 +353,12 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
}
@override
_RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, double? circleness }) {
_RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, double? circleness, double? eccentricity }) {
return _RoundedRectangleToCircleBorder(
side: side ?? this.side,
borderRadius: borderRadius ?? this.borderRadius,
circleness: circleness ?? this.circleness,
eccentricity: eccentricity ?? this.eccentricity,
);
}
......@@ -371,6 +406,9 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder {
@override
String toString() {
if (eccentricity != 0.0) {
return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder that is ${(eccentricity * 100).toStringAsFixed(1)}% oval)';
}
return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
}
}
......@@ -55,6 +55,7 @@ class StadiumBorder extends OutlinedBorder {
return _StadiumToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
circleness: 1.0 - t,
eccentricity: a.eccentricity,
);
}
if (a is RoundedRectangleBorder) {
......@@ -77,6 +78,7 @@ class StadiumBorder extends OutlinedBorder {
return _StadiumToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
circleness: t,
eccentricity: b.eccentricity,
);
}
if (b is RoundedRectangleBorder) {
......@@ -127,7 +129,7 @@ class StadiumBorder extends OutlinedBorder {
case BorderStyle.none:
break;
case BorderStyle.solid:
final Radius radius = Radius.circular(rect.shortestSide / 2.0);
final Radius radius = Radius.circular(rect.shortestSide / 2);
final RRect borderRect = RRect.fromRectAndRadius(rect, radius);
final RRect adjustedRect;
switch (side.strokeAlign) {
......@@ -138,7 +140,7 @@ class StadiumBorder extends OutlinedBorder {
adjustedRect = borderRect;
break;
case StrokeAlign.outside:
adjustedRect = borderRect.inflate(side.width /2);
adjustedRect = borderRect.inflate(side.width / 2);
break;
}
canvas.drawRRect(
......@@ -171,14 +173,23 @@ class _StadiumToCircleBorder extends OutlinedBorder {
const _StadiumToCircleBorder({
super.side,
this.circleness = 0.0,
required this.eccentricity,
}) : assert(side != null),
assert(circleness != null);
final double circleness;
final double eccentricity;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
switch (side.strokeAlign) {
case StrokeAlign.inside:
return EdgeInsets.all(side.width);
case StrokeAlign.center:
return EdgeInsets.all(side.width / 2);
case StrokeAlign.outside:
return EdgeInsets.zero;
}
}
@override
......@@ -186,6 +197,7 @@ class _StadiumToCircleBorder extends OutlinedBorder {
return _StadiumToCircleBorder(
side: side.scale(t),
circleness: t,
eccentricity: eccentricity,
);
}
......@@ -196,18 +208,21 @@ class _StadiumToCircleBorder extends OutlinedBorder {
return _StadiumToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
circleness: circleness * t,
eccentricity: eccentricity,
);
}
if (a is CircleBorder) {
return _StadiumToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
circleness: circleness + (1.0 - circleness) * (1.0 - t),
eccentricity: a.eccentricity,
);
}
if (a is _StadiumToCircleBorder) {
return _StadiumToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
circleness: ui.lerpDouble(a.circleness, circleness, t)!,
eccentricity: ui.lerpDouble(a.eccentricity, eccentricity, t)!,
);
}
return super.lerpFrom(a, t);
......@@ -220,18 +235,21 @@ class _StadiumToCircleBorder extends OutlinedBorder {
return _StadiumToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
circleness: circleness * (1.0 - t),
eccentricity: eccentricity,
);
}
if (b is CircleBorder) {
return _StadiumToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
circleness: circleness + (1.0 - circleness) * t,
eccentricity: b.eccentricity,
);
}
if (b is _StadiumToCircleBorder) {
return _StadiumToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
circleness: ui.lerpDouble(circleness, b.circleness, t)!,
eccentricity: ui.lerpDouble(eccentricity, b.eccentricity, t)!,
);
}
return super.lerpTo(b, t);
......@@ -242,7 +260,8 @@ class _StadiumToCircleBorder extends OutlinedBorder {
return rect;
}
if (rect.width < rect.height) {
final double delta = circleness * (rect.height - rect.width) / 2.0;
final double partialDelta = (rect.height - rect.width) / 2;
final double delta = circleness * partialDelta * (1.0 - eccentricity);
return Rect.fromLTRB(
rect.left,
rect.top + delta,
......@@ -250,7 +269,8 @@ class _StadiumToCircleBorder extends OutlinedBorder {
rect.bottom - delta,
);
} else {
final double delta = circleness * (rect.width - rect.height) / 2.0;
final double partialDelta = (rect.width - rect.height) / 2;
final double delta = circleness * partialDelta * (1.0 - eccentricity);
return Rect.fromLTRB(
rect.left + delta,
rect.top,
......@@ -261,7 +281,23 @@ class _StadiumToCircleBorder extends OutlinedBorder {
}
BorderRadius _adjustBorderRadius(Rect rect) {
return BorderRadius.circular(rect.shortestSide / 2.0);
final BorderRadius circleRadius = BorderRadius.circular(rect.shortestSide / 2);
if (eccentricity != 0.0) {
if (rect.width < rect.height) {
return BorderRadius.lerp(
circleRadius,
BorderRadius.all(Radius.elliptical(rect.width / 2, (0.5 + eccentricity / 2) * rect.height / 2)),
circleness,
)!;
} else {
return BorderRadius.lerp(
circleRadius,
BorderRadius.all(Radius.elliptical((0.5 + eccentricity / 2) * rect.width / 2, rect.height / 2)),
circleness,
)!;
}
}
return circleRadius;
}
@override
......@@ -277,10 +313,11 @@ class _StadiumToCircleBorder extends OutlinedBorder {
}
@override
_StadiumToCircleBorder copyWith({ BorderSide? side, double? circleness }) {
_StadiumToCircleBorder copyWith({ BorderSide? side, double? circleness, double? eccentricity }) {
return _StadiumToCircleBorder(
side: side ?? this.side,
circleness: circleness ?? this.circleness,
eccentricity: eccentricity ?? this.eccentricity,
);
}
......@@ -327,8 +364,10 @@ class _StadiumToCircleBorder extends OutlinedBorder {
@override
String toString() {
return 'StadiumBorder($side, ${(circleness * 100).toStringAsFixed(1)}% '
'of the way to being a CircleBorder)';
if (eccentricity != 0.0) {
return 'StadiumBorder($side, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder that is ${(eccentricity * 100).toStringAsFixed(1)}% oval)';
}
return 'StadiumBorder($side, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
}
}
......
......@@ -2014,11 +2014,11 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
RRect get _defaultClip {
assert(hasSize);
assert(_shape != null);
final Rect rect = Offset.zero & size;
switch (_shape) {
case BoxShape.rectangle:
return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size);
return (borderRadius ?? BorderRadius.zero).toRRect(rect);
case BoxShape.circle:
final Rect rect = Offset.zero & size;
return RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
}
}
......
......@@ -12,11 +12,29 @@ void main() {
test('CircleBorder defaults', () {
const CircleBorder border = CircleBorder();
expect(border.side, BorderSide.none);
expect(border.eccentricity, 0.0);
});
test('CircleBorder getInnerPath and getOuterPath', () {
const Rect circleRect = Rect.fromLTWH(50, 0, 100, 100);
const Rect rect = Rect.fromLTWH(0, 0, 200, 100);
expect(const CircleBorder().getInnerPath(rect).getBounds(), circleRect);
expect(const CircleBorder().getOuterPath(rect).getBounds(), circleRect);
const CircleBorder oval = CircleBorder(eccentricity: 1.0);
expect(oval.getOuterPath(rect).getBounds(), rect);
expect(oval.getInnerPath(rect).getBounds(), rect);
const CircleBorder o10 = CircleBorder(side: BorderSide(width: 10.0), eccentricity: 1.0);
expect(o10.getOuterPath(rect).getBounds(), Offset.zero & const Size(200, 100));
expect(o10.getInnerPath(rect).getBounds(), const Offset(10, 10) & const Size(180, 80));
});
test('CircleBorder copyWith, ==, hashCode', () {
expect(const CircleBorder(), const CircleBorder().copyWith());
expect(const CircleBorder().hashCode, const CircleBorder().copyWith().hashCode);
expect(const CircleBorder(eccentricity: 0.5).hashCode, const CircleBorder().copyWith(eccentricity: 0.5).hashCode);
const BorderSide side = BorderSide(width: 10.0, color: Color(0xff123456));
expect(const CircleBorder().copyWith(side: side), const CircleBorder(side: side));
});
......
// 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/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
test('OvalBorder defaults', () {
const OvalBorder border = OvalBorder();
expect(border.side, BorderSide.none);
});
test('OvalBorder copyWith, ==, hashCode', () {
expect(const OvalBorder(), const OvalBorder().copyWith());
expect(const OvalBorder().hashCode, const OvalBorder().copyWith().hashCode);
const BorderSide side = BorderSide(width: 10.0, color: Color(0xff123456));
expect(const OvalBorder().copyWith(side: side), const OvalBorder(side: side));
});
test('OvalBorder', () {
const OvalBorder c10 = OvalBorder(side: BorderSide(width: 10.0));
const OvalBorder c15 = OvalBorder(side: BorderSide(width: 15.0));
const OvalBorder c20 = OvalBorder(side: BorderSide(width: 20.0));
expect(c10.dimensions, const EdgeInsets.all(10.0));
expect(c10.scale(2.0), c20);
expect(c20.scale(0.5), c10);
expect(ShapeBorder.lerp(c10, c20, 0.0), c10);
expect(ShapeBorder.lerp(c10, c20, 0.5), c15);
expect(ShapeBorder.lerp(c10, c20, 1.0), c20);
expect(
c10.getInnerPath(const Rect.fromLTWH(0, 0, 100, 40)),
isPathThat(
includes: const <Offset>[ Offset(12, 19), Offset(50, 10), Offset(88, 19), Offset(50, 29) ],
excludes: const <Offset>[ Offset(17, 26), Offset(15, 15), Offset(74, 10), Offset(76, 28) ],
),
);
expect(
c10.getOuterPath(const Rect.fromLTWH(0, 0, 100, 20)),
isPathThat(
includes: const <Offset>[ Offset(2, 9), Offset(50, 0), Offset(98, 9), Offset(50, 19) ],
excludes: const <Offset>[ Offset(7, 16), Offset(10, 2), Offset(84, 1), Offset(86, 18) ],
),
);
});
}
......@@ -45,13 +45,26 @@ void main() {
test('ShapeDecoration.lerp and hit test', () {
const Decoration a = ShapeDecoration(shape: CircleBorder());
const Decoration b = ShapeDecoration(shape: RoundedRectangleBorder());
const Decoration c = ShapeDecoration(shape: OvalBorder());
expect(Decoration.lerp(a, b, 0.0), a);
expect(Decoration.lerp(a, b, 1.0), b);
expect(Decoration.lerp(a, c, 0.0), a);
expect(Decoration.lerp(a, c, 1.0), c);
expect(Decoration.lerp(b, c, 0.0), b);
expect(Decoration.lerp(b, c, 1.0), c);
const Size size = Size(200.0, 100.0); // at t=0.5, width will be 150 (x=25 to x=175).
expect(a.hitTest(size, const Offset(20.0, 50.0)), isFalse);
expect(c.hitTest(size, const Offset(50, 5.0)), isFalse);
expect(c.hitTest(size, const Offset(5, 30.0)), isFalse);
expect(Decoration.lerp(a, b, 0.1)!.hitTest(size, const Offset(20.0, 50.0)), isFalse);
expect(Decoration.lerp(a, b, 0.5)!.hitTest(size, const Offset(20.0, 50.0)), isFalse);
expect(Decoration.lerp(a, b, 0.9)!.hitTest(size, const Offset(20.0, 50.0)), isTrue);
expect(Decoration.lerp(a, c, 0.1)!.hitTest(size, const Offset(30.0, 50.0)), isFalse);
expect(Decoration.lerp(a, c, 0.5)!.hitTest(size, const Offset(30.0, 50.0)), isTrue);
expect(Decoration.lerp(a, c, 0.9)!.hitTest(size, const Offset(30.0, 50.0)), isTrue);
expect(Decoration.lerp(b, c, 0.1)!.hitTest(size, const Offset(45.0, 10.0)), isTrue);
expect(Decoration.lerp(b, c, 0.5)!.hitTest(size, const Offset(30.0, 10.0)), isTrue);
expect(Decoration.lerp(b, c, 0.9)!.hitTest(size, const Offset(10.0, 30.0)), isTrue);
expect(b.hitTest(size, const Offset(20.0, 50.0)), isTrue);
});
......@@ -107,6 +120,16 @@ void main() {
);
expect(clipPath, isLookLikeExpectedPath);
});
test('ShapeDecoration.getClipPath for oval', () {
const ShapeDecoration decoration = ShapeDecoration(shape: OvalBorder());
const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 50.0);
final Path clipPath = decoration.getClipPath(rect, TextDirection.ltr);
final Matcher isLookLikeExpectedPath = isPathThat(
includes: const <Offset>[ Offset(50.0, 10.0), ],
excludes: const <Offset>[ Offset(1.0, 1.0), Offset(15.0, 1.0), Offset(99.0, 19.0), ],
);
expect(clipPath, isLookLikeExpectedPath);
});
}
class TestImageProvider extends ImageProvider<TestImageProvider> {
......
......@@ -1525,19 +1525,15 @@ class _RendersOnPhysicalModel extends _MatchRenderObject<RenderPhysicalShape, Re
return false;
}
if (
borderRadius == null &&
if (borderRadius == null &&
shape == BoxShape.rectangle &&
!assertRoundedRectangle(shapeClipper, BorderRadius.zero, matchState)
) {
!assertRoundedRectangle(shapeClipper, BorderRadius.zero, matchState)) {
return false;
}
if (
borderRadius == null &&
if (borderRadius == null &&
shape == BoxShape.circle &&
!assertCircle(shapeClipper, matchState)
) {
!assertCircle(shapeClipper, matchState)) {
return false;
}
......
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