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 "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/canvas.h"
12 #include "ui/gfx/geometry/rect.h"
13 #include "ui/gfx/skia_util.h"
14 #include "ui/resources/grit/ui_resources.h"
15 #include "ui/views/painter.h"
16 #include "ui/views/view.h"
22 BorderImages::BorderImages(const int border_image_ids
[],
23 const int arrow_image_ids
[],
24 int border_interior_thickness
,
25 int arrow_interior_thickness
,
27 : border_painter(Painter::CreateImageGridPainter(border_image_ids
)),
29 border_interior_thickness(border_interior_thickness
),
31 arrow_interior_thickness(arrow_interior_thickness
),
32 corner_radius(corner_radius
) {
33 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
34 border_thickness
= rb
.GetImageSkiaNamed(border_image_ids
[0])->width();
35 if (arrow_image_ids
[0] != 0) {
36 left_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[0]);
37 top_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[1]);
38 right_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[2]);
39 bottom_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[3]);
40 arrow_thickness
= top_arrow
.height();
44 BorderImages::~BorderImages() {}
46 } // namespace internal
50 // Bubble border and arrow image resource ids. They don't use the IMAGE_GRID
51 // macro because there is no center image.
52 const int kNoShadowImages
[] = {
53 IDR_BUBBLE_TL
, IDR_BUBBLE_T
, IDR_BUBBLE_TR
,
54 IDR_BUBBLE_L
, 0, IDR_BUBBLE_R
,
55 IDR_BUBBLE_BL
, IDR_BUBBLE_B
, IDR_BUBBLE_BR
};
56 const int kNoShadowArrows
[] = {
57 IDR_BUBBLE_L_ARROW
, IDR_BUBBLE_T_ARROW
,
58 IDR_BUBBLE_R_ARROW
, IDR_BUBBLE_B_ARROW
, };
60 const int kBigShadowImages
[] = {
61 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT
,
62 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP
,
63 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT
,
64 IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT
,
66 IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT
,
67 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT
,
68 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM
,
69 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT
};
70 const int kBigShadowArrows
[] = {
71 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT
,
72 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP
,
73 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT
,
74 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM
};
76 const int kSmallShadowImages
[] = {
77 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT
,
78 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP
,
79 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT
,
80 IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT
,
82 IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT
,
83 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT
,
84 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM
,
85 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT
};
86 const int kSmallShadowArrows
[] = {
87 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT
,
88 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP
,
89 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT
,
90 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM
};
92 using internal::BorderImages
;
94 // Returns the cached BorderImages for the given |shadow| type.
95 BorderImages
* GetBorderImages(BubbleBorder::Shadow shadow
) {
96 // Keep a cache of bubble border image-set painters, arrows, and metrics.
97 static BorderImages
* kBorderImages
[BubbleBorder::SHADOW_COUNT
] = { NULL
};
99 CHECK_LT(shadow
, BubbleBorder::SHADOW_COUNT
);
100 struct BorderImages
*& set
= kBorderImages
[shadow
];
105 case BubbleBorder::NO_SHADOW
:
106 case BubbleBorder::NO_SHADOW_OPAQUE_BORDER
:
107 set
= new BorderImages(kNoShadowImages
, kNoShadowArrows
, 6, 7, 4);
109 case BubbleBorder::BIG_SHADOW
:
110 set
= new BorderImages(kBigShadowImages
, kBigShadowArrows
, 23, 9, 2);
112 case BubbleBorder::SMALL_SHADOW
:
113 set
= new BorderImages(kSmallShadowImages
, kSmallShadowArrows
, 5, 6, 2);
115 case BubbleBorder::SHADOW_COUNT
:
125 const int BubbleBorder::kStroke
= 1;
127 BubbleBorder::BubbleBorder(Arrow arrow
, Shadow shadow
, SkColor color
)
130 arrow_paint_type_(PAINT_NORMAL
),
131 alignment_(ALIGN_ARROW_TO_MID_ANCHOR
),
133 background_color_(color
),
134 use_theme_background_color_(false) {
135 DCHECK(shadow
< SHADOW_COUNT
);
136 images_
= GetBorderImages(shadow
);
139 BubbleBorder::~BubbleBorder() {}
141 gfx::Rect
BubbleBorder::GetBounds(const gfx::Rect
& anchor_rect
,
142 const gfx::Size
& contents_size
) const {
143 int x
= anchor_rect
.x();
144 int y
= anchor_rect
.y();
145 int w
= anchor_rect
.width();
146 int h
= anchor_rect
.height();
147 const gfx::Size
size(GetSizeForContentsSize(contents_size
));
148 const int arrow_offset
= GetArrowOffset(size
);
149 const int arrow_size
=
150 images_
->arrow_interior_thickness
+ kStroke
- images_
->arrow_thickness
;
151 const bool mid_anchor
= alignment_
== ALIGN_ARROW_TO_MID_ANCHOR
;
153 // Calculate the bubble coordinates based on the border and arrow settings.
154 if (is_arrow_on_horizontal(arrow_
)) {
155 if (is_arrow_on_left(arrow_
)) {
156 x
+= mid_anchor
? w
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
157 } else if (is_arrow_at_center(arrow_
)) {
158 x
+= w
/ 2 - arrow_offset
;
160 x
+= mid_anchor
? w
/ 2 + arrow_offset
- size
.width() :
161 w
- size
.width() + GetBorderThickness() - kStroke
;
163 y
+= is_arrow_on_top(arrow_
) ? h
+ arrow_size
: -arrow_size
- size
.height();
164 } else if (has_arrow(arrow_
)) {
165 x
+= is_arrow_on_left(arrow_
) ? w
+ arrow_size
: -arrow_size
- size
.width();
166 if (is_arrow_on_top(arrow_
)) {
167 y
+= mid_anchor
? h
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
168 } else if (is_arrow_at_center(arrow_
)) {
169 y
+= h
/ 2 - arrow_offset
;
171 y
+= mid_anchor
? h
/ 2 + arrow_offset
- size
.height() :
172 h
- size
.height() + GetBorderThickness() - kStroke
;
175 x
+= (w
- size
.width()) / 2;
176 y
+= (arrow_
== NONE
) ? h
: (h
- size
.height()) / 2;
179 return gfx::Rect(x
, y
, size
.width(), size
.height());
182 int BubbleBorder::GetBorderThickness() const {
183 return images_
->border_thickness
- images_
->border_interior_thickness
;
186 int BubbleBorder::GetBorderCornerRadius() const {
187 return images_
->corner_radius
;
190 int BubbleBorder::GetArrowOffset(const gfx::Size
& border_size
) const {
191 const int edge_length
= is_arrow_on_horizontal(arrow_
) ?
192 border_size
.width() : border_size
.height();
193 if (is_arrow_at_center(arrow_
) && arrow_offset_
== 0)
194 return edge_length
/ 2;
196 // Calculate the minimum offset to not overlap arrow and corner images.
197 const int min
= images_
->border_thickness
+ (images_
->top_arrow
.width() / 2);
198 // Ensure the returned value will not cause image overlap, if possible.
199 return std::max(min
, std::min(arrow_offset_
, edge_length
- min
));
202 void BubbleBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
203 gfx::Rect
bounds(view
.GetContentsBounds());
204 bounds
.Inset(-GetBorderThickness(), -GetBorderThickness());
205 const gfx::Rect arrow_bounds
= GetArrowRect(view
.GetLocalBounds());
206 if (arrow_bounds
.IsEmpty()) {
207 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
211 // Clip the arrow bounds out to avoid painting the overlapping edge area.
213 SkRect
arrow_rect(gfx::RectToSkRect(arrow_bounds
));
214 canvas
->sk_canvas()->clipRect(arrow_rect
, SkRegion::kDifference_Op
);
215 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
218 DrawArrow(canvas
, arrow_bounds
);
221 gfx::Insets
BubbleBorder::GetInsets() const {
222 // The insets contain the stroke and shadow pixels outside the bubble fill.
223 const int inset
= GetBorderThickness();
224 if ((arrow_paint_type_
== PAINT_NONE
) || !has_arrow(arrow_
))
225 return gfx::Insets(inset
, inset
, inset
, inset
);
227 int first_inset
= inset
;
228 int second_inset
= std::max(inset
, images_
->arrow_thickness
);
229 if (is_arrow_on_horizontal(arrow_
) ?
230 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
))
231 std::swap(first_inset
, second_inset
);
232 return is_arrow_on_horizontal(arrow_
) ?
233 gfx::Insets(first_inset
, inset
, second_inset
, inset
) :
234 gfx::Insets(inset
, first_inset
, inset
, second_inset
);
237 gfx::Size
BubbleBorder::GetMinimumSize() const {
238 return GetSizeForContentsSize(gfx::Size());
241 gfx::Size
BubbleBorder::GetSizeForContentsSize(
242 const gfx::Size
& contents_size
) const {
243 // Enlarge the contents size by the thickness of the border images.
244 gfx::Size
size(contents_size
);
245 const gfx::Insets insets
= GetInsets();
246 size
.Enlarge(insets
.width(), insets
.height());
248 // Ensure the bubble is large enough to not overlap border and arrow images.
249 const int min
= 2 * images_
->border_thickness
;
250 const int min_with_arrow_width
= min
+ images_
->top_arrow
.width();
251 const int min_with_arrow_thickness
= images_
->border_thickness
+
252 std::max(images_
->arrow_thickness
+ images_
->border_interior_thickness
,
253 images_
->border_thickness
);
254 // Only take arrow image sizes into account when the bubble tip is shown.
255 if (arrow_paint_type_
== PAINT_NONE
|| !has_arrow(arrow_
))
256 size
.SetToMax(gfx::Size(min
, min
));
257 else if (is_arrow_on_horizontal(arrow_
))
258 size
.SetToMax(gfx::Size(min_with_arrow_width
, min_with_arrow_thickness
));
260 size
.SetToMax(gfx::Size(min_with_arrow_thickness
, min_with_arrow_width
));
264 gfx::ImageSkia
* BubbleBorder::GetArrowImage() const {
265 if (!has_arrow(arrow_
))
267 if (is_arrow_on_horizontal(arrow_
)) {
268 return is_arrow_on_top(arrow_
) ?
269 &images_
->top_arrow
: &images_
->bottom_arrow
;
271 return is_arrow_on_left(arrow_
) ?
272 &images_
->left_arrow
: &images_
->right_arrow
;
275 gfx::Rect
BubbleBorder::GetArrowRect(const gfx::Rect
& bounds
) const {
276 if (!has_arrow(arrow_
) || arrow_paint_type_
!= PAINT_NORMAL
)
280 int offset
= GetArrowOffset(bounds
.size());
281 const int half_length
= images_
->top_arrow
.width() / 2;
282 const gfx::Insets insets
= GetInsets();
284 if (is_arrow_on_horizontal(arrow_
)) {
285 origin
.set_x(is_arrow_on_left(arrow_
) || is_arrow_at_center(arrow_
) ?
286 offset
: bounds
.width() - offset
);
287 origin
.Offset(-half_length
, 0);
288 if (is_arrow_on_top(arrow_
))
289 origin
.set_y(insets
.top() - images_
->arrow_thickness
);
291 origin
.set_y(bounds
.height() - insets
.bottom());
293 origin
.set_y(is_arrow_on_top(arrow_
) || is_arrow_at_center(arrow_
) ?
294 offset
: bounds
.height() - offset
);
295 origin
.Offset(0, -half_length
);
296 if (is_arrow_on_left(arrow_
))
297 origin
.set_x(insets
.left() - images_
->arrow_thickness
);
299 origin
.set_x(bounds
.width() - insets
.right());
301 return gfx::Rect(origin
, GetArrowImage()->size());
304 void BubbleBorder::DrawArrow(gfx::Canvas
* canvas
,
305 const gfx::Rect
& arrow_bounds
) const {
306 canvas
->DrawImageInt(*GetArrowImage(), arrow_bounds
.x(), arrow_bounds
.y());
307 const bool horizontal
= is_arrow_on_horizontal(arrow_
);
308 const int thickness
= images_
->arrow_interior_thickness
;
309 float tip_x
= horizontal
? arrow_bounds
.CenterPoint().x() :
310 is_arrow_on_left(arrow_
) ? arrow_bounds
.right() - thickness
:
311 arrow_bounds
.x() + thickness
;
312 float tip_y
= !horizontal
? arrow_bounds
.CenterPoint().y() + 0.5f
:
313 is_arrow_on_top(arrow_
) ? arrow_bounds
.bottom() - thickness
:
314 arrow_bounds
.y() + thickness
;
315 const bool positive_offset
= horizontal
?
316 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
);
317 const int offset_to_next_vertex
= positive_offset
?
318 images_
->arrow_interior_thickness
: -images_
->arrow_interior_thickness
;
322 path
.moveTo(SkDoubleToScalar(tip_x
), SkDoubleToScalar(tip_y
));
323 path
.lineTo(SkDoubleToScalar(tip_x
+ offset_to_next_vertex
),
324 SkDoubleToScalar(tip_y
+ offset_to_next_vertex
));
325 const int multiplier
= horizontal
? 1 : -1;
326 path
.lineTo(SkDoubleToScalar(tip_x
- multiplier
* offset_to_next_vertex
),
327 SkDoubleToScalar(tip_y
+ multiplier
* offset_to_next_vertex
));
331 paint
.setStyle(SkPaint::kFill_Style
);
332 paint
.setColor(background_color_
);
334 canvas
->DrawPath(path
, paint
);
337 internal::BorderImages
* BubbleBorder::GetImagesForTest() const {
341 void BubbleBackground::Paint(gfx::Canvas
* canvas
, views::View
* view
) const {
342 if (border_
->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER
)
343 canvas
->DrawColor(border_
->background_color());
345 // Fill the contents with a round-rect region to match the border images.
347 paint
.setAntiAlias(true);
348 paint
.setStyle(SkPaint::kFill_Style
);
349 paint
.setColor(border_
->background_color());
351 gfx::Rect
bounds(view
->GetLocalBounds());
352 bounds
.Inset(border_
->GetInsets());
354 canvas
->DrawRoundRect(bounds
, border_
->GetBorderCornerRadius(), paint
);