1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/style/rounded_rect_cutout_path_builder.h"
#include <ostream>
#include "base/test/gtest_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/geometry/size_f.h"
// Pretty print SkRect for failure logs.
void PrintTo(const SkRect& size, std::ostream* os) {
*os << "(" << size.x() << ", " << size.y() << ") [" << size.width() << ", "
<< size.height() << "]";
}
namespace ash {
namespace {
constexpr gfx::SizeF kViewSize(114.f, 432.f);
TEST(RoundedRectCutoutPathBuilderTest, RectanglePoints) {
RoundedRectCutoutPathBuilder builder(kViewSize);
builder.CornerRadius(0);
SkPath path = builder.Build();
// A radius of 0 should be a rectangle so we have 4 points and the starting
// point.
EXPECT_EQ(path.countPoints(), 4 + 1);
}
TEST(RoundedRectCutoutPathBuilderTest, RoundedCorners) {
RoundedRectCutoutPathBuilder builder(kViewSize);
builder.CornerRadius(16);
SkPath path = builder.Build();
// A rounded rect has 12 points (3 for each ronded corner, one for the control
// point in conic and two for the start and end) and the starting
// point.
EXPECT_EQ(path.countPoints(), 12 + 1);
}
TEST(RoundedRectCutoutPathBuilderTest, OneCutout) {
RoundedRectCutoutPathBuilder builder(kViewSize);
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kLowerRight,
gfx::SizeF(30, 30));
builder.CornerRadius(0);
SkPath path = builder.Build();
// Cutouts have 3 rounded corners each. Each rounded corner has 3 points (so 9
// total). There are 3 other corners and the starting point. 13 total.
EXPECT_EQ(path.countPoints(), 9 + 3 + 1);
}
TEST(RoundedRectCutoutPathBuilderTest, TwoCutouts) {
RoundedRectCutoutPathBuilder builder(kViewSize);
builder
.AddCutout(RoundedRectCutoutPathBuilder::Corner::kLowerRight,
gfx::SizeF(30, 30))
.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperRight,
gfx::SizeF(40, 20))
.CornerRadius(0);
SkPath path = builder.Build();
// 2 cutouts, 2 normal corners, and the starting point.
EXPECT_EQ(path.countPoints(), 9 + 9 + 2 + 1);
// Ensure the path is complete.
EXPECT_TRUE(path.isLastContourClosed());
// The bounds should be equal to the View that the path will clip.
EXPECT_THAT(
path.getBounds(),
testing::Eq(SkRect::MakeSize({kViewSize.width(), kViewSize.height()})));
}
TEST(RoundedRectCutoutPathBuilderTest, RemoveCutout) {
RoundedRectCutoutPathBuilder builder(kViewSize);
builder.CornerRadius(0);
// Add cutout.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF(40, 40));
// Remove the cutout.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF());
// The resulting path should be a rectangle.
SkPath path = builder.Build();
EXPECT_EQ(path.countPoints(), 5);
SkRect bounds;
EXPECT_TRUE(path.isRect(&bounds));
EXPECT_THAT(
bounds,
testing::Eq(SkRect::MakeSize({kViewSize.width(), kViewSize.height()})));
}
TEST(RoundedRectCutoutPathBuilderTest, ExtraLargeCutout) {
RoundedRectCutoutPathBuilder builder(gfx::SizeF{100.0f, 100.0f});
// Add cutout that is more than half the height.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF(55.0f, 55.0f));
SkPath path = builder.Build();
// 3 * 3 points for the rounded corners. 9 points in the cutout. 1 starting
// point.
EXPECT_EQ(path.countPoints(), 9 + 9 + 1);
SkRect bounds = path.getBounds();
EXPECT_THAT(bounds, testing::Eq(SkRect::MakeSize({100.0f, 100.0f})));
}
TEST(RoundedRectCutoutPathBuilderDeathTest, MaximumCutout) {
RoundedRectCutoutPathBuilder builder(gfx::SizeF{100.0f, 100.0f});
builder.CornerRadius(4);
builder.CutoutOuterCornerRadius(8);
// cutout + outer corner + corner radius is allowed to equal the bounds.
// 4 + 8 + 88 = 100.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF(88.0f, 55.0f));
SkPath path = builder.Build();
EXPECT_FALSE(path.isEmpty());
}
TEST(RoundedRectCutoutPathBuilderDeathTest, CutoutTooLarge) {
RoundedRectCutoutPathBuilder builder(gfx::SizeF{100.0f, 100.0f});
builder.CornerRadius(4);
builder.CutoutOuterCornerRadius(8);
// When cutout + outer corner + corner radius is larger than the
// bounds, we expect a crash. 4 + 8 + 89 = 101.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF(89.0f, 55.0f));
EXPECT_CHECK_DEATH_WITH(
{
// This should crash because the cutout is larger than the bounds.
SkPath path = builder.Build();
},
"must be less than or equal to bounds");
}
TEST(RoundedRectCutoutPathBuilderDeathTest, CutoutsIntersect) {
RoundedRectCutoutPathBuilder builder(gfx::SizeF{100.0f, 100.0f});
builder.CornerRadius(8);
// Technically, this can be drawn but looks strange. So this crashes because
// we require that there is at least `outer_corner_radius * 2` between
// cutouts.
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
gfx::SizeF(70.0f, 70.0f));
builder.AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperRight,
gfx::SizeF(30.0f, 70.0f));
EXPECT_CHECK_DEATH_WITH(
{
// Cutouts overlap so this should crash.
SkPath path = builder.Build();
},
"cutouts intersect");
}
} // namespace
} // namespace ash
|