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