Home | History | Annotate | Download | only in bubble
      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 
     34 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 
    116 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 
    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
    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 
    198 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 
    351 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 
    390 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 
    426 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