// Copyright 2014 The Flutter 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 '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 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 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://docs.google.com/document/d/e/2PACX-1vRVPWGtR85bawGynRSWzYTKgQtqrxCnxXCKC5xM9ab3IvtRHueku4rRIuJ4TbedzyMz2oy2pkzM71-_/pub // s1, s2 are the two knobs controlling the behavior of the bezier curve. const s1 = 21.0; const s2 = 6.0; final 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 p = List.filled(6, null); // 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(); } }