1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/material.dart';
8
9/// A rectangle with a smooth circular notch.
10///
11/// See also:
12///
13/// * [CircleBorder], a [ShapeBorder] that describes a circle.
14class WaterfallNotchedRectangle extends NotchedShape {
15 /// Creates a [WaterfallNotchedRectangle].
16 ///
17 /// The same object can be used to create multiple shapes.
18 const WaterfallNotchedRectangle();
19
20 /// Creates a [Path] that describes a rectangle with a smooth circular notch.
21 ///
22 /// `host` is the bounding box for the returned shape. Conceptually this is
23 /// the rectangle to which the notch will be applied.
24 ///
25 /// `guest` is the bounding box of a circle that the notch accommodates. All
26 /// points in the circle bounded by `guest` will be outside of the returned
27 /// path.
28 ///
29 /// The notch is curve that smoothly connects the host's top edge and
30 /// the guest circle.
31 @override
32 Path getOuterPath(Rect host, Rect? guest) {
33 if (guest == null || !host.overlaps(guest)) {
34 return Path()..addRect(host);
35 }
36
37 // The guest's shape is a circle bounded by the guest rectangle.
38 // So the guest's radius is half the guest width.
39 final double notchRadius = guest.width / 2.0;
40
41 // We build a path for the notch from 3 segments:
42 // Segment A - a Bezier curve from the host's top edge to segment B.
43 // Segment B - an arc with radius notchRadius.
44 // Segment C - a Bezier curve from segment B back to the host's top edge.
45 //
46 // A detailed explanation and the derivation of the formulas below is
47 // available at: https://goo.gl/Ufzrqn
48
49 // s1, s2 are the two knobs controlling the behavior of the bezier curve.
50 const double s1 = 21.0;
51 const double s2 = 6.0;
52
53 final double r = notchRadius;
54 final double a = -1.0 * r - s2;
55 final double b = host.top - guest.center.dy;
56
57 final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
58 final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
59 final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
60 final double p2yA = math.sqrt(r * r - p2xA * p2xA);
61 final double p2yB = math.sqrt(r * r - p2xB * p2xB);
62
63 final List<Offset?> p = List<Offset?>.filled(6, null);
64
65 // p0, p1, and p2 are the control points for segment A.
66 p[0] = Offset(a - s1, b);
67 p[1] = Offset(a, b);
68 final double cmp = b < 0 ? -1.0 : 1.0;
69 p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB);
70
71 // p3, p4, and p5 are the control points for segment B, which is a mirror
72 // of segment A around the y axis.
73 p[3] = Offset(-1.0 * p[2]!.dx, p[2]!.dy);
74 p[4] = Offset(-1.0 * p[1]!.dx, p[1]!.dy);
75 p[5] = Offset(-1.0 * p[0]!.dx, p[0]!.dy);
76
77 // translate all points back to the absolute coordinate system.
78 for (int i = 0; i < p.length; i += 1) {
79 p[i] = p[i]! + guest.center;
80 }
81
82 return Path()
83 ..moveTo(host.left, host.top)
84 ..lineTo(p[0]!.dx, p[0]!.dy)
85 ..quadraticBezierTo(p[1]!.dx, p[1]!.dy, p[2]!.dx, p[2]!.dy)
86 ..arcToPoint(p[3]!, radius: Radius.circular(notchRadius), clockwise: false)
87 ..quadraticBezierTo(p[4]!.dx, p[4]!.dy, p[5]!.dx, p[5]!.dy)
88 ..lineTo(host.right, host.top)
89 ..lineTo(host.right, host.bottom)
90 ..lineTo(host.left, host.bottom)
91 ..close();
92 }
93}
94