| // 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)'; |
| } |
| } |