continuous_rectangle_border.dart 5.14 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 OutlinedBorder {
36
  /// The arguments must not be null.
37
  const ContinuousRectangleBorder({
38
    super.side,
39
    this.borderRadius = BorderRadius.zero,
40
  });
41 42 43 44 45 46 47

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

48
  @override
49 50 51 52
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);

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

  @override
60
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
61 62
    if (a is ContinuousRectangleBorder) {
      return ContinuousRectangleBorder(
63
        side: BorderSide.lerp(a.side, side, t),
64
        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
65 66 67 68 69 70
      );
    }
    return super.lerpFrom(a, t);
  }

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

  @override
122
  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
123 124 125 126
    return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
  }

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

131
  @override
132
  ContinuousRectangleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius }) {
133 134 135 136 137 138
    return ContinuousRectangleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
    );
  }

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

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

  @override
166
  int get hashCode => Object.hash(side, borderRadius);
167 168 169

  @override
  String toString() {
170
    return '${objectRuntimeType(this, 'ContinuousRectangleBorder')}($side, $borderRadius)';
171
  }
172
}