rounded_rectangle_border.dart 9.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
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
// 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.
27
class RoundedRectangleBorder extends OutlinedBorder {
28 29 30
  /// Creates a rounded rectangle border.
  ///
  /// The arguments must not be null.
31
  const RoundedRectangleBorder({
32
    super.side,
33
    this.borderRadius = BorderRadius.zero,
34
  }) : assert(side != null),
35
       assert(borderRadius != null);
36 37

  /// The radii for each corner.
38
  final BorderRadiusGeometry borderRadius;
39 40 41

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

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

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

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

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

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

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

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

  @override
133 134
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
135
      return false;
136 137 138
    return other is RoundedRectangleBorder
        && other.side == side
        && other.borderRadius == borderRadius;
139 140 141
  }

  @override
142
  int get hashCode => Object.hash(side, borderRadius);
143 144 145

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

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

159
  final BorderRadiusGeometry borderRadius;
160 161 162 163 164

  final double circleness;

  @override
  EdgeInsetsGeometry get dimensions {
165
    return EdgeInsets.all(side.width);
166 167 168 169
  }

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

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

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

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

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

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

271
  @override
272
  _RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, double? circleness }) {
273 274 275 276 277 278 279
    return _RoundedRectangleToCircleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
      circleness: circleness ?? this.circleness,
    );
  }

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

  @override
300 301
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
302
      return false;
303 304 305 306
    return other is _RoundedRectangleToCircleBorder
        && other.side == side
        && other.borderRadius == borderRadius
        && other.circleness == circleness;
307 308 309
  }

  @override
310
  int get hashCode => Object.hash(side, borderRadius, circleness);
311 312 313 314 315 316

  @override
  String toString() {
    return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
  }
}