continuous_rectangle_border.dart 5.04 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
// Copyright 2018 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:math' as math;

import 'basic_types.dart';
import 'border_radius.dart';
import 'borders.dart';
import 'edge_insets.dart';

12 13
/// A rectangular border with smooth continuous transitions between the straight
/// sides and the rounded corners.
14 15 16 17 18
///
/// {@tool sample}
/// ```dart
/// Widget build(BuildContext context) {
///   return Material(
19
///     shape: ContinuousRectangleBorder(
20 21 22 23 24 25 26 27 28
///       borderRadius: BorderRadius.circular(28.0),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
29 30 31 32 33
/// * [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].
class ContinuousRectangleBorder extends ShapeBorder {
34
  /// The arguments must not be null.
35
  const ContinuousRectangleBorder({
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
  }) : assert(side != null),
       assert(borderRadius != null);

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

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

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);

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

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

  @override
  ShapeBorder lerpTo(ShapeBorder b, double t) {
    assert(t != null);
76 77
    if (b is ContinuousRectangleBorder) {
      return ContinuousRectangleBorder(
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
        side: BorderSide.lerp(side, b.side, t),
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
      );
    }
    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
    /// of [rrect] to avoid strange tie-fighter shapes.
    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)
      ..lineTo(right, bottom - blRadiusX)
      ..cubicTo(right, bottom, right, bottom, right - blRadiusY, bottom)
      ..lineTo(left + brRadiusX, bottom)
      ..cubicTo(left, bottom, left, bottom, left, bottom - brRadiusY)
      ..close();
  }

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

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

  @override
136
  void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    if (rect.isEmpty)
      return;
    switch (side.style) {
      case BorderStyle.none:
      break;
      case BorderStyle.solid:
        final Path path = getOuterPath(rect, textDirection: textDirection);
        final Paint paint = side.toPaint();
        canvas.drawPath(path, paint);
        break;
    }
  }

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType)
      return false;
154
    final ContinuousRectangleBorder typedOther = other;
155 156 157 158 159 160 161 162 163 164 165
    return side == typedOther.side
        && borderRadius == typedOther.borderRadius;
  }

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

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