mirror of
https://github.com/flutter/flutter
synced 2024-09-21 17:22:30 +00:00
Added BeveledRectangleBorder ShapeBorder (#16279)
This commit is contained in:
parent
d0c0426007
commit
af9e15f361
|
@ -19,6 +19,7 @@ library painting;
|
|||
|
||||
export 'src/painting/alignment.dart';
|
||||
export 'src/painting/basic_types.dart';
|
||||
export 'src/painting/beveled_rectangle_border.dart';
|
||||
export 'src/painting/binding.dart';
|
||||
export 'src/painting/border_radius.dart';
|
||||
export 'src/painting/borders.dart';
|
||||
|
|
151
packages/flutter/lib/src/painting/beveled_rectangle_border.dart
Normal file
151
packages/flutter/lib/src/painting/beveled_rectangle_border.dart
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2018 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 'border_radius.dart';
|
||||
import 'borders.dart';
|
||||
import 'edge_insets.dart';
|
||||
|
||||
/// A rectangular border with flattened or "beveled" corners.
|
||||
///
|
||||
/// The line segments that connect the rectangle's four sides will
|
||||
/// begin and at locations offset by the corresponding border radius,
|
||||
/// but not farther than the side's center. If all the border radii
|
||||
/// exceed the sides' half widths/heights the resulting shape is
|
||||
/// diamond made by connecting the centers of the sides.
|
||||
class BeveledRectangleBorder extends ShapeBorder {
|
||||
/// Creates a border like a [RoundedRectangleBorder] except that the corners
|
||||
/// are joined by straight lines instead of arcs.
|
||||
///
|
||||
/// The arguments must not be null.
|
||||
const BeveledRectangleBorder({
|
||||
this.side: BorderSide.none,
|
||||
this.borderRadius: BorderRadius.zero,
|
||||
}) : assert(side != null),
|
||||
assert(borderRadius != null);
|
||||
|
||||
/// The style of this border.
|
||||
final BorderSide side;
|
||||
|
||||
/// The radii for each corner.
|
||||
///
|
||||
/// Each corner [Radius] defines the endpoints of a line segment that
|
||||
/// spans the corner. The endpoints are located in the same place as
|
||||
/// they would be for [RoundedRectangleBorder], but they're connected
|
||||
/// by a straight line instead of an arc.
|
||||
///
|
||||
/// Negative radius values are clamped to 0.0 by [getInnerPath] and
|
||||
/// [getOuterPath].
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions {
|
||||
return new EdgeInsets.all(side.width);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) {
|
||||
return new BeveledRectangleBorder(
|
||||
side: side.scale(t),
|
||||
borderRadius: borderRadius * t,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpFrom(ShapeBorder a, double t) {
|
||||
assert(t != null);
|
||||
if (a is BeveledRectangleBorder) {
|
||||
return new BeveledRectangleBorder(
|
||||
side: BorderSide.lerp(a.side, side, t),
|
||||
borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t),
|
||||
);
|
||||
}
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder lerpTo(ShapeBorder b, double t) {
|
||||
assert(t != null);
|
||||
if (b is BeveledRectangleBorder) {
|
||||
return new BeveledRectangleBorder(
|
||||
side: BorderSide.lerp(side, b.side, t),
|
||||
borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t),
|
||||
);
|
||||
}
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
Path _getPath(RRect rrect) {
|
||||
final Offset centerLeft = new Offset(rrect.left, rrect.center.dy);
|
||||
final Offset centerRight = new Offset(rrect.right, rrect.center.dy);
|
||||
final Offset centerTop = new Offset(rrect.center.dx, rrect.top);
|
||||
final Offset centerBottom = new Offset(rrect.center.dx, rrect.bottom);
|
||||
|
||||
final double tlRadiusX = math.max(0.0, rrect.tlRadiusX);
|
||||
final double tlRadiusY = math.max(0.0, rrect.tlRadiusY);
|
||||
final double trRadiusX = math.max(0.0, rrect.trRadiusX);
|
||||
final double trRadiusY = math.max(0.0, rrect.trRadiusY);
|
||||
final double blRadiusX = math.max(0.0, rrect.blRadiusX);
|
||||
final double blRadiusY = math.max(0.0, rrect.blRadiusY);
|
||||
final double brRadiusX = math.max(0.0, rrect.brRadiusX);
|
||||
final double brRadiusY = math.max(0.0, rrect.brRadiusY);
|
||||
|
||||
final List<Offset> vertices = <Offset>[
|
||||
new Offset(rrect.left, math.min(centerLeft.dy, rrect.top + tlRadiusY)),
|
||||
new Offset(math.min(centerTop.dx, rrect.left + tlRadiusX), rrect.top),
|
||||
new Offset(math.max(centerTop.dx, rrect.right -trRadiusX), rrect.top),
|
||||
new Offset(rrect.right, math.min(centerRight.dy, rrect.top + trRadiusY)),
|
||||
new Offset(rrect.right, math.max(centerRight.dy, rrect.bottom - brRadiusY)),
|
||||
new Offset(math.max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom),
|
||||
new Offset(math.min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom),
|
||||
new Offset(rrect.left, math.max(centerLeft.dy, rrect.bottom - blRadiusY)),
|
||||
];
|
||||
|
||||
return new Path()..addPolygon(vertices, true);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, { TextDirection textDirection }) {
|
||||
return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, { TextDirection textDirection }) {
|
||||
return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
|
||||
if (rect.isEmpty)
|
||||
return;
|
||||
switch (side.style) {
|
||||
case BorderStyle.none:
|
||||
break;
|
||||
case BorderStyle.solid:
|
||||
final Path path = getOuterPath(rect, textDirection: textDirection)
|
||||
..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
|
||||
canvas.drawPath(path, side.toPaint());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (runtimeType != other.runtimeType)
|
||||
return false;
|
||||
final BeveledRectangleBorder typedOther = other;
|
||||
return side == typedOther.side
|
||||
&& borderRadius == typedOther.borderRadius;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(side, borderRadius);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType($side, $borderRadius)';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2018 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('BeveledRectangleBorder scale and lerp', () {
|
||||
final BeveledRectangleBorder c10 = new BeveledRectangleBorder(side: const BorderSide(width: 10.0), borderRadius: new BorderRadius.circular(100.0));
|
||||
final BeveledRectangleBorder c15 = new BeveledRectangleBorder(side: const BorderSide(width: 15.0), borderRadius: new BorderRadius.circular(150.0));
|
||||
final BeveledRectangleBorder c20 = new BeveledRectangleBorder(side: const BorderSide(width: 20.0), borderRadius: new BorderRadius.circular(200.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);
|
||||
});
|
||||
|
||||
test('BeveledRectangleBorder BorderRadius.zero', () {
|
||||
final Rect rect1 = new Rect.fromLTRB(10.0, 20.0, 30.0, 40.0);
|
||||
final Matcher looksLikeRect1 = isPathThat(
|
||||
includes: const <Offset>[ const Offset(10.0, 20.0), const Offset(20.0, 30.0) ],
|
||||
excludes: const <Offset>[ const Offset(9.0, 19.0), const Offset(31.0, 41.0) ],
|
||||
);
|
||||
|
||||
// Default border radius and border side are zero, i.e. just a rectangle.
|
||||
expect(const BeveledRectangleBorder().getOuterPath(rect1), looksLikeRect1);
|
||||
expect(const BeveledRectangleBorder().getInnerPath(rect1), looksLikeRect1);
|
||||
|
||||
|
||||
// Represents the inner path when borderSide.width = 4, which is just rect1
|
||||
// inset by 4 on all sides.
|
||||
final Matcher looksLikeInnerPath = isPathThat(
|
||||
includes: const <Offset>[ const Offset(14.0, 24.0), const Offset(16.0, 26.0) ],
|
||||
excludes: const <Offset>[ const Offset(9.0, 23.0), const Offset(27.0, 37.0) ],
|
||||
);
|
||||
|
||||
const BorderSide side = const BorderSide(width: 4.0);
|
||||
expect(const BeveledRectangleBorder(side: side).getOuterPath(rect1), looksLikeRect1);
|
||||
expect(const BeveledRectangleBorder(side: side).getInnerPath(rect1), looksLikeInnerPath);
|
||||
});
|
||||
|
||||
test('BeveledRectangleBorder non-zero BorderRadius', () {
|
||||
final Rect rect = new Rect.fromLTRB(10.0, 20.0, 30.0, 40.0);
|
||||
final Matcher looksLikeRect = isPathThat(
|
||||
includes: const <Offset>[ const Offset(15.0, 25.0), const Offset(20.0, 30.0) ],
|
||||
excludes: const <Offset>[ const Offset(10.0, 20.0), const Offset(30.0, 40.0) ],
|
||||
);
|
||||
const BeveledRectangleBorder border = const BeveledRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(const Radius.circular(5.0))
|
||||
);
|
||||
expect(border.getOuterPath(rect), looksLikeRect);
|
||||
expect(border.getInnerPath(rect), looksLikeRect);
|
||||
});
|
||||
|
||||
}
|
Loading…
Reference in a new issue