1 // Copyright 2016 The Chromium 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
5 #include "ui/views/border.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <set>
10 #include <utility>
11 #include <vector>
12
13 #include "cc/paint/paint_recorder.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "third_party/skia/include/core/SkCanvas.h"
16 #include "third_party/skia/include/core/SkPaint.h"
17 #include "third_party/skia/include/core/SkRRect.h"
18 #include "third_party/skia/include/core/SkRect.h"
19 #include "third_party/skia/include/core/SkRefCnt.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/geometry/size.h"
22 #include "ui/views/painter.h"
23 #include "ui/views/test/views_test_base.h"
24 #include "ui/views/view.h"
25
26 namespace {
27
28 class MockCanvas : public SkCanvas {
29 public:
30 struct DrawRectCall {
DrawRectCall__anona01c7e800111::MockCanvas::DrawRectCall31 DrawRectCall(const SkRect& rect, const SkPaint& paint)
32 : rect(rect), paint(paint) {}
33
operator <__anona01c7e800111::MockCanvas::DrawRectCall34 bool operator<(const DrawRectCall& other) const {
35 return std::tie(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom) <
36 std::tie(other.rect.fLeft, other.rect.fTop, other.rect.fRight,
37 other.rect.fBottom);
38 }
39
40 SkRect rect;
41 SkPaint paint;
42 };
43
44 struct DrawRRectCall {
DrawRRectCall__anona01c7e800111::MockCanvas::DrawRRectCall45 DrawRRectCall(const SkRRect& rrect, const SkPaint& paint)
46 : rrect(rrect), paint(paint) {}
47
operator <__anona01c7e800111::MockCanvas::DrawRRectCall48 bool operator<(const DrawRRectCall& other) const {
49 SkRect rect = rrect.rect();
50 SkRect other_rect = other.rrect.rect();
51 return std::tie(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom) <
52 std::tie(other_rect.fLeft, other_rect.fTop, other_rect.fRight,
53 other_rect.fBottom);
54 }
55
56 SkRRect rrect;
57 SkPaint paint;
58 };
59
MockCanvas(int width,int height)60 MockCanvas(int width, int height) : SkCanvas(width, height) {}
61
62 // Return calls in sorted order.
draw_rect_calls()63 std::vector<DrawRectCall> draw_rect_calls() {
64 return std::vector<DrawRectCall>(draw_rect_calls_.begin(),
65 draw_rect_calls_.end());
66 }
67
68 // Return calls in sorted order.
draw_rrect_calls()69 std::vector<DrawRRectCall> draw_rrect_calls() {
70 return std::vector<DrawRRectCall>(draw_rrect_calls_.begin(),
71 draw_rrect_calls_.end());
72 }
73
draw_paint_calls() const74 const std::vector<SkPaint>& draw_paint_calls() const {
75 return draw_paint_calls_;
76 }
77
last_clip_bounds() const78 const SkRect& last_clip_bounds() const { return last_clip_bounds_; }
79
80 // SkCanvas overrides:
onDrawRect(const SkRect & rect,const SkPaint & paint)81 void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
82 draw_rect_calls_.insert(DrawRectCall(rect, paint));
83 }
84
onDrawRRect(const SkRRect & rrect,const SkPaint & paint)85 void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
86 draw_rrect_calls_.insert(DrawRRectCall(rrect, paint));
87 }
88
onDrawPaint(const SkPaint & paint)89 void onDrawPaint(const SkPaint& paint) override {
90 draw_paint_calls_.push_back(paint);
91 }
92
onClipRect(const SkRect & rect,SkClipOp op,ClipEdgeStyle edge_style)93 void onClipRect(const SkRect& rect,
94 SkClipOp op,
95 ClipEdgeStyle edge_style) override {
96 last_clip_bounds_ = rect;
97 }
98
99 private:
100 // Stores all the calls for querying by the test, in sorted order.
101 std::set<DrawRectCall> draw_rect_calls_;
102 std::set<DrawRRectCall> draw_rrect_calls_;
103
104 // Stores the onDrawPaint calls in chronological order.
105 std::vector<SkPaint> draw_paint_calls_;
106 SkRect last_clip_bounds_;
107
108 DISALLOW_COPY_AND_ASSIGN(MockCanvas);
109 };
110
111 // Simple Painter that will be used to test BorderPainter.
112 class MockPainter : public views::Painter {
113 public:
114 MockPainter() = default;
115
116 // Gets the canvas given to the last call to Paint().
given_canvas() const117 gfx::Canvas* given_canvas() const { return given_canvas_; }
118
119 // Gets the size given to the last call to Paint().
given_size() const120 const gfx::Size& given_size() const { return given_size_; }
121
122 // Painter overrides:
GetMinimumSize() const123 gfx::Size GetMinimumSize() const override {
124 // Just return some arbitrary size.
125 return gfx::Size(60, 40);
126 }
127
Paint(gfx::Canvas * canvas,const gfx::Size & size)128 void Paint(gfx::Canvas* canvas, const gfx::Size& size) override {
129 // Just record the arguments.
130 given_canvas_ = canvas;
131 given_size_ = size;
132 }
133
134 private:
135 gfx::Canvas* given_canvas_ = nullptr;
136 gfx::Size given_size_;
137
138 DISALLOW_COPY_AND_ASSIGN(MockPainter);
139 };
140
141 } // namespace
142
143 namespace views {
144
145 class BorderTest : public ViewsTestBase {
146 public:
147 enum {
148 // The canvas should be much bigger than the view.
149 kCanvasWidth = 1000,
150 kCanvasHeight = 500,
151 };
152
SetUp()153 void SetUp() override {
154 ViewsTestBase::SetUp();
155
156 view_ = std::make_unique<views::View>();
157 view_->SetSize(gfx::Size(100, 50));
158 recorder_ = std::make_unique<cc::PaintRecorder>();
159 canvas_ = std::make_unique<gfx::Canvas>(
160 recorder_->beginRecording(SkRect::MakeWH(kCanvasWidth, kCanvasHeight)),
161 1.0f);
162 }
163
TearDown()164 void TearDown() override {
165 ViewsTestBase::TearDown();
166
167 canvas_.reset();
168 recorder_.reset();
169 view_.reset();
170 }
171
DrawIntoMockCanvas()172 std::unique_ptr<MockCanvas> DrawIntoMockCanvas() {
173 sk_sp<cc::PaintRecord> record = recorder_->finishRecordingAsPicture();
174 std::unique_ptr<MockCanvas> mock(
175 new MockCanvas(kCanvasWidth, kCanvasHeight));
176 record->Playback(mock.get());
177 return mock;
178 }
179
180 protected:
181 std::unique_ptr<cc::PaintRecorder> recorder_;
182 std::unique_ptr<views::View> view_;
183 std::unique_ptr<gfx::Canvas> canvas_;
184 };
185
TEST_F(BorderTest,NullBorder)186 TEST_F(BorderTest, NullBorder) {
187 std::unique_ptr<Border> border(NullBorder());
188 EXPECT_FALSE(border);
189 }
190
TEST_F(BorderTest,SolidBorder)191 TEST_F(BorderTest, SolidBorder) {
192 const SkColor kBorderColor = SK_ColorMAGENTA;
193 std::unique_ptr<Border> border(CreateSolidBorder(3, kBorderColor));
194 EXPECT_EQ(gfx::Size(6, 6), border->GetMinimumSize());
195 EXPECT_EQ(gfx::Insets(3, 3, 3, 3), border->GetInsets());
196 border->Paint(*view_, canvas_.get());
197
198 std::unique_ptr<MockCanvas> mock = DrawIntoMockCanvas();
199 std::vector<MockCanvas::DrawRectCall> draw_rect_calls =
200 mock->draw_rect_calls();
201
202 gfx::Rect bounds = view_->GetLocalBounds();
203 bounds.Inset(border->GetInsets());
204
205 ASSERT_EQ(1u, mock->draw_paint_calls().size());
206 EXPECT_EQ(kBorderColor, mock->draw_paint_calls()[0].getColor());
207 EXPECT_EQ(gfx::RectF(bounds), gfx::SkRectToRectF(mock->last_clip_bounds()));
208 }
209
TEST_F(BorderTest,RoundedRectBorder)210 TEST_F(BorderTest, RoundedRectBorder) {
211 std::unique_ptr<Border> border(CreateRoundedRectBorder(
212 3, LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW),
213 SK_ColorBLUE));
214 EXPECT_EQ(gfx::Size(6, 6), border->GetMinimumSize());
215 EXPECT_EQ(gfx::Insets(3, 3, 3, 3), border->GetInsets());
216 border->Paint(*view_, canvas_.get());
217
218 std::unique_ptr<MockCanvas> mock = DrawIntoMockCanvas();
219 SkRRect expected_rrect;
220 expected_rrect.setRectXY(SkRect::MakeLTRB(1.5, 1.5, 98.5, 48.5), 4, 4);
221 EXPECT_TRUE(mock->draw_rect_calls().empty());
222 std::vector<MockCanvas::DrawRRectCall> draw_rrect_calls =
223 mock->draw_rrect_calls();
224 ASSERT_EQ(1u, draw_rrect_calls.size());
225 EXPECT_EQ(expected_rrect, draw_rrect_calls[0].rrect);
226 EXPECT_EQ(3, draw_rrect_calls[0].paint.getStrokeWidth());
227 EXPECT_EQ(SK_ColorBLUE, draw_rrect_calls[0].paint.getColor());
228 EXPECT_EQ(SkPaint::kStroke_Style, draw_rrect_calls[0].paint.getStyle());
229 EXPECT_TRUE(draw_rrect_calls[0].paint.isAntiAlias());
230 }
231
TEST_F(BorderTest,EmptyBorder)232 TEST_F(BorderTest, EmptyBorder) {
233 constexpr gfx::Insets kInsets(1, 2, 3, 4);
234
235 std::unique_ptr<Border> border(CreateEmptyBorder(
236 kInsets.top(), kInsets.left(), kInsets.bottom(), kInsets.right()));
237 // The EmptyBorder has no minimum size despite nonzero insets.
238 EXPECT_EQ(gfx::Size(), border->GetMinimumSize());
239 EXPECT_EQ(kInsets, border->GetInsets());
240 // Should have no effect.
241 border->Paint(*view_, canvas_.get());
242
243 std::unique_ptr<Border> border2(CreateEmptyBorder(kInsets));
244 EXPECT_EQ(kInsets, border2->GetInsets());
245 }
246
TEST_F(BorderTest,SolidSidedBorder)247 TEST_F(BorderTest, SolidSidedBorder) {
248 constexpr SkColor kBorderColor = SK_ColorMAGENTA;
249 constexpr gfx::Insets kInsets(1, 2, 3, 4);
250
251 std::unique_ptr<Border> border(
252 CreateSolidSidedBorder(kInsets.top(), kInsets.left(), kInsets.bottom(),
253 kInsets.right(), kBorderColor));
254 EXPECT_EQ(gfx::Size(6, 4), border->GetMinimumSize());
255 EXPECT_EQ(kInsets, border->GetInsets());
256 border->Paint(*view_, canvas_.get());
257
258 std::unique_ptr<MockCanvas> mock = DrawIntoMockCanvas();
259 std::vector<MockCanvas::DrawRectCall> draw_rect_calls =
260 mock->draw_rect_calls();
261
262 gfx::Rect bounds = view_->GetLocalBounds();
263 bounds.Inset(border->GetInsets());
264
265 ASSERT_EQ(1u, mock->draw_paint_calls().size());
266 EXPECT_EQ(kBorderColor, mock->draw_paint_calls().front().getColor());
267 EXPECT_EQ(gfx::RectF(bounds), gfx::SkRectToRectF(mock->last_clip_bounds()));
268 }
269
TEST_F(BorderTest,BorderPainter)270 TEST_F(BorderTest, BorderPainter) {
271 constexpr gfx::Insets kInsets(1, 2, 3, 4);
272
273 std::unique_ptr<MockPainter> painter(new MockPainter());
274 MockPainter* painter_ptr = painter.get();
275 std::unique_ptr<Border> border(
276 CreateBorderPainter(std::move(painter), kInsets));
277 EXPECT_EQ(gfx::Size(60, 40), border->GetMinimumSize());
278 EXPECT_EQ(kInsets, border->GetInsets());
279
280 border->Paint(*view_, canvas_.get());
281
282 // Expect that the Painter was called with our canvas and the view's size.
283 EXPECT_EQ(canvas_.get(), painter_ptr->given_canvas());
284 EXPECT_EQ(view_->size(), painter_ptr->given_size());
285 }
286
TEST_F(BorderTest,ExtraInsetsBorder)287 TEST_F(BorderTest, ExtraInsetsBorder) {
288 constexpr SkColor kBorderColor = SK_ColorMAGENTA;
289 constexpr int kOriginalInset = 3;
290 std::unique_ptr<Border> border =
291 CreateSolidBorder(kOriginalInset, kBorderColor);
292 constexpr gfx::Insets kOriginalInsets(kOriginalInset);
293 EXPECT_EQ(kOriginalInsets.size(), border->GetMinimumSize());
294 EXPECT_EQ(kOriginalInsets, border->GetInsets());
295 EXPECT_EQ(kBorderColor, border->color());
296
297 constexpr int kExtraInset = 2;
298 constexpr gfx::Insets kExtraInsets(kExtraInset);
299 std::unique_ptr<Border> extra_insets_border =
300 CreatePaddedBorder(std::move(border), kExtraInsets);
301 constexpr gfx::Insets kTotalInsets(kOriginalInset + kExtraInset);
302 EXPECT_EQ(kTotalInsets.size(), extra_insets_border->GetMinimumSize());
303 EXPECT_EQ(kTotalInsets, extra_insets_border->GetInsets());
304 EXPECT_EQ(kBorderColor, extra_insets_border->color());
305
306 extra_insets_border->Paint(*view_, canvas_.get());
307
308 std::unique_ptr<MockCanvas> mock = DrawIntoMockCanvas();
309 std::vector<MockCanvas::DrawRectCall> draw_rect_calls =
310 mock->draw_rect_calls();
311
312 gfx::Rect bounds = view_->GetLocalBounds();
313 // We only use the wrapped border's insets for painting the border. The extra
314 // insets of the ExtraInsetsBorder are applied within the wrapped border.
315 bounds.Inset(extra_insets_border->GetInsets() - gfx::Insets(kExtraInset));
316
317 ASSERT_EQ(1u, mock->draw_paint_calls().size());
318 EXPECT_EQ(kBorderColor, mock->draw_paint_calls().front().getColor());
319 EXPECT_EQ(gfx::RectF(bounds), gfx::SkRectToRectF(mock->last_clip_bounds()));
320 }
321
322 } // namespace views
323