| Index: ash/system/tray/system_tray_bubble_view.cc
|
| diff --git a/ash/system/tray/system_tray_bubble_view.cc b/ash/system/tray/system_tray_bubble_view.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..33a5527d8e30a4ba923c3525f122644a094b2570
|
| --- /dev/null
|
| +++ b/ash/system/tray/system_tray_bubble_view.cc
|
| @@ -0,0 +1,299 @@
|
| +// Copyright (c) 2012 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 "ash/system/tray/system_tray_bubble_view.h"
|
| +
|
| +#include "ash/shell.h"
|
| +#include "ash/shell_window_ids.h"
|
| +#include "ash/system/tray/tray_constants.h"
|
| +#include "ash/wm/shelf_layout_manager.h"
|
| +#include "grit/ash_strings.h"
|
| +#include "third_party/skia/include/core/SkCanvas.h"
|
| +#include "third_party/skia/include/core/SkColor.h"
|
| +#include "third_party/skia/include/core/SkPaint.h"
|
| +#include "third_party/skia/include/core/SkPath.h"
|
| +#include "third_party/skia/include/effects/SkBlurImageFilter.h"
|
| +#include "ui/base/accessibility/accessible_view_state.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +#include "ui/gfx/canvas.h"
|
| +#include "ui/gfx/screen.h"
|
| +#include "ui/views/bubble/bubble_frame_view.h"
|
| +#include "ui/views/layout/box_layout.h"
|
| +
|
| +namespace ash {
|
| +
|
| +namespace {
|
| +
|
| +const int kShadowThickness = 4;
|
| +const int kBottomLineHeight = 1;
|
| +const int kSystemTrayBubbleHorizontalInset = 1;
|
| +const int kSystemTrayBubbleVerticalInset = 1;
|
| +
|
| +const int kArrowHeight = 10;
|
| +const int kArrowWidth = 20;
|
| +const int kArrowPaddingFromRight = 20;
|
| +const int kArrowPaddingFromBottom = 17;
|
| +
|
| +const SkColor kShadowColor = SkColorSetARGB(0xff, 0, 0, 0);
|
| +
|
| +void DrawBlurredShadowAroundView(gfx::Canvas* canvas,
|
| + int top,
|
| + int bottom,
|
| + int width,
|
| + const gfx::Insets& inset) {
|
| + SkPath path;
|
| + path.incReserve(4);
|
| + path.moveTo(SkIntToScalar(inset.left() + kShadowThickness),
|
| + SkIntToScalar(top + kShadowThickness + 1));
|
| + path.lineTo(SkIntToScalar(inset.left() + kShadowThickness),
|
| + SkIntToScalar(bottom));
|
| + path.lineTo(SkIntToScalar(width),
|
| + SkIntToScalar(bottom));
|
| + path.lineTo(SkIntToScalar(width),
|
| + SkIntToScalar(top + kShadowThickness + 1));
|
| +
|
| + SkPaint paint;
|
| + paint.setColor(kShadowColor);
|
| + paint.setStyle(SkPaint::kStroke_Style);
|
| + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + paint.setStrokeWidth(SkIntToScalar(3));
|
| + paint.setImageFilter(new SkBlurImageFilter(
|
| + SkIntToScalar(3), SkIntToScalar(3)))->unref();
|
| + canvas->sk_canvas()->drawPath(path, paint);
|
| +}
|
| +
|
| +class SystemTrayBubbleBorder : public views::BubbleBorder {
|
| + public:
|
| + SystemTrayBubbleBorder(views::View* owner,
|
| + views::BubbleBorder::ArrowLocation arrow_location,
|
| + int arrow_offset)
|
| + : views::BubbleBorder(arrow_location,
|
| + views::BubbleBorder::NO_SHADOW),
|
| + owner_(owner),
|
| + arrow_offset_(arrow_offset) {
|
| + set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
|
| + }
|
| +
|
| + virtual ~SystemTrayBubbleBorder() {}
|
| +
|
| + private:
|
| + // Overridden from views::BubbleBorder.
|
| + // Override views::BubbleBorder to set the bubble on top of the anchor when
|
| + // it has no arrow.
|
| + virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
|
| + const gfx::Size& contents_size) const OVERRIDE {
|
| + if (arrow_location() != NONE) {
|
| + return views::BubbleBorder::GetBounds(position_relative_to,
|
| + contents_size);
|
| + }
|
| +
|
| + gfx::Size border_size(contents_size);
|
| + gfx::Insets insets;
|
| + GetInsets(&insets);
|
| + border_size.Enlarge(insets.width(), insets.height());
|
| +
|
| + const int kArrowOverlap = 3;
|
| + int x = position_relative_to.x() +
|
| + position_relative_to.width() / 2 - border_size.width() / 2;
|
| + // Position the bubble on top of the anchor.
|
| + int y = position_relative_to.y() +
|
| + kArrowOverlap - border_size.height();
|
| + return gfx::Rect(x, y, border_size.width(), border_size.height());
|
| + }
|
| +
|
| + // Overridden from views::Border.
|
| + virtual void Paint(const views::View& view,
|
| + gfx::Canvas* canvas) const OVERRIDE {
|
| + gfx::Insets inset;
|
| + GetInsets(&inset);
|
| + DrawBlurredShadowAroundView(canvas, 0, owner_->height(), owner_->width(),
|
| + inset);
|
| +
|
| + // Draw the bottom line.
|
| + int y = owner_->height() + 1;
|
| + canvas->FillRect(gfx::Rect(inset.left(), y, owner_->width(),
|
| + kBottomLineHeight), kBorderDarkColor);
|
| +
|
| + if (!Shell::GetInstance()->shelf()->IsVisible() ||
|
| + arrow_location() == views::BubbleBorder::NONE)
|
| + return;
|
| +
|
| + // Draw the arrow after drawing child borders, so that the arrow can cover
|
| + // the its overlap section with child border.
|
| + SkPath path;
|
| + path.incReserve(4);
|
| + if (arrow_location() == views::BubbleBorder::BOTTOM_RIGHT) {
|
| + int tip_x = base::i18n::IsRTL() ? arrow_offset_ :
|
| + owner_->width() - arrow_offset_;
|
| + tip_x = std::min(std::max(kArrowWidth / 2, tip_x),
|
| + owner_->width() - kArrowWidth / 2);
|
| + int left_base_x = tip_x - kArrowWidth / 2;
|
| + int left_base_y = y;
|
| + int tip_y = left_base_y + kArrowHeight;
|
| + path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y));
|
| + path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
|
| + path.lineTo(SkIntToScalar(left_base_x + kArrowWidth),
|
| + SkIntToScalar(left_base_y));
|
| + } else {
|
| + int tip_y = y - arrow_offset_;
|
| + tip_y = std::min(std::max(kArrowWidth / 2, tip_y),
|
| + owner_->height() - kArrowWidth / 2);
|
| + int top_base_y = tip_y - kArrowWidth / 2;
|
| + int top_base_x, tip_x;
|
| + if (arrow_location() == views::BubbleBorder::LEFT_BOTTOM) {
|
| + top_base_x = inset.left() + kSystemTrayBubbleHorizontalInset;
|
| + tip_x = top_base_x - kArrowHeight;
|
| + } else {
|
| + DCHECK(arrow_location() == views::BubbleBorder::RIGHT_BOTTOM);
|
| + top_base_x = inset.left() + owner_->width() -
|
| + kSystemTrayBubbleHorizontalInset;
|
| + tip_x = top_base_x + kArrowHeight;
|
| + }
|
| + path.moveTo(SkIntToScalar(top_base_x), SkIntToScalar(top_base_y));
|
| + path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
|
| + path.lineTo(SkIntToScalar(top_base_x),
|
| + SkIntToScalar(top_base_y + kArrowWidth));
|
| + }
|
| +
|
| + SkPaint paint;
|
| + paint.setStyle(SkPaint::kFill_Style);
|
| + paint.setColor(kHeaderBackgroundColorDark);
|
| + canvas->DrawPath(path, paint);
|
| +
|
| + // Now draw the arrow border.
|
| + paint.setStyle(SkPaint::kStroke_Style);
|
| + paint.setColor(kBorderDarkColor);
|
| + canvas->DrawPath(path, paint);
|
| +
|
| + }
|
| +
|
| + views::View* owner_;
|
| + const int arrow_offset_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SystemTrayBubbleBorder);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +namespace internal {
|
| +
|
| +SystemTrayBubbleView::SystemTrayBubbleView(
|
| + views::View* anchor,
|
| + views::BubbleBorder::ArrowLocation arrow_location,
|
| + Host* host,
|
| + bool can_activate)
|
| + : views::BubbleDelegateView(anchor, arrow_location),
|
| + host_(host),
|
| + can_activate_(can_activate),
|
| + max_height_(0),
|
| + bubble_width_(kTrayPopupWidth) {
|
| + set_margin(0);
|
| + set_parent_window(ash::Shell::GetInstance()->GetContainer(
|
| + ash::internal::kShellWindowId_SettingBubbleContainer));
|
| + set_notify_enter_exit_on_child(true);
|
| + SetPaintToLayer(true);
|
| + SetFillsBoundsOpaquely(true);
|
| +}
|
| +
|
| +SystemTrayBubbleView::~SystemTrayBubbleView() {
|
| + // Inform host items (models) that their views are being destroyed.
|
| + if (host_)
|
| + host_->BubbleViewDestroyed();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::SetBubbleBorder(int arrow_offset) {
|
| + SystemTrayBubbleBorder* bubble_border = new SystemTrayBubbleBorder(
|
| + this, arrow_location(), arrow_offset);
|
| + GetBubbleFrameView()->SetBubbleBorder(bubble_border);
|
| + // Recalculate with new border.
|
| + SizeToContents();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::UpdateAnchor() {
|
| + SizeToContents();
|
| + GetWidget()->GetRootView()->SchedulePaint();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::SetMaxHeight(int height) {
|
| + max_height_ = height;
|
| + if (GetWidget())
|
| + SizeToContents();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::Init() {
|
| + views::BoxLayout* layout =
|
| + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
|
| + layout->set_spread_blank_space(true);
|
| + SetLayoutManager(layout);
|
| + set_background(NULL);
|
| +}
|
| +
|
| +gfx::Rect SystemTrayBubbleView::GetAnchorRect() {
|
| + gfx::Rect rect;
|
| + if (host_)
|
| + rect = host_->GetAnchorRect();
|
| + // TODO(jennyz): May need to add left/right alignment in the following code.
|
| + if (rect.IsEmpty()) {
|
| + rect = gfx::Screen::GetPrimaryMonitor().bounds();
|
| + rect = gfx::Rect(
|
| + base::i18n::IsRTL() ? kPaddingFromRightEdgeOfScreenBottomAlignment :
|
| + rect.width() - kPaddingFromRightEdgeOfScreenBottomAlignment,
|
| + rect.height() - kPaddingFromBottomOfScreenBottomAlignment,
|
| + 0, 0);
|
| + }
|
| + return rect;
|
| +}
|
| +
|
| +gfx::Rect SystemTrayBubbleView::GetBubbleBounds() {
|
| + // Same as BubbleDelegateView implementation, but don't try mirroring.
|
| + return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(),
|
| + GetPreferredSize(), false /*try_mirroring_arrow*/);
|
| +}
|
| +
|
| +bool SystemTrayBubbleView::CanActivate() const {
|
| + return can_activate_;
|
| +}
|
| +
|
| +gfx::Size SystemTrayBubbleView::GetPreferredSize() {
|
| + gfx::Size size = views::BubbleDelegateView::GetPreferredSize();
|
| + int height = size.height();
|
| + if (max_height_ != 0 && height > max_height_)
|
| + height = max_height_;
|
| + return gfx::Size(bubble_width_, height);
|
| +}
|
| +
|
| +void SystemTrayBubbleView::OnMouseEntered(const views::MouseEvent& event) {
|
| + if (host_)
|
| + host_->OnMouseEnteredView();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::OnMouseExited(const views::MouseEvent& event) {
|
| + if (host_)
|
| + host_->OnMouseExitedView();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::GetAccessibleState(ui::AccessibleViewState* state) {
|
| + if (can_activate_) {
|
| + state->role = ui::AccessibilityTypes::ROLE_WINDOW;
|
| + state->name = l10n_util::GetStringUTF16(
|
| + IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
|
| + }
|
| +}
|
| +
|
| +void SystemTrayBubbleView::ChildPreferredSizeChanged(View* child) {
|
| + SizeToContents();
|
| +}
|
| +
|
| +void SystemTrayBubbleView::ViewHierarchyChanged(bool is_add,
|
| + views::View* parent,
|
| + views::View* child) {
|
| + if (is_add && child == this) {
|
| + parent->SetPaintToLayer(true);
|
| + parent->SetFillsBoundsOpaquely(true);
|
| + parent->layer()->SetMasksToBounds(true);
|
| + }
|
| +}
|
| +
|
| +} // namespace internal
|
| +} // namespace ash
|
|
|