blob: b828bda854597b934145e30b659858f25209716e [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'border_radius.dart';
import 'borders.dart';
import 'circle_border.dart';
import 'edge_insets.dart';
/// A rectangular border with rounded corners.
///
/// Typically used with [ShapeDecoration] to draw a box with a rounded
/// rectangle.
///
/// This shape can interpolate to and from [CircleBorder].
///
/// See also:
///
/// * [BorderSide], which is used to describe each side of the box.
/// * [Border], which, when used with [BoxDecoration], can also
/// describe a rounded rectangle.
class RoundedRectangleBorder extends OutlinedBorder {
/// Creates a rounded rectangle border.
///
/// The arguments must not be null.
const RoundedRectangleBorder({
BorderSide side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
}) : assert(side != null),
assert(borderRadius != null),
super(side: side);
/// The radii for each corner.
final BorderRadiusGeometry borderRadius;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
}
@override
ShapeBorder scale(double t) {
return RoundedRectangleBorder(
side: side.scale(t),
borderRadius: borderRadius * t,
);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
assert(t != null);
if (a is RoundedRectangleBorder) {
return RoundedRectangleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
);
}
if (a is CircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: borderRadius,
circleness: 1.0 - t,
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
assert(t != null);
if (b is RoundedRectangleBorder) {
return RoundedRectangleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
);
}
if (b is CircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: borderRadius,
circleness: t,
);
}
return super.lerpTo(b, t);
}
/// Returns a copy of this RoundedRectangleBorder with the given fields
/// replaced with the new values.
@override
RoundedRectangleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius }) {
return RoundedRectangleBorder(
side: side ?? this.side,
borderRadius: borderRadius ?? this.borderRadius,
);
}
@override
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
}
@override
Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addRRect(borderRadius.resolve(textDirection).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(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
} else {
final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
final RRect inner = outer.deflate(width);
final Paint paint = Paint()
..color = side.color;
canvas.drawDRRect(outer, inner, paint);
}
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is RoundedRectangleBorder
&& other.side == side
&& other.borderRadius == borderRadius;
}
@override
int get hashCode => hashValues(side, borderRadius);
@override
String toString() {
return '${objectRuntimeType(this, 'RoundedRectangleBorder')}($side, $borderRadius)';
}
}
class _RoundedRectangleToCircleBorder extends OutlinedBorder {
const _RoundedRectangleToCircleBorder({
BorderSide side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
required this.circleness,
}) : assert(side != null),
assert(borderRadius != null),
assert(circleness != null),
super(side: side);
final BorderRadiusGeometry borderRadius;
final double circleness;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
}
@override
ShapeBorder scale(double t) {
return _RoundedRectangleToCircleBorder(
side: side.scale(t),
borderRadius: borderRadius * t,
circleness: t,
);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
assert(t != null);
if (a is RoundedRectangleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
circleness: circleness * t,
);
}
if (a is CircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: borderRadius,
circleness: circleness + (1.0 - circleness) * (1.0 - t),
);
}
if (a is _RoundedRectangleToCircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
circleness: ui.lerpDouble(a.circleness, circleness, t)!,
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is RoundedRectangleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
circleness: circleness * (1.0 - t),
);
}
if (b is CircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: borderRadius,
circleness: circleness + (1.0 - circleness) * t,
);
}
if (b is _RoundedRectangleToCircleBorder) {
return _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, 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 Rect.fromLTRB(
rect.left,
rect.top + delta,
rect.right,
rect.bottom - delta,
);
} else {
final double delta = circleness * (rect.width - rect.height) / 2.0;
return Rect.fromLTRB(
rect.left + delta,
rect.top,
rect.right - delta,
rect.bottom,
);
}
}
BorderRadius? _adjustBorderRadius(Rect rect, TextDirection? textDirection) {
final BorderRadius resolvedRadius = borderRadius.resolve(textDirection);
if (circleness == 0.0)
return resolvedRadius;
return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), circleness);
}
@override
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)).deflate(side.width));
}
@override
Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)));
}
@override
_RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, double? circleness }) {
return _RoundedRectangleToCircleBorder(
side: side ?? this.side,
borderRadius: borderRadius ?? this.borderRadius,
circleness: circleness ?? this.circleness,
);
}
@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, textDirection)!.toRRect(_adjustRect(rect)), side.toPaint());
} else {
final RRect outer = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
final RRect inner = outer.deflate(width);
final Paint paint = Paint()
..color = side.color;
canvas.drawDRRect(outer, inner, paint);
}
}
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _RoundedRectangleToCircleBorder
&& other.side == side
&& other.borderRadius == borderRadius
&& other.circleness == circleness;
}
@override
int get hashCode => hashValues(side, borderRadius, circleness);
@override
String toString() {
return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
}
}