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