| import 'dart:math' as math; |
| |
| import 'package:flutter/material.dart'; |
| |
| /// A rectangle with a smooth circular notch. |
| /// |
| /// See also: |
| /// |
| /// * [CircleBorder], a [ShapeBorder] that describes a circle. |
| class WaterfallNotchedRectangle extends NotchedShape { |
| /// Creates a [WaterfallNotchedRectangle]. |
| /// |
| /// The same object can be used to create multiple shapes. |
| const WaterfallNotchedRectangle(); |
| |
| /// 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 accommodates. 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. |
| @override |
| Path getOuterPath(Rect host, Rect guest) { |
| if (guest == null || !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 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 curve 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 |
| |
| // s1, s2 are the two knobs controlling the behavior of the bezzier curve. |
| const s1 = 21.0; |
| const s2 = 6.0; |
| |
| final r = notchRadius; |
| final a = -1.0 * r - s2; |
| final b = host.top - guest.center.dy; |
| |
| final n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r)); |
| final p2xA = ((a * r * r) - n2) / (a * a + b * b); |
| final p2xB = ((a * r * r) + n2) / (a * a + b * b); |
| final p2yA = math.sqrt(r * r - p2xA * p2xA); |
| final p2yB = math.sqrt(r * r - p2xB * p2xB); |
| |
| final p = List<Offset>.filled(6, null, growable: false); |
| |
| // p0, p1, and p2 are the control points for segment A. |
| p[0] = Offset(a - s1, b); |
| p[1] = Offset(a, b); |
| final 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 (var i = 0; i < p.length; i += 1) { |
| p[i] = 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(); |
| } |
| } |