continuous_rectangle_border.dart 5.32 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
import 'dart:math' as math;

8 9
import 'package:flutter/foundation.dart';

10 11 12 13 14
import 'basic_types.dart';
import 'border_radius.dart';
import 'borders.dart';
import 'edge_insets.dart';

15 16
/// A rectangular border with smooth continuous transitions between the straight
/// sides and the rounded corners.
17
///
18
/// {@tool snippet}
19 20 21
/// ```dart
/// Widget build(BuildContext context) {
///   return Material(
22
///     shape: ContinuousRectangleBorder(
23 24 25 26 27 28 29 30 31
///       borderRadius: BorderRadius.circular(28.0),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
32 33 34 35
///  * [RoundedRectangleBorder] Which creates rectangles with rounded corners,
///    however its straight sides change into a rounded corner with a circular
///    radius in a step function instead of gradually like the
///    [ContinuousRectangleBorder].
36
class ContinuousRectangleBorder extends OutlinedBorder {
37
  /// The arguments must not be null.
38
  const ContinuousRectangleBorder({
39
    BorderSide side = BorderSide.none,
40 41
    this.borderRadius = BorderRadius.zero,
  }) : assert(side != null),
42 43
       assert(borderRadius != null),
       super(side: side);
44 45 46 47 48 49 50

  /// The radius for each corner.
  ///
  /// Negative radius values are clamped to 0.0 by [getInnerPath] and
  /// [getOuterPath].
  final BorderRadiusGeometry borderRadius;

51
   @override
52 53 54 55
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);

  @override
  ShapeBorder scale(double t) {
56
    return ContinuousRectangleBorder(
57 58 59 60 61 62
      side: side.scale(t),
      borderRadius: borderRadius * t,
    );
  }

  @override
63
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
64
    assert(t != null);
65 66
    if (a is ContinuousRectangleBorder) {
      return ContinuousRectangleBorder(
67
        side: BorderSide.lerp(a.side, side, t),
68
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
69 70 71 72 73 74
      );
    }
    return super.lerpFrom(a, t);
  }

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

  double _clampToShortest(RRect rrect, double value) {
    return value > rrect.shortestSide ? rrect.shortestSide : value;
  }

  Path _getPath(RRect rrect) {
    final double left = rrect.left;
    final double right = rrect.right;
    final double top = rrect.top;
    final double bottom = rrect.bottom;
    //  Radii will be clamped to the value of the shortest side
96
    // of rrect to avoid strange tie-fighter shapes.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
    final double tlRadiusX =
      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusX));
    final double tlRadiusY =
      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusY));
    final double trRadiusX =
      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusX));
    final double trRadiusY =
      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusY));
    final double blRadiusX =
      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusX));
    final double blRadiusY =
      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusY));
    final double brRadiusX =
      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusX));
    final double brRadiusY =
      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusY));

    return Path()
      ..moveTo(left, top + tlRadiusX)
      ..cubicTo(left, top, left, top, left + tlRadiusY, top)
      ..lineTo(right - trRadiusX, top)
      ..cubicTo(right, top, right, top, right, top + trRadiusY)
119 120 121 122
      ..lineTo(right, bottom - brRadiusX)
      ..cubicTo(right, bottom, right, bottom, right - brRadiusY, bottom)
      ..lineTo(left + blRadiusX, bottom)
      ..cubicTo(left, bottom, left, bottom, left, bottom - blRadiusY)
123 124 125 126
      ..close();
  }

  @override
127
  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
128 129 130 131
    return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
  }

  @override
132
  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
133 134 135
    return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
  }

136
  @override
137
  ContinuousRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius }) {
138 139 140 141 142 143
    return ContinuousRectangleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
    );
  }

144
  @override
145
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
146 147 148 149
    if (rect.isEmpty)
      return;
    switch (side.style) {
      case BorderStyle.none:
150
        break;
151 152 153 154 155 156 157 158 159
      case BorderStyle.solid:
        final Path path = getOuterPath(rect, textDirection: textDirection);
        final Paint paint = side.toPaint();
        canvas.drawPath(path, paint);
        break;
    }
  }

  @override
160 161
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
162
      return false;
163 164 165
    return other is ContinuousRectangleBorder
        && other.side == side
        && other.borderRadius == borderRadius;
166 167 168 169 170 171 172
  }

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

  @override
  String toString() {
173
    return '${objectRuntimeType(this, 'ContinuousRectangleBorder')}($side, $borderRadius)';
174
  }
175
}