circle_border.dart 6.48 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:ui' as ui show lerpDouble;
Ian Hickson's avatar
Ian Hickson committed
6

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

Ian Hickson's avatar
Ian Hickson committed
9 10
import 'basic_types.dart';
import 'borders.dart';
11
import 'edge_insets.dart';
Ian Hickson's avatar
Ian Hickson committed
12 13 14 15 16 17 18 19 20

/// A border that fits a circle within the available space.
///
/// Typically used with [ShapeDecoration] to draw a circle.
///
/// The [dimensions] assume that the border is being used in a square space.
/// When applied to a rectangular space, the border paints in the center of the
/// rectangle.
///
21 22 23 24 25 26
/// The [eccentricity] parameter describes how much a circle will deform to
/// fit the rectangle it is a border for. A value of zero implies no
/// deformation (a circle touching at least two sides of the rectangle), a
/// value of one implies full deformation (an oval touching all sides of the
/// rectangle).
///
Ian Hickson's avatar
Ian Hickson committed
27 28
/// See also:
///
29
///  * [OvalBorder], which draws a Circle touching all the edges of the box.
Ian Hickson's avatar
Ian Hickson committed
30
///  * [BorderSide], which is used to describe each side of the box.
31
///  * [Border], which, when used with [BoxDecoration], can also describe a circle.
32
class CircleBorder extends OutlinedBorder {
Ian Hickson's avatar
Ian Hickson committed
33 34 35
  /// Create a circle border.
  ///
  /// The [side] argument must not be null.
36 37 38 39 40 41 42 43 44 45 46
  const CircleBorder({ super.side, this.eccentricity = 0.0 })
      : assert(side != null),
        assert(eccentricity != null),
        assert(eccentricity >= 0.0, 'The eccentricity argument $eccentricity is not greater than or equal to zero.'),
        assert(eccentricity <= 1.0, 'The eccentricity argument $eccentricity is not less than or equal to one.');

  /// Defines the ratio (0.0-1.0) from which the border will deform
  /// to fit a rectangle.
  /// When 0.0, it draws a circle touching at least two sides of the rectangle.
  /// When 1.0, it draws an oval touching all sides of the rectangle.
  final double eccentricity;
Ian Hickson's avatar
Ian Hickson committed
47

48 49 50 51 52 53 54 55 56 57 58 59
  @override
  EdgeInsetsGeometry get dimensions {
    switch (side.strokeAlign) {
      case StrokeAlign.inside:
        return EdgeInsets.all(side.width);
      case StrokeAlign.center:
        return EdgeInsets.all(side.width / 2);
      case StrokeAlign.outside:
        return EdgeInsets.zero;
    }
  }

Ian Hickson's avatar
Ian Hickson committed
60
  @override
61
  ShapeBorder scale(double t) => CircleBorder(side: side.scale(t), eccentricity: eccentricity);
Ian Hickson's avatar
Ian Hickson committed
62 63

  @override
64
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
65
    if (a is CircleBorder) {
66 67 68 69
      return CircleBorder(
        side: BorderSide.lerp(a.side, side, t),
        eccentricity: clampDouble(ui.lerpDouble(a.eccentricity, eccentricity, t)!, 0.0, 1.0),
      );
70
    }
Ian Hickson's avatar
Ian Hickson committed
71 72 73 74
    return super.lerpFrom(a, t);
  }

  @override
75
  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
76
    if (b is CircleBorder) {
77 78 79 80
      return CircleBorder(
        side: BorderSide.lerp(side, b.side, t),
        eccentricity: clampDouble(ui.lerpDouble(eccentricity, b.eccentricity, t)!, 0.0, 1.0),
      );
81
    }
Ian Hickson's avatar
Ian Hickson committed
82 83 84 85
    return super.lerpTo(b, t);
  }

  @override
86
  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
