diff options
Diffstat (limited to 'chrome/browser/ui/views/bubble/bubble_border.cc')
-rw-r--r-- | chrome/browser/ui/views/bubble/bubble_border.cc | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/bubble/bubble_border.cc b/chrome/browser/ui/views/bubble/bubble_border.cc new file mode 100644 index 0000000..9cd36d5 --- /dev/null +++ b/chrome/browser/ui/views/bubble/bubble_border.cc @@ -0,0 +1,444 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/bubble/bubble_border.h" + +#include "base/logging.h" +#include "grit/ui_resources.h" +#include "grit/ui_resources_standard.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/path.h" + +// static +SkBitmap* BubbleBorder::left_ = NULL; +SkBitmap* BubbleBorder::top_left_ = NULL; +SkBitmap* BubbleBorder::top_ = NULL; +SkBitmap* BubbleBorder::top_right_ = NULL; +SkBitmap* BubbleBorder::right_ = NULL; +SkBitmap* BubbleBorder::bottom_right_ = NULL; +SkBitmap* BubbleBorder::bottom_ = NULL; +SkBitmap* BubbleBorder::bottom_left_ = NULL; +SkBitmap* BubbleBorder::top_arrow_ = NULL; +SkBitmap* BubbleBorder::bottom_arrow_ = NULL; +SkBitmap* BubbleBorder::left_arrow_ = NULL; +SkBitmap* BubbleBorder::right_arrow_ = NULL; + +// static +int BubbleBorder::arrow_offset_; + +// The height inside the arrow image, in pixels. +static const int kArrowInteriorHeight = 7; + +gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& position_relative_to, + const gfx::Size& contents_size) const { + // Desired size is size of contents enlarged by the size of the border images. + gfx::Size border_size(contents_size); + gfx::Insets insets; + GetInsets(&insets); + border_size.Enlarge(insets.left() + insets.right(), + insets.top() + insets.bottom()); + + // Screen position depends on the arrow location. + // The arrow should overlap the target by some amount since there is space + // for shadow between arrow tip and bitmap bounds. + const int kArrowOverlap = 3; + int x = position_relative_to.x(); + int y = position_relative_to.y(); + int w = position_relative_to.width(); + int h = position_relative_to.height(); + int arrow_offset = override_arrow_offset_ ? override_arrow_offset_ : + arrow_offset_; + + // Calculate bubble x coordinate. + switch (arrow_location_) { + case TOP_LEFT: + case BOTTOM_LEFT: + x += w / 2 - arrow_offset; + break; + + case TOP_RIGHT: + case BOTTOM_RIGHT: + x += w / 2 + arrow_offset - border_size.width() + 1; + break; + + case LEFT_TOP: + case LEFT_BOTTOM: + x += w - kArrowOverlap; + break; + + case RIGHT_TOP: + case RIGHT_BOTTOM: + x += kArrowOverlap - border_size.width(); + break; + + case NONE: + case FLOAT: + x += w / 2 - border_size.width() / 2; + break; + } + + // Calculate bubble y coordinate. + switch (arrow_location_) { + case TOP_LEFT: + case TOP_RIGHT: + y += h - kArrowOverlap; + break; + + case BOTTOM_LEFT: + case BOTTOM_RIGHT: + y += kArrowOverlap - border_size.height(); + break; + + case LEFT_TOP: + case RIGHT_TOP: + y += h / 2 - arrow_offset; + break; + + case LEFT_BOTTOM: + case RIGHT_BOTTOM: + y += h / 2 + arrow_offset - border_size.height() + 1; + break; + + case NONE: + y += h; + break; + + case FLOAT: + y += h / 2 - border_size.height() / 2; + break; + } + + return gfx::Rect(x, y, border_size.width(), border_size.height()); +} + +void BubbleBorder::GetInsets(gfx::Insets* insets) const { + int top = top_->height(); + int bottom = bottom_->height(); + int left = left_->width(); + int right = right_->width(); + switch (arrow_location_) { + case TOP_LEFT: + case TOP_RIGHT: + top = std::max(top, top_arrow_->height()); + break; + + case BOTTOM_LEFT: + case BOTTOM_RIGHT: + bottom = std::max(bottom, bottom_arrow_->height()); + break; + + case LEFT_TOP: + case LEFT_BOTTOM: + left = std::max(left, left_arrow_->width()); + break; + + case RIGHT_TOP: + case RIGHT_BOTTOM: + right = std::max(right, right_arrow_->width()); + break; + + case NONE: + case FLOAT: + // Nothing to do. + break; + } + insets->Set(top, left, bottom, right); +} + +int BubbleBorder::SetArrowOffset(int offset, const gfx::Size& contents_size) { + gfx::Size border_size(contents_size); + gfx::Insets insets; + GetInsets(&insets); + border_size.Enlarge(insets.left() + insets.right(), + insets.top() + insets.bottom()); + offset = std::max(arrow_offset_, + std::min(offset, (is_arrow_on_horizontal(arrow_location_) ? + border_size.width() : border_size.height()) - arrow_offset_)); + override_arrow_offset_ = offset; + return override_arrow_offset_; +} + +// static +void BubbleBorder::InitClass() { + static bool initialized = false; + if (!initialized) { + // Load images. + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + left_ = rb.GetBitmapNamed(IDR_BUBBLE_L); + top_left_ = rb.GetBitmapNamed(IDR_BUBBLE_TL); + top_ = rb.GetBitmapNamed(IDR_BUBBLE_T); + top_right_ = rb.GetBitmapNamed(IDR_BUBBLE_TR); + right_ = rb.GetBitmapNamed(IDR_BUBBLE_R); + bottom_right_ = rb.GetBitmapNamed(IDR_BUBBLE_BR); + bottom_ = rb.GetBitmapNamed(IDR_BUBBLE_B); + bottom_left_ = rb.GetBitmapNamed(IDR_BUBBLE_BL); + left_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_L_ARROW); + top_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_T_ARROW); + right_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_R_ARROW); + bottom_arrow_ = rb.GetBitmapNamed(IDR_BUBBLE_B_ARROW); + + // Calculate horizontal and vertical insets for arrow by ensuring that + // the widest arrow and corner images will have enough room to avoid overlap + int offset_x = + (std::max(top_arrow_->width(), bottom_arrow_->width()) / 2) + + std::max(std::max(top_left_->width(), top_right_->width()), + std::max(bottom_left_->width(), bottom_right_->width())); + int offset_y = + (std::max(left_arrow_->height(), right_arrow_->height()) / 2) + + std::max(std::max(top_left_->height(), top_right_->height()), + std::max(bottom_left_->height(), bottom_right_->height())); + arrow_offset_ = std::max(offset_x, offset_y); + + initialized = true; + } +} + +void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) const { + // Convenience shorthand variables. + const int tl_width = top_left_->width(); + const int tl_height = top_left_->height(); + const int t_height = top_->height(); + const int tr_width = top_right_->width(); + const int tr_height = top_right_->height(); + const int l_width = left_->width(); + const int r_width = right_->width(); + const int br_width = bottom_right_->width(); + const int br_height = bottom_right_->height(); + const int b_height = bottom_->height(); + const int bl_width = bottom_left_->width(); + const int bl_height = bottom_left_->height(); + + gfx::Insets insets; + GetInsets(&insets); + const int top = insets.top() - t_height; + const int bottom = view.height() - insets.bottom() + b_height; + const int left = insets.left() - l_width; + const int right = view.width() - insets.right() + r_width; + const int height = bottom - top; + const int width = right - left; + + // |arrow_offset| is offset of arrow from the begining of the edge. + int arrow_offset = arrow_offset_; + if (override_arrow_offset_) + arrow_offset = override_arrow_offset_; + else if (is_arrow_on_horizontal(arrow_location_) && + !is_arrow_on_left(arrow_location_)) { + arrow_offset = view.width() - arrow_offset - 1; + } else if (!is_arrow_on_horizontal(arrow_location_) && + !is_arrow_on_top(arrow_location_)) { + arrow_offset = view.height() - arrow_offset - 1; + } + + // Left edge. + if (arrow_location_ == LEFT_TOP || arrow_location_ == LEFT_BOTTOM) { + int start_y = top + tl_height; + int before_arrow = arrow_offset - start_y - left_arrow_->height() / 2; + int after_arrow = + height - tl_height - bl_height - left_arrow_->height() - before_arrow; + DrawArrowInterior(canvas, + false, + left_arrow_->width() - kArrowInteriorHeight, + start_y + before_arrow + left_arrow_->height() / 2, + kArrowInteriorHeight, + left_arrow_->height() / 2 - 1); + DrawEdgeWithArrow(canvas, + false, + left_, + left_arrow_, + left, + start_y, + before_arrow, + after_arrow, + left_->width() - left_arrow_->width()); + } else { + canvas->TileImageInt(*left_, left, top + tl_height, l_width, + height - tl_height - bl_height); + } + + // Top left corner. + canvas->DrawBitmapInt(*top_left_, left, top); + + // Top edge. + if (arrow_location_ == TOP_LEFT || arrow_location_ == TOP_RIGHT) { + int start_x = left + tl_width; + int before_arrow = arrow_offset - start_x - top_arrow_->width() / 2; + int after_arrow = + width - tl_width - tr_width - top_arrow_->width() - before_arrow; + DrawArrowInterior(canvas, + true, + start_x + before_arrow + top_arrow_->width() / 2, + top_arrow_->height() - kArrowInteriorHeight, + 1 - top_arrow_->width() / 2, + kArrowInteriorHeight); + DrawEdgeWithArrow(canvas, + true, + top_, + top_arrow_, + start_x, + top, + before_arrow, + after_arrow, + top_->height() - top_arrow_->height()); + } else { + canvas->TileImageInt(*top_, left + tl_width, top, + width - tl_width - tr_width, t_height); + } + + // Top right corner. + canvas->DrawBitmapInt(*top_right_, right - tr_width, top); + + // Right edge. + if (arrow_location_ == RIGHT_TOP || arrow_location_ == RIGHT_BOTTOM) { + int start_y = top + tr_height; + int before_arrow = arrow_offset - start_y - right_arrow_->height() / 2; + int after_arrow = height - tl_height - bl_height - + right_arrow_->height() - before_arrow; + DrawArrowInterior(canvas, + false, + right - r_width + kArrowInteriorHeight, + start_y + before_arrow + right_arrow_->height() / 2, + -kArrowInteriorHeight, + right_arrow_->height() / 2 - 1); + DrawEdgeWithArrow(canvas, + false, + right_, + right_arrow_, + right - r_width, + start_y, + before_arrow, + after_arrow, + 0); + } else { + canvas->TileImageInt(*right_, right - r_width, top + tr_height, r_width, + height - tr_height - br_height); + } + + // Bottom right corner. + canvas->DrawBitmapInt(*bottom_right_, right - br_width, bottom - br_height); + + // Bottom edge. + if (arrow_location_ == BOTTOM_LEFT || arrow_location_ == BOTTOM_RIGHT) { + int start_x = left + bl_width; + int before_arrow = arrow_offset - start_x - bottom_arrow_->width() / 2; + int after_arrow = + width - bl_width - br_width - bottom_arrow_->width() - before_arrow; + DrawArrowInterior(canvas, + true, + start_x + before_arrow + bottom_arrow_->width() / 2, + bottom - b_height + kArrowInteriorHeight, + 1 - bottom_arrow_->width() / 2, + -kArrowInteriorHeight); + DrawEdgeWithArrow(canvas, + true, + bottom_, + bottom_arrow_, + start_x, + bottom - b_height, + before_arrow, + after_arrow, + 0); + } else { + canvas->TileImageInt(*bottom_, left + bl_width, bottom - b_height, + width - bl_width - br_width, b_height); + } + + // Bottom left corner. + canvas->DrawBitmapInt(*bottom_left_, left, bottom - bl_height); +} + +void BubbleBorder::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) const { + /* Here's what the parameters mean: + * start_x + * . + * . ┌───┐ ┬ offset + * start_y..........┌────┬────────┤ ▲ ├────────┬────┐ + * │ / │--------│∙ ∙│--------│ \ │ + * │ / ├────────┴───┴────────┤ \ │ + * ├───┬┘ └┬───┤ + * └───┬────┘ └───┬────┘ + * before_arrow ─┘ └─ after_arrow + */ + if (before_arrow) { + canvas->TileImageInt(*edge, start_x, start_y, + is_horizontal ? before_arrow : edge->width(), + is_horizontal ? edge->height() : before_arrow); + } + + canvas->DrawBitmapInt(*arrow, + start_x + (is_horizontal ? before_arrow : offset), + start_y + (is_horizontal ? offset : before_arrow)); + + if (after_arrow) { + start_x += (is_horizontal ? before_arrow + arrow->width() : 0); + start_y += (is_horizontal ? 0 : before_arrow + arrow->height()); + canvas->TileImageInt(*edge, start_x, start_y, + is_horizontal ? after_arrow : edge->width(), + is_horizontal ? edge->height() : after_arrow); + } +} + +void BubbleBorder::DrawArrowInterior(gfx::Canvas* canvas, + bool is_horizontal, + int tip_x, + int tip_y, + int shift_x, + int shift_y) const { + /* This function fills the interior of the arrow with background color. + * It draws isosceles triangle under semitransparent arrow tip. + * + * Here's what the parameters mean: + * + * ┌──────── |tip_x| + * ┌─────┐ + * │ ▲ │ ──── |tip y| + * │∙∙∙∙∙│ ┐ + * └─────┘ └─── |shift_x| (offset from tip to vertexes of isosceles triangle) + * └────────── |shift_y| + */ + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(background_color_); + gfx::Path path; + path.incReserve(4); + path.moveTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); + path.lineTo(SkIntToScalar(tip_x + shift_x), + SkIntToScalar(tip_y + shift_y)); + if (is_horizontal) + path.lineTo(SkIntToScalar(tip_x - shift_x), SkIntToScalar(tip_y + shift_y)); + else + path.lineTo(SkIntToScalar(tip_x + shift_x), SkIntToScalar(tip_y - shift_y)); + path.close(); + canvas->AsCanvasSkia()->drawPath(path, paint); +} + +///////////////////////// + +void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const { + // The border of this view creates an anti-aliased round-rect region for the + // contents, which we need to fill with the background color. + // NOTE: This doesn't handle an arrow location of "NONE", which has square top + // corners. + SkPaint paint; + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(border_->background_color()); + gfx::Path path; + gfx::Rect bounds(view->GetContentsBounds()); + SkRect rect; + rect.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()), + SkIntToScalar(bounds.right()), SkIntToScalar(bounds.bottom())); + SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius()); + path.addRoundRect(rect, radius, radius); + canvas->AsCanvasSkia()->drawPath(path, paint); +} |