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
ash / style / rounded_rect_cutout_path_builder_unittest.cc [blame]
// 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