1 // Copyright (c) 2012 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.
5 #include "ui/views/bubble/bubble_border.h"
9 #include "base/logging.h"
10 #include "third_party/skia/include/core/SkPaint.h"
11 #include "third_party/skia/include/core/SkPath.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/geometry/rect.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/resources/grit/ui_resources.h"
17 #include "ui/views/painter.h"
18 #include "ui/views/resources/grit/views_resources.h"
19 #include "ui/views/view.h"
25 BorderImages::BorderImages(const int border_image_ids
[],
26 const int arrow_image_ids
[],
27 int border_interior_thickness
,
28 int arrow_interior_thickness
,
30 : border_thickness(border_interior_thickness
),
31 border_interior_thickness(border_interior_thickness
),
32 arrow_thickness(arrow_interior_thickness
),
33 arrow_interior_thickness(arrow_interior_thickness
),
34 arrow_width(2 * arrow_interior_thickness
),
35 corner_radius(corner_radius
) {
36 if (!border_image_ids
)
39 border_painter
.reset(Painter::CreateImageGridPainter(border_image_ids
));
40 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
41 border_thickness
= rb
.GetImageSkiaNamed(border_image_ids
[0])->width();
43 if (arrow_image_ids
[0] != 0) {
44 left_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[0]);
45 top_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[1]);
46 right_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[2]);
47 bottom_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[3]);
48 arrow_width
= top_arrow
.width();
49 arrow_thickness
= top_arrow
.height();
53 BorderImages::~BorderImages() {}
55 } // namespace internal
59 // Bubble border and arrow image resource ids. They don't use the IMAGE_GRID
60 // macro because there is no center image.
61 const int kNoShadowImages
[] = {
62 IDR_BUBBLE_TL
, IDR_BUBBLE_T
, IDR_BUBBLE_TR
,
63 IDR_BUBBLE_L
, 0, IDR_BUBBLE_R
,
64 IDR_BUBBLE_BL
, IDR_BUBBLE_B
, IDR_BUBBLE_BR
};
65 const int kNoShadowArrows
[] = {
66 IDR_BUBBLE_L_ARROW
, IDR_BUBBLE_T_ARROW
,
67 IDR_BUBBLE_R_ARROW
, IDR_BUBBLE_B_ARROW
, };
69 const int kBigShadowImages
[] = {
70 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT
,
71 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP
,
72 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT
,
73 IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT
,
75 IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT
,
76 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT
,
77 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM
,
78 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT
};
79 const int kBigShadowArrows
[] = {
80 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT
,
81 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP
,
82 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT
,
83 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM
};
85 const int kSmallShadowImages
[] = {
86 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT
,
87 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP
,
88 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT
,
89 IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT
,
91 IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT
,
92 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT
,
93 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM
,
94 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT
};
95 const int kSmallShadowArrows
[] = {
96 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT
,
97 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP
,
98 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT
,
99 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM
};
101 using internal::BorderImages
;
103 // Returns the cached BorderImages for the given |shadow| type.
104 BorderImages
* GetBorderImages(BubbleBorder::Shadow shadow
) {
105 // Keep a cache of bubble border image-set painters, arrows, and metrics.
106 static BorderImages
* kBorderImages
[BubbleBorder::SHADOW_COUNT
] = { NULL
};
108 CHECK_LT(shadow
, BubbleBorder::SHADOW_COUNT
);
109 struct BorderImages
*& set
= kBorderImages
[shadow
];
114 case BubbleBorder::NO_SHADOW
:
115 case BubbleBorder::NO_SHADOW_OPAQUE_BORDER
:
116 set
= new BorderImages(kNoShadowImages
, kNoShadowArrows
, 6, 7, 4);
118 case BubbleBorder::BIG_SHADOW
:
119 set
= new BorderImages(kBigShadowImages
, kBigShadowArrows
, 23, 9, 2);
121 case BubbleBorder::SMALL_SHADOW
:
122 set
= new BorderImages(kSmallShadowImages
, kSmallShadowArrows
, 5, 6, 2);
124 case BubbleBorder::NO_ASSETS
:
125 set
= new BorderImages(nullptr, nullptr, 17, 8, 2);
127 case BubbleBorder::SHADOW_COUNT
:
137 const int BubbleBorder::kStroke
= 1;
139 BubbleBorder::BubbleBorder(Arrow arrow
, Shadow shadow
, SkColor color
)
142 arrow_paint_type_(PAINT_NORMAL
),
143 alignment_(ALIGN_ARROW_TO_MID_ANCHOR
),
145 background_color_(color
),
146 use_theme_background_color_(false) {
147 DCHECK(shadow
< SHADOW_COUNT
);
148 images_
= GetBorderImages(shadow
);
151 BubbleBorder::~BubbleBorder() {}
153 gfx::Rect
BubbleBorder::GetBounds(const gfx::Rect
& anchor_rect
,
154 const gfx::Size
& contents_size
) const {
155 int x
= anchor_rect
.x();
156 int y
= anchor_rect
.y();
157 int w
= anchor_rect
.width();
158 int h
= anchor_rect
.height();
159 const gfx::Size
size(GetSizeForContentsSize(contents_size
));
160 const int arrow_offset
= GetArrowOffset(size
);
161 const int arrow_size
=
162 images_
->arrow_interior_thickness
+ kStroke
- images_
->arrow_thickness
;
163 const bool mid_anchor
= alignment_
== ALIGN_ARROW_TO_MID_ANCHOR
;
165 // Calculate the bubble coordinates based on the border and arrow settings.
166 if (is_arrow_on_horizontal(arrow_
)) {
167 if (is_arrow_on_left(arrow_
)) {
168 x
+= mid_anchor
? w
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
169 } else if (is_arrow_at_center(arrow_
)) {
170 x
+= w
/ 2 - arrow_offset
;
172 x
+= mid_anchor
? w
/ 2 + arrow_offset
- size
.width() :
173 w
- size
.width() + GetBorderThickness() - kStroke
;
175 y
+= is_arrow_on_top(arrow_
) ? h
+ arrow_size
: -arrow_size
- size
.height();
176 } else if (has_arrow(arrow_
)) {
177 x
+= is_arrow_on_left(arrow_
) ? w
+ arrow_size
: -arrow_size
- size
.width();
178 if (is_arrow_on_top(arrow_
)) {
179 y
+= mid_anchor
? h
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
180 } else if (is_arrow_at_center(arrow_
)) {
181 y
+= h
/ 2 - arrow_offset
;
183 y
+= mid_anchor
? h
/ 2 + arrow_offset
- size
.height() :
184 h
- size
.height() + GetBorderThickness() - kStroke
;
187 x
+= (w
- size
.width()) / 2;
188 y
+= (arrow_
== NONE
) ? h
: (h
- size
.height()) / 2;
191 return gfx::Rect(x
, y
, size
.width(), size
.height());
194 int BubbleBorder::GetBorderThickness() const {
195 return images_
->border_thickness
- images_
->border_interior_thickness
;
198 int BubbleBorder::GetBorderCornerRadius() const {
199 return images_
->corner_radius
;
202 int BubbleBorder::GetArrowOffset(const gfx::Size
& border_size
) const {
203 const int edge_length
= is_arrow_on_horizontal(arrow_
) ?
204 border_size
.width() : border_size
.height();
205 if (is_arrow_at_center(arrow_
) && arrow_offset_
== 0)
206 return edge_length
/ 2;
208 // Calculate the minimum offset to not overlap arrow and corner images.
209 const int min
= images_
->border_thickness
+ (images_
->arrow_width
/ 2);
210 // Ensure the returned value will not cause image overlap, if possible.
211 return std::max(min
, std::min(arrow_offset_
, edge_length
- min
));
214 void BubbleBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
215 gfx::Rect
bounds(view
.GetContentsBounds());
216 bounds
.Inset(-GetBorderThickness(), -GetBorderThickness());
217 const gfx::Rect arrow_bounds
= GetArrowRect(view
.GetLocalBounds());
218 if (arrow_bounds
.IsEmpty()) {
219 if (images_
->border_painter
)
220 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
223 if (!images_
->border_painter
) {
224 DrawArrow(canvas
, arrow_bounds
);
228 // Clip the arrow bounds out to avoid painting the overlapping edge area.
230 SkRect
arrow_rect(gfx::RectToSkRect(arrow_bounds
));
231 canvas
->sk_canvas()->clipRect(arrow_rect
, SkRegion::kDifference_Op
);
232 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
235 DrawArrow(canvas
, arrow_bounds
);
238 gfx::Insets
BubbleBorder::GetInsets() const {
239 // The insets contain the stroke and shadow pixels outside the bubble fill.
240 const int inset
= GetBorderThickness();
241 if ((arrow_paint_type_
== PAINT_NONE
) || !has_arrow(arrow_
))
242 return gfx::Insets(inset
, inset
, inset
, inset
);
244 int first_inset
= inset
;
245 int second_inset
= std::max(inset
, images_
->arrow_thickness
);
246 if (is_arrow_on_horizontal(arrow_
) ?
247 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
))
248 std::swap(first_inset
, second_inset
);
249 return is_arrow_on_horizontal(arrow_
) ?
250 gfx::Insets(first_inset
, inset
, second_inset
, inset
) :
251 gfx::Insets(inset
, first_inset
, inset
, second_inset
);
254 gfx::Size
BubbleBorder::GetMinimumSize() const {
255 return GetSizeForContentsSize(gfx::Size());
258 gfx::Size
BubbleBorder::GetSizeForContentsSize(
259 const gfx::Size
& contents_size
) const {
260 // Enlarge the contents size by the thickness of the border images.
261 gfx::Size
size(contents_size
);
262 const gfx::Insets insets
= GetInsets();
263 size
.Enlarge(insets
.width(), insets
.height());
265 // Ensure the bubble is large enough to not overlap border and arrow images.
266 const int min
= 2 * images_
->border_thickness
;
267 const int min_with_arrow_width
= min
+ images_
->arrow_width
;
268 const int min_with_arrow_thickness
= images_
->border_thickness
+
269 std::max(images_
->arrow_thickness
+ images_
->border_interior_thickness
,
270 images_
->border_thickness
);
271 // Only take arrow image sizes into account when the bubble tip is shown.
272 if (arrow_paint_type_
== PAINT_NONE
|| !has_arrow(arrow_
))
273 size
.SetToMax(gfx::Size(min
, min
));
274 else if (is_arrow_on_horizontal(arrow_
))
275 size
.SetToMax(gfx::Size(min_with_arrow_width
, min_with_arrow_thickness
));
277 size
.SetToMax(gfx::Size(min_with_arrow_thickness
, min_with_arrow_width
));
281 gfx::ImageSkia
* BubbleBorder::GetArrowImage() const {
282 if (!has_arrow(arrow_
))
284 if (is_arrow_on_horizontal(arrow_
)) {
285 return is_arrow_on_top(arrow_
) ?
286 &images_
->top_arrow
: &images_
->bottom_arrow
;
288 return is_arrow_on_left(arrow_
) ?
289 &images_
->left_arrow
: &images_
->right_arrow
;
292 gfx::Rect
BubbleBorder::GetArrowRect(const gfx::Rect
& bounds
) const {
293 if (!has_arrow(arrow_
) || arrow_paint_type_
!= PAINT_NORMAL
)
297 int offset
= GetArrowOffset(bounds
.size());
298 const int half_length
= images_
->arrow_width
/ 2;
299 const gfx::Insets insets
= GetInsets();
301 if (is_arrow_on_horizontal(arrow_
)) {
302 origin
.set_x(is_arrow_on_left(arrow_
) || is_arrow_at_center(arrow_
) ?
303 offset
: bounds
.width() - offset
);
304 origin
.Offset(-half_length
, 0);
305 if (is_arrow_on_top(arrow_
))
306 origin
.set_y(insets
.top() - images_
->arrow_thickness
);
308 origin
.set_y(bounds
.height() - insets
.bottom());
310 origin
.set_y(is_arrow_on_top(arrow_
) || is_arrow_at_center(arrow_
) ?
311 offset
: bounds
.height() - offset
);
312 origin
.Offset(0, -half_length
);
313 if (is_arrow_on_left(arrow_
))
314 origin
.set_x(insets
.left() - images_
->arrow_thickness
);
316 origin
.set_x(bounds
.width() - insets
.right());
319 if (shadow_
!= NO_ASSETS
)
320 return gfx::Rect(origin
, GetArrowImage()->size());
322 // With no assets, return the size enclosing the path filled in DrawArrow().
323 DCHECK_EQ(2 * images_
->arrow_interior_thickness
, images_
->arrow_width
);
324 int width
= images_
->arrow_width
;
325 int height
= images_
->arrow_interior_thickness
;
326 if (!is_arrow_on_horizontal(arrow_
))
327 std::swap(width
, height
);
328 return gfx::Rect(origin
, gfx::Size(width
, height
));
331 void BubbleBorder::DrawArrow(gfx::Canvas
* canvas
,
332 const gfx::Rect
& arrow_bounds
) const {
333 canvas
->DrawImageInt(*GetArrowImage(), arrow_bounds
.x(), arrow_bounds
.y());
334 const bool horizontal
= is_arrow_on_horizontal(arrow_
);
335 const int thickness
= images_
->arrow_interior_thickness
;
336 float tip_x
= horizontal
? arrow_bounds
.CenterPoint().x() :
337 is_arrow_on_left(arrow_
) ? arrow_bounds
.right() - thickness
:
338 arrow_bounds
.x() + thickness
;
339 float tip_y
= !horizontal
? arrow_bounds
.CenterPoint().y() + 0.5f
:
340 is_arrow_on_top(arrow_
) ? arrow_bounds
.bottom() - thickness
:
341 arrow_bounds
.y() + thickness
;
342 const bool positive_offset
= horizontal
?
343 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
);
344 const int offset_to_next_vertex
= positive_offset
?
345 images_
->arrow_interior_thickness
: -images_
->arrow_interior_thickness
;
349 path
.moveTo(SkDoubleToScalar(tip_x
), SkDoubleToScalar(tip_y
));
350 path
.lineTo(SkDoubleToScalar(tip_x
+ offset_to_next_vertex
),
351 SkDoubleToScalar(tip_y
+ offset_to_next_vertex
));
352 const int multiplier
= horizontal
? 1 : -1;
353 path
.lineTo(SkDoubleToScalar(tip_x
- multiplier
* offset_to_next_vertex
),
354 SkDoubleToScalar(tip_y
+ multiplier
* offset_to_next_vertex
));
358 paint
.setStyle(SkPaint::kFill_Style
);
359 paint
.setColor(background_color_
);
361 canvas
->DrawPath(path
, paint
);
364 internal::BorderImages
* BubbleBorder::GetImagesForTest() const {
368 void BubbleBackground::Paint(gfx::Canvas
* canvas
, views::View
* view
) const {
369 if (border_
->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER
)
370 canvas
->DrawColor(border_
->background_color());
372 // Fill the contents with a round-rect region to match the border images.
374 paint
.setAntiAlias(true);
375 paint
.setStyle(SkPaint::kFill_Style
);
376 paint
.setColor(border_
->background_color());
378 gfx::Rect
bounds(view
->GetLocalBounds());
379 bounds
.Inset(border_
->GetInsets());
381 canvas
->DrawRoundRect(bounds
, border_
->GetBorderCornerRadius(), paint
);