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
15SkBitmap* BubbleBorder::left_ = NULL;
16SkBitmap* BubbleBorder::top_left_ = NULL;
17SkBitmap* BubbleBorder::top_ = NULL;
18SkBitmap* BubbleBorder::top_right_ = NULL;
19SkBitmap* BubbleBorder::right_ = NULL;
20SkBitmap* BubbleBorder::bottom_right_ = NULL;
21SkBitmap* BubbleBorder::bottom_ = NULL;
22SkBitmap* BubbleBorder::bottom_left_ = NULL;
23SkBitmap* BubbleBorder::top_arrow_ = NULL;
24SkBitmap* BubbleBorder::bottom_arrow_ = NULL;
25SkBitmap* BubbleBorder::left_arrow_ = NULL;
26SkBitmap* BubbleBorder::right_arrow_ = NULL;
27
28// static
29int BubbleBorder::arrow_offset_;
30
31// The height inside the arrow image, in pixels.
32static const int kArrowInteriorHeight = 7;
33
34gfx::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
116void 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
150int 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
164void 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
198void 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
351void 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
390void 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
426void 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