| import 'dart:math' as math; |
| |
| import 'basic_types.dart'; |
| |
| /// A shape with a notch in its outline. |
| /// |
| /// Typically used as the outline of a 'host' widget to make a notch that |
| /// accommodates a 'guest' widget. e.g the [BottomAppBar] may have a notch to |
| /// accomodate the [FloatingActionBar]. |
| |
| /// See also: [ShapeBorder], which defines a shaped border without a dynamic |
| /// notch. |
| abstract class NotchedShape { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const NotchedShape(); |
| |
| /// Creates a [Path] that describes the outline of the shape. |
| /// |
| /// The `host` is the bounding rectangle of the shape. |
| /// |
| /// Rhe `guest` is the bounding rectangle of the shape for which a notch will |
| /// be made. |
| Path getOuterPath(Rect host, Rect guest); |
| } |
| |
| /// A rectangle with a smooth circular notch. |
| class CircularNotchedRectangle implements NotchedShape { |
| /// Creates a `CircularNotchedRectangle`. |
| /// |
| /// The same object can be used to create multiple shapes. |
| const CircularNotchedRectangle(); |
| |
| /// Creates a [Path] that describes a rectangle with a smooth circular notch. |
| /// |
| /// `host` is the bounding box for the returned shape. Conceptually this is |
| /// the rectangle to which the notch will be applied. |
| /// |
| /// `guest` is the bounding box of a circle that the notch accomodates. All |
| /// points in the circle bounded by `guest` will be outside of the returned |
| /// path. |
| /// |
| /// The notch is curve that smoothly connects the host's top edge and |
| /// the guest circle. |
| // TODO(amirh): add an example diagram here. |
| @override |
| Path getOuterPath(Rect host, Rect guest) { |
| if (!host.overlaps(guest)) |
| return Path()..addRect(host); |
| |
| // The guest's shape is a circle bounded by the guest rectangle. |
| // So the guest's radius is half the guest width. |
| final double notchRadius = guest.width / 2.0; |
| |
| // We build a path for the notch from 3 segments: |
| // Segment A - a Bezier curve from the host's top edge to segment B. |
| // Segment B - an arc with radius notchRadius. |
| // Segment C - a Bezier curver from segment B back to the host's top edge. |
| // |
| // A detailed explanation and the derivation of the formulas below is |
| // available at: https://goo.gl/Ufzrqn |
| |
| const double s1 = 15.0; |
| const double s2 = 1.0; |
| |
| final double r = notchRadius; |
| final double a = -1.0 * r - s2; |
| final double b = host.top - guest.center.dy; |
| |
| final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r)); |
| final double p2xA = ((a * r * r) - n2) / (a * a + b * b); |
| final double p2xB = ((a * r * r) + n2) / (a * a + b * b); |
| final double p2yA = math.sqrt(r * r - p2xA * p2xA); |
| final double p2yB = math.sqrt(r * r - p2xB * p2xB); |
| |
| final List<Offset> p = List<Offset>(6); |
| |
| // p0, p1, and p2 are the control points for segment A. |
| p[0] = Offset(a - s1, b); |
| p[1] = Offset(a, b); |
| final double cmp = b < 0 ? -1.0 : 1.0; |
| p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB); |
| |
| // p3, p4, and p5 are the control points for segment B, which is a mirror |
| // of segment A around the y axis. |
| p[3] = Offset(-1.0 * p[2].dx, p[2].dy); |
| p[4] = Offset(-1.0 * p[1].dx, p[1].dy); |
| p[5] = Offset(-1.0 * p[0].dx, p[0].dy); |
| |
| // translate all points back to the absolute coordinate system. |
| for (int i = 0; i < p.length; i += 1) |
| p[i] += guest.center; |
| |
| return Path() |
| ..moveTo(host.left, host.top) |
| ..lineTo(p[0].dx, p[0].dy) |
| ..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy) |
| ..arcToPoint( |
| p[3], |
| radius: Radius.circular(notchRadius), |
| clockwise: false, |
| ) |
| ..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy) |
| ..lineTo(host.right, host.top) |
| ..lineTo(host.right, host.bottom) |
| ..lineTo(host.left, host.bottom) |
| ..close(); |
| } |
| } |