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

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

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

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

  @override
56
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
57
    assert(t != null);
58
    if (a is RoundedRectangleBorder) {
59
      return RoundedRectangleBorder(
60
        side: BorderSide.lerp(a.side, side, t),
61
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
62 63 64
      );
    }
    if (a is CircleBorder) {
65
      return _RoundedRectangleToCircleBorder(
66 67 68 69 70 71 72 73 74
        side: BorderSide.lerp(a.side, side, t),
        borderRadius: borderRadius,
        circleness: 1.0 - t,
      );
    }
    return super.lerpFrom(a, t);
  }

  @override
75
  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
76
    assert(t != null);
77
    if (b is RoundedRectangleBorder) {
78
      return RoundedRectangleBorder(
79
        side: BorderSide.lerp(side, b.side, t),
80
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
81 82 83
      );
    }
    if (b is CircleBorder) {
84
      return _RoundedRectangleToCircleBorder(
85 86 87 88 89 90 91 92
        side: BorderSide.lerp(side, b.side, t),
        borderRadius: borderRadius,
        circleness: t,
      );
    }
    return super.lerpTo(b, t);
  }

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

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

  @override
110
  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
111
    return Path()
112
      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
113 114 115
  }

  @override
116
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
117 118 119 120 121 122
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
        final double width = side.width;
        if (width == 0.0) {
123
          canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
124
        } else {
125
          final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
126
          final RRect inner = outer.deflate(width);
127
          final Paint paint = Paint()
128 129 130 131 132 133 134
            ..color = side.color;
          canvas.drawDRRect(outer, inner, paint);
        }
    }
  }

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

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

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

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

162
  final BorderRadiusGeometry borderRadius;
163 164 165 166 167

  final double circleness;

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

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

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

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

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

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

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

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

283
  @override
284
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
285 286 287 288 289 290
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
        final double width = side.width;
        if (width == 0.0) {
291
          canvas.drawRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)), side.toPaint());
292
        } else {
293
          final RRect outer = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
294
          final RRect inner = outer.deflate(width);
295
          final Paint paint = Paint()
296 297 298 299 300 301 302
            ..color = side.color;
          canvas.drawDRRect(outer, inner, paint);
        }
    }
  }

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

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