1 // Copyright (c) 2011 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 "chrome/browser/ui/views/bubble/bubble_border.h"
6
7 #include "base/logging.h"
8 #include "grit/theme_resources.h"
9 #include "third_party/skia/include/core/SkBitmap.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/canvas_skia.h"
12 #include "ui/gfx/path.h"
13
14 // static
15 SkBitmap* BubbleBorder::left_ = NULL;
16 SkBitmap* BubbleBorder::top_left_ = NULL;
17 SkBitmap* BubbleBorder::top_ = NULL;
18 SkBitmap* BubbleBorder::top_right_ = NULL;
19 SkBitmap* BubbleBorder::right_ = NULL;
20 SkBitmap* BubbleBorder::bottom_right_ = NULL;
21 SkBitmap* BubbleBorder::bottom_ = NULL;
22 SkBitmap* BubbleBorder::bottom_left_ = NULL;
23 SkBitmap* BubbleBorder::top_arrow_ = NULL;
24 SkBitmap* BubbleBorder::bottom_arrow_ = NULL;
25 SkBitmap* BubbleBorder::left_arrow_ = NULL;
26 SkBitmap* BubbleBorder::right_arrow_ = NULL;
27
28 // static
29 int BubbleBorder::arrow_offset_;
30
31 // The height inside the arrow image, in pixels.
32 static const int kArrowInteriorHeight = 7;
33
GetBounds(const gfx::Rect & position_relative_to,const gfx::Size & contents_size) const34 gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& position_relative_to,
35 const gfx::Size& contents_size) const {
36 // Desired size is size of contents enlarged by the size of the border images.
37 gfx::Size border_size(contents_size);
38 gfx::Insets insets;
39 GetInsets(&insets);
40 border_size.Enlarge(insets.left() + insets.right(),
41 insets.top() + insets.bottom());
42
43 // Screen position depends on the arrow location.
44 // The arrow should overlap the target by some amount since there is space
45 // for shadow between arrow tip and bitmap bounds.
46 const int kArrowOverlap = 3;
47 int x = position_relative_to.x();
48 int y = position_relative_to.y();
49 int w = position_relative_to.width();
50 int h = position_relative_to.height();
51 int arrow_offset = override_arrow_offset_ ? override_arrow_offset_ :
52 arrow_offset_;
53
54 // Calculate bubble x coordinate.
55 switch (arrow_location_) {
56 case TOP_LEFT:
57 case BOTTOM_LEFT:
58 x += w / 2 - arrow_offset;
59 break;
60
61 case TOP_RIGHT:
62 case BOTTOM_RIGHT:
63 x += w / 2 + arrow_offset - border_size.width() + 1;
64 break;
65
66 case LEFT_TOP:
67 case LEFT_BOTTOM:
68 x += w - kArrowOverlap;
69 break;
70
71 case RIGHT_TOP:
72 case RIGHT_BOTTOM:
73 x += kArrowOverlap - border_size.width();
74 break;
75
76 case NONE:
77 case FLOAT:
78 x += w / 2 - border_size.width() / 2;
79 break;
80 }
81
82 // Calculate bubble y coordinate.
83 switch (arrow_location_) {
84 case TOP_LEFT:
85 case TOP_RIGHT:
86 y += h - kArrowOverlap;
87 break;
88
89 case BOTTOM_LEFT:
90 case BOTTOM_RIGHT:
91 y += kArrowOverlap - border_size.height();
92 break;
93
94 case LEFT_TOP:
95 case RIGHT_TOP:
96 y += h / 2 - arrow_offset;
97 break;
98
99 case LEFT_BOTTOM:
100 case RIGHT_BOTTOM:
101 y += h / 2 + arrow_offset - border_size.height() + 1;
102 break;
103
104 case NONE:
105 y += h;
106 break;
107
108 case FLOAT:
109 y += h / 2 - border_size.height() / 2;
110 break;
111 }
112
113 return gfx::Rect(x, y, border_size.width(), border_size.height());
114 }
115
GetInsets(gfx::Insets * insets) const116 void BubbleBorder::GetInsets(gfx::Insets* insets) const {
117 int top = top_->height();
118 int bottom = bottom_->height();
119 int left = left_->width();
120 int right = right_->width();
121 switch (arrow_location_) {
122 case TOP_LEFT:
123 case TOP_RIGHT:
124 top = std::max(top, top_arrow_->height());
125 break;
126
127 case BOTTOM_LEFT:
128 case BOTTOM_RIGHT:
129 bottom = std::max(bottom, bottom_arrow_->height());
130 break;
131
132 case LEFT_TOP:
133 case LEFT_BOTTOM:
134 left = std::max(left, left_arrow_->width());
135 break;
136
137 case RIGHT_TOP:
138 case RIGHT_BOTTOM:
139 right = std::max(right, right_arrow_->width());
140 break;
141
142 case NONE:
143 case FLOAT:
144 // Nothing to do.
145 break;
146 }
147 insets->Set(top, left, bottom, right);
148 }
149
SetArrowOffset(int offset,const gfx::Size & contents_size)150 int BubbleBorder::SetArrowOffset(int offset, const gfx::Size& contents_size) {
151 gfx::Size border_size(contents_size);
152 gfx::Insets insets;
153 GetInsets(&insets);
154 border_size.Enlarge(insets.left() + insets.right(),
155 insets.top() + insets.bottom());
156 offset = std::max(arrow_offset_,
157 std::min(offset, (is_arrow_on_horizontal(arrow_location_) ?
158 border_size.width() : border_size.height()) - arrow_offset_));
159 override_arrow_offset_ = offset;
160 return override_arrow_offset_;
161 }
162
163 // static
InitClass()164 void BubbleBorder::InitClass() {
165 static bool initialized = false;
166 if (!initialized) {
167 // Load images.
168 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
169 left_ = rb.GetBitmapNamed(IDR_BUBBLE_L);
170 top_left_ = rb.GetBitmapNamed(IDR_BUBBLE_TL);
171 top_ = rb.GetBitmapNamed(IDR_BUBBLE_T);
172 top_right_ = rb.GetBitmapNamed(IDR_BUBBLE_TR);
173 right_ = rb.GetBitmapNamed(IDR_BUBBLE_R);
174 bottom_right_ = rb.GetBitmapNamed(IDR_BUBBLE_BR);
175 bottom_ = rb.GetBitmapNamed(IDR_BUBBLE_B);
176 bottom_left_ = rb.GetBitmapNamed(IDR_BUBBLE_BL);
177 left_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_L_ARROW);
178 top_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_T_ARROW);
179 right_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_R_ARROW);
180 bottom_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_B_ARROW);
181
182 // Calculate horizontal and vertical insets for arrow by ensuring that
183 // the widest arrow and corner images will have enough room to avoid overlap
184 int offset_x =
185 (std::max(top_arrow_->width(), bottom_arrow_->width()) / 2) +
186 std::max(std::max(top_left_->width(), top_right_->width()),
187 std::max(bottom_left_->width(), bottom_right_->width()));
188 int offset_y =
189 (std::max(left_arrow_->height(), right_arrow_->height()) / 2) +
190 std::max(std::max(top_left_->height(), top_right_->height()),
191 std::max(bottom_left_->height(), bottom_right_->height()));
192 arrow_offset_ = std::max(offset_x, offset_y);
193
194 initialized = true;
195 }
196 }
197
Paint(const views::View & view,gfx::Canvas * canvas) const198 void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) const {
199 // Convenience shorthand variables.
200 const int tl_width = top_left_->width();
201 const int tl_height = top_left_->height();
202 const int t_height = top_->height();
203 const int tr_width = top_right_->width();
204 const int tr_height = top_right_->height();
205 const int l_width = left_->width();
206 const int r_width = right_->width();
207 const int br_width = bottom_right_->width();
208 const int br_height = bottom_right_->height();
209 const int b_height = bottom_->height();
210 const int bl_width = bottom_left_->width();
211 const int bl_height = bottom_left_->height();
212
213 gfx::Insets insets;
214 GetInsets(&insets);
215 const int top = insets.top() - t_height;
216 const int bottom = view.height() - insets.bottom() + b_height;
217 const int left = insets.left() - l_width;
218 const int right = view.width() - insets.right() + r_width;
219 const int height = bottom - top;
220 const int width = right - left;
221
222 // |arrow_offset| is offset of arrow from the begining of the edge.
223 int arrow_offset = arrow_offset_;
224 if (override_arrow_offset_)
225 arrow_offset = override_arrow_offset_;
226 else if (is_arrow_on_horizontal(arrow_location_) &&
227 !is_arrow_on_left(arrow_location_)) {
228 arrow_offset = view.width() - arrow_offset - 1;
229 } else if (!is_arrow_on_horizontal(arrow_location_) &&
230 !is_arrow_on_top(arrow_location_)) {
231 arrow_offset = view.height() - arrow_offset - 1;
232 }
233
234 // Left edge.
235 if (arrow_location_ == LEFT_TOP || arrow_location_ == LEFT_BOTTOM) {
236 int start_y = top + tl_height;
237 int before_arrow = arrow_offset - start_y - left_arrow_->height() / 2;
238 int after_arrow =
239 height - tl_height - bl_height - left_arrow_->height() - before_arrow;
240 DrawArrowInterior(canvas,
241 false,
242 left_arrow_->width() - kArrowInteriorHeight,
243 start_y + before_arrow + left_arrow_->height() / 2,
244 kArrowInteriorHeight,
245 left_arrow_->height() / 2 - 1);
246 DrawEdgeWithArrow(canvas,
247 false,
248 left_,
249 left_arrow_,
250 left,
251 start_y,
252 before_arrow,
253 after_arrow,
254 left_->width() - left_arrow_->width());
255 } else {
256 canvas->TileImageInt(*left_, left, top + tl_height, l_width,
257 height - tl_height - bl_height);
258 }
259
260 // Top left corner.
261 canvas->DrawBitmapInt(*top_left_, left, top);
262
263 // Top edge.
264 if (arrow_location_ == TOP_LEFT || arrow_location_ == TOP_RIGHT) {
265 int start_x = left + tl_width;
266 int before_arrow = arrow_offset - start_x - top_arrow_->width() / 2;
267 int after_arrow =
268 width - tl_width - tr_width - top_arrow_->width() - before_arrow;
269 DrawArrowInterior(canvas,
270 true,
271 start_x + before_arrow + top_arrow_->width() / 2,
272 top_arrow_->height() - kArrowInteriorHeight,
273 1 - top_arrow_->width() / 2,
274 kArrowInteriorHeight);
275 DrawEdgeWithArrow(canvas,
276 true,
277 top_,
278 top_arrow_,
279 start_x,
280 top,
281 before_arrow,
282 after_arrow,
283 top_->height() - top_arrow_->height());
284 } else {
285 canvas->TileImageInt(*top_, left + tl_width, top,
286 width - tl_width - tr_width, t_height);
287 }
288
289 // Top right corner.
290 canvas->DrawBitmapInt(*top_right_, right - tr_width, top);
291
292 // Right edge.
293 if (arrow_location_ == RIGHT_TOP || arrow_location_ == RIGHT_BOTTOM) {
294 int start_y = top + tr_height;
295 int before_arrow = arrow_offset - start_y - right_arrow_->height() / 2;
296 int after_arrow = height - tl_height - bl_height -
297 right_arrow_->height() - before_arrow;
298 DrawArrowInterior(canvas,
299 false,
300 right - r_width + kArrowInteriorHeight,
301 start_y + before_arrow + right_arrow_->height() / 2,
302 -kArrowInteriorHeight,
303 right_arrow_->height() / 2 - 1);
304 DrawEdgeWithArrow(canvas,
305 false,
306 right_,
307 right_arrow_,
308 right - r_width,
309 start_y,
310 before_arrow,
311 after_arrow,
312 0);
313 } else {
314 canvas->TileImageInt(*right_, right - r_width, top + tr_height, r_width,
315 height - tr_height - br_height);
316 }
317
318 // Bottom right corner.
319 canvas->DrawBitmapInt(*bottom_right_, right - br_width, bottom - br_height);
320
321 // Bottom edge.
322 if (arrow_location_ == BOTTOM_LEFT || arrow_location_ == BOTTOM_RIGHT) {
323 int start_x = left + bl_width;
324 int before_arrow = arrow_offset - start_x - bottom_arrow_->width() / 2;
325 int after_arrow =
326 width - bl_width - br_width - bottom_arrow_->width() - before_arrow;
327 DrawArrowInterior(canvas,
328 true,
329 start_x + before_arrow + bottom_arrow_->width() / 2,
330 bottom - b_height + kArrowInteriorHeight,
331 1 - bottom_arrow_->width() / 2,
332 -kArrowInteriorHeight);
333 DrawEdgeWithArrow(canvas,
334 true,
335 bottom_,
336 bottom_arrow_,
337 start_x,
338 bottom - b_height,
339 before_arrow,
340 after_arrow,
341 0);
342 } else {
343 canvas->TileImageInt(*bottom_, left + bl_width, bottom - b_height,
344 width - bl_width - br_width, b_height);
345 }
346
347 // Bottom left corner.
348 canvas->DrawBitmapInt(*bottom_left_, left, bottom - bl_height);
349 }
350
DrawEdgeWithArrow(gfx::Canvas * canvas,bool is_horizontal,SkBitmap * edge,SkBitmap * arrow,int start_x,int start_y,int before_arrow,int after_arrow,int offset) const351 void BubbleBorder::DrawEdgeWithArrow(gfx::Canvas* canvas,
352 bool is_horizontal,
353 SkBitmap* edge,
354 SkBitmap* arrow,
355 int start_x,
356 int start_y,
357 int before_arrow,
358 int after_arrow,
359 int offset) const {
360 /* Here's what the parameters mean:
361 * start_x
362 * .
363 * . ┌───┐ ┬ offset
364 * start_y..........┌────┬────────┤ ▲ ├────────┬────┐
365 * │ / │--------│∙ ∙│--------│ \ │
366 * │ / ├────────┴───┴────────┤ \ │
367 * ├───┬┘ └┬───┤
368 * └───┬────┘ └───┬────┘
369 * before_arrow ─┘ └─ after_arrow
370 */
371 if (before_arrow) {
372 canvas->TileImageInt(*edge, start_x, start_y,
373 is_horizontal ? before_arrow : edge->width(),
374 is_horizontal ? edge->height() : before_arrow);
375 }
376
377 canvas->DrawBitmapInt(*arrow,
378 start_x + (is_horizontal ? before_arrow : offset),
379 start_y + (is_horizontal ? offset : before_arrow));
380
381 if (after_arrow) {
382 start_x += (is_horizontal ? before_arrow + arrow->width() : 0);
383 start_y += (is_horizontal ? 0 : before_arrow + arrow->height());
384 canvas->TileImageInt(*edge, start_x, start_y,
385 is_horizontal ? after_arrow : edge->width(),
386 is_horizontal ? edge->height() : after_arrow);
387 }
388 }
389
DrawArrowInterior(gfx::Canvas * canvas,bool is_horizontal,int tip_x,int tip_y,int shift_x,int shift_y) const390 void BubbleBorder::DrawArrowInterior(gfx::Canvas* canvas,
391 bool is_horizontal,
392 int tip_x,
393 int tip_y,
394 int shift_x,
395 int shift_y) const {
396 /* This function fills the interior of the arrow with background color.
397 * It draws isosceles triangle under semitransparent arrow tip.
398 *
399 * Here's what the parameters mean:
400 *
401 * ┌──────── |tip_x|
402 * ┌─────┐
403 * │ ▲ │ ──── |tip y|
404 * │∙∙∙∙∙│ ┐
405 * └─────┘ └─── |shift_x| (offset from tip to vertexes of isosceles triangle)
406 * └────────── |shift_y|
407 */
408 SkPaint paint;
409 paint.setStyle(SkPaint::kFill_Style);
410 paint.setColor(background_color_);
411 gfx::Path path;
412 path.incReserve(4);
413 path.moveTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
414 path.lineTo(SkIntToScalar(tip_x + shift_x),
415 SkIntToScalar(tip_y + shift_y));
416 if (is_horizontal)
417 path.lineTo(SkIntToScalar(tip_x - shift_x), SkIntToScalar(tip_y + shift_y));
418 else
419 path.lineTo(SkIntToScalar(tip_x + shift_x), SkIntToScalar(tip_y - shift_y));
420 path.close();
421 canvas->AsCanvasSkia()->drawPath(path, paint);
422 }
423
424 /////////////////////////
425
Paint(gfx::Canvas * canvas,views::View * view) const426 void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const {
427 // The border of this view creates an anti-aliased round-rect region for the
428 // contents, which we need to fill with the background color.
429 // NOTE: This doesn't handle an arrow location of "NONE", which has square top
430 // corners.
431 SkPaint paint;
432 paint.setAntiAlias(true);
433 paint.setStyle(SkPaint::kFill_Style);
434 paint.setColor(border_->background_color());
435 gfx::Path path;
436 gfx::Rect bounds(view->GetContentsBounds());
437 SkRect rect;
438 rect.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()),
439 SkIntToScalar(bounds.right()), SkIntToScalar(bounds.bottom()));
440 SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
441 path.addRoundRect(rect, radius, radius);
442 canvas->AsCanvasSkia()->drawPath(path, paint);
443 }
444