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 "base/memory/scoped_ptr.h"
11 #include "grit/ui_resources.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/image/image_skia.h"
15 #include "ui/gfx/rect.h"
16 #include "ui/gfx/skia_util.h"
17 #include "ui/views/painter.h"
18 #include "ui/views/view.h"
24 // A helper that combines each border image-set painter with arrows and metrics.
26 BorderImages(const int border_image_ids
[],
27 const int arrow_image_ids
[],
28 int border_interior_thickness
,
29 int arrow_interior_thickness
,
32 scoped_ptr
<Painter
> border_painter
;
33 gfx::ImageSkia left_arrow
;
34 gfx::ImageSkia top_arrow
;
35 gfx::ImageSkia right_arrow
;
36 gfx::ImageSkia bottom_arrow
;
38 // The thickness of border and arrow images and their interior areas.
39 // Thickness is the width of left/right and the height of top/bottom images.
40 // The interior is measured without including stroke or shadow pixels.
42 int border_interior_thickness
;
44 int arrow_interior_thickness
;
45 // The corner radius of the bubble's rounded-rect interior area.
49 BorderImages::BorderImages(const int border_image_ids
[],
50 const int arrow_image_ids
[],
51 int border_interior_thickness
,
52 int arrow_interior_thickness
,
54 : border_painter(Painter::CreateImageGridPainter(border_image_ids
)),
56 border_interior_thickness(border_interior_thickness
),
58 arrow_interior_thickness(arrow_interior_thickness
),
59 corner_radius(corner_radius
) {
60 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
61 border_thickness
= rb
.GetImageSkiaNamed(border_image_ids
[0])->width();
62 if (arrow_image_ids
[0] != 0) {
63 left_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[0]);
64 top_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[1]);
65 right_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[2]);
66 bottom_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[3]);
67 arrow_thickness
= top_arrow
.height();
71 } // namespace internal
75 // The border and arrow stroke size used in image assets, in pixels.
76 const int kStroke
= 1;
78 // Bubble border and arrow image resource ids. They don't use the IMAGE_GRID
79 // macro because there is no center image.
80 const int kNoShadowImages
[] = {
81 IDR_BUBBLE_TL
, IDR_BUBBLE_T
, IDR_BUBBLE_TR
,
82 IDR_BUBBLE_L
, 0, IDR_BUBBLE_R
,
83 IDR_BUBBLE_BL
, IDR_BUBBLE_B
, IDR_BUBBLE_BR
};
84 const int kNoShadowArrows
[] = {
85 IDR_BUBBLE_L_ARROW
, IDR_BUBBLE_T_ARROW
,
86 IDR_BUBBLE_R_ARROW
, IDR_BUBBLE_B_ARROW
, };
88 const int kBigShadowImages
[] = {
89 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT
,
90 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP
,
91 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT
,
92 IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT
,
94 IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT
,
95 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT
,
96 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM
,
97 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT
};
98 const int kBigShadowArrows
[] = {
99 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT
,
100 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP
,
101 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT
,
102 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM
};
104 const int kSmallShadowImages
[] = {
105 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT
,
106 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP
,
107 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT
,
108 IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT
,
110 IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT
,
111 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT
,
112 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM
,
113 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT
};
114 const int kSmallShadowArrows
[] = {
115 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT
,
116 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP
,
117 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT
,
118 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM
};
120 using internal::BorderImages
;
122 // Returns the cached BorderImages for the given |shadow| type.
123 BorderImages
* GetBorderImages(BubbleBorder::Shadow shadow
) {
124 // Keep a cache of bubble border image-set painters, arrows, and metrics.
125 static BorderImages
* kBorderImages
[BubbleBorder::SHADOW_COUNT
] = { NULL
};
127 CHECK_LT(shadow
, BubbleBorder::SHADOW_COUNT
);
128 struct BorderImages
*& set
= kBorderImages
[shadow
];
133 case BubbleBorder::NO_SHADOW
:
134 case BubbleBorder::NO_SHADOW_OPAQUE_BORDER
:
135 set
= new BorderImages(kNoShadowImages
, kNoShadowArrows
, 6, 7, 4);
137 case BubbleBorder::BIG_SHADOW
:
138 set
= new BorderImages(kBigShadowImages
, kBigShadowArrows
, 23, 9, 2);
140 case BubbleBorder::SMALL_SHADOW
:
141 set
= new BorderImages(kSmallShadowImages
, kSmallShadowArrows
, 5, 6, 2);
143 case BubbleBorder::SHADOW_COUNT
:
153 BubbleBorder::BubbleBorder(Arrow arrow
, Shadow shadow
, SkColor color
)
156 arrow_paint_type_(PAINT_NORMAL
),
157 alignment_(ALIGN_ARROW_TO_MID_ANCHOR
),
159 background_color_(color
),
160 use_theme_background_color_(false) {
161 DCHECK(shadow
< SHADOW_COUNT
);
162 images_
= GetBorderImages(shadow
);
165 BubbleBorder::~BubbleBorder() {}
167 gfx::Rect
BubbleBorder::GetBounds(const gfx::Rect
& anchor_rect
,
168 const gfx::Size
& contents_size
) const {
169 int x
= anchor_rect
.x();
170 int y
= anchor_rect
.y();
171 int w
= anchor_rect
.width();
172 int h
= anchor_rect
.height();
173 const gfx::Size
size(GetSizeForContentsSize(contents_size
));
174 const int arrow_offset
= GetArrowOffset(size
);
175 const int arrow_size
=
176 images_
->arrow_interior_thickness
+ kStroke
- images_
->arrow_thickness
;
177 const bool mid_anchor
= alignment_
== ALIGN_ARROW_TO_MID_ANCHOR
;
179 // Calculate the bubble coordinates based on the border and arrow settings.
180 if (is_arrow_on_horizontal(arrow_
)) {
181 if (is_arrow_on_left(arrow_
)) {
182 x
+= mid_anchor
? w
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
183 } else if (is_arrow_at_center(arrow_
)) {
184 x
+= w
/ 2 - arrow_offset
;
186 x
+= mid_anchor
? w
/ 2 + arrow_offset
- size
.width() :
187 w
- size
.width() + GetBorderThickness() - kStroke
;
189 y
+= is_arrow_on_top(arrow_
) ? h
+ arrow_size
: -arrow_size
- size
.height();
190 } else if (has_arrow(arrow_
)) {
191 x
+= is_arrow_on_left(arrow_
) ? w
+ arrow_size
: -arrow_size
- size
.width();
192 if (is_arrow_on_top(arrow_
)) {
193 y
+= mid_anchor
? h
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
194 } else if (is_arrow_at_center(arrow_
)) {
195 y
+= h
/ 2 - arrow_offset
;
197 y
+= mid_anchor
? h
/ 2 + arrow_offset
- size
.height() :
198 h
- size
.height() + GetBorderThickness() - kStroke
;
201 x
+= (w
- size
.width()) / 2;
202 y
+= (arrow_
== NONE
) ? h
: (h
- size
.height()) / 2;
205 return gfx::Rect(x
, y
, size
.width(), size
.height());
208 int BubbleBorder::GetBorderThickness() const {
209 return images_
->border_thickness
- images_
->border_interior_thickness
;
212 int BubbleBorder::GetBorderCornerRadius() const {
213 return images_
->corner_radius
;
216 int BubbleBorder::GetArrowOffset(const gfx::Size
& border_size
) const {
217 const int edge_length
= is_arrow_on_horizontal(arrow_
) ?
218 border_size
.width() : border_size
.height();
219 if (is_arrow_at_center(arrow_
) && arrow_offset_
== 0)
220 return edge_length
/ 2;
222 // Calculate the minimum offset to not overlap arrow and corner images.
223 const int min
= images_
->border_thickness
+ (images_
->top_arrow
.width() / 2);
224 // Ensure the returned value will not cause image overlap, if possible.
225 return std::max(min
, std::min(arrow_offset_
, edge_length
- min
));
228 void BubbleBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
229 gfx::Rect
bounds(view
.GetContentsBounds());
230 bounds
.Inset(-GetBorderThickness(), -GetBorderThickness());
231 const gfx::Rect arrow_bounds
= GetArrowRect(view
.GetLocalBounds());
232 if (arrow_bounds
.IsEmpty()) {
233 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
237 // Clip the arrow bounds out to avoid painting the overlapping edge area.
239 SkRect
arrow_rect(gfx::RectToSkRect(arrow_bounds
));
240 canvas
->sk_canvas()->clipRect(arrow_rect
, SkRegion::kDifference_Op
);
241 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
244 DrawArrow(canvas
, arrow_bounds
);
247 gfx::Insets
BubbleBorder::GetInsets() const {
248 // The insets contain the stroke and shadow pixels outside the bubble fill.
249 const int inset
= GetBorderThickness();
250 if ((arrow_paint_type_
== PAINT_NONE
) || !has_arrow(arrow_
))
251 return gfx::Insets(inset
, inset
, inset
, inset
);
253 int first_inset
= inset
;
254 int second_inset
= std::max(inset
, images_
->arrow_thickness
);
255 if (is_arrow_on_horizontal(arrow_
) ?
256 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
))
257 std::swap(first_inset
, second_inset
);
258 return is_arrow_on_horizontal(arrow_
) ?
259 gfx::Insets(first_inset
, inset
, second_inset
, inset
) :
260 gfx::Insets(inset
, first_inset
, inset
, second_inset
);
263 gfx::Size
BubbleBorder::GetMinimumSize() const {
264 return GetSizeForContentsSize(gfx::Size());
267 gfx::Size
BubbleBorder::GetSizeForContentsSize(
268 const gfx::Size
& contents_size
) const {
269 // Enlarge the contents size by the thickness of the border images.
270 gfx::Size
size(contents_size
);
271 const gfx::Insets insets
= GetInsets();
272 size
.Enlarge(insets
.width(), insets
.height());
274 // Ensure the bubble is large enough to not overlap border and arrow images.
275 const int min
= 2 * images_
->border_thickness
;
276 const int min_with_arrow_width
= min
+ images_
->top_arrow
.width();
277 const int min_with_arrow_thickness
= images_
->border_thickness
+
278 std::max(images_
->arrow_thickness
+ images_
->border_interior_thickness
,
279 images_
->border_thickness
);
280 // Only take arrow image sizes into account when the bubble tip is shown.
281 if (arrow_paint_type_
== PAINT_TRANSPARENT
|| !has_arrow(arrow_
))
282 size
.SetToMax(gfx::Size(min
, min
));
283 else if (is_arrow_on_horizontal(arrow_
))
284 size
.SetToMax(gfx::Size(min_with_arrow_width
, min_with_arrow_thickness
));
286 size
.SetToMax(gfx::Size(min_with_arrow_thickness
, min_with_arrow_width
));
290 gfx::ImageSkia
* BubbleBorder::GetArrowImage() const {
291 if (!has_arrow(arrow_
))
293 if (is_arrow_on_horizontal(arrow_
)) {
294 return is_arrow_on_top(arrow_
) ?
295 &images_
->top_arrow
: &images_
->bottom_arrow
;
297 return is_arrow_on_left(arrow_
) ?
298 &images_
->left_arrow
: &images_
->right_arrow
;
301 gfx::Rect
BubbleBorder::GetArrowRect(const gfx::Rect
& bounds
) const {
302 if (!has_arrow(arrow_
) || arrow_paint_type_
!= PAINT_NORMAL
)
306 int offset
= GetArrowOffset(bounds
.size());
307 const int half_length
= images_
->top_arrow
.width() / 2;
308 const gfx::Insets insets
= GetInsets();
310 if (is_arrow_on_horizontal(arrow_
)) {
311 origin
.set_x(is_arrow_on_left(arrow_
) || is_arrow_at_center(arrow_
) ?
312 offset
: bounds
.width() - offset
);
313 origin
.Offset(-half_length
, 0);
314 if (is_arrow_on_top(arrow_
))
315 origin
.set_y(insets
.top() - images_
->arrow_thickness
);
317 origin
.set_y(bounds
.height() - insets
.bottom());
319 origin
.set_y(is_arrow_on_top(arrow_
) || is_arrow_at_center(arrow_
) ?
320 offset
: bounds
.height() - offset
);
321 origin
.Offset(0, -half_length
);
322 if (is_arrow_on_left(arrow_
))
323 origin
.set_x(insets
.left() - images_
->arrow_thickness
);
325 origin
.set_x(bounds
.width() - insets
.right());
327 return gfx::Rect(origin
, GetArrowImage()->size());
330 void BubbleBorder::DrawArrow(gfx::Canvas
* canvas
,
331 const gfx::Rect
& arrow_bounds
) const {
332 canvas
->DrawImageInt(*GetArrowImage(), arrow_bounds
.x(), arrow_bounds
.y());
333 const bool horizontal
= is_arrow_on_horizontal(arrow_
);
334 const int thickness
= images_
->arrow_interior_thickness
;
335 float tip_x
= horizontal
? arrow_bounds
.CenterPoint().x() :
336 is_arrow_on_left(arrow_
) ? arrow_bounds
.right() - thickness
:
337 arrow_bounds
.x() + thickness
;
338 float tip_y
= !horizontal
? arrow_bounds
.CenterPoint().y() + 0.5f
:
339 is_arrow_on_top(arrow_
) ? arrow_bounds
.bottom() - thickness
:
340 arrow_bounds
.y() + thickness
;
341 const bool positive_offset
= horizontal
?
342 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
);
343 const int offset_to_next_vertex
= positive_offset
?
344 images_
->arrow_interior_thickness
: -images_
->arrow_interior_thickness
;
348 path
.moveTo(SkDoubleToScalar(tip_x
), SkDoubleToScalar(tip_y
));
349 path
.lineTo(SkDoubleToScalar(tip_x
+ offset_to_next_vertex
),
350 SkDoubleToScalar(tip_y
+ offset_to_next_vertex
));
351 const int multiplier
= horizontal
? 1 : -1;
352 path
.lineTo(SkDoubleToScalar(tip_x
- multiplier
* offset_to_next_vertex
),
353 SkDoubleToScalar(tip_y
+ multiplier
* offset_to_next_vertex
));
357 paint
.setStyle(SkPaint::kFill_Style
);
358 paint
.setColor(background_color_
);
360 canvas
->DrawPath(path
, paint
);
363 void BubbleBackground::Paint(gfx::Canvas
* canvas
, views::View
* view
) const {
364 if (border_
->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER
)
365 canvas
->DrawColor(border_
->background_color());
367 // Fill the contents with a round-rect region to match the border images.
369 paint
.setAntiAlias(true);
370 paint
.setStyle(SkPaint::kFill_Style
);
371 paint
.setColor(border_
->background_color());
373 gfx::Rect
bounds(view
->GetLocalBounds());
374 bounds
.Inset(border_
->GetInsets());
376 SkScalar radius
= SkIntToScalar(border_
->GetBorderCornerRadius());
377 path
.addRoundRect(gfx::RectToSkRect(bounds
), radius
, radius
);
378 canvas
->DrawPath(path
, paint
);