87 88 89 90 91 92 93 94 95 96 97 98 99 100
    final double delta;
    switch (side.strokeAlign) {
      case StrokeAlign.inside:
        delta = side.width;
        break;
      case StrokeAlign.center:
        delta = side.width / 2.0;
        break;
      case StrokeAlign.outside:
        delta = 0;
        break;
    }
    final Rect adjustedRect = _adjustRect(rect).deflate(delta);
    return Path()..addOval(adjustedRect);
Ian Hickson's avatar
Ian Hickson committed
101 102 103
  }

  @override
104
  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
105
      return Path()..addOval(_adjustRect(rect));
Ian Hickson's avatar
Ian Hickson committed
106 107
  }

108 109 110 111 112 113 114 115 116 117 118 119
  @override
  void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) {
    if (eccentricity == 0.0) {
      canvas.drawCircle(rect.center, rect.shortestSide / 2.0, paint);
    } else {
      canvas.drawOval(_adjustRect(rect), paint);
    }
  }

  @override
  bool get preferPaintInterior => true;

120
  @override
121 122
  CircleBorder copyWith({ BorderSide? side, double? eccentricity }) {
    return CircleBorder(side: side ?? this.side, eccentricity: eccentricity ?? this.eccentricity);
123 124
  }

Ian Hickson's avatar
Ian Hickson committed
125
  @override
126
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
Ian Hickson's avatar
Ian Hickson committed
127 128 129 130
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
131
        if (eccentricity != 0.0) {
132
          final Rect borderRect = _adjustRect(rect);
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
          final Rect adjustedRect;
          switch (side.strokeAlign) {
            case StrokeAlign.inside:
              adjustedRect = borderRect.deflate(side.width / 2.0);
              break;
            case StrokeAlign.center:
              adjustedRect = borderRect;
              break;
            case StrokeAlign.outside:
              adjustedRect = borderRect.inflate(side.width / 2.0);
              break;
          }
          canvas.drawOval(adjustedRect, side.toPaint());
        } else {
          final double radius;
          switch (side.strokeAlign) {
            case StrokeAlign.inside:
              radius = (rect.shortestSide - side.width) / 2.0;
              break;
            case StrokeAlign.center:
              radius = rect.shortestSide / 2.0;
              break;
            case StrokeAlign.outside:
              radius = (rect.shortestSide + side.width) / 2.0;
              break;
          }
          canvas.drawCircle(rect.center, radius, side.toPaint());
160
        }
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    }
  }

  Rect _adjustRect(Rect rect) {
    if (eccentricity == 0.0 || rect.width == rect.height) {
      return Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0);
    }
    if (rect.width < rect.height) {
      final double delta = (1.0 - eccentricity) * (rect.height - rect.width) / 2.0;
      return Rect.fromLTRB(
        rect.left,
        rect.top + delta,
        rect.right,
        rect.bottom - delta,
      );
    } else {
      final double delta = (1.0 - eccentricity) * (rect.width - rect.height) / 2.0;
      return Rect.fromLTRB(
        rect.left + delta,
        rect.top,
        rect.right - delta,
        rect.bottom,
      );
Ian Hickson's avatar
Ian Hickson committed
184 185 186 187
    }
  }

  @override
188
  bool operator ==(Object other) {
189
    if (other.runtimeType != runtimeType) {
Ian Hickson's avatar
Ian Hickson committed
190
      return false;
191
    }
192
    return other is CircleBorder
193 194
        && other.side == side
        && other.eccentricity == eccentricity;
Ian Hickson's avatar
Ian Hickson committed
195 196 197
  }

  @override
198
  int get hashCode => Object.hash(side, eccentricity);
Ian Hickson's avatar
Ian Hickson committed
199 200 201

  @override
  String toString() {
202 203 204
    if (eccentricity != 0.0) {
      return '${objectRuntimeType(this, 'CircleBorder')}($side, eccentricity: $eccentricity)';
    }
205
    return '${objectRuntimeType(this, 'CircleBorder')}($side)';
Ian Hickson's avatar
Ian Hickson committed
206 207
  }
}