beveled_rectangle_border.dart 5.49 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 15 16 17 18 19 20 21
import 'basic_types.dart';
import 'border_radius.dart';
import 'borders.dart';
import 'edge_insets.dart';

/// A rectangular border with flattened or "beveled" corners.
///
/// The line segments that connect the rectangle's four sides will
/// begin and at locations offset by the corresponding border radius,
/// but not farther than the side's center. If all the border radii
/// exceed the sides' half widths/heights the resulting shape is
/// diamond made by connecting the centers of the sides.
22
class BeveledRectangleBorder extends OutlinedBorder {
23 24 25 26 27
  /// Creates a border like a [RoundedRectangleBorder] except that the corners
  /// are joined by straight lines instead of arcs.
  ///
  /// The arguments must not be null.
  const BeveledRectangleBorder({
28
    BorderSide side = BorderSide.none,
29
    this.borderRadius = BorderRadius.zero,
30
  }) : assert(side != null),
31 32
       assert(borderRadius != null),
       super(side: side);
33 34 35 36 37 38 39 40 41 42 43 44 45 46

  /// The radii for each corner.
  ///
  /// Each corner [Radius] defines the endpoints of a line segment that
  /// spans the corner. The endpoints are located in the same place as
  /// they would be for [RoundedRectangleBorder], but they're connected
  /// by a straight line instead of an arc.
  ///
  /// Negative radius values are clamped to 0.0 by [getInnerPath] and
  /// [getOuterPath].
  final BorderRadiusGeometry borderRadius;

  @override
  EdgeInsetsGeometry get dimensions {
47
    return EdgeInsets.all(side.width);
48 49 50 51
  }

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

  @override
59
  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
60 61
    assert(t != null);
    if (a is BeveledRectangleBorder) {
62
      return BeveledRectangleBorder(
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
    assert(t != null);
    if (b is BeveledRectangleBorder) {
74
      return BeveledRectangleBorder(
75
        side: BorderSide.lerp(side, b.side, t),
76
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
77 78 79 80 81
      );
    }
    return super.lerpTo(b, t);
  }

82 83 84
  /// Returns a copy of this RoundedRectangleBorder with the given fields
  /// replaced with the new values.
  @override
85
  BeveledRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius }) {
86 87 88 89 90 91
    return BeveledRectangleBorder(
      side: side ?? this.side,
      borderRadius: borderRadius ?? this.borderRadius,
    );
  }

92
  Path _getPath(RRect rrect) {
93 94 95 96
    final Offset centerLeft = Offset(rrect.left, rrect.center.dy);
    final Offset centerRight = Offset(rrect.right, rrect.center.dy);
    final Offset centerTop = Offset(rrect.center.dx, rrect.top);
    final Offset centerBottom = Offset(rrect.center.dx, rrect.bottom);
97 98 99 100 101 102 103 104 105 106 107

    final double tlRadiusX = math.max(0.0, rrect.tlRadiusX);
    final double tlRadiusY = math.max(0.0, rrect.tlRadiusY);
    final double trRadiusX = math.max(0.0, rrect.trRadiusX);
    final double trRadiusY = math.max(0.0, rrect.trRadiusY);
    final double blRadiusX = math.max(0.0, rrect.blRadiusX);
    final double blRadiusY = math.max(0.0, rrect.blRadiusY);
    final double brRadiusX = math.max(0.0, rrect.brRadiusX);
    final double brRadiusY = math.max(0.0, rrect.brRadiusY);

    final List<Offset> vertices = <Offset>[
108 109 110 111 112 113 114 115
      Offset(rrect.left, math.min(centerLeft.dy, rrect.top + tlRadiusY)),
      Offset(math.min(centerTop.dx, rrect.left + tlRadiusX), rrect.top),
      Offset(math.max(centerTop.dx, rrect.right -trRadiusX), rrect.top),
      Offset(rrect.right, math.min(centerRight.dy, rrect.top + trRadiusY)),
      Offset(rrect.right, math.max(centerRight.dy, rrect.bottom - brRadiusY)),
      Offset(math.max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom),
      Offset(math.min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom),
      Offset(rrect.left, math.max(centerLeft.dy, rrect.bottom  - blRadiusY)),
116 117
    ];

118
    return Path()..addPolygon(vertices, true);
119 120 121
  }

  @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 131
    return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
  }

  @override
132
  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
133 134 135 136 137 138
    if (rect.isEmpty)
      return;
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
139
        final Path path = getOuterPath(rect, textDirection: textDirection)
140 141 142 143 144 145 146
          ..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
        canvas.drawPath(path, side.toPaint());
        break;
    }
  }

  @override
147 148
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
149
      return false;
150 151 152
    return other is BeveledRectangleBorder
        && other.side == side
        && other.borderRadius == borderRadius;
153 154 155 156 157 158 159
  }

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

  @override
  String toString() {
160
    return '${objectRuntimeType(this, 'BeveledRectangleBorder')}($side, $borderRadius)';
161 162
  }
}