continuous_rectangle_border.dart 5.11 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// 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;

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

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

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

  @override
  ShapeBorder lerpFrom(ShapeBorder a, double t) {
    assert(t != null);
66 67
    if (a is ContinuousRectangleBorder) {
      return ContinuousRectangleBorder(
68 69 70 71 72 73 74 75 76 77
        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);
78 79
    if (b is ContinuousRectangleBorder) {
      return ContinuousRectangleBorder(
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
        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)
120 121 122 123
      ..lineTo(right, bottom - brRadiusX)
      ..cubicTo(right, bottom, right, bottom, right - brRadiusY, bottom)
      ..lineTo(left + blRadiusX, bottom)
      ..cubicTo(left, bottom, left, bottom, left, bottom - blRadiusY)
124 125 126 127
      ..close();
  }

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

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

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

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

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

  @override
  String toString() {
166
    return '${objectRuntimeType(this, 'ContinuousRectangleBorder')}($side, $borderRadius)';
167
  }
168
}