beveled_rectangle_border.dart 5.11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// 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';

/// 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.
class BeveledRectangleBorder extends ShapeBorder {
  /// 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({
25 26
    this.side = BorderSide.none,
    this.borderRadius = BorderRadius.zero,
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
  }) : assert(side != null),
       assert(borderRadius != null);

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

  /// 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 {
46
    return EdgeInsets.all(side.width);
47 48 49 50
  }

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

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

  @override
  ShapeBorder lerpTo(ShapeBorder b, double t) {
    assert(t != null);
    if (b is BeveledRectangleBorder) {
73
      return BeveledRectangleBorder(
74
        side: BorderSide.lerp(side, b.side, t),
75
        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
76 77 78 79 80 81
      );
    }
    return super.lerpTo(b, t);
  }

  Path _getPath(RRect rrect) {
82 83 84 85
    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);
86 87 88 89 90 91 92 93 94 95 96

    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>[
97 98 99 100 101 102 103 104
      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)),
105 106
    ];

107
    return Path()..addPolygon(vertices, true);
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  }

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

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

  @override
  void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
    if (rect.isEmpty)
      return;
    switch (side.style) {
      case BorderStyle.none:
        break;
      case BorderStyle.solid:
      final Path path = getOuterPath(rect, textDirection: textDirection)
          ..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
        canvas.drawPath(path, side.toPaint());
        break;
    }
  }

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType)
      return false;
    final BeveledRectangleBorder typedOther = other;
    return side == typedOther.side
        && borderRadius == typedOther.borderRadius;
  }

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

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