rounded_rectangle_border.dart 8.83 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
// Copyright 2017 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: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 ShapeBorder {
  /// Creates a rounded rectangle border.
  ///
  /// The arguments must not be null.
31
  const RoundedRectangleBorder({
32 33
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
34 35 36 37 38 39 40
  }) : assert(side != null),
       assert(borderRadius != null);

  /// The style of this border.
  final BorderSide side;

  /// 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 94 95
        side: BorderSide.lerp(side, b.side, t),
        borderRadius: borderRadius,
        circleness: t,
      );
    }
    return super.lerpTo(b, t);
  }

  @override
  Path getInnerPath(Rect rect, { TextDirection textDirection }) {
96
    return Path()
97
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
98 99 100 101
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection textDirection }) {
102
    return Path()
103
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
104 105 106 107 108 109 110 111 112 113
  }

  @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) {
114
          canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
115
        } else {
116
          final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
117
          final RRect inner = outer.deflate(width);
118
          final Paint paint = Paint()
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
            ..color = side.color;
          canvas.drawDRRect(outer, inner, paint);
        }
    }
  }

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType)
      return false;
    final RoundedRectangleBorder typedOther = other;
    return side == typedOther.side
        && borderRadius == typedOther.borderRadius;
  }

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

  @override
  String toString() {
    return '$runtimeType($side, $borderRadius)';
  }
}

class _RoundedRectangleToCircleBorder extends ShapeBorder {
144
  const _RoundedRectangleToCircleBorder({
145 146
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
147 148 149 150 151 152 153
    @required this.circleness,
  }) : assert(side != null),
       assert(borderRadius != null),
       assert(circleness != null);

  final BorderSide side;

154
  final BorderRadiusGeometry borderRadius;
155 156 157 158 159

  final double circleness;

  @override
  EdgeInsetsGeometry get dimensions {
160
    return EdgeInsets.all(side.width);
161 162 163 164
  }

  @override
  ShapeBorder scale(double t) {
165
    return _RoundedRectangleToCircleBorder(
166 167 168 169 170 171 172 173
      side: side.scale(t),
      borderRadius: borderRadius * t,
      circleness: t,
    );
  }

  @override
  ShapeBorder lerpFrom(ShapeBorder a, double t) {
174
    assert(t != null);
175
    if (a is RoundedRectangleBorder) {
176
      return _RoundedRectangleToCircleBorder(
177
        side: BorderSide.lerp(a.side, side, t),
178
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
179 180 181 182
        circleness: circleness * t,
      );
    }
    if (a is CircleBorder) {
183
      return _RoundedRectangleToCircleBorder(
184 185 186 187 188 189
        side: BorderSide.lerp(a.side, side, t),
        borderRadius: borderRadius,
        circleness: circleness + (1.0 - circleness) * (1.0 - t),
      );
    }
    if (a is _RoundedRectangleToCircleBorder) {
190
      return _RoundedRectangleToCircleBorder(
191
        side: BorderSide.lerp(a.side, side, t),
192
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
193 194 195 196 197 198 199 200 201
        circleness: ui.lerpDouble(a.circleness, circleness, t),
      );
    }
    return super.lerpFrom(a, t);
  }

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

247 248
  BorderRadius _adjustBorderRadius(Rect rect, TextDirection textDirection) {
    final BorderRadius resolvedRadius = borderRadius.resolve(textDirection);
249
    if (circleness == 0.0)
250 251
      return resolvedRadius;
    return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), circleness);
252 253 254 255
  }

  @override
  Path getInnerPath(Rect rect, { TextDirection textDirection }) {
256
    return Path()
257
      ..addRRect(_adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect)).deflate(side.width));
258 259 260 261
  }

  @override
  Path getOuterPath(Rect rect, { TextDirection textDirection }) {
262
    return Path()
263
      ..addRRect(_adjustBorderRadius(rect, textDirection).toRRect(_adjustRect(rect)));
264 265 266 267 268 269 270 271 272 273
  }

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

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType)
      return false;
    final _RoundedRectangleToCircleBorder typedOther = other;
    return side == typedOther.side
        && borderRadius == typedOther.borderRadius
        && circleness == typedOther.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)';
  }
}