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 |