rounded_rectangle_border.dart 9.46 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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.
29
class RoundedRectangleBorder extends OutlinedBorder {
30 31 32
  /// Creates a rounded rectangle border.
  ///
  /// The arguments must not be null.
33
  const RoundedRectangleBorder({
34
    BorderSide side = BorderSide.none,
35
    this.borderRadius = BorderRadius.zero,
36
  }) : assert(side != null),
37 38
       assert(borderRadius != null),
       super(side: side);
39 40

  /// The radii for each corner.
41
  final BorderRadiusGeometry borderRadius;
42 43 44

  @override
  EdgeInsetsGeometry get dimensions {
45
    return EdgeInsets.all(side.width);
46 47 48 49
  }

  @override
  ShapeBorder scale(double t) {
50
    return RoundedRectangleBorder(
51 52 53 54 55 56 57
      side: side.scale(t),
      borderRadius: borderRadius * t,
    );
  }

  @override
  ShapeBorder lerpFrom(ShapeBorder a, double t) {
58
    assert(t != null);
59
    if (a is RoundedRectangleBorder) {
60
      return RoundedRectangleBorder(
61
        side: BorderSide.lerp(a.side, side, t),
62
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
63 64 65
      );
    }
    if (a is CircleBorder) {
66
      return _RoundedRectangleToCircleBorder(
67 68 69 70 71 72 73 74 75 76
        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) {
77
    assert(t != null);
78
    if (b is RoundedRectangleBorder) {
79
      return RoundedRectangleBorder(
80
        side: BorderSide.lerp(side, b.side, t),
81
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
82 83 84
      );
    }
    if (b is CircleBorder) {
85
      return _RoundedRectangleToCircleBorder(
86 87 88 89 90 91 92 93
        side: BorderSide.lerp(side, b.side, t),
        borderRadius: borderRadius,
        circleness: t,
      );
    }
    return super.lerpTo(b, t);
  }

94 95 96 97 98 99 100 101 102 103
  /// Returns a copy of this RoundedRectangleBorder with the given fields
  /// replaced with the new values.
  @override
  RoundedRectangleBorder copyWith({ BorderSide side, BorderRadius borderRadius }) {
    return RoundedRectangleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
    );
  }

104 105
  @override
  Path getInnerPath(Rect rect, { TextDirection textDirection }) {
106
    return Path()
107
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
108 109 110 111
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection textDirection }) {
112
    return Path()
113
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
114 115 116 117 118 119 120 121 122 123
  }

  @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) {
124
          canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
125
        } else {
126
          final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
127
          final RRect inner = outer.deflate(width);
128
          final Paint paint = Paint()
129 130 131 132 133 134 135
            ..color = side.color;
          canvas.drawDRRect(outer, inner, paint);
        }
    }
  }

  @override
136 137
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
138
      return false;
139 140 141
    return other is RoundedRectangleBorder
        && other.side == side
        && other.borderRadius == borderRadius;
142 143 144 145 146 147 148
  }

  @override
  int get hashCode => hashValues(side, borderRadius);

  @override
  String toString() {
149
    return '${objectRuntimeType(this, 'RoundedRectangleBorder')}($side, $borderRadius)';
150 151 152
  }
}

153
class _RoundedRectangleToCircleBorder extends OutlinedBorder {
154
  const _RoundedRectangleToCircleBorder({
155
    BorderSide side = BorderSide.none,
156
    this.borderRadius = BorderRadius.zero,
157 158 159
    @required this.circleness,
  }) : assert(side != null),
       assert(borderRadius != null),
160 161
       assert(circleness != null),
       super(side: side);
162

163
  final BorderRadiusGeometry borderRadius;
164 165 166 167 168

  final double circleness;

  @override
  EdgeInsetsGeometry get dimensions {
169
    return EdgeInsets.all(side.width);
170 171 172 173
  }

  @override
  ShapeBorder scale(double t) {
174
    return _RoundedRectangleToCircleBorder(
175 176 177 178 179 180 181 182
      side: side.scale(t),
      borderRadius: borderRadius * t,
      circleness: t,
    );
  }

  @override
  ShapeBorder lerpFrom(ShapeBorder a, double t) {
183
    assert(t != null);
184
    if (a is RoundedRectangleBorder) {
185
      return _RoundedRectangleToCircleBorder(
186
        side: BorderSide.lerp(a.side, side, t),
187
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
188 189 190 191
        circleness: circleness * t,
      );
    }
    if (a is CircleBorder) {
192
      return _RoundedRectangleToCircleBorder(
193 194 195 196 197 198
        side: BorderSide.lerp(a.side, side, t),
        borderRadius: borderRadius,
        circleness: circleness + (1.0 - circleness) * (1.0 - t),
      );
    }
    if (a is _RoundedRectangleToCircleBorder) {
199
      return _RoundedRectangleToCircleBorder(
200
        side: BorderSide.lerp(a.side, side, t),
201
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
202 203 204 205 206 207 208 209 210
        circleness: ui.lerpDouble(a.circleness, circleness, t),
      );
    }
    return super.lerpFrom(a, t);
  }

  @override
  ShapeBorder lerpTo(ShapeBorder b, double t) {
    if (b is RoundedRectangleBorder) {
211
      return _RoundedRectangleToCircleBorder(
212
        side: BorderSide.lerp(side, b.side, t),
213
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
214 215 216 217
        circleness: circleness * (1.0 - t),
      );
    }
    if (b is CircleBorder) {
218
      return _RoundedRectangleToCircleBorder(
219 220 221 222 223 224
        side: BorderSide.lerp(side, b.side, t),
        borderRadius: borderRadius,
        circleness: circleness + (1.0 - circleness) * t,
      );
    }
    if (b is _RoundedRectangleToCircleBorder) {
225
      return _RoundedRectangleToCircleBorder(
226
        side: BorderSide.lerp(side, b.side, t),
227
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
228 229 230 231 232 233 234 235 236 237 238
        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;
239
      return Rect.fromLTRB(
240 241 242 243 244 245 246
        rect.left,
        rect.top + delta,
        rect.right,
        rect.bottom - delta,
      );
    } else {
      final double delta = circleness * (rect.width - rect.height) / 2.0;
247
      return Rect.fromLTRB(
248 249 250 251 252 253 254 255
        rect.left + delta,
        rect.top,
        rect.right - delta,
        rect.bottom,
      );
    }
  }

256 257
  BorderRadius _adjustBorderRadius(Rect rect, TextDirection textDirection) {
    final BorderRadius resolvedRadius = borderRadius.resolve(textDirection);
258
    if (circleness == 0.0)
259 260
      return resolvedRadius;
    return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), circleness);
261 262 263 264
  }

  @override
  Path getInnerPath(Rect rect, { TextDirection textDirection }) {
265
    return Path()
266
      ..addRRect(_adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect)).deflate(side.width));
267 268 269 270
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection textDirection }) {
271
    return Path()
272
      ..addRRect(_adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect)));
273 274
  }

275 276 277 278 279 280 281 282 283
  @override
  _RoundedRectangleToCircleBorder copyWith({ BorderSide side, BorderRadius borderRadius, double circleness }) {
    return _RoundedRectangleToCircleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
      circleness: circleness ?? this.circleness,
    );
  }

284 285 286 287 288 289 290 291
  @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) {
292
          canvas.drawRRect(_adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect)), side.toPaint());
293
        } else {
294
          final RRect outer = _adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect));
295
          final RRect inner = outer.deflate(width);
296
          final Paint paint = Paint()
297 298 299 300 301 302 303
            ..color = side.color;
          canvas.drawDRRect(outer, inner, paint);
        }
    }
  }

  @override
304 305
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
306
      return false;
307 308 309 310
    return other is _RoundedRectangleToCircleBorder
        && other.side == side
        && other.borderRadius == borderRadius
        && other.circleness == circleness;
311 312 313 314 315 316 317 318 319 320
  }

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