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/resources/grit/views_resources.h"
17 #include "ui/views/view.h"
23 BorderImages::BorderImages(const int border_image_ids
[],
24 const int arrow_image_ids
[],
25 int border_interior_thickness
,
26 int arrow_interior_thickness
,
28 : border_thickness(border_interior_thickness
),
29 border_interior_thickness(border_interior_thickness
),
30 arrow_thickness(arrow_interior_thickness
),
31 arrow_interior_thickness(arrow_interior_thickness
),
32 arrow_width(2 * arrow_interior_thickness
),
33 corner_radius(corner_radius
) {
34 if (!border_image_ids
)
37 border_painter
.reset(Painter::CreateImageGridPainter(border_image_ids
));
38 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
39 border_thickness
= rb
.GetImageSkiaNamed(border_image_ids
[0])->width();
41 if (arrow_image_ids
[0] != 0) {
42 left_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[0]);
43 top_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[1]);
44 right_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[2]);
45 bottom_arrow
= *rb
.GetImageSkiaNamed(arrow_image_ids
[3]);
46 arrow_width
= top_arrow
.width();
47 arrow_thickness
= top_arrow
.height();
51 BorderImages::~BorderImages() {}
53 } // namespace internal
57 // Bubble border and arrow image resource ids. They don't use the IMAGE_GRID
58 // macro because there is no center image.
59 const int kNoShadowImages
[] = {
60 IDR_BUBBLE_TL
, IDR_BUBBLE_T
, IDR_BUBBLE_TR
,
61 IDR_BUBBLE_L
, 0, IDR_BUBBLE_R
,
62 IDR_BUBBLE_BL
, IDR_BUBBLE_B
, IDR_BUBBLE_BR
};
63 const int kNoShadowArrows
[] = {
64 IDR_BUBBLE_L_ARROW
, IDR_BUBBLE_T_ARROW
,
65 IDR_BUBBLE_R_ARROW
, IDR_BUBBLE_B_ARROW
, };
67 const int kBigShadowImages
[] = {
68 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_LEFT
,
69 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP
,
70 IDR_WINDOW_BUBBLE_SHADOW_BIG_TOP_RIGHT
,
71 IDR_WINDOW_BUBBLE_SHADOW_BIG_LEFT
,
73 IDR_WINDOW_BUBBLE_SHADOW_BIG_RIGHT
,
74 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_LEFT
,
75 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM
,
76 IDR_WINDOW_BUBBLE_SHADOW_BIG_BOTTOM_RIGHT
};
77 const int kBigShadowArrows
[] = {
78 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_LEFT
,
79 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_TOP
,
80 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_RIGHT
,
81 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_BIG_BOTTOM
};
83 const int kSmallShadowImages
[] = {
84 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT
,
85 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP
,
86 IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT
,
87 IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT
,
89 IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT
,
90 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT
,
91 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM
,
92 IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT
};
93 const int kSmallShadowArrows
[] = {
94 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_LEFT
,
95 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_TOP
,
96 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_RIGHT
,
97 IDR_WINDOW_BUBBLE_SHADOW_SPIKE_SMALL_BOTTOM
};
99 using internal::BorderImages
;
101 // Returns the cached BorderImages for the given |shadow| type.
102 BorderImages
* GetBorderImages(BubbleBorder::Shadow shadow
) {
103 // Keep a cache of bubble border image-set painters, arrows, and metrics.
104 static BorderImages
* kBorderImages
[BubbleBorder::SHADOW_COUNT
] = { NULL
};
106 CHECK_LT(shadow
, BubbleBorder::SHADOW_COUNT
);
107 struct BorderImages
*& set
= kBorderImages
[shadow
];
112 case BubbleBorder::NO_SHADOW
:
113 case BubbleBorder::NO_SHADOW_OPAQUE_BORDER
:
114 set
= new BorderImages(kNoShadowImages
, kNoShadowArrows
, 6, 7, 4);
116 case BubbleBorder::BIG_SHADOW
:
117 set
= new BorderImages(kBigShadowImages
, kBigShadowArrows
, 23, 9, 2);
119 case BubbleBorder::SMALL_SHADOW
:
120 set
= new BorderImages(kSmallShadowImages
, kSmallShadowArrows
, 5, 6, 2);
122 case BubbleBorder::NO_ASSETS
:
123 set
= new BorderImages(nullptr, nullptr, 17, 8, 2);
125 case BubbleBorder::SHADOW_COUNT
:
135 const int BubbleBorder::kStroke
= 1;
137 BubbleBorder::BubbleBorder(Arrow arrow
, Shadow shadow
, SkColor color
)
140 arrow_paint_type_(PAINT_NORMAL
),
141 alignment_(ALIGN_ARROW_TO_MID_ANCHOR
),
143 background_color_(color
),
144 use_theme_background_color_(false) {
145 DCHECK(shadow
< SHADOW_COUNT
);
146 images_
= GetBorderImages(shadow
);
149 BubbleBorder::~BubbleBorder() {}
151 gfx::Rect
BubbleBorder::GetBounds(const gfx::Rect
& anchor_rect
,
152 const gfx::Size
& contents_size
) const {
153 int x
= anchor_rect
.x();
154 int y
= anchor_rect
.y();
155 int w
= anchor_rect
.width();
156 int h
= anchor_rect
.height();
157 const gfx::Size
size(GetSizeForContentsSize(contents_size
));
158 const int arrow_offset
= GetArrowOffset(size
);
159 const int arrow_size
=
160 images_
->arrow_interior_thickness
+ kStroke
- images_
->arrow_thickness
;
161 const bool mid_anchor
= alignment_
== ALIGN_ARROW_TO_MID_ANCHOR
;
163 // Calculate the bubble coordinates based on the border and arrow settings.
164 if (is_arrow_on_horizontal(arrow_
)) {
165 if (is_arrow_on_left(arrow_
)) {
166 x
+= mid_anchor
? w
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
167 } else if (is_arrow_at_center(arrow_
)) {
168 x
+= w
/ 2 - arrow_offset
;
170 x
+= mid_anchor
? w
/ 2 + arrow_offset
- size
.width() :
171 w
- size
.width() + GetBorderThickness() - kStroke
;
173 y
+= is_arrow_on_top(arrow_
) ? h
+ arrow_size
: -arrow_size
- size
.height();
174 } else if (has_arrow(arrow_
)) {
175 x
+= is_arrow_on_left(arrow_
) ? w
+ arrow_size
: -arrow_size
- size
.width();
176 if (is_arrow_on_top(arrow_
)) {
177 y
+= mid_anchor
? h
/ 2 - arrow_offset
: kStroke
- GetBorderThickness();
178 } else if (is_arrow_at_center(arrow_
)) {
179 y
+= h
/ 2 - arrow_offset
;
181 y
+= mid_anchor
? h
/ 2 + arrow_offset
- size
.height() :
182 h
- size
.height() + GetBorderThickness() - kStroke
;
185 x
+= (w
- size
.width()) / 2;
186 y
+= (arrow_
== NONE
) ? h
: (h
- size
.height()) / 2;
189 return gfx::Rect(x
, y
, size
.width(), size
.height());
192 int BubbleBorder::GetBorderThickness() const {
193 return images_
->border_thickness
- images_
->border_interior_thickness
;
196 int BubbleBorder::GetBorderCornerRadius() const {
197 return images_
->corner_radius
;
200 int BubbleBorder::GetArrowOffset(const gfx::Size
& border_size
) const {
201 const int edge_length
= is_arrow_on_horizontal(arrow_
) ?
202 border_size
.width() : border_size
.height();
203 if (is_arrow_at_center(arrow_
) && arrow_offset_
== 0)
204 return edge_length
/ 2;
206 // Calculate the minimum offset to not overlap arrow and corner images.
207 const int min
= images_
->border_thickness
+ (images_
->arrow_width
/ 2);
208 // Ensure the returned value will not cause image overlap, if possible.
209 return std::max(min
, std::min(arrow_offset_
, edge_length
- min
));
212 void BubbleBorder::Paint(const views::View
& view
, gfx::Canvas
* canvas
) {
213 gfx::Rect
bounds(view
.GetContentsBounds());
214 bounds
.Inset(-GetBorderThickness(), -GetBorderThickness());
215 const gfx::Rect arrow_bounds
= GetArrowRect(view
.GetLocalBounds());
216 if (arrow_bounds
.IsEmpty()) {
217 if (images_
->border_painter
)
218 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
221 if (!images_
->border_painter
) {
222 DrawArrow(canvas
, arrow_bounds
);
226 // Clip the arrow bounds out to avoid painting the overlapping edge area.
228 SkRect
arrow_rect(gfx::RectToSkRect(arrow_bounds
));
229 canvas
->sk_canvas()->clipRect(arrow_rect
, SkRegion::kDifference_Op
);
230 Painter::PaintPainterAt(canvas
, images_
->border_painter
.get(), bounds
);
233 DrawArrow(canvas
, arrow_bounds
);
236 gfx::Insets
BubbleBorder::GetInsets() const {
237 // The insets contain the stroke and shadow pixels outside the bubble fill.
238 const int inset
= GetBorderThickness();
239 if ((arrow_paint_type_
== PAINT_NONE
) || !has_arrow(arrow_
))
240 return gfx::Insets(inset
, inset
, inset
, inset
);
242 int first_inset
= inset
;
243 int second_inset
= std::max(inset
, images_
->arrow_thickness
);
244 if (is_arrow_on_horizontal(arrow_
) ?
245 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
))
246 std::swap(first_inset
, second_inset
);
247 return is_arrow_on_horizontal(arrow_
) ?
248 gfx::Insets(first_inset
, inset
, second_inset
, inset
) :
249 gfx::Insets(inset
, first_inset
, inset
, second_inset
);
252 gfx::Size
BubbleBorder::GetMinimumSize() const {
253 return GetSizeForContentsSize(gfx::Size());
256 gfx::Size
BubbleBorder::GetSizeForContentsSize(
257 const gfx::Size
& contents_size
) const {
258 // Enlarge the contents size by the thickness of the border images.
259 gfx::Size
size(contents_size
);
260 const gfx::Insets insets
= GetInsets();
261 size
.Enlarge(insets
.width(), insets
.height());
263 // Ensure the bubble is large enough to not overlap border and arrow images.
264 const int min
= 2 * images_
->border_thickness
;
265 const int min_with_arrow_width
= min
+ images_
->arrow_width
;
266 const int min_with_arrow_thickness
= images_
->border_thickness
+
267 std::max(images_
->arrow_thickness
+ images_
->border_interior_thickness
,
268 images_
->border_thickness
);
269 // Only take arrow image sizes into account when the bubble tip is shown.
270 if (arrow_paint_type_
== PAINT_NONE
|| !has_arrow(arrow_
))
271 size
.SetToMax(gfx::Size(min
, min
));
272 else if (is_arrow_on_horizontal(arrow_
))
273 size
.SetToMax(gfx::Size(min_with_arrow_width
, min_with_arrow_thickness
));
275 size
.SetToMax(gfx::Size(min_with_arrow_thickness
, min_with_arrow_width
));
279 gfx::ImageSkia
* BubbleBorder::GetArrowImage() const {
280 if (!has_arrow(arrow_
))
282 if (is_arrow_on_horizontal(arrow_
)) {
283 return is_arrow_on_top(arrow_
) ?
284 &images_
->top_arrow
: &images_
->bottom_arrow
;
286 return is_arrow_on_left(arrow_
) ?
287 &images_
->left_arrow
: &images_
->right_arrow
;
290 gfx::Rect
BubbleBorder::GetArrowRect(const gfx::Rect
& bounds
) const {
291 if (!has_arrow(arrow_
) || arrow_paint_type_
!= PAINT_NORMAL
)
295 int offset
= GetArrowOffset(bounds
.size());
296 const int half_length
= images_
->arrow_width
/ 2;
297 const gfx::Insets insets
= GetInsets();
299 if (is_arrow_on_horizontal(arrow_
)) {
300 origin
.set_x(is_arrow_on_left(arrow_
) || is_arrow_at_center(arrow_
) ?
301 offset
: bounds
.width() - offset
);
302 origin
.Offset(-half_length
, 0);
303 if (is_arrow_on_top(arrow_
))
304 origin
.set_y(insets
.top() - images_
->arrow_thickness
);
306 origin
.set_y(bounds
.height() - insets
.bottom());
308 origin
.set_y(is_arrow_on_top(arrow_
) || is_arrow_at_center(arrow_
) ?
309 offset
: bounds
.height() - offset
);
310 origin
.Offset(0, -half_length
);
311 if (is_arrow_on_left(arrow_
))
312 origin
.set_x(insets
.left() - images_
->arrow_thickness
);
314 origin
.set_x(bounds
.width() - insets
.right());
317 if (shadow_
!= NO_ASSETS
)
318 return gfx::Rect(origin
, GetArrowImage()->size());
320 // With no assets, return the size enclosing the path filled in DrawArrow().
321 DCHECK_EQ(2 * images_
->arrow_interior_thickness
, images_
->arrow_width
);
322 int width
= images_
->arrow_width
;
323 int height
= images_
->arrow_interior_thickness
;
324 if (!is_arrow_on_horizontal(arrow_
))
325 std::swap(width
, height
);
326 return gfx::Rect(origin
, gfx::Size(width
, height
));
329 void BubbleBorder::DrawArrow(gfx::Canvas
* canvas
,
330 const gfx::Rect
& arrow_bounds
) const {
331 canvas
->DrawImageInt(*GetArrowImage(), arrow_bounds
.x(), arrow_bounds
.y());
332 const bool horizontal
= is_arrow_on_horizontal(arrow_
);
333 const int thickness
= images_
->arrow_interior_thickness
;
334 float tip_x
= horizontal
? arrow_bounds
.CenterPoint().x() :
335 is_arrow_on_left(arrow_
) ? arrow_bounds
.right() - thickness
:
336 arrow_bounds
.x() + thickness
;
337 float tip_y
= !horizontal
? arrow_bounds
.CenterPoint().y() + 0.5f
:
338 is_arrow_on_top(arrow_
) ? arrow_bounds
.bottom() - thickness
:
339 arrow_bounds
.y() + thickness
;
340 const bool positive_offset
= horizontal
?
341 is_arrow_on_top(arrow_
) : is_arrow_on_left(arrow_
);
342 const int offset_to_next_vertex
= positive_offset
?
343 images_
->arrow_interior_thickness
: -images_
->arrow_interior_thickness
;
347 path
.moveTo(SkDoubleToScalar(tip_x
), SkDoubleToScalar(tip_y
));
348 path
.lineTo(SkDoubleToScalar(tip_x
+ offset_to_next_vertex
),
349 SkDoubleToScalar(tip_y
+ offset_to_next_vertex
));
350 const int multiplier
= horizontal
? 1 : -1;
351 path
.lineTo(SkDoubleToScalar(tip_x
- multiplier
* offset_to_next_vertex
),
352 SkDoubleToScalar(tip_y
+ multiplier
* offset_to_next_vertex
));
356 paint
.setStyle(SkPaint::kFill_Style
);
357 paint
.setColor(background_color_
);
359 canvas
->DrawPath(path
, paint
);
362 internal::BorderImages
* BubbleBorder::GetImagesForTest() const {
366 void BubbleBackground::Paint(gfx::Canvas
* canvas
, views::View
* view
) const {
367 if (border_
->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER
)
368 canvas
->DrawColor(border_
->background_color());
370 // Fill the contents with a round-rect region to match the border images.
372 paint
.setAntiAlias(true);
373 paint
.setStyle(SkPaint::kFill_Style
);
374 paint
.setColor(border_
->background_color());
376 gfx::Rect
bounds(view
->GetLocalBounds());
377 bounds
.Inset(border_
->GetInsets());
379 canvas
->DrawRoundRect(bounds
, border_
->GetBorderCornerRadius(), paint
);