mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Adding proper accommodation for textScaleFactor in chips, and StadiumBorder border. (#12533)
In order to allow chips to be properly drawn when they expand in size (without using IntrinsicHeight), I needed a BoxDecoration shape that would be dependent upon the rendered height of the widget. This seemed to be pretty generally useful, so I added a new ShapeDecoration called StadiumBorder. It uses the minimum dimension to adjust the BorderRadius of a rounded rect in the shape decoration. I also converted some uses of BoxShape to be case statements, updated the chips to use the StadiumBorder decoration, and updated some of the metrics to match the Material spec, as well as implementing lerping to and from StadiumBorder.
This commit is contained in:
parent
5c1320e5b9
commit
05e10633f2
@ -28,7 +28,7 @@ class _ChipDemoState extends State<ChipDemo> {
|
||||
),
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(child: const Text('B')),
|
||||
label: const Text('Blueberry')
|
||||
label: const Text('Blueberry'),
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -37,6 +37,7 @@ export 'src/painting/images.dart';
|
||||
export 'src/painting/matrix_utils.dart';
|
||||
export 'src/painting/rounded_rectangle_border.dart';
|
||||
export 'src/painting/shape_decoration.dart';
|
||||
export 'src/painting/stadium_border.dart';
|
||||
export 'src/painting/text_painter.dart';
|
||||
export 'src/painting/text_span.dart';
|
||||
export 'src/painting/text_style.dart';
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'debug.dart';
|
||||
@ -11,17 +12,6 @@ import 'feedback.dart';
|
||||
import 'icons.dart';
|
||||
import 'tooltip.dart';
|
||||
|
||||
const double _kChipHeight = 32.0;
|
||||
const double _kAvatarDiamater = _kChipHeight;
|
||||
|
||||
const TextStyle _kLabelStyle = const TextStyle(
|
||||
inherit: false,
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
);
|
||||
|
||||
/// A material design chip.
|
||||
///
|
||||
/// Chips represent complex entities in small blocks, such as a contact.
|
||||
@ -29,7 +19,8 @@ const TextStyle _kLabelStyle = const TextStyle(
|
||||
/// Supplying a non-null [onDeleted] callback will cause the chip to include a
|
||||
/// button for deleting the chip.
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget.
|
||||
/// Requires one of its ancestors to be a [Material] widget. The [label]
|
||||
/// and [border] arguments must not be null.
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
@ -57,11 +48,25 @@ class Chip extends StatelessWidget {
|
||||
this.avatar,
|
||||
@required this.label,
|
||||
this.onDeleted,
|
||||
this.labelStyle,
|
||||
TextStyle labelStyle,
|
||||
this.deleteButtonTooltipMessage,
|
||||
this.backgroundColor,
|
||||
this.deleteIconColor,
|
||||
}) : super(key: key);
|
||||
this.border: const StadiumBorder(),
|
||||
}) : assert(label != null),
|
||||
assert(border != null),
|
||||
labelStyle = labelStyle ?? _defaultLabelStyle,
|
||||
super(key: key);
|
||||
|
||||
static const TextStyle _defaultLabelStyle = const TextStyle(
|
||||
inherit: false,
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
);
|
||||
|
||||
static const double _chipHeight = 32.0;
|
||||
|
||||
/// A widget to display prior to the chip's label.
|
||||
///
|
||||
@ -90,6 +95,11 @@ class Chip extends StatelessWidget {
|
||||
/// widget's label.
|
||||
final Color backgroundColor;
|
||||
|
||||
/// The border to draw around the chip.
|
||||
///
|
||||
/// Defaults to a [StadiumBorder].
|
||||
final ShapeBorder border;
|
||||
|
||||
/// Color for delete icon, the default being black.
|
||||
///
|
||||
/// This has no effect when [onDelete] is null since no delete icon will be
|
||||
@ -116,8 +126,8 @@ class Chip extends StatelessWidget {
|
||||
children.add(new ExcludeSemantics(
|
||||
child: new Container(
|
||||
margin: const EdgeInsetsDirectional.only(end: 8.0),
|
||||
width: _kAvatarDiamater,
|
||||
height: _kAvatarDiamater,
|
||||
width: _chipHeight,
|
||||
height: _chipHeight,
|
||||
child: avatar,
|
||||
),
|
||||
));
|
||||
@ -125,7 +135,8 @@ class Chip extends StatelessWidget {
|
||||
|
||||
children.add(new Flexible(
|
||||
child: new DefaultTextStyle(
|
||||
style: labelStyle ?? _kLabelStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: labelStyle,
|
||||
child: label,
|
||||
),
|
||||
));
|
||||
@ -135,12 +146,14 @@ class Chip extends StatelessWidget {
|
||||
children.add(new GestureDetector(
|
||||
onTap: Feedback.wrapForTap(onDeleted, context),
|
||||
child: new Tooltip(
|
||||
// TODO(gspencer): Internationalize this text.
|
||||
// https://github.com/flutter/flutter/issues/12378
|
||||
message: deleteButtonTooltipMessage ?? 'Delete "$label"',
|
||||
child: new Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: new Icon(
|
||||
Icons.cancel,
|
||||
size: 18.0,
|
||||
size: 24.0,
|
||||
color: deleteIconColor ?? Colors.black54,
|
||||
),
|
||||
),
|
||||
@ -151,17 +164,21 @@ class Chip extends StatelessWidget {
|
||||
return new Semantics(
|
||||
container: true,
|
||||
child: new Container(
|
||||
height: _kChipHeight,
|
||||
constraints: const BoxConstraints(minHeight: _chipHeight),
|
||||
padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
|
||||
decoration: new BoxDecoration(
|
||||
decoration: new ShapeDecoration(
|
||||
color: backgroundColor ?? Colors.grey.shade300,
|
||||
borderRadius: new BorderRadius.circular(16.0),
|
||||
shape: border,
|
||||
),
|
||||
child: new Row(
|
||||
children: children,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
child: new Center(
|
||||
widthFactor: 1.0,
|
||||
heightFactor: 1.0,
|
||||
child: new Row(
|
||||
children: children,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -475,16 +475,19 @@ class Border extends BoxBorder {
|
||||
case BorderStyle.none:
|
||||
return;
|
||||
case BorderStyle.solid:
|
||||
if (shape == BoxShape.circle) {
|
||||
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
|
||||
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
|
||||
return;
|
||||
switch (shape) {
|
||||
case BoxShape.circle:
|
||||
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
|
||||
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
|
||||
break;
|
||||
case BoxShape.rectangle:
|
||||
if (borderRadius != null) {
|
||||
BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
|
||||
return;
|
||||
}
|
||||
BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
|
||||
break;
|
||||
}
|
||||
if (borderRadius != null) {
|
||||
BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
|
||||
return;
|
||||
}
|
||||
BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -777,16 +780,19 @@ class BorderDirectional extends BoxBorder {
|
||||
case BorderStyle.none:
|
||||
return;
|
||||
case BorderStyle.solid:
|
||||
if (shape == BoxShape.circle) {
|
||||
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
|
||||
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
|
||||
return;
|
||||
switch (shape) {
|
||||
case BoxShape.circle:
|
||||
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
|
||||
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
|
||||
break;
|
||||
case BoxShape.rectangle:
|
||||
if (borderRadius != null) {
|
||||
BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
|
||||
return;
|
||||
}
|
||||
BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
|
||||
break;
|
||||
}
|
||||
if (borderRadius != null) {
|
||||
BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
|
||||
return;
|
||||
}
|
||||
BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class CircleBorder extends ShapeBorder {
|
||||
/// Create a circle border.
|
||||
///
|
||||
/// The [side] argument must not be null.
|
||||
const CircleBorder([ this.side = BorderSide.none ]) : assert(side != null);
|
||||
const CircleBorder({ this.side = BorderSide.none }) : assert(side != null);
|
||||
|
||||
/// The style of this border.
|
||||
final BorderSide side;
|
||||
@ -36,19 +36,19 @@ class CircleBorder extends ShapeBorder {
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) => new CircleBorder(side.scale(t));
|
||||
ShapeBorder scale(double t) => new CircleBorder(side: side.scale(t));
|
||||
|
||||
@override
|
||||
ShapeBorder lerpFrom(ShapeBorder a, double t) {
|
||||
if (a is CircleBorder)
|
||||
return new CircleBorder(BorderSide.lerp(a.side, side, t));
|
||||
return new CircleBorder(side: BorderSide.lerp(a.side, side, t));
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpTo(ShapeBorder b, double t) {
|
||||
if (b is CircleBorder)
|
||||
return new CircleBorder(BorderSide.lerp(side, b.side, t));
|
||||
return new CircleBorder(side: BorderSide.lerp(side, b.side, t));
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ class ShapeDecoration extends Decoration {
|
||||
case BoxShape.circle:
|
||||
if (source.border != null) {
|
||||
assert(source.border.isUniform);
|
||||
shape = new CircleBorder(source.border.top);
|
||||
shape = new CircleBorder(side: source.border.top);
|
||||
} else {
|
||||
shape = const CircleBorder();
|
||||
}
|
||||
|
414
packages/flutter/lib/src/painting/stadium_border.dart
Normal file
414
packages/flutter/lib/src/painting/stadium_border.dart
Normal file
@ -0,0 +1,414 @@
|
||||
// Copyright 2017 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 'dart:ui' as ui show lerpDouble;
|
||||
|
||||
import 'basic_types.dart';
|
||||
import 'border_radius.dart';
|
||||
import 'borders.dart';
|
||||
import 'circle_border.dart';
|
||||
import 'edge_insets.dart';
|
||||
import 'rounded_rectangle_border.dart';
|
||||
|
||||
/// A border that fits a stadium-shaped border (a box with semicircles on the ends)
|
||||
/// within the rectangle of the widget it is applied to.
|
||||
///
|
||||
/// Typically used with [ShapeDecoration] to draw a stadium border.
|
||||
///
|
||||
/// If the rectangle is taller than it is wide, then the semicircles will be on the
|
||||
/// top and bottom, and on the left and right otherwise.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [BorderSide], which is used to describe the border of the stadium.
|
||||
class StadiumBorder extends ShapeBorder {
|
||||
/// Create a stadium border.
|
||||
///
|
||||
/// The [side] argument must not be null.
|
||||
const StadiumBorder({this.side = BorderSide.none}) : assert(side != null);
|
||||
|
||||
/// The style of this border.
|
||||
final BorderSide side;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return new EdgeInsets.all(side.width);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) => new StadiumBorder(side: side.scale(t));
|
||||
|
||||
@override
|
||||
ShapeBorder lerpFrom(ShapeBorder a, double t) {
|
||||
if (a is StadiumBorder)
|
||||
return new StadiumBorder(side: BorderSide.lerp(a.side, side, t));
|
||||
if (a is CircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
circleness: 1.0 - t,
|
||||
);
|
||||
}
|
||||
if (a is RoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
borderRadius: a.borderRadius,
|
||||
rectness: 1.0 - t,
|
||||
);
|
||||
}
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpTo(ShapeBorder b, double t) {
|
||||
if (b is StadiumBorder)
|
||||
return new StadiumBorder(side: BorderSide.lerp(side, b.side, t));
|
||||
if (b is CircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
circleness: t,
|
||||
);
|
||||
}
|
||||
if (b is RoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
borderRadius: b.borderRadius,
|
||||
rectness: t,
|
||||
);
|
||||
}
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
|
||||
final Radius radius = new Radius.circular(rect.shortestSide / 2.0);
|
||||
return new Path()
|
||||
..addRRect(new RRect.fromRectAndRadius(rect, radius).deflate(side.width));
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
|
||||
final Radius radius = new Radius.circular(rect.shortestSide / 2.0);
|
||||
return new Path()
|
||||
..addRRect(new RRect.fromRectAndRadius(rect, radius));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
|
||||
switch (side.style) {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final Radius radius = new Radius.circular(rect.shortestSide / 2.0);
|
||||
canvas.drawRRect(
|
||||
new RRect.fromRectAndRadius(rect, radius).deflate(side.width / 2.0),
|
||||
side.toPaint(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
final StadiumBorder typedOther = other;
|
||||
return side == typedOther.side;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => side.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType($side)';
|
||||
}
|
||||
}
|
||||
|
||||
// Class to help with transitioning to/from a CircleBorder.
|
||||
class _StadiumToCircleBorder extends ShapeBorder {
|
||||
const _StadiumToCircleBorder({
|
||||
this.side: BorderSide.none,
|
||||
this.circleness: 0.0,
|
||||
}) : assert(side != null),
|
||||
assert(circleness != null);
|
||||
|
||||
final BorderSide side;
|
||||
|
||||
final double circleness;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return new EdgeInsets.all(side.width);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: side.scale(t),
|
||||
circleness: t,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpFrom(ShapeBorder a, double t) {
|
||||
if (a is StadiumBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
circleness: circleness * t,
|
||||
);
|
||||
}
|
||||
if (a is CircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
circleness: circleness + (1.0 - circleness) * (1.0 - t),
|
||||
);
|
||||
}
|
||||
if (a is _StadiumToCircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
circleness: ui.lerpDouble(a.circleness, circleness, t),
|
||||
);
|
||||
}
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpTo(ShapeBorder b, double t) {
|
||||
if (b is StadiumBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
circleness: circleness * (1.0 - t),
|
||||
);
|
||||
}
|
||||
if (b is CircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
circleness: circleness + (1.0 - circleness) * t,
|
||||
);
|
||||
}
|
||||
if (b is _StadiumToCircleBorder) {
|
||||
return new _StadiumToCircleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
circleness: ui.lerpDouble(circleness, b.circleness, t),
|
||||
);
|
||||
}
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
Rect _adjustRect(Rect rect) {
|
||||
if (circleness == 0.0 || rect.width == rect.height)
|
||||
return rect;
|
||||
if (rect.width < rect.height) {
|
||||
final double delta = circleness * (rect.height - rect.width) / 2.0;
|
||||
return new Rect.fromLTRB(
|
||||
rect.left,
|
||||
rect.top + delta,
|
||||
rect.right,
|
||||
rect.bottom - delta,
|
||||
);
|
||||
} else {
|
||||
final double delta = circleness * (rect.width - rect.height) / 2.0;
|
||||
return new Rect.fromLTRB(
|
||||
rect.left + delta,
|
||||
rect.top,
|
||||
rect.right - delta,
|
||||
rect.bottom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BorderRadius _adjustBorderRadius(Rect rect) {
|
||||
return new BorderRadius.circular(rect.shortestSide / 2.0);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
|
||||
return new Path()
|
||||
..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)).deflate(side.width));
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
|
||||
return new Path()
|
||||
..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
|
||||
switch (side.style) {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final double width = side.width;
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = _adjustBorderRadius(rect).toRRect(_adjustRect(rect));
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = new Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
final _StadiumToCircleBorder typedOther = other;
|
||||
return side == typedOther.side
|
||||
&& circleness == typedOther.circleness;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(side, circleness);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StadiumBorder($side, ${(circleness * 100).toStringAsFixed(1)}% '
|
||||
'of the way to being a CircleBorder)';
|
||||
}
|
||||
}
|
||||
|
||||
// Class to help with transitioning to/from a RoundedRectBorder.
|
||||
class _StadiumToRoundedRectangleBorder extends ShapeBorder {
|
||||
const _StadiumToRoundedRectangleBorder({
|
||||
this.side: BorderSide.none,
|
||||
this.borderRadius: BorderRadius.zero,
|
||||
this.rectness: 0.0,
|
||||
}) : assert(side != null),
|
||||
assert(borderRadius != null),
|
||||
assert(rectness != null);
|
||||
|
||||
final BorderSide side;
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
final double rectness;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return new EdgeInsets.all(side.width);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: side.scale(t),
|
||||
borderRadius: borderRadius * t,
|
||||
rectness: t,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpFrom(ShapeBorder a, double t) {
|
||||
if (a is StadiumBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
borderRadius: borderRadius,
|
||||
rectness: rectness * t,
|
||||
);
|
||||
}
|
||||
if (a is RoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
borderRadius: borderRadius,
|
||||
rectness: rectness + (1.0 - rectness) * (1.0 - t),
|
||||
);
|
||||
}
|
||||
if (a is _StadiumToRoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t),
|
||||
rectness: ui.lerpDouble(a.rectness, rectness, t),
|
||||
);
|
||||
}
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpTo(ShapeBorder b, double t) {
|
||||
if (b is StadiumBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
borderRadius: borderRadius,
|
||||
rectness: rectness * (1.0 - t),
|
||||
);
|
||||
}
|
||||
if (b is RoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
borderRadius: borderRadius,
|
||||
rectness: rectness + (1.0 - rectness) * t,
|
||||
);
|
||||
}
|
||||
if (b is _StadiumToRoundedRectangleBorder) {
|
||||
return new _StadiumToRoundedRectangleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t),
|
||||
rectness: ui.lerpDouble(rectness, b.rectness, t),
|
||||
);
|
||||
}
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
BorderRadius _adjustBorderRadius(Rect rect) {
|
||||
return BorderRadius.lerp(
|
||||
borderRadius,
|
||||
new BorderRadius.all(new Radius.circular(rect.shortestSide / 2.0)),
|
||||
(1.0 - rectness)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
|
||||
return new Path()
|
||||
..addRRect(_adjustBorderRadius(rect).toRRect(rect).deflate(side.width));
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
|
||||
return new Path()
|
||||
..addRRect(_adjustBorderRadius(rect).toRRect(rect));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
|
||||
switch (side.style) {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final double width = side.width;
|
||||
if (width == 0.0) {
|
||||
canvas.drawRRect(_adjustBorderRadius(rect).toRRect(rect), side.toPaint());
|
||||
} else {
|
||||
final RRect outer = _adjustBorderRadius(rect).toRRect(rect);
|
||||
final RRect inner = outer.deflate(width);
|
||||
final Paint paint = new Paint()
|
||||
..color = side.color;
|
||||
canvas.drawDRRect(outer, inner, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
final _StadiumToRoundedRectangleBorder typedOther = other;
|
||||
return side == typedOther.side
|
||||
&& borderRadius == typedOther.borderRadius
|
||||
&& rectness == typedOther.rectness;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(side, borderRadius, rectness);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StadiumBorder($side, $borderRadius, '
|
||||
'${(rectness * 100).toStringAsFixed(1)}% of the way to being a '
|
||||
'RoundedRectangleBorder)';
|
||||
}
|
||||
}
|
@ -361,7 +361,7 @@ class RenderLimitedBox extends RenderProxyBox {
|
||||
|
||||
/// Attempts to size the child to a specific aspect ratio.
|
||||
///
|
||||
/// The render object first tries the largest width permited by the layout
|
||||
/// The render object first tries the largest width permitted by the layout
|
||||
/// constraints. The height of the render object is determined by applying the
|
||||
/// given aspect ratio to the width, expressed as a ratio of width to height.
|
||||
///
|
||||
@ -1372,12 +1372,15 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
||||
@override
|
||||
RRect get _defaultClip {
|
||||
assert(hasSize);
|
||||
if (_shape == BoxShape.rectangle) {
|
||||
return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size);
|
||||
} else {
|
||||
final Rect rect = Offset.zero & size;
|
||||
return new RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
|
||||
assert(_shape != null);
|
||||
switch (_shape) {
|
||||
case BoxShape.rectangle:
|
||||
return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size);
|
||||
case BoxShape.circle:
|
||||
final Rect rect = Offset.zero & size;
|
||||
return new RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1885,7 +1885,7 @@ class _OffstageElement extends SingleChildRenderObjectElement {
|
||||
|
||||
/// A widget that attempts to size the child to a specific aspect ratio.
|
||||
///
|
||||
/// The widget first tries the largest width permited by the layout
|
||||
/// The widget first tries the largest width permitted by the layout
|
||||
/// constraints. The height of the widget is determined by applying the
|
||||
/// given aspect ratio to the width, expressed as a ratio of width to height.
|
||||
///
|
||||
|
@ -36,12 +36,17 @@ class BoxConstraintsTween extends Tween<BoxConstraints> {
|
||||
/// This class specializes the interpolation of [Tween<BoxConstraints>] to use
|
||||
/// [Decoration.lerp].
|
||||
///
|
||||
/// Typically this will only have useful results if the [begin] and [end]
|
||||
/// decorations have the same type; decorations of differing types generally do
|
||||
/// not have a useful animation defined, and will just jump to the [end]
|
||||
/// immediately.
|
||||
/// For [ShapeDecoration]s which know how to [ShapeDecoration.lerpTo] or
|
||||
/// [ShapeDecoration.lerpFrom] each other, this will produce a smooth
|
||||
/// interpolation between decorations.
|
||||
///
|
||||
/// See [Tween] for a discussion on how to use interpolation objects.
|
||||
/// See also:
|
||||
/// * [Tween] for a discussion on how to use interpolation objects.
|
||||
/// * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and
|
||||
/// [StadiumBorder] for examples of shape borders that can be smoothly
|
||||
/// interpolated.
|
||||
/// * [BoxBorder] for a border that can only be smoothly interpolated between other
|
||||
/// [BoxBorder]s.
|
||||
class DecorationTween extends Tween<Decoration> {
|
||||
/// Creates a decoration tween.
|
||||
///
|
||||
|
@ -254,6 +254,159 @@ void main() {
|
||||
expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byType(Icon)).dx));
|
||||
});
|
||||
|
||||
testWidgets('Chip responds to textScaleFactor', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('A')
|
||||
),
|
||||
label: const Text('Chip A'),
|
||||
),
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('B')
|
||||
),
|
||||
label: const Text('Chip B'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
|
||||
// https://github.com/flutter/flutter/issues/12357
|
||||
expect(
|
||||
tester.getSize(find.text('Chip A')),
|
||||
anyOf(const Size(79.0, 13.0), const Size(78.0, 13.0)),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.text('Chip B')),
|
||||
anyOf(const Size(79.0, 13.0), const Size(78.0, 13.0)),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byType(Chip).first),
|
||||
anyOf(const Size(131.0, 32.0), const Size(130.0, 32.0))
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byType(Chip).last),
|
||||
anyOf(const Size(131.0, 32.0), const Size(130.0, 32.0))
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new MediaQuery(
|
||||
data: const MediaQueryData(textScaleFactor: 3.0),
|
||||
child: new Material(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('A')
|
||||
),
|
||||
label: const Text('Chip A'),
|
||||
),
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('B')
|
||||
),
|
||||
label: const Text('Chip B'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
|
||||
// https://github.com/flutter/flutter/issues/12357
|
||||
expect(tester.getSize(find.text('Chip A')), anyOf(const Size(234.0, 39.0), const Size(235.0, 39.0)));
|
||||
expect(tester.getSize(find.text('Chip B')), anyOf(const Size(234.0, 39.0), const Size(235.0, 39.0)));
|
||||
expect(tester.getSize(find.byType(Chip).first).width, anyOf(286.0, 287.0));
|
||||
expect(tester.getSize(find.byType(Chip).first).height, equals(39.0));
|
||||
expect(tester.getSize(find.byType(Chip).last).width, anyOf(286.0, 287.0));
|
||||
expect(tester.getSize(find.byType(Chip).last).height, equals(39.0));
|
||||
|
||||
// Check that individual text scales are taken into account.
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('A')
|
||||
),
|
||||
label: const Text('Chip A', textScaleFactor: 3.0),
|
||||
),
|
||||
const Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('B')
|
||||
),
|
||||
label: const Text('Chip B'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
|
||||
// https://github.com/flutter/flutter/issues/12357
|
||||
expect(tester.getSize(find.text('Chip A')), anyOf(const Size(234.0, 39.0), const Size(235.0, 39.0)));
|
||||
expect(tester.getSize(find.text('Chip B')), anyOf(const Size(78.0, 13.0), const Size(79.0, 13.0)));
|
||||
expect(tester.getSize(find.byType(Chip).first).width, anyOf(286.0, 287.0));
|
||||
expect(tester.getSize(find.byType(Chip).first).height, equals(39.0));
|
||||
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(130.0, 32.0), const Size(131.0, 32.0)));
|
||||
});
|
||||
|
||||
testWidgets('Labels can be non-text widgets', (WidgetTester tester) async {
|
||||
final Key keyA = new GlobalKey();
|
||||
final Key keyB = new GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Material(
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('A')
|
||||
),
|
||||
label: new Text('Chip A', key: keyA),
|
||||
),
|
||||
new Chip(
|
||||
avatar: const CircleAvatar(
|
||||
child: const Text('B')
|
||||
),
|
||||
label: new Container(key: keyB, width: 10.0, height: 10.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// TODO(gspencer): Update this test when the font metric bug is fixed to remove the anyOfs.
|
||||
// https://github.com/flutter/flutter/issues/12357
|
||||
expect(
|
||||
tester.getSize(find.byKey(keyA)),
|
||||
anyOf(const Size(79.0, 13.0), const Size(78.0, 13.0)),
|
||||
);
|
||||
expect(tester.getSize(find.byKey(keyB)), const Size(10.0, 10.0));
|
||||
expect(
|
||||
tester.getSize(find.byType(Chip).first),
|
||||
anyOf(const Size(131.0, 32.0), const Size(130.0, 32.0)),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Chip).last), const Size(62.0, 32.0));
|
||||
});
|
||||
|
||||
|
||||
|
||||
testWidgets('Chip padding - LTR', (WidgetTester tester) async {
|
||||
final GlobalKey keyA = new GlobalKey();
|
||||
final GlobalKey keyB = new GlobalKey();
|
||||
@ -281,10 +434,10 @@ void main() {
|
||||
);
|
||||
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(0.0, 284.0));
|
||||
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(32.0, 316.0));
|
||||
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(40.0, 284.0));
|
||||
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(774.0, 316.0));
|
||||
expect(tester.getTopLeft(find.byType(Icon)), const Offset(778.0, 291.0));
|
||||
expect(tester.getBottomRight(find.byType(Icon)), const Offset(796.0, 309.0));
|
||||
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(40.0, 0.0));
|
||||
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(768.0, 600.0));
|
||||
expect(tester.getTopLeft(find.byType(Icon)), const Offset(772.0, 288.0));
|
||||
expect(tester.getBottomRight(find.byType(Icon)), const Offset(796.0, 312.0));
|
||||
});
|
||||
|
||||
testWidgets('Chip padding - RTL', (WidgetTester tester) async {
|
||||
@ -314,9 +467,9 @@ void main() {
|
||||
);
|
||||
expect(tester.getTopRight(find.byKey(keyA)), const Offset(800.0 - 0.0, 284.0));
|
||||
expect(tester.getBottomLeft(find.byKey(keyA)), const Offset(800.0 - 32.0, 316.0));
|
||||
expect(tester.getTopRight(find.byKey(keyB)), const Offset(800.0 - 40.0, 284.0));
|
||||
expect(tester.getBottomLeft(find.byKey(keyB)), const Offset(800.0 - 774.0, 316.0));
|
||||
expect(tester.getTopRight(find.byType(Icon)), const Offset(800.0 - 778.0, 291.0));
|
||||
expect(tester.getBottomLeft(find.byType(Icon)), const Offset(800.0 - 796.0, 309.0));
|
||||
expect(tester.getTopRight(find.byKey(keyB)), const Offset(800.0 - 40.0, 0.0));
|
||||
expect(tester.getBottomLeft(find.byKey(keyB)), const Offset(800.0 - 768.0, 600.0));
|
||||
expect(tester.getTopRight(find.byType(Icon)), const Offset(800.0 - 772.0, 288.0));
|
||||
expect(tester.getBottomLeft(find.byType(Icon)), const Offset(800.0 - 796.0, 312.0));
|
||||
});
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ import 'common_matchers.dart';
|
||||
|
||||
void main() {
|
||||
test('CircleBorder', () {
|
||||
final CircleBorder c10 = const CircleBorder(const BorderSide(width: 10.0));
|
||||
final CircleBorder c15 = const CircleBorder(const BorderSide(width: 15.0));
|
||||
final CircleBorder c20 = const CircleBorder(const BorderSide(width: 20.0));
|
||||
final CircleBorder c10 = const CircleBorder(side: const BorderSide(width: 10.0));
|
||||
final CircleBorder c15 = const CircleBorder(side: const BorderSide(width: 15.0));
|
||||
final CircleBorder c20 = const CircleBorder(side: const BorderSide(width: 20.0));
|
||||
expect(c10.dimensions, const EdgeInsets.all(10.0));
|
||||
expect(c10.scale(2.0), c20);
|
||||
expect(c20.scale(0.5), c10);
|
||||
|
@ -38,7 +38,7 @@ void main() {
|
||||
|
||||
test('RoundedRectangleBorder and CircleBorder', () {
|
||||
final RoundedRectangleBorder r = new RoundedRectangleBorder(side: BorderSide.none, borderRadius: new BorderRadius.circular(10.0));
|
||||
final CircleBorder c = const CircleBorder(BorderSide.none);
|
||||
final CircleBorder c = const CircleBorder(side: BorderSide.none);
|
||||
final Rect rect = new Rect.fromLTWH(0.0, 0.0, 100.0, 20.0); // center is x=40..60 y=10
|
||||
final Matcher looksLikeR = isPathThat(
|
||||
includes: const <Offset>[ const Offset(30.0, 10.0), const Offset(50.0, 10.0), ],
|
||||
|
@ -21,7 +21,7 @@ void main() {
|
||||
expect(() => new ShapeDecoration(color: colorR, shape: null), throwsAssertionError);
|
||||
expect(
|
||||
new ShapeDecoration.fromBoxDecoration(const BoxDecoration(shape: BoxShape.circle)),
|
||||
const ShapeDecoration(shape: const CircleBorder(BorderSide.none)),
|
||||
const ShapeDecoration(shape: const CircleBorder(side: BorderSide.none)),
|
||||
);
|
||||
expect(
|
||||
new ShapeDecoration.fromBoxDecoration(new BoxDecoration(shape: BoxShape.rectangle, borderRadius: new BorderRadiusDirectional.circular(100.0))),
|
||||
@ -29,7 +29,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
new ShapeDecoration.fromBoxDecoration(new BoxDecoration(shape: BoxShape.circle, border: new Border.all(color: colorG))),
|
||||
new ShapeDecoration(shape: new CircleBorder(new BorderSide(color: colorG))),
|
||||
new ShapeDecoration(shape: new CircleBorder(side: new BorderSide(color: colorG))),
|
||||
);
|
||||
expect(
|
||||
new ShapeDecoration.fromBoxDecoration(new BoxDecoration(shape: BoxShape.rectangle, border: new Border.all(color: colorR))),
|
||||
|
161
packages/flutter/test/painting/stadium_border_test.dart
Normal file
161
packages/flutter/test/painting/stadium_border_test.dart
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2017 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/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import 'common_matchers.dart';
|
||||
|
||||
void main() {
|
||||
test('StadiumBorder', () {
|
||||
final StadiumBorder c10 = const StadiumBorder(side: const BorderSide(width: 10.0));
|
||||
final StadiumBorder c15 = const StadiumBorder(side: const BorderSide(width: 15.0));
|
||||
final StadiumBorder c20 = const StadiumBorder(side: const 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);
|
||||
|
||||
final StadiumBorder c1 = const StadiumBorder(side: const BorderSide(width: 1.0));
|
||||
expect(c1.getOuterPath(new Rect.fromCircle(center: Offset.zero, radius: 1.0)), isUnitCircle);
|
||||
final StadiumBorder c2 = const StadiumBorder(side: const BorderSide(width: 1.0));
|
||||
expect(c2.getInnerPath(new Rect.fromCircle(center: Offset.zero, radius: 2.0)), isUnitCircle);
|
||||
final Rect rect = new Rect.fromLTRB(10.0, 20.0, 100.0, 200.0);
|
||||
expect(
|
||||
(Canvas canvas) => c10.paint(canvas, rect),
|
||||
paints
|
||||
..rrect(
|
||||
rrect: new RRect.fromRectAndRadius(rect.deflate(5.0), new Radius.circular(rect.shortestSide / 2.0 - 5.0)),
|
||||
strokeWidth: 10.0,
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('StadiumBorder and CircleBorder', () {
|
||||
final StadiumBorder stadium = const StadiumBorder(side: BorderSide.none);
|
||||
final CircleBorder circle = const CircleBorder(side: BorderSide.none);
|
||||
final Rect rect = new Rect.fromLTWH(0.0, 0.0, 100.0, 20.0);
|
||||
final Matcher looksLikeS = isPathThat(
|
||||
includes: const <Offset>[ const Offset(30.0, 10.0), const Offset(50.0, 10.0), ],
|
||||
excludes: const <Offset>[ const Offset(1.0, 1.0), const Offset(99.0, 19.0), ],
|
||||
);
|
||||
final Matcher looksLikeC = isPathThat(
|
||||
includes: const <Offset>[ const Offset(50.0, 10.0), ],
|
||||
excludes: const <Offset>[ const Offset(1.0, 1.0), const Offset(30.0, 10.0), const Offset(99.0, 19.0), ],
|
||||
);
|
||||
expect(stadium.getOuterPath(rect), looksLikeS);
|
||||
expect(circle.getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.9).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.9), stadium, 0.1).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.9), stadium, 0.9).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.1), circle, 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.1), circle, 0.9).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.1), ShapeBorder.lerp(stadium, circle, 0.9), 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.1), ShapeBorder.lerp(stadium, circle, 0.9), 0.9).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(stadium, ShapeBorder.lerp(stadium, circle, 0.9), 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(stadium, ShapeBorder.lerp(stadium, circle, 0.9), 0.9).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(circle, ShapeBorder.lerp(stadium, circle, 0.1), 0.1).getOuterPath(rect), looksLikeC);
|
||||
expect(ShapeBorder.lerp(circle, ShapeBorder.lerp(stadium, circle, 0.1), 0.9).getOuterPath(rect), looksLikeS);
|
||||
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.1).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 10.0% of the way to being a CircleBorder)');
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.2).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 20.0% of the way to being a CircleBorder)');
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.1), ShapeBorder.lerp(stadium, circle, 0.9), 0.9).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 82.0% of the way to being a CircleBorder)');
|
||||
|
||||
expect(ShapeBorder.lerp(circle, stadium, 0.9).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 10.0% of the way to being a CircleBorder)');
|
||||
expect(ShapeBorder.lerp(circle, stadium, 0.8).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 20.0% of the way to being a CircleBorder)');
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, circle, 0.9), ShapeBorder.lerp(stadium, circle, 0.1), 0.1).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), 82.0% of the way to being a CircleBorder)');
|
||||
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.1), ShapeBorder.lerp(stadium, circle, 0.1));
|
||||
expect(ShapeBorder.lerp(stadium, circle, 0.1).hashCode, ShapeBorder.lerp(stadium, circle, 0.1).hashCode);
|
||||
|
||||
final ShapeBorder direct50 = ShapeBorder.lerp(stadium, circle, 0.5);
|
||||
final ShapeBorder indirect50 = ShapeBorder.lerp(ShapeBorder.lerp(circle, stadium, 0.1), ShapeBorder.lerp(circle, stadium, 0.9), 0.5);
|
||||
expect(direct50, indirect50);
|
||||
expect(direct50.hashCode, indirect50.hashCode);
|
||||
expect(direct50.toString(), indirect50.toString());
|
||||
});
|
||||
|
||||
test('StadiumBorder and RoundedRectBorder', () {
|
||||
final StadiumBorder stadium = const StadiumBorder(side: BorderSide.none);
|
||||
final RoundedRectangleBorder rrect = const RoundedRectangleBorder(side: BorderSide.none);
|
||||
final Rect rect = new Rect.fromLTWH(0.0, 0.0, 100.0, 50.0);
|
||||
final Matcher looksLikeS = isPathThat(
|
||||
includes: const <Offset>[
|
||||
const Offset(25.0, 25.0),
|
||||
const Offset(50.0, 25.0),
|
||||
const Offset(7.33, 7.33),
|
||||
],
|
||||
excludes: const <Offset>[
|
||||
const Offset(0.001, 0.001),
|
||||
const Offset(99.999, 0.001),
|
||||
const Offset(99.999, 49.999),
|
||||
const Offset(0.001, 49.999),
|
||||
],
|
||||
);
|
||||
final Matcher looksLikeR = isPathThat(
|
||||
includes: const <Offset>[
|
||||
const Offset(25.0, 25.0),
|
||||
const Offset(50.0, 25.0),
|
||||
const Offset(7.33, 7.33),
|
||||
const Offset(4.0, 4.0),
|
||||
const Offset(96.0, 4.0),
|
||||
const Offset(96.0, 46.0),
|
||||
const Offset(4.0, 46.0),
|
||||
],
|
||||
);
|
||||
expect(stadium.getOuterPath(rect), looksLikeS);
|
||||
expect(rrect.getOuterPath(rect), looksLikeR);
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.9).getOuterPath(rect), looksLikeR);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.9), stadium, 0.1).getOuterPath(rect), looksLikeR);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.9), stadium, 0.9).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.1), rrect, 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.1), rrect, 0.9).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.1), ShapeBorder.lerp(stadium, rrect, 0.9), 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.1), ShapeBorder.lerp(stadium, rrect, 0.9), 0.9).getOuterPath(rect), looksLikeR);
|
||||
expect(ShapeBorder.lerp(stadium, ShapeBorder.lerp(stadium, rrect, 0.9), 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(stadium, ShapeBorder.lerp(stadium, rrect, 0.9), 0.9).getOuterPath(rect), looksLikeR);
|
||||
expect(ShapeBorder.lerp(rrect, ShapeBorder.lerp(stadium, rrect, 0.1), 0.1).getOuterPath(rect), looksLikeS);
|
||||
expect(ShapeBorder.lerp(rrect, ShapeBorder.lerp(stadium, rrect, 0.1), 0.9).getOuterPath(rect), looksLikeS);
|
||||
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.1).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 10.0% of the way to being a RoundedRectangleBorder)');
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.2).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 20.0% of the way to being a RoundedRectangleBorder)');
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.1), ShapeBorder.lerp(stadium, rrect, 0.9), 0.9).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 82.0% of the way to being a RoundedRectangleBorder)');
|
||||
|
||||
expect(ShapeBorder.lerp(rrect, stadium, 0.9).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 10.0% of the way to being a RoundedRectangleBorder)');
|
||||
expect(ShapeBorder.lerp(rrect, stadium, 0.8).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 20.0% of the way to being a RoundedRectangleBorder)');
|
||||
expect(ShapeBorder.lerp(ShapeBorder.lerp(stadium, rrect, 0.9), ShapeBorder.lerp(stadium, rrect, 0.1), 0.1).toString(),
|
||||
'StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), '
|
||||
'BorderRadius.zero, 82.0% of the way to being a RoundedRectangleBorder)');
|
||||
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.1), ShapeBorder.lerp(stadium, rrect, 0.1));
|
||||
expect(ShapeBorder.lerp(stadium, rrect, 0.1).hashCode, ShapeBorder.lerp(stadium, rrect, 0.1).hashCode);
|
||||
|
||||
final ShapeBorder direct50 = ShapeBorder.lerp(stadium, rrect, 0.5);
|
||||
final ShapeBorder indirect50 = ShapeBorder.lerp(ShapeBorder.lerp(rrect, stadium, 0.1), ShapeBorder.lerp(rrect, stadium, 0.9), 0.5);
|
||||
expect(direct50, indirect50);
|
||||
expect(direct50.hashCode, indirect50.hashCode);
|
||||
expect(direct50.toString(), indirect50.toString());
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user