Commit 0e34c7f2 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

CircleBorder (#12570)

parent 52fbcefe
......@@ -25,6 +25,7 @@ export 'src/painting/box_border.dart';
export 'src/painting/box_decoration.dart';
export 'src/painting/box_fit.dart';
export 'src/painting/box_shadow.dart';
export 'src/painting/circle_border.dart';
export 'src/painting/colors.dart';
export 'src/painting/decoration.dart';
export 'src/painting/edge_insets.dart';
......
......@@ -484,7 +484,7 @@ class Border extends BoxBorder {
@override
String toString() {
if (isUniform)
return 'Border.all($top)';
return '$runtimeType.all($top)';
final List<String> arguments = <String>[];
if (top != BorderSide.none)
arguments.add('top: $top');
......@@ -494,7 +494,7 @@ class Border extends BoxBorder {
arguments.add('bottom: $bottom');
if (left != BorderSide.none)
arguments.add('left: $left');
return 'Border(${arguments.join(", ")})';
return '$runtimeType(${arguments.join(", ")})';
}
}
......@@ -807,6 +807,6 @@ class BorderDirectional extends BoxBorder {
arguments.add('end: $end');
if (bottom != BorderSide.none)
arguments.add('bottom: $bottom');
return 'BorderDirectional(${arguments.join(", ")})';
return '$runtimeType(${arguments.join(", ")})';
}
}
// 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:math' as math;
import 'basic_types.dart';
import 'borders.dart';
import 'edge_insets.dart';
/// A border that fits a circle within the available space.
///
/// Typically used with [ShapeDecoration] to draw a circle.
///
/// The [dimensions] assume that the border is being used in a square space.
/// When applied to a rectangular space, the border paints in the center of the
/// rectangle.
///
/// See also:
///
/// * [BorderSide], which is used to describe each side of the box.
/// * [Border], which, when used with [BoxDecoration], can also
/// describe a circle.
class CircleBorder extends ShapeBorder {
/// Create a circle border.
///
/// The [side] argument must not be null.
const CircleBorder(this.side) : 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 CircleBorder(side.scale(t));
@override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is CircleBorder)
return new CircleBorder(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 super.lerpTo(b, t);
}
@override
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
return new Path()
..addOval(new Rect.fromCircle(
center: rect.center,
radius: math.max(0.0, rect.shortestSide / 2.0 - side.width),
));
}
@override
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
return new Path()
..addOval(new Rect.fromCircle(
center: rect.center,
radius: rect.shortestSide / 2.0,
));
}
@override
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
switch (side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
canvas.drawCircle(rect.center, (rect.shortestSide - side.width) / 2.0, side.toPaint());
}
}
@override
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType)
return false;
final CircleBorder typedOther = other;
return side == typedOther.side;
}
@override
int get hashCode => side.hashCode;
@override
String toString() {
return '$runtimeType($side)';
}
}
......@@ -115,202 +115,204 @@ void main() {
expect(() => BoxBorder.lerp(new SillyBorder(), const Border(), 2.0), throwsFlutterError);
});
void verifyPath(Path path, {
Iterable<Offset> includes: const <Offset>[],
Iterable<Offset> excludes: const <Offset>[],
}) {
for (Offset offset in includes)
expect(path.contains(offset), isTrue, reason: 'Offset $offset should be inside the path.');
for (Offset offset in excludes)
expect(path.contains(offset), isFalse, reason: 'Offset $offset should be outside the path.');
}
test('BoxBorder.getInnerPath / BoxBorder.getOuterPath', () {
// for Border, BorderDirectional
final Border border = const Border(top: const BorderSide(width: 10.0), right: const BorderSide(width: 20.0));
final BorderDirectional borderDirectional = const BorderDirectional(top: const BorderSide(width: 10.0), end: const BorderSide(width: 20.0));
verifyPath(
expect(
border.getOuterPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.rtl),
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
);
verifyPath(
isPathThat(
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
),
);
expect(
border.getInnerPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.rtl),
// inner path is a rect from 50.0,70.0 to 90.0,190.0
includes: <Offset>[
const Offset(50.0, 70.0),
const Offset(55.0, 70.0),
const Offset(50.0, 75.0),
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(110.0, 80.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(91.0, 191.0),
const Offset(109.0, 189.0),
const Offset(110.0, 190.0),
const Offset(1000.0, 1000.0),
],
);
verifyPath(
isPathThat(
includes: <Offset>[
const Offset(50.0, 70.0),
const Offset(55.0, 70.0),
const Offset(50.0, 75.0),
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(110.0, 80.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(91.0, 191.0),
const Offset(109.0, 189.0),
const Offset(110.0, 190.0),
const Offset(1000.0, 1000.0),
],
)
);
expect(
borderDirectional.getOuterPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.rtl),
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
);
verifyPath(
isPathThat(
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
),
);
expect(
borderDirectional.getInnerPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.rtl),
// inner path is a rect from 70.0,70.0 to 110.0,190.0
includes: <Offset>[
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(50.0, 60.0),
const Offset(50.0, 70.0),
const Offset(50.0, 75.0),
const Offset(55.0, 70.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 191.0),
const Offset(110.0, 191.0),
const Offset(111.0, 190.0),
const Offset(111.0, 191.0),
const Offset(1000.0, 1000.0),
],
);
verifyPath(
isPathThat(
includes: <Offset>[
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(50.0, 60.0),
const Offset(50.0, 70.0),
const Offset(50.0, 75.0),
const Offset(55.0, 70.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 191.0),
const Offset(110.0, 191.0),
const Offset(111.0, 190.0),
const Offset(111.0, 191.0),
const Offset(1000.0, 1000.0),
],
),
);
expect(
borderDirectional.getOuterPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.ltr),
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
);
verifyPath(
isPathThat(
includes: <Offset>[
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(60.0, 70.0),
const Offset(80.0, 190.0),
const Offset(109.0, 189.0),
const Offset(110.0, 80.0),
const Offset(110.0, 190.0),
],
excludes: <Offset>[
const Offset(40.0, 60.0),
const Offset(50.0, 50.0),
const Offset(111.0, 190.0),
const Offset(110.0, 191.0),
const Offset(111.0, 191.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(1000.0, 1000.0),
],
),
);
expect(
borderDirectional.getInnerPath(new Rect.fromLTRB(50.0, 60.0, 110.0, 190.0), textDirection: TextDirection.ltr),
// inner path is a rect from 50.0,70.0 to 90.0,190.0
includes: <Offset>[
const Offset(50.0, 70.0),
const Offset(50.0, 75.0),
const Offset(55.0, 70.0),
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
],
excludes: <Offset>[
const Offset(50.0, 50.0),
const Offset(40.0, 60.0),
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(110.0, 80.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(91.0, 191.0),
const Offset(109.0, 189.0),
const Offset(110.0, 190.0),
const Offset(1000.0, 1000.0),
],
isPathThat(
includes: <Offset>[
const Offset(50.0, 70.0),
const Offset(50.0, 75.0),
const Offset(55.0, 70.0),
const Offset(70.0, 70.0),
const Offset(70.0, 71.0),
const Offset(71.0, 70.0),
const Offset(71.0, 71.0),
const Offset(80.0, 180.0),
const Offset(80.0, 190.0),
const Offset(89.0, 189.0),
const Offset(90.0, 190.0),
],
excludes: <Offset>[
const Offset(50.0, 50.0),
const Offset(40.0, 60.0),
const Offset(50.0, 60.0),
const Offset(60.0, 60.0),
const Offset(0.0, 0.0),
const Offset(-10.0, -10.0),
const Offset(0.0, -10.0),
const Offset(-10.0, 0.0),
const Offset(110.0, 80.0),
const Offset(89.0, 191.0),
const Offset(90.0, 191.0),
const Offset(91.0, 189.0),
const Offset(91.0, 190.0),
const Offset(91.0, 191.0),
const Offset(109.0, 189.0),
const Offset(110.0, 190.0),
const Offset(1000.0, 1000.0),
],
),
);
});
......
// 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';
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));
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 Matcher isUnitCircle = isPathThat(
includes: <Offset>[
const Offset(-0.6035617555492896, 0.2230970398703236),
const Offset(-0.7738478165627277, 0.5640447581420576),
const Offset(-0.46090034164788385, -0.692017006684612),
const Offset(-0.2138540316101296, -0.09997005339529785),
const Offset(-0.46919827227410416, 0.29581721423767027),
const Offset(-0.43628713652733153, 0.5065324817995975),
const Offset(0.0, 0.0),
const Offset(0.49296904381712725, -0.5922438805080081),
const Offset(0.2901141594861445, -0.3181478162967859),
const Offset(0.45229946324502146, 0.4324593232323706),
const Offset(0.11827752132593572, 0.806442226027837),
const Offset(0.8854165569581154, -0.08604230149167624),
],
excludes: <Offset>[
const Offset(-100.0, -100.0),
const Offset(-100.0, 100.0),
const Offset(-1.1104403014186688, -1.1234939207590569),
const Offset(-1.1852827482514838, -0.5029551986333607),
const Offset(-1.0253256532179804, -0.02034402043932526),
const Offset(-1.4488532714237397, 0.4948740308904742),
const Offset(-1.03142206223176, 0.81070400258819),
const Offset(-1.006747917852356, 1.3712062218039343),
const Offset(-0.5241429900291878, -1.2852518410112541),
const Offset(-0.8879593765104428, -0.9999680025850874),
const Offset(-0.9120835110799488, -0.4361605900585557),
const Offset(-0.8184877240407303, 1.1202520775469589),
const Offset(-0.15746058420492282, -1.1905035795387513),
const Offset(-0.11519948876183506, 1.3848147258237393),
const Offset(0.0035741796943844495, -1.3383908620447724),
const Offset(0.34408827443814394, 1.4514436242950461),
const Offset(0.709487222145941, -1.3468012918181573),
const Offset(0.6287522653614315, -0.8315879623940617),
const Offset(0.9716071801865485, 0.24311969613525442),
const Offset(0.7632982576031955, 0.8329765574976169),
const Offset(0.9923766847309081, 1.0592617071813715),
const Offset(1.2696730082820435, -1.0353385446957046),
const Offset(1.4266154921521208, -0.8382633931857755),
const Offset(1.298035226938996, -0.11544603567954526),
const Offset(1.4143230992455558, 0.10842501221141165),
const Offset(1.465352952354424, 0.6999947490821032),
const Offset(1.0462985816010146, 1.3874230508561505),
const Offset(100.0, -100.0),
const Offset(100.0, 100.0),
],
);
expect(c10.getInnerPath(new Rect.fromCircle(center: Offset.zero, radius: 1.0).inflate(10.0)), isUnitCircle);
expect(c10.getOuterPath(new Rect.fromCircle(center: Offset.zero, radius: 1.0)), isUnitCircle);
expect(
(Canvas canvas) => c10.paint(canvas, new Rect.fromLTWH(10.0, 20.0, 30.0, 40.0)),
paints
..circle(x: 25.0, y: 40.0, radius: 10.0, strokeWidth: 10.0)
);
});
}
......@@ -327,6 +327,65 @@ abstract class PaintPattern {
void something(PaintPatternPredicate predicate);
}
/// Matches a [Path] that contains (as defined by [Path.contains]) the given
/// `includes` points and does not contain the given `excludes` points.
Matcher isPathThat({
Iterable<Offset> includes: const <Offset>[],
Iterable<Offset> excludes: const <Offset>[],
}) {
return new _PathMatcher(includes.toList(), excludes.toList());
}
class _PathMatcher extends Matcher {
_PathMatcher(this.includes, this.excludes);
List<Offset> includes;
List<Offset> excludes;
@override
bool matches(Object object, Map<dynamic, dynamic> matchState) {
if (object is! Path) {
matchState[this] = 'The given object ($object) was not a Path.';
return false;
}
final Path path = object;
final List<String> errors = <String>[];
for (Offset offset in includes) {
if (!path.contains(offset))
errors.add('Offset $offset should be inside the path, but is not.');
}
for (Offset offset in excludes) {
if (path.contains(offset))
errors.add('Offset $offset should be outside the path, but is not.');
}
if (errors.isEmpty)
return true;
matchState[this] = 'Not all the given points were inside or outside the path as expected:\n ${errors.join("\n ")}';
return false;
}
@override
Description describe(Description description) {
String points(List<Offset> list) {
final int count = list.length;
if (count == 1)
return 'one particular point';
return '$count particular points';
}
return description.add('A Path that contains ${points(includes)} but does not contain ${points(excludes)}.');
}
@override
Description describeMismatch(
dynamic item,
Description description,
Map<dynamic, dynamic> matchState,
bool verbose,
) {
return description.add(matchState[this]);
}
}
class _MismatchedCall {
const _MismatchedCall(this.message, this.callIntroduction, this.call) : assert(call != null);
final String message;
......
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