Commit f3444fcf authored by Dragoș Tiselice's avatar Dragoș Tiselice Committed by GitHub

Added BorderRadius. (#5072)

* Added custom radii to RRect.

This is the first commit towads an implementation of
MergeableMaterial. It adds custom radii to RRect.

* Renamed RRect constructors and added BorderRadius.

BorderRadius is a class similar to EdgeInsets that lets you define
all rounded corners of a rounded rectangle easily.
parent 51f8fb99
......@@ -417,7 +417,7 @@ class ItemImageBox extends StatelessWidget {
child: new Container(
decoration: new BoxDecoration(
backgroundColor: Colors.black54,
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
),
padding: new EdgeInsets.all(4.0),
child: new RichText(
......
......@@ -76,8 +76,7 @@ class VendorItem extends StatelessWidget {
new SizedBox(
width: 24.0,
child: new ClipRRect(
xRadius: 12.0,
yRadius: 12.0,
borderRadius: new BorderRadius.circular(12.0),
child: new Image.asset(vendor.avatarAsset, fit: ImageFit.cover)
)
),
......
......@@ -119,7 +119,7 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
const double _kMidpoint = 0.5;
const double _kEdgeSize = Checkbox.width;
const double _kEdgeRadius = 1.0;
const Radius _kEdgeRadius = const Radius.circular(1.0);
const double _kStrokeWidth = 2.0;
class _RenderCheckbox extends RenderToggleable {
......@@ -159,7 +159,7 @@ class _RenderCheckbox extends RenderToggleable {
double rectSize = _kEdgeSize - inset * _kStrokeWidth;
Rect rect = new Rect.fromLTWH(offsetX + inset, offsetY + inset, rectSize, rectSize);
RRect outer = new RRect.fromRectXY(rect, _kEdgeRadius, _kEdgeRadius);
RRect outer = new RRect.fromRectAndRadius(rect, _kEdgeRadius);
if (t <= 0.5) {
// Outline
RRect inner = outer.deflate(math.min(rectSize / 2.0, _kStrokeWidth + rectSize * t));
......
......@@ -112,7 +112,7 @@ class Chip extends StatelessWidget {
padding: new EdgeInsets.only(left: leftPadding, right: rightPadding),
decoration: new BoxDecoration(
backgroundColor: Colors.grey[300],
borderRadius: 16.0
borderRadius: new BorderRadius.circular(16.0)
),
child: new Row(
children: children,
......
......@@ -37,7 +37,7 @@ class _DropDownMenuPainter extends CustomPainter {
// configuration in the paint() function and you must provide some sort
// of onChanged callback here.
backgroundColor: color,
borderRadius: 2.0,
borderRadius: new BorderRadius.circular(2.0),
boxShadow: kElevationToShadow[elevation]
).createBoxPainter(),
super(repaint: resize);
......
......@@ -45,11 +45,11 @@ enum MaterialType {
///
/// * [MaterialType]
/// * [Material]
const Map<MaterialType, double> kMaterialEdges = const <MaterialType, double>{
final Map<MaterialType, BorderRadius> kMaterialEdges = <MaterialType, BorderRadius> {
MaterialType.canvas: null,
MaterialType.card: 2.0,
MaterialType.card: new BorderRadius.circular(2.0),
MaterialType.circle: null,
MaterialType.button: 2.0,
MaterialType.button: new BorderRadius.circular(2.0),
MaterialType.transparency: null,
};
......@@ -257,8 +257,7 @@ class _MaterialState extends State<Material> {
contents = new ClipOval(child: contents);
} else if (kMaterialEdges[config.type] != null) {
contents = new ClipRRect(
xRadius: kMaterialEdges[config.type],
yRadius: kMaterialEdges[config.type],
borderRadius: kMaterialEdges[config.type],
child: contents
);
}
......
......@@ -334,7 +334,7 @@ class _RenderSwitch extends RenderToggleable {
size.width - 2.0 * trackHorizontalPadding,
_kTrackHeight
);
final RRect trackRRect = new RRect.fromRectXY(trackRect, _kTrackRadius, _kTrackRadius);
final RRect trackRRect = new RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
canvas.drawRRect(trackRRect, paint);
final Point thumbPosition = new Point(
......
......@@ -284,7 +284,7 @@ class _TooltipOverlay extends StatelessWidget {
child: new Container(
decoration: new BoxDecoration(
backgroundColor: Colors.grey[700],
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
),
height: height,
padding: padding,
......
......@@ -17,6 +17,7 @@ export 'dart:ui' show
PaintingStyle,
Path,
Point,
Radius,
RRect,
RSTransform,
Rect,
......
......@@ -14,18 +14,6 @@ import 'edge_insets.dart';
export 'edge_insets.dart' show EdgeInsets;
double _getEffectiveBorderRadius(Rect rect, double borderRadius) {
assert(rect != null);
assert(borderRadius != null);
double shortestSide = rect.shortestSide;
// In principle, we should use shortestSide / 2.0, but we don't want to
// run into floating point rounding errors. Instead, we just use
// shortestSide and let Canvas do any remaining clamping.
// The right long-term fix is to do layout using fixed precision
// arithmetic. (see also "_applyFloatingPointHack")
return borderRadius > shortestSide ? shortestSide : borderRadius;
}
/// The style of line to draw for a [BorderSide] in a [Border].
enum BorderStyle {
/// Skip the border.
......@@ -232,7 +220,7 @@ class Border {
/// Paints the border within the given rect on the given canvas.
void paint(Canvas canvas, Rect rect, {
BoxShape shape: BoxShape.rectangle,
double borderRadius: null
BorderRadius borderRadius: null
}) {
if (isUniform) {
if (borderRadius != null) {
......@@ -334,12 +322,12 @@ class Border {
}
}
void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) {
void _paintBorderWithRadius(Canvas canvas, Rect rect,
BorderRadius borderRadius) {
assert(isUniform);
Paint paint = new Paint()
..color = top.color;
double radius = _getEffectiveBorderRadius(rect, borderRadius);
RRect outer = new RRect.fromRectXY(rect, radius, radius);
RRect outer = borderRadius.toRRect(rect);
double width = top.width;
if (width == 0.0) {
paint
......@@ -347,7 +335,7 @@ class Border {
..strokeWidth = 0.0;
canvas.drawRRect(outer, paint);
} else {
RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
RRect inner = outer.deflate(width);
canvas.drawDRRect(outer, inner, paint);
}
}
......@@ -1127,6 +1115,114 @@ enum BoxShape {
circle
}
/// An immutable set of radii for each corner of a rectangle. Only used with
/// [BoxShape.rectangle].
class BorderRadius {
/// Creates a border radius where all radii are [radius].
const BorderRadius.all(Radius radius) : this.only(
topLeft: radius,
topRight: radius,
bottomRight: radius,
bottomLeft: radius
);
/// Creates a border radius where all radii are [Radius.circular(radius)].
BorderRadius.circular(double radius) : this.all(
new Radius.circular(radius)
);
/// Creates a vertically symmetric border radius where the top and bottom
/// sides of the rectangle have the same radii.
const BorderRadius.vertical({
Radius top: Radius.zero,
Radius bottom: Radius.zero
}) : this.only(
topLeft: top,
topRight: top,
bottomRight: bottom,
bottomLeft: bottom
);
/// Creates a horizontally symmetrical border radius where the left and right
/// sides of the rectangle have the same radii.
const BorderRadius.horizontal({
Radius left: Radius.zero,
Radius right: Radius.zero
}) : this.only(
topLeft: left,
topRight: right,
bottomRight: right,
bottomLeft: left
);
/// Creates a border radius with only the given non-zero values. The other
/// corners will be right angles.
const BorderRadius.only({
this.topLeft: Radius.zero,
this.topRight: Radius.zero,
this.bottomRight: Radius.zero,
this.bottomLeft: Radius.zero
});
/// A border radius with all zero radii.
static const BorderRadius zero = const BorderRadius.all(Radius.zero);
/// The top-left [Radius].
final Radius topLeft;
/// The top-right [Radius].
final Radius topRight;
/// The bottom-right [Radius].
final Radius bottomRight;
/// The bottom-left [Radius].
final Radius bottomLeft;
/// Linearly interpolates between two [BorderRadius] objects.
///
/// If either is null, this function interpolates from [BorderRadius.zero].
static BorderRadius lerp(BorderRadius a, BorderRadius b, double t) {
if (a == null && b == null)
return null;
return new BorderRadius.only(
topLeft: Radius.lerp(a.topLeft, b.topLeft, t),
topRight: Radius.lerp(a.topRight, b.topRight, t),
bottomRight: Radius.lerp(a.bottomRight, b.bottomRight, t),
bottomLeft: Radius.lerp(a.bottomLeft, b.bottomLeft, t)
);
}
/// Creates a [RRect] from the current border radius and a [Rect].
RRect toRRect(Rect rect) {
return new RRect.fromRectAndCorners(
rect,
topLeft: topLeft,
topRight: topRight,
bottomRight: bottomRight,
bottomLeft: bottomLeft
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! BorderRadius)
return false;
final BorderRadius typedOther = other;
return topLeft == typedOther.topLeft &&
topRight == typedOther.topRight &&
bottomRight == typedOther.bottomRight &&
bottomLeft == typedOther.bottomLeft;
}
@override
int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft);
@override
String toString() {
return 'BorderRadius($topLeft, $topRight, $bottomRight, $bottomLeft)';
}
}
/// An immutable description of how to paint a box.
class BoxDecoration extends Decoration {
/// Creates a box decoration.
......@@ -1168,10 +1264,10 @@ class BoxDecoration extends Decoration {
/// A border to draw above the background.
final Border border;
/// If non-null, the corners of this box are rounded by this radius.
/// If non-null, the corners of this box are rounded by this [BorderRadius].
///
/// Applies only to boxes with rectangular shapes.
final double borderRadius;
final BorderRadius borderRadius;
/// A list of shadows cast by this box behind the background.
final List<BoxShadow> boxShadow;
......@@ -1193,7 +1289,7 @@ class BoxDecoration extends Decoration {
backgroundColor: Color.lerp(null, backgroundColor, factor),
backgroundImage: backgroundImage,
border: Border.lerp(null, border, factor),
borderRadius: ui.lerpDouble(null, borderRadius, factor),
borderRadius: BorderRadius.lerp(null, borderRadius, factor),
boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
gradient: gradient,
shape: shape
......@@ -1220,7 +1316,7 @@ class BoxDecoration extends Decoration {
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
backgroundImage: b.backgroundImage,
border: Border.lerp(a.border, b.border, t),
borderRadius: ui.lerpDouble(a.borderRadius, b.borderRadius, t),
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
gradient: b.gradient,
shape: b.shape
......@@ -1304,7 +1400,7 @@ class BoxDecoration extends Decoration {
switch (shape) {
case BoxShape.rectangle:
if (borderRadius != null) {
RRect bounds = new RRect.fromRectXY(Point.origin & size, borderRadius, borderRadius);
RRect bounds = borderRadius.toRRect(Point.origin & size);
return bounds.contains(position);
}
return true;
......@@ -1370,8 +1466,7 @@ class _BoxDecorationPainter extends BoxPainter {
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
double radius = _getEffectiveBorderRadius(rect, _decoration.borderRadius);
canvas.drawRRect(new RRect.fromRectXY(rect, radius, radius), paint);
canvas.drawRRect(_decoration.borderRadius.toRRect(rect), paint);
}
break;
}
......
......@@ -943,45 +943,32 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
/// Clips its child using a rounded rectangle.
///
/// Creates a rounded rectangle from its layout dimensions and the given x and
/// y radius values and prevents its child from painting outside that rounded
/// Creates a rounded rectangle from its layout dimensions and the given border
/// radius and prevents its child from painting outside that rounded
/// rectangle.
class RenderClipRRect extends RenderProxyBox {
/// Creates a rounded-rectangular clip.
///
/// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
/// right-angled corners.
RenderClipRRect({
RenderBox child,
double xRadius,
double yRadius
}) : _xRadius = xRadius, _yRadius = yRadius, super(child) {
assert(_xRadius != null);
assert(_yRadius != null);
}
/// The radius of the rounded corners in the horizontal direction in logical pixels.
///
/// Values are clamped to be between zero and half the width of the render
/// object.
double get xRadius => _xRadius;
double _xRadius;
set xRadius (double newXRadius) {
assert(newXRadius != null);
if (_xRadius == newXRadius)
return;
_xRadius = newXRadius;
markNeedsPaint();
BorderRadius borderRadius: BorderRadius.zero
}) : _borderRadius = borderRadius, super(child) {
assert(_borderRadius != null);
}
/// The radius of the rounded corners in the vertical direction in logical pixels.
/// The border radius of the rounded corners..
///
/// Values are clamped to be between zero and half the height of the render
/// object.
double get yRadius => _yRadius;
double _yRadius;
set yRadius (double newYRadius) {
assert(newYRadius != null);
if (_yRadius == newYRadius)
/// Values are clamped so that horizontal and vertical radii sums do not
/// exceed width/height.
BorderRadius get borderRadius => _borderRadius;
BorderRadius _borderRadius;
set borderRadius (BorderRadius value) {
assert(value != null);
if (_borderRadius == value)
return;
_yRadius = newYRadius;
_borderRadius = value;
markNeedsPaint();
}
......@@ -992,7 +979,7 @@ class RenderClipRRect extends RenderProxyBox {
void paint(PaintingContext context, Offset offset) {
if (child != null) {
Rect rect = Point.origin & size;
RRect rrect = new RRect.fromRectXY(rect, xRadius, yRadius);
RRect rrect = borderRadius.toRRect(rect);
context.pushClipRRect(needsCompositing, offset, rect, rrect, super.paint);
}
}
......
......@@ -277,31 +277,23 @@ class ClipRRect extends SingleChildRenderObjectWidget {
/// Creates a rounded-rectangular clip.
ClipRRect({
Key key,
@required this.xRadius,
@required this.yRadius,
@required this.borderRadius,
Widget child
}) : super(key: key, child: child);
/// The radius of the rounded corners in the horizontal direction in logical pixels.
/// The border radius of the rounded corners.
///
/// Values are clamped to be between zero and half the width of the render
/// object.
final double xRadius;
/// The radius of the rounded corners in the vertical direction in logical pixels.
///
/// Values are clamped to be between zero and half the height of the render
/// object.
final double yRadius;
final BorderRadius borderRadius;
@override
RenderClipRRect createRenderObject(BuildContext context) => new RenderClipRRect(xRadius: xRadius, yRadius: yRadius);
RenderClipRRect createRenderObject(BuildContext context) => new RenderClipRRect(borderRadius: borderRadius);
@override
void updateRenderObject(BuildContext context, RenderClipRRect renderObject) {
renderObject
..xRadius = xRadius
..yRadius = yRadius;
..borderRadius = borderRadius;
}
}
......
// Copyright 2015 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:test/test.dart';
void main() {
test("RRect.contains()", () {
RRect rrect = new RRect.fromRectAndCorners(
new Rect.fromLTRB(1.0, 1.0, 2.0, 2.0),
topLeft: const Radius.circular(0.5),
topRight: const Radius.circular(0.25),
bottomRight: const Radius.elliptical(0.25, 0.75),
bottomLeft: Radius.zero
);
expect(rrect.contains(const Point(1.0, 1.0)), isFalse);
expect(rrect.contains(const Point(1.1, 1.1)), isFalse);
expect(rrect.contains(const Point(1.15, 1.15)), isTrue);
expect(rrect.contains(const Point(2.0, 1.0)), isFalse);
expect(rrect.contains(const Point(1.93, 1.07)), isFalse);
expect(rrect.contains(const Point(1.97, 1.7)), isFalse);
expect(rrect.contains(const Point(1.7, 1.97)), isTrue);
expect(rrect.contains(const Point(1.0, 1.99)), isTrue);
});
test("RRect.contains() large radii", () {
RRect rrect = new RRect.fromRectAndCorners(
new Rect.fromLTRB(1.0, 1.0, 2.0, 2.0),
topLeft: const Radius.circular(5000.0),
topRight: const Radius.circular(2500.0),
bottomRight: const Radius.elliptical(2500.0, 7500.0),
bottomLeft: Radius.zero
);
expect(rrect.contains(const Point(1.0, 1.0)), isFalse);
expect(rrect.contains(const Point(1.1, 1.1)), isFalse);
expect(rrect.contains(const Point(1.15, 1.15)), isTrue);
expect(rrect.contains(const Point(2.0, 1.0)), isFalse);
expect(rrect.contains(const Point(1.93, 1.07)), isFalse);
expect(rrect.contains(const Point(1.97, 1.7)), isFalse);
expect(rrect.contains(const Point(1.7, 1.97)), isTrue);
expect(rrect.contains(const Point(1.0, 1.99)), isTrue);
});
}
......@@ -33,12 +33,12 @@ class MarkdownStyle extends MarkdownStyleRaw{
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
backgroundColor: Colors.blue[100],
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
backgroundColor: Colors.grey[100],
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
)
);
......@@ -67,12 +67,12 @@ class MarkdownStyle extends MarkdownStyleRaw{
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
backgroundColor: Colors.blue[100],
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
backgroundColor: Colors.grey[100],
borderRadius: 2.0
borderRadius: new BorderRadius.circular(2.0)
)
);
}
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