Add OvalBorder and BoxShape.oval (#103833)
This commit is contained in:
parent
c8c2e3d399
commit
22cb06b57f
@ -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)';
|
||||
}
|
||||
}
|
||||
|
65
packages/flutter/lib/src/painting/oval_border.dart
Normal file
65
packages/flutter/lib/src/painting/oval_border.dart
Normal file
@ -0,0 +1,65 @@
|
||||
// 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));
|
||||
});
|
||||
|
48
packages/flutter/test/painting/oval_border_test.dart
Normal file
48
packages/flutter/test/painting/oval_border_test.dart
Normal file
@ -0,0 +1,48 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user