• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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