| // Copyright 2013 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. | |
| #include "flutter/impeller/geometry/round_rect.h" | |
| namespace impeller { | |
| static inline void NormalizeEmptyToZero(Size& radii) { | |
| if (radii.IsEmpty()) { | |
| radii = Size(); | |
| } | |
| } | |
| static inline void AdjustScale(Scalar& radius1, | |
| Scalar& radius2, | |
| Scalar dimension, | |
| Scalar& scale) { | |
| FML_DCHECK(radius1 >= 0.0f && radius2 >= 0.0f); | |
| FML_DCHECK(dimension > 0.0f); | |
| if (radius1 + radius2 > dimension) { | |
| scale = std::min(scale, dimension / (radius1 + radius2)); | |
| } | |
| } | |
| RoundRect RoundRect::MakeRectRadii(const Rect& bounds, | |
| const RoundingRadii& in_radii) { | |
| if (bounds.IsEmpty() || !bounds.IsFinite() || // | |
| in_radii.AreAllCornersEmpty() || !in_radii.IsFinite()) { | |
| // preserve the empty bounds as they might be strokable | |
| return RoundRect(bounds, RoundingRadii()); | |
| } | |
| // Copy the incoming radii so that we can work on normalizing them to the | |
| // particular rectangle they are paired with without disturbing the caller. | |
| RoundingRadii radii = in_radii; | |
| // If any corner is flat or has a negative value, normalize it to zeros | |
| // We do this first so that the unnecessary non-flat part of that radius | |
| // does not contribute to the global scaling below. | |
| NormalizeEmptyToZero(radii.top_left); | |
| NormalizeEmptyToZero(radii.top_right); | |
| NormalizeEmptyToZero(radii.bottom_left); | |
| NormalizeEmptyToZero(radii.bottom_right); | |
| // Now determine a global scale to apply to all of the radii to ensure | |
| // that none of the adjacent pairs of radius values sum to larger than | |
| // the corresponding dimension of the rectangle. | |
| Size size = bounds.GetSize(); | |
| Scalar scale = 1.0f; | |
| // clang-format off | |
| AdjustScale(radii.top_left.width, radii.top_right.width, size.width, | |
| scale); | |
| AdjustScale(radii.bottom_left.width, radii.bottom_right.width, size.width, | |
| scale); | |
| AdjustScale(radii.top_left.height, radii.bottom_left.height, size.height, | |
| scale); | |
| AdjustScale(radii.top_right.height, radii.bottom_right.height, size.height, | |
| scale); | |
| // clang-format on | |
| if (scale < 1.0f) { | |
| radii = radii * scale; | |
| } | |
| return RoundRect(bounds, radii); | |
| } | |
| // Determine if p is inside the elliptical corner curve defined by the | |
| // indicated corner point and the indicated radii. | |
| // p - is the test point in absolute coordinates | |
| // corner - is the location of the associated corner in absolute coordinates | |
| // direction - is the sign of (corner - center), or the sign of coordinates | |
| // as they move in the direction of the corner from inside the | |
| // rect ((-1,-1) for the upper left corner for instance) | |
| // radii - the non-negative X and Y size of the corner's radii. | |
| static bool CornerContains(const Point& p, | |
| const Point& corner, | |
| const Point& direction, | |
| const Size& radii) { | |
| FML_DCHECK(radii.width >= 0.0f && radii.height >= 0.0f); | |
| if (radii.IsEmpty()) { | |
| // This corner is not curved, therefore the containment is the same as | |
| // the previously checked bounds containment. | |
| return true; | |
| } | |
| // The positive X,Y distance between the corner and the point. | |
| Point corner_relative = (corner - p) * direction; | |
| // The distance from the "center" of the corner's elliptical curve. | |
| // If both numbers are positive then we need to do an elliptical distance | |
| // check to determine if it is inside the curve. | |
| // If either number is negative, then the point is outside this quadrant | |
| // and is governed by inclusion in the bounds and inclusion within other | |
| // corners of this round rect. In that case, we return true here to allow | |
| // further evaluation within other quadrants. | |
| Point quadrant_relative = radii - corner_relative; | |
| if (quadrant_relative.x <= 0.0f || quadrant_relative.y <= 0.0f) { | |
| // Not within the curved quadrant of this corner, therefore "inside" | |
| // relative to this one corner. | |
| return true; | |
| } | |
| // Dividing the quadrant_relative point by the radii gives a corresponding | |
| // location within a unit circle which can be more easily tested for | |
| // containment. We can use x^2 + y^2 and compare it against the radius | |
| // squared (1.0) to avoid the sqrt. | |
| Point quadrant_unit_circle_point = quadrant_relative / radii; | |
| return quadrant_unit_circle_point.GetLengthSquared() <= 1.0; | |
| } | |
| // The sign of the direction that points move as they approach the indicated | |
| // corner from within the rectangle. | |
| static constexpr Point kUpperLeftDirection(-1.0f, -1.0f); | |
| static constexpr Point kUpperRightDirection(1.0f, -1.0f); | |
| static constexpr Point kLowerLeftDirection(-1.0f, 1.0f); | |
| static constexpr Point kLowerRightDirection(1.0f, 1.0f); | |
| [[nodiscard]] bool RoundRect::Contains(const Point& p) const { | |
| if (!bounds_.Contains(p)) { | |
| return false; | |
| } | |
| if (!CornerContains(p, bounds_.GetLeftTop(), kUpperLeftDirection, | |
| radii_.top_left) || | |
| !CornerContains(p, bounds_.GetRightTop(), kUpperRightDirection, | |
| radii_.top_right) || | |
| !CornerContains(p, bounds_.GetLeftBottom(), kLowerLeftDirection, | |
| radii_.bottom_left) || | |
| !CornerContains(p, bounds_.GetRightBottom(), kLowerRightDirection, | |
| radii_.bottom_right)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace impeller